pax_global_header00006660000000000000000000000064141534356700014522gustar00rootroot0000000000000052 comment=d070c014da9afdd4e9084cfe1db0c8e8ab78b1db PyNN-0.10.0/000077500000000000000000000000001415343567000124245ustar00rootroot00000000000000PyNN-0.10.0/.github/000077500000000000000000000000001415343567000137645ustar00rootroot00000000000000PyNN-0.10.0/.github/workflows/000077500000000000000000000000001415343567000160215ustar00rootroot00000000000000PyNN-0.10.0/.github/workflows/full-test.yml000066400000000000000000000035641415343567000204730ustar00rootroot00000000000000# This workflow will install Python dependencies, run tests and lint with a variety of Python versions # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions name: Run all tests on: push: branches: [ $default-branch ] pull_request: branches: [ $default-branch ] jobs: test: name: Test on ${{ matrix.os }} with Python ${{ matrix.python-version }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: python-version: ["3.8", "3.9"] os: ["ubuntu-latest", "windows-latest"] steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 with: python-version: ${{ matrix.python-version }} - name: Install basic dependencies run: | python -m pip install --upgrade pip python -m pip install coverage coveralls nose-testconfig python -m pip install -r requirements.txt - name: Install Brian 2 run: | python -m pip install brian2 - name: Install NEURON if: startsWith(matrix.os, 'ubuntu') run: | python -m pip install neuron - name: Install PyNN itself run: | python setup.py install - name: Run unit tests run: | nosetests --nologcapture --where=test/unittests --verbosity=3 test_assembly.py test_brian.py test_connectors_parallel.py test_connectors_serial.py test_core.py test_descriptions.py test_files.py test_idmixin.py test_lowlevelapi.py test_neuron.py test_parameters.py test_population.py test_populationview.py test_projection.py test_random.py test_recording.py test_simulation_control.py test_space.py test_standardmodels.py test_utility_functions.py - name: Run system tests run: | nosetests --nologcapture --where=test/system test_brian2.py test_neuron.pyPyNN-0.10.0/.gitignore000066400000000000000000000025241415343567000144170ustar00rootroot00000000000000/examples/PyNN_NeuroML2_Export.nml # Editor temporary/working/backup files .#* [#]*# *~ *$ *.bak *.kdev4 *.komodoproject *.orig .project .pydevproject .spyderproject .settings *.tmp* *.swp .idea # Compiled source *.py[ocd] x86_64 i386 i686 _build # Python files build dist MANIFEST doc/_build # Egg metadata *.egg-info *.egg *.EGG *.EGG-INFO *.pkl # OS generated files .directory .gdb_history .DS_Store? Icon? Thumbs.db # coverage files .coverage cover # files from running examples examples/Results examples/tools/Results examples/nineml_mechanisms examples/*.dat examples/*.svg /examples/LEMS_Sim_PyNN_NeuroML2_Export.xml /examples/Potjans2014/*.svg /examples/Potjans2014/*.mod /examples/Potjans2014/*.png /examples/Potjans2014/results/* /examples/Potjans2014/*.nml /examples/Potjans2014/LEMS*.xml /examples/Potjans2014/*netpyne.py /examples/Potjans2014/*.ini /examples/Potjans2014/*.h5 /examples/Potjans2014/*.pov /examples/Potjans2014/*main.json /examples/Potjans2014/*.so /test/system/LEMS_Sim_Test0.xml /test/system/Test0.net.nml /examples/Potjans2014/*.spikes /examples/Potjans2014/*.dat /examples/Potjans2014/*_nrn.py /examples/PyNN_NeuroML2_Export.net.nml /examples/input.spikes /examples/output.spikes /examples/Potjans2014/clean.sh /test/benchmarks/*.h5 # Other *.btr *.log tmp /pynnpg.sh /cleanpynn.sh .eggs/ /test/system/tmp_serialization_test PyNN-0.10.0/.travis.yml000066400000000000000000000007531415343567000145420ustar00rootroot00000000000000language: python dist: bionic matrix: include: - python: 3.7 env: PYENV=py37 - python: 3.8 env: PYENV=py38 - python: 3.9 env: PYENV=py39 before_install: - sudo apt-get install -y libgsl0-dev openmpi-bin libopenmpi-dev python-dev install: - source ci/install.sh script: bash ci/test_script.sh after_success: - bash ci/upload_coveralls.sh cache: directories: - $HOME/nest-3.0 - $HOME/nrn-8.0.0 - $HOME/build/nest-3.0 - $HOME/.cache/pip PyNN-0.10.0/AUTHORS000066400000000000000000000045511415343567000135010ustar00rootroot00000000000000The following people have contributed to PyNN. Their affiliations at the time of the contributions are shown below. * Andrew Davison [1, 20] * Pierre Yger [1, 9] * Eilif Muller [7, 13] * Jens Kremkow [5,6] * Daniel Brüderle [2] * Laurent Perrinet [6] * Jochen Eppler [3,4] * Dejan Pecevski [8] * Nicolas Debeissat [10] * Mikael Djurfeldt [12, 15] * Michael Schmuker [11] * Bernhard Kaplan [2] * Thomas Natschlaeger [8] * Subhasis Ray [14] * Yury Zaytsev [16] * Jan Antolik [1] * Alexandre Gravier * Thomas Close [17] * Oliver Breitwieser [2] * Jannis Schücker [16] * Maximilian Schmidt [16] * Christian Roessert [13] * Shailesh Appukuttan [1, 20] * Elodie Legouée [1] * Joffrey Gonin [1] * Ankur Sinha [18] * Håkon Mørk [19] * Andrei Moise [20] * Onur Ates [20] * Rémy Cagnol [21] 1. Unité de Neuroscience, Information et Complexité, CNRS, Gif sur Yvette, France 2. Kirchhoff Institute for Physics, University of Heidelberg, Heidelberg, Germany 3. Honda Research Institute Europe GmbH, Offenbach, Germany 4. Bernstein Center for Computational Neuroscience, Albert-Ludwigs-University, Freiburg, Germany 5. Neurobiology and Biophysics, Institute of Biology III, Albert-Ludwigs-University, Freiburg, Germany 6. Institut de Neurosciences Cognitives de la Méditerranée, CNRS, Marseille, France 7. Laboratory of Computational Neuroscience, Ecole Polytechnique Fédérale de Lausanne, Lausanne, Switzerland 8. Institute for Theoretical Computer Science, Graz University of Technology, Graz, Austria 9. Department of Bioengineering, Imperial College London 10. INRIA, Sophia Antipolis, France 11. Neuroinformatics & Theoretical Neuroscience, Freie Universität Berlin, Berlin, Germany 12. International Neuroinformatics Coordinating Facility, Stockholm, Sweden 13. Blue Brain Project, Ecole Polytechnique Fédérale de Lausanne, Lausanne, Switzerland 14. NCBS, Bangalore, India 15. PDC, KTH, Stockholm, Sweden 16. Institute of Neuroscience and Medicine (INM-6), Jülich Research Center, Jülich, Germany 17. Okinawa Institute of Science and Technology (OIST), Onna-son, Okinawa, Japan 18. Biocomputation group, University of Hertfordshire, Hatfield, United Kingdom. 19. Norwegian University of Life Sciences, Ås, Norway 20. Paris-Saclay Institute of Neuroscience, CNRS/Université Paris-Saclay, Gif sur Yvette, France 21. Faculty of Mathematics and Physics, Charles University, Prague, Czechia PyNN-0.10.0/CODE_OF_CONDUCT.md000066400000000000000000000062341415343567000152300ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pynn-maintainers@protonmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ PyNN-0.10.0/CONTRIBUTING.md000066400000000000000000000001051415343567000146510ustar00rootroot00000000000000See http://neuralensemble.org/docs/PyNN/developers/contributing.html PyNN-0.10.0/LICENSE000066400000000000000000000511511415343567000134340ustar00rootroot00000000000000 CeCILL FREE SOFTWARE LICENSE AGREEMENT Notice This Agreement is a Free Software license agreement that is the result of discussions between its authors in order to ensure compliance with the two main principles guiding its drafting: * firstly, compliance with the principles governing the distribution of Free Software: access to source code, broad rights granted to users, * secondly, the election of a governing law, French law, with which it is conformant, both as regards the law of torts and intellectual property law, and the protection that it offers to both authors and holders of the economic rights over software. The authors of the CeCILL (for Ce[a] C[nrs] I[nria] L[ogiciel] L[ibre]) license are: Commissariat à l'Energie Atomique - CEA, a public scientific, technical and industrial research establishment, having its principal place of business at 25 rue Leblanc, immeuble Le Ponant D, 75015 Paris, France. Centre National de la Recherche Scientifique - CNRS, a public scientific and technological establishment, having its principal place of business at 3 rue Michel-Ange, 75794 Paris cedex 16, France. Institut National de Recherche en Informatique et en Automatique - INRIA, a public scientific and technological establishment, having its principal place of business at Domaine de Voluceau, Rocquencourt, BP 105, 78153 Le Chesnay cedex, France. Preamble The purpose of this Free Software license agreement is to grant users the right to modify and redistribute the software governed by this license within the framework of an open source distribution model. The exercising of these rights is conditional upon certain obligations for users so as to preserve this status for all subsequent redistributions. In consideration of access to the source code and the rights to copy, modify and redistribute granted by the license, users are provided only with a limited warranty and the software's author, the holder of the economic rights, and the successive licensors only have limited liability. In this respect, the risks associated with loading, using, modifying and/or developing or reproducing the software by the user are brought to the user's attention, given its Free Software status, which may make it complicated to use, with the result that its use is reserved for developers and experienced professionals having in-depth computer knowledge. Users are therefore encouraged to load and test the suitability of the software as regards their requirements in conditions enabling the security of their systems and/or data to be ensured and, more generally, to use and operate it in the same conditions of security. This Agreement may be freely reproduced and published, provided it is not altered, and that no provisions are either added or removed herefrom. This Agreement may apply to any or all software for which the holder of the economic rights decides to submit the use thereof to its provisions. Article 1 - DEFINITIONS For the purpose of this Agreement, when the following expressions commence with a capital letter, they shall have the following meaning: Agreement: means this license agreement, and its possible subsequent versions and annexes. Software: means the software in its Object Code and/or Source Code form and, where applicable, its documentation, "as is" when the Licensee accepts the Agreement. Initial Software: means the Software in its Source Code and possibly its Object Code form and, where applicable, its documentation, "as is" when it is first distributed under the terms and conditions of the Agreement. Modified Software: means the Software modified by at least one Contribution. Source Code: means all the Software's instructions and program lines to which access is required so as to modify the Software. Object Code: means the binary files originating from the compilation of the Source Code. Holder: means the holder(s) of the economic rights over the Initial Software. Licensee: means the Software user(s) having accepted the Agreement. Contributor: means a Licensee having made at least one Contribution. Licensor: means the Holder, or any other individual or legal entity, who distributes the Software under the Agreement. Contribution: means any or all modifications, corrections, translations, adaptations and/or new functions integrated into the Software by any or all Contributors, as well as any or all Internal Modules. Module: means a set of sources files including their documentation that enables supplementary functions or services in addition to those offered by the Software. External Module: means any or all Modules, not derived from the Software, so that this Module and the Software run in separate address spaces, with one calling the other when they are run. Internal Module: means any or all Module, connected to the Software so that they both execute in the same address space. GNU GPL: means the GNU General Public License version 2 or any subsequent version, as published by the Free Software Foundation Inc. Parties: mean both the Licensee and the Licensor. These expressions may be used both in singular and plural form. Article 2 - PURPOSE The purpose of the Agreement is the grant by the Licensor to the Licensee of a non-exclusive, transferable and worldwide license for the Software as set forth in Article 5 hereinafter for the whole term of the protection granted by the rights over said Software. Article 3 - ACCEPTANCE 3.1 The Licensee shall be deemed as having accepted the terms and conditions of this Agreement upon the occurrence of the first of the following events: * (i) loading the Software by any or all means, notably, by downloading from a remote server, or by loading from a physical medium; * (ii) the first time the Licensee exercises any of the rights granted hereunder. 3.2 One copy of the Agreement, containing a notice relating to the characteristics of the Software, to the limited warranty, and to the fact that its use is restricted to experienced users has been provided to the Licensee prior to its acceptance as set forth in Article 3.1 hereinabove, and the Licensee hereby acknowledges that it has read and understood it. Article 4 - EFFECTIVE DATE AND TERM 4.1 EFFECTIVE DATE The Agreement shall become effective on the date when it is accepted by the Licensee as set forth in Article 3.1. 4.2 TERM The Agreement shall remain in force for the entire legal term of protection of the economic rights over the Software. Article 5 - SCOPE OF RIGHTS GRANTED The Licensor hereby grants to the Licensee, who accepts, the following rights over the Software for any or all use, and for the term of the Agreement, on the basis of the terms and conditions set forth hereinafter. Besides, if the Licensor owns or comes to own one or more patents protecting all or part of the functions of the Software or of its components, the Licensor undertakes not to enforce the rights granted by these patents against successive Licensees using, exploiting or modifying the Software. If these patents are transferred, the Licensor undertakes to have the transferees subscribe to the obligations set forth in this paragraph. 5.1 RIGHT OF USE The Licensee is authorized to use the Software, without any limitation as to its fields of application, with it being hereinafter specified that this comprises: 1. permanent or temporary reproduction of all or part of the Software by any or all means and in any or all form. 2. loading, displaying, running, or storing the Software on any or all medium. 3. entitlement to observe, study or test its operation so as to determine the ideas and principles behind any or all constituent elements of said Software. This shall apply when the Licensee carries out any or all loading, displaying, running, transmission or storage operation as regards the Software, that it is entitled to carry out hereunder. 5.2 ENTITLEMENT TO MAKE CONTRIBUTIONS The right to make Contributions includes the right to translate, adapt, arrange, or make any or all modifications to the Software, and the right to reproduce the resulting software. The Licensee is authorized to make any or all Contributions to the Software provided that it includes an explicit notice that it is the author of said Contribution and indicates the date of the creation thereof. 5.3 RIGHT OF DISTRIBUTION In particular, the right of distribution includes the right to publish, transmit and communicate the Software to the general public on any or all medium, and by any or all means, and the right to market, either in consideration of a fee, or free of charge, one or more copies of the Software by any means. The Licensee is further authorized to distribute copies of the modified or unmodified Software to third parties according to the terms and conditions set forth hereinafter. 5.3.1 DISTRIBUTION OF SOFTWARE WITHOUT MODIFICATION The Licensee is authorized to distribute true copies of the Software in Source Code or Object Code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by: 1. a copy of the Agreement, 2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9, and that, in the event that only the Object Code of the Software is redistributed, the Licensee allows future Licensees unhindered access to the full Source Code of the Software by indicating how to access it, it being understood that the additional cost of acquiring the Source Code shall not exceed the cost of transferring the data. 5.3.2 DISTRIBUTION OF MODIFIED SOFTWARE When the Licensee makes a Contribution to the Software, the terms and conditions for the distribution of the resulting Modified Software become subject to all the provisions of this Agreement. The Licensee is authorized to distribute the Modified Software, in source code or object code form, provided that said distribution complies with all the provisions of the Agreement and is accompanied by: 1. a copy of the Agreement, 2. a notice relating to the limitation of both the Licensor's warranty and liability as set forth in Articles 8 and 9, and that, in the event that only the object code of the Modified Software is redistributed, the Licensee allows future Licensees unhindered access to the full source code of the Modified Software by indicating how to access it, it being understood that the additional cost of acquiring the source code shall not exceed the cost of transferring the data. 5.3.3 DISTRIBUTION OF EXTERNAL MODULES When the Licensee has developed an External Module, the terms and conditions of this Agreement do not apply to said External Module, that may be distributed under a separate license agreement. 5.3.4 COMPATIBILITY WITH THE GNU GPL The Licensee can include a code that is subject to the provisions of one of the versions of the GNU GPL in the Modified or unmodified Software, and distribute that entire code under the terms of the same version of the GNU GPL. The Licensee can include the Modified or unmodified Software in a code that is subject to the provisions of one of the versions of the GNU GPL, and distribute that entire code under the terms of the same version of the GNU GPL. Article 6 - INTELLECTUAL PROPERTY 6.1 OVER THE INITIAL SOFTWARE The Holder owns the economic rights over the Initial Software. Any or all use of the Initial Software is subject to compliance with the terms and conditions under which the Holder has elected to distribute its work and no one shall be entitled to modify the terms and conditions for the distribution of said Initial Software. The Holder undertakes that the Initial Software will remain ruled at least by this Agreement, for the duration set forth in Article 4.2. 6.2 OVER THE CONTRIBUTIONS The Licensee who develops a Contribution is the owner of the intellectual property rights over this Contribution as defined by applicable law. 6.3 OVER THE EXTERNAL MODULES The Licensee who develops an External Module is the owner of the intellectual property rights over this External Module as defined by applicable law and is free to choose the type of agreement that shall govern its distribution. 6.4 JOINT PROVISIONS The Licensee expressly undertakes: 1. not to remove, or modify, in any manner, the intellectual property notices attached to the Software; 2. to reproduce said notices, in an identical manner, in the copies of the Software modified or not. The Licensee undertakes not to directly or indirectly infringe the intellectual property rights of the Holder and/or Contributors on the Software and to take, where applicable, vis-à-vis its staff, any and all measures required to ensure respect of said intellectual property rights of the Holder and/or Contributors. Article 7 - RELATED SERVICES 7.1 Under no circumstances shall the Agreement oblige the Licensor to provide technical assistance or maintenance services for the Software. However, the Licensor is entitled to offer this type of services. The terms and conditions of such technical assistance, and/or such maintenance, shall be set forth in a separate instrument. Only the Licensor offering said maintenance and/or technical assistance services shall incur liability therefor. 7.2 Similarly, any Licensor is entitled to offer to its licensees, under its sole responsibility, a warranty, that shall only be binding upon itself, for the redistribution of the Software and/or the Modified Software, under terms and conditions that it is free to decide. Said warranty, and the financial terms and conditions of its application, shall be subject of a separate instrument executed between the Licensor and the Licensee. Article 8 - LIABILITY 8.1 Subject to the provisions of Article 8.2, the Licensee shall be entitled to claim compensation for any direct loss it may have suffered from the Software as a result of a fault on the part of the relevant Licensor, subject to providing evidence thereof. 8.2 The Licensor's liability is limited to the commitments made under this Agreement and shall not be incurred as a result of in particular: (i) loss due the Licensee's total or partial failure to fulfill its obligations, (ii) direct or consequential loss that is suffered by the Licensee due to the use or performance of the Software, and (iii) more generally, any consequential loss. In particular the Parties expressly agree that any or all pecuniary or business loss (i.e. loss of data, loss of profits, operating loss, loss of customers or orders, opportunity cost, any disturbance to business activities) or any or all legal proceedings instituted against the Licensee by a third party, shall constitute consequential loss and shall not provide entitlement to any or all compensation from the Licensor. Article 9 - WARRANTY 9.1 The Licensee acknowledges that the scientific and technical state-of-the-art when the Software was distributed did not enable all possible uses to be tested and verified, nor for the presence of possible defects to be detected. In this respect, the Licensee's attention has been drawn to the risks associated with loading, using, modifying and/or developing and reproducing the Software which are reserved for experienced users. The Licensee shall be responsible for verifying, by any or all means, the suitability of the product for its requirements, its good working order, and for ensuring that it shall not cause damage to either persons or properties. 9.2 The Licensor hereby represents, in good faith, that it is entitled to grant all the rights over the Software (including in particular the rights set forth in Article 5). 9.3 The Licensee acknowledges that the Software is supplied "as is" by the Licensor without any other express or tacit warranty, other than that provided for in Article 9.2 and, in particular, without any warranty as to its commercial value, its secured, safe, innovative or relevant nature. Specifically, the Licensor does not warrant that the Software is free from any error, that it will operate without interruption, that it will be compatible with the Licensee's own equipment and software configuration, nor that it will meet the Licensee's requirements. 9.4 The Licensor does not either expressly or tacitly warrant that the Software does not infringe any third party intellectual property right relating to a patent, software or any other property right. Therefore, the Licensor disclaims any and all liability towards the Licensee arising out of any or all proceedings for infringement that may be instituted in respect of the use, modification and redistribution of the Software. Nevertheless, should such proceedings be instituted against the Licensee, the Licensor shall provide it with technical and legal assistance for its defense. Such technical and legal assistance shall be decided on a case-by-case basis between the relevant Licensor and the Licensee pursuant to a memorandum of understanding. The Licensor disclaims any and all liability as regards the Licensee's use of the name of the Software. No warranty is given as regards the existence of prior rights over the name of the Software or as regards the existence of a trademark. Article 10 - TERMINATION 10.1 In the event of a breach by the Licensee of its obligations hereunder, the Licensor may automatically terminate this Agreement thirty (30) days after notice has been sent to the Licensee and has remained ineffective. 10.2 A Licensee whose Agreement is terminated shall no longer be authorized to use, modify or distribute the Software. However, any licenses that it may have granted prior to termination of the Agreement shall remain valid subject to their having been granted in compliance with the terms and conditions hereof. Article 11 - MISCELLANEOUS 11.1 EXCUSABLE EVENTS Neither Party shall be liable for any or all delay, or failure to perform the Agreement, that may be attributable to an event of force majeure, an act of God or an outside cause, such as defective functioning or interruptions of the electricity or telecommunications networks, network paralysis following a virus attack, intervention by government authorities, natural disasters, water damage, earthquakes, fire, explosions, strikes and labor unrest, war, etc. 11.2 Any failure by either Party, on one or more occasions, to invoke one or more of the provisions hereof, shall under no circumstances be interpreted as being a waiver by the interested Party of its right to invoke said provision(s) subsequently. 11.3 The Agreement cancels and replaces any or all previous agreements, whether written or oral, between the Parties and having the same purpose, and constitutes the entirety of the agreement between said Parties concerning said purpose. No supplement or modification to the terms and conditions hereof shall be effective as between the Parties unless it is made in writing and signed by their duly authorized representatives. 11.4 In the event that one or more of the provisions hereof were to conflict with a current or future applicable act or legislative text, said act or legislative text shall prevail, and the Parties shall make the necessary amendments so as to comply with said act or legislative text. All other provisions shall remain effective. Similarly, invalidity of a provision of the Agreement, for any reason whatsoever, shall not cause the Agreement as a whole to be invalid. 11.5 LANGUAGE The Agreement is drafted in both French and English and both versions are deemed authentic. Article 12 - NEW VERSIONS OF THE AGREEMENT 12.1 Any person is authorized to duplicate and distribute copies of this Agreement. 12.2 So as to ensure coherence, the wording of this Agreement is protected and may only be modified by the authors of the License, who reserve the right to periodically publish updates or new versions of the Agreement, each with a separate number. These subsequent versions may address new issues encountered by Free Software. 12.3 Any Software distributed under a given version of the Agreement may only be subsequently distributed under the same version of the Agreement or a subsequent version, subject to the provisions of Article 5.3.4. Article 13 - GOVERNING LAW AND JURISDICTION 13.1 The Agreement is governed by French law. The Parties agree to endeavor to seek an amicable solution to any disagreements or disputes that may arise during the performance of the Agreement. 13.2 Failing an amicable solution within two (2) months as from their occurrence, and unless emergency proceedings are necessary, the disagreements or disputes shall be referred to the Paris Courts having jurisdiction, by the more diligent Party. Version 2.0 dated 2006-09-05. PyNN-0.10.0/MANIFEST.in000066400000000000000000000007531415343567000141670ustar00rootroot00000000000000include pyNN/neuron/nmodl/*.mod include pyNN/nest/extensions/*.h include pyNN/nest/extensions/*.cpp include pyNN/nest/extensions/CMakeLists.txt include pyNN/nest/extensions/sli/* include pyNN/descriptions/templates/*/*.txt include test/parameters/* include test/system/*.py include test/system/scenarios/*.py include test/unittests/*.py include examples/*.py include doc/*.txt include doc/*/*.txt include LICENSE include AUTHORS include README.rst include changelog include requirements.txt PyNN-0.10.0/README.rst000066400000000000000000000037051415343567000141200ustar00rootroot00000000000000PyNN ==== PyNN (pronounced '*pine*') is a simulator-independent language for building neuronal network models. In other words, you can write the code for a model once, using the PyNN API and the Python programming language, and then run it without modification on any simulator that PyNN supports (currently NEURON, NEST and Brian 2) and on a number of neuromorphic hardware systems. The PyNN API aims to support modelling at a high-level of abstraction (populations of neurons, layers, columns and the connections between them) while still allowing access to the details of individual neurons and synapses when required. PyNN provides a library of standard neuron, synapse and synaptic plasticity models, which have been verified to work the same on the different supported simulators. PyNN also provides a set of commonly-used connectivity algorithms (e.g. all-to-all, random, distance-dependent, small-world) but makes it easy to provide your own connectivity in a simulator-independent way. Even if you don't wish to run simulations on multiple simulators, you may benefit from writing your simulation code using PyNN's powerful, high-level interface. In this case, you can use any neuron or synapse model supported by your simulator, and are not restricted to the standard models. - Home page: http://neuralensemble.org/PyNN/ - Documentation: http://neuralensemble.org/docs/PyNN/ - Mailing list: https://groups.google.com/forum/?fromgroups#!forum/neuralensemble - Bug reports: https://github.com/NeuralEnsemble/PyNN/issues :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. .. image:: https://travis-ci.org/NeuralEnsemble/PyNN.png?branch=master :target: https://travis-ci.org/NeuralEnsemble/PyNN :alt: Unit Test Status .. image:: https://coveralls.io/repos/NeuralEnsemble/PyNN/badge.svg?branch=master&service=github :target: https://coveralls.io/github/NeuralEnsemble/PyNN?branch=master :alt: Test coverage PyNN-0.10.0/changelog000066400000000000000000001226621415343567000143070ustar00rootroot00000000000000============== Release 0.10.0 ============== see doc/releases/0.10.0.txt ============= Release 0.9.6 ============= see doc/releases/0.9.6.txt ============= Release 0.9.5 ============= see doc/releases/0.9.5.txt ============= Release 0.9.4 ============= see doc/releases/0.9.4.txt ============= Release 0.9.3 ============= see doc/releases/0.9.3.txt ============= Release 0.9.2 ============= see doc/releases/0.9.2.txt ============= Release 0.9.1 ============= see doc/releases/0.9.1.txt ============= Release 0.9.0 ============= see doc/releases/0.9.0.txt ============= Release 0.8.3 ============= see doc/releases/0.8.3.txt ============= Release 0.8.2 ============= see doc/releases/0.8.2.txt ============= Release 0.8.1 ============= see doc/releases/0.8.1.txt ============= Release 0.8.0 ============= see doc/releases/0.8.0.txt ================ Release 0.8.0rc1 ================ see doc/releases/0.8.0-rc-1.txt ================ Release 0.8beta2 ================ see doc/releases/0.8-beta-2.txt =============== Release 0.8beta1 ================ see doc/releases/0.8-beta-1.txt ============================================================ Release 0.8alpha2 (ee4d813c668313afff9cc5ccee7d306dd0d8760f) ============================================================ see doc/releases/0.8-alpha-2.txt ========================= Release 0.8alpha1 (r1271) ========================= see doc/releases/0.8-alpha-1.txt ===================== Release 0.7.5 (r1258) ===================== * works with NEST 2.2 * Fixed problem with nest.Recorder.read_local_data when running with more than one thread (see ticket:244) * Fixed a problem with weights changes not being taken into account in Brain backend (see ticket:235) * Fixed a problem when trying to retrieve data in the middle of a run, with NEST backend (see ticket:236) ===================== Release 0.7.4 (r1195) ===================== * Some small fixes to the `random` module (see ticket:231) * NumpyBinaryFile now works with NumPy 1.6 ===================== Release 0.7.3 (r1124) ===================== * Some fixes to the `CSAConnector` class * the Brian backend now includes the value at t=0 in recorded data (see ticket:225) * start times for `CurrentSources` in the NEST backend are now corrected for the connection delay * start times for `CurrentSources` in the Brian backend are now correct (there was something funny happening with clocks, before.) ==================== Release 0.7.2 (r995) ==================== Fixed a bug whereby the `connect()` function didn't work with single IDs (see ticket:195) ==================== Release 0.7.1 (r958) ==================== The main reason for this release is to add copyright statements, without which the validity of the CeCILL licence could be questioned. There are also some minor bug fixes. ==================== Release 0.7.0 (r929) ==================== This release sees a major extension of the API with the addition of the `PopulationView` and `Assembly` classes, which aim to make building large, structured networks much simpler and cleaner. A `PopulationView` allows a sub-set of the neurons from a `Population` to be encapsulated in an object. We call it a "view", rather than a "sub-population", to emphasize the fact that the neurons are not copied: they are the same neurons as in the parent `Population`, and any operations on either view or parent (setting parameter values, recording, etc.) will be reflected in the other. An `Assembly` is a list of `Population` and/or `PopulationView` objects, enabling multiple cell types to be encapsulated in a single object. `PopulationView` and `Assembly` objects behave in most ways like `Population`: you can record them, connect them using a `Projection`, you can have views of views... The "low-level API" (rechristened "procedural API") has been reimplemented in in terms of `Population` and `Projection`. For example, `create()` now returns a `Population` object rather than a list of IDs, and `connect()` returns a `Projection`object. This change should be almost invisible to the user, since `Population` now behaves very much like a list of IDs (can be sliced, joined, etc.). There has been a major change to cell addressing: Populations now always store cells in a one-dimensional array, which means cells no longer have an address but just an index. To specify the spatial structure of a Population, pass a Structure object to the constructor, e.g. `p = Population((12,10), IF_cond_exp)` is now `p = Population(120, IF_cond_exp, structure=Grid2D(1.2))` although the former syntax still works, for backwards compatibility. The reasons for doing this are: (i) we can now have more interesting structures than just grids (ii) efficiency (less juggling addresses, flattening) (iii) simplicity (less juggling addresses, less code). The API for setting initial values has changed: this is now done via the `initialize()` function or the `Population.initialize()` method, rather than by having `v_init` and similar parameters for cell models. Other API changes: - simplification of the `record_X()` methods. - enhanced `describe()` methods: can now use Jinja2 or Cheetah templating engines to produce much nicer, better formatted network descriptions. - connections and neuron positions can now be saved to various binary formats as well as to text files. - added some new connectors: `SmallWorldConnector` and `CSAConnector` (CSA = Connection Set Algebra) - native neuron and synapse models are now supported using a NativeModelType subclass, rather than specified as strings. This simplifies the code internally and increases the range of PyNN functionality that can be used with native models (e.g. you can now record any variable from a native NEST or NEURON model). For NEST, there is a class factory ``native_cell_type()``, for NEURON the NativeModelType subclasses have to be written by hand. Backend changes: - the NEST backend has been updated to work with NEST version 2.0.0. - the Brian backend has seen extensive work on performance and on bringing it to feature parity with the other backends. Details: * Where `Population.initial_values` contains arrays, these arrays now consistently contain only enough values for local cells. Before, there was some inconsistency about how this was handled. Still need more tests to be sure it's really working as expected. * Allow override of default_maxstep for NEURON backend as setup paramter. This is for the case that the user wants to add network connections across nodes after simulation start time. * Discovered that when using NEST with mpi4py, you must `import nest` first and let it do the MPI initialization. The only time this seems to be a problem with PyNN is if a user imports `pyNN.random` before `pyNN.nest`. It would be nice to handle this more gracefully, but for now I've just added a test that NEST and mpi4py agree on the rank, and a hopefully useful error message. * Added a new `setup()` option for `pyNN.nest`: `recording_precision`. By default, `recording_precision` is 3 for on-grid and 15 for off-grid. * Partially fixed the `pyNN.nest` implementation of `TsodyksMarkramMechanism` (cf ticket:172). The 'tsodyks_synapse' model has a 'tau_psc' parameter, which should be set to the same value as the decay time constant of the post-synaptic current (which is a parameter of the neuron model). I consider this only a partial fix, because if 'tau_syn_E' or 'tau_syn_I' is changed after the creation of the Projection, 'tau_psc' will not be updated to match (unlike in the `pyNN.neuron` implementation. I'm also not sure how well it will work with native neuron models. * reverted `pyNN.nest` to reading/resetting the current time from the kernel rather than keeping track of it within PyNN. NEST warns that this is dangerous, but all the tests pass, so let's wait and see. * In `HH_cond_exp`, conductances are now in µS, as for all other conductances in PyNN, instead of nS. * NEURON now supports Tsodyks-Markram synapses for current-based exponential synapses (before it was only for conductance-based). * NEURON backend now supports the IF_cond_exp_gsfa_grr model. * Simplification of the record_X() methods. With the addition of the `PopulationView` class, the selection logic implemented by the `record_from` and `rng` arguments duplicated that in `Population.__getitem__()` and `Population.sample()`, and so these arguments have been removed, and the `record_X()` methods now record all neurons within a `Population`, `PopulationView` or `Assembly`. Examples of syntax changes: `pop.record_v([pop[0], pop[17]])` --> `pop[(0, 17)].record_v()` `pop.record(10, rng=rng)` --> `pop.sample(10, rng).record() * Added a `sample()` method to `Population`, which returns a `PopulationView` of a random sample of the neurons in the parent population. * Added the EIF_cond_exp/alpha_isfa/ista and HH_cond_exp standard models in Brian. * Added a `gather` option to the `Population.get()` method. * `brian.setup()` now accepts a number of additional arguments in `extra_params`, For example, extra_params={'useweave': True} will lead to inline C++ code generation * Wrote a first draft of a developers' guide. * Considerably extended the `core.LazyArray` class, as a basis for a possible rewrite of the `connectors` module. * The `random` module now uses `mpi4py` to determine the MPI rank and num_processes, rather than receiving these as arguments to the RNG constructor (see ticket:164). * Many fixes and performance enhancements for the `brian` module, which now supports synaptic plasticity. * Made the `describe()` method of `Population`, `Projection`, etc. much more powerful by adding a simple plugin-like structure for templating engines. Jinja2 and Cheetah currently available, with fallback to string.Template. * No more GSL warning every time! Just raise an Exception if we attempt to use GSLRNG and pygsl is not available. * Added some more flexibility to init_logging: logfile=None -> stderr, format includes size & rank, user can override log-level * NEST __init__.py changed to query NEST for filling NEST_SYNAPSE_TYPES. Allows _S connectors if available for use with inh_gamma_generator * Started to move synapse dynamics related stuff out of Projection and into the synapse dynamics-related classes, where it belongs. * Added an `Assembly` class to the API. An `Assembly` is a list of `Population` and/or `PopulationView` objects, enabling multiple cell types to be encapsulated in a single object. An `Assembly` object is intended to behave in most ways like `Population`: you can record them, connect them using a `Projection`... * Added a new "spike_precision" option to `nest.setup()` (see http://neuralensemble.org/trac/PyNN/wiki/SimulatorSpecificOptions) * Updated the NEST backend to work with version 2.0.0 * Rewrote the test suite, making a much cleaner distinction between unit tests, which now make heavy use of mock objects to better-isolate components, and system tests. Test suite now runs with nose (http://somethingaboutorange.com/mrl/projects/nose), in order to facilitate continuous integration testing. * the "low-level API" (rechristened "procedural API") has been reimplemented in in terms of `Population` and `Projection`, with the aim of improved maintainability. For example, `create()` now returns a `Population` object rather than a list of IDs, and `connect()` returns a `Projection`object. With the addition of `PopulationView` and `Assembly`, `Population` now behaves much more like a list of IDs (can be sliced, joined, etc.), so this should have minimal impact on existing simulation scripts. * Changed the format of connection files, as written by `saveConnections()` and read by `FromFileConnector`: files no longer contain the population label. Connections can now also be written to NumpyBinaryFile or PickleFile objects, instead of just text files. Same for `Population.save_positions()`. * Added CSAConnector, which wraps the Connection Set Algebra for use by PyNN. Requires the csa package: http://pypi.python.org/pypi/csa/ * Added a `PopulationView` class to the API. This allows a sub-set of the neurons from a `Population` to be encapsulated in an object. We call it a "view", rather than a "sub-population", to emphasize the fact that the neurons are not copied: they are the same neurons as in the parent `Population`, and any operations on either view or parent (setting parameter values, recording, etc.) will be reflected in the other. `PopulationView` objects are intended to behave in most ways like `Population`: you can record them, connect them using a `Projection`, you can have views of views... * A major change to cell addressing: Populations now always store cells in a one-dimensional array, which means cells no longer have an address but just an index. To specify the spatial structure of a Population, pass a Structure object to the constructor, e.g. `p = Population((12,10), IF_cond_exp)` is now `p = Population(120, IF_cond_exp, structure=Grid2D(1.2))` although the former syntax still works, for backwards compatibility. The reasons for doing this are: (i) we can now have more interesting structures than just grids (ii) efficiency (less juggling addresses, flattening) (iii) simplicity (less juggling addresses, less code - this opens the way to a much easier implementation of sub-populations). * Removed the `v_init` parameter from all cell models. Setting initial values of state variables is now done via the `initialize()` function or the `Population.initialize()` method. * Enhance the distance expressions by allowing expressions such as (d[0] < 0.1) & (d[1] < 0.2). Complex forms can therefore now be drawn, such as squares, ellipses, and so on. * Added an `n_connections` flag to the DistanceDependentProbabiblityConnector in order to be able to constrain the total number of connections. Can be useful for normalizations * Added a simple SmallWorldConnector. Cells are connected within a certain degree d. Then, all the connections are rewired with a probability given by a rewiring parameter and new targets are uniformly selected among all the possible targets. * Added a method to save cell positions to file. * Added a progress bar to connectors. Now, a verbose flag allows to display or not a progress bar indicating the percentage of connections established. * New implementation of the connector classes, with much improved performance and scaling with MPI, and extension of distance-dependent weights and delays to all connectors. In addition, a `safe` flag has been added to all connectors: on by default, a user can turn it off to avoid tests on weights and delays. * Added the ability to set the `atol` and `rtol` parameters of NEURON's cvode solver in the `extra_params` argument of `setup()` (patch from Johannes Partzsch). * Made `nest`s handling of the refractory period consistent with the other backends. Made the default refractory period 0.1 ms rather than 0.0 ms, since NEST appears not to handle zero refractory period. * Moved standard model (cells and synapses) machinery, the `Space` class, and `Error` classes out of `common` into their own modules. ==================== Release 0.6.0 (r710) ==================== There have been three major changes to the API in this version. 1. Spikes, membrane potential and synaptic conductances can now be saved to file in various binary formats. To do this, pass a PyNN `File` object to `Population.print_X()`, instead of a filename. There are various types of PyNN `File` objects, defined in the `recording.files` module, e.g., `StandardTextFile`, `PickleFile`, `NumpyBinaryFile`, `HDF5ArrayFile`. 2. Added a `reset()` function and made the behaviour of `setup()` consistent across simulators. `reset()` sets the simulation time to zero and sets membrane potentials to their initial values, but does not change the network structure. `setup()` destroys any previously defined network. 3. The possibility of expressing distance-dependent weights and delays was extended to the `AllToAllConnector` and `FixedProbabilityConnector` classes. To reduce the number of arguments to the constructors, the arguments affecting the spatial topology (periodic boundary conditions, etc.) were moved to a new `Space` class, so that only a single `Space` instance need be passed to the `Connector` constructor. Details: * Switched to using the point process-based AdExp mechanism in NEURON. * Factored out most of the commonality between the `Recorder` classes of each backend into a parent class `recording.Recorder`, and tidied up the `recording` module. * Can now pass a PyNN File object to `Population.print_X()`, instead of a filename. There are various types of PyNN File objects, defined in the `recording.files` module, e.g., `StandardTextFile`, `PickleFile`, `NumpyBinaryFile`, `HDF5ArrayFile`. * Added an attribute `conductance_based` to `StandardCellType`, to make the determination of synapse type for a given cell more robust. * PyNN now uses a named logger, which makes it easier to control logging levels when using PyNN within a larger application. * implemented gather for `Projection.saveConnections()` * Added a test script (test_mpi.py) to check whether serial and distributed simulations give the same results * Added a `size()` method to `Projection`, to give the total number of connections across all nodes (unlike `__len__()`, which gives only the connections on the local node * Speeded up record() by a huge factor (from 10 s for 12000 cells to less than 0.1 s) by removing an unecessary conditional path (since all IDs now have an attribute "local") * `synapse_type` is now passed to the `ConnectionManager` constructor, not to the `connect()` method, since (a) it is fixed for a given connection manager, (b) it is needed in other methods than just `connect()`; fixed weight unit conversion in `brian` module. * Updated connection handling in `nest` module to work with NEST version 1.9.8498. Will not now work with previous NEST versions * The `neuron` back-end now supports having both static and Tsodyks-Markram synapses on the same neuron (previously, the T-M synapses replaced the static synapses) - in agreement with `nest` and common sense. Thanks to Bartosz Telenczuk for reporting this. * Added a `compatible_output` mode for the `saveConnections()` method. True by default, it allows connections to be reloaded from a file. If False, then the raw connections are stored, which makes for easier postprocessing. * Added an ACSource` current source to the `nest` module. * Fixed Hoc build directory problem in setup.py - see ticket:147 * `Population.get_v()` and the other "`get`" methods now return cell indices (starting from 0) rather than cell IDs. This behaviour now matches that of `Population.print_v()`, etc. See ticket:119 if you think this is a bad idea. * Implemented `reset()` and made the behaviour of `setup()` consistent across simulators. Almost all unit tests now pass. * Moved the base `Connector` class from `common` to `connectors`. Put the `distances` function inside a `Space` class, to allow more convenient specification of topology parameters. Extended the possibility of expressing distance-dependent weights and delays to the `AllToAllConnector` and `FixedProbabilityConnector`. * `Projection.setWeights()` and `setDelays()` now accept a 2D array argument (ref ticket:136), to be symmetric with `getWeights()` and `getDelays()`. For distributed simulations, each node only takes the values it needs from the array. * `FixedProbabilityConnector` is now more strict, and checks that `p_connect` is less than 1 (see ticket:148). This makes no difference to the behaviour, but could act as a check for errors in user code. * Fixed problem with changing `SpikeSourcePoisson` rate during a simulation (see ticket:152) ==================== Release 0.5.0 (r652) ==================== There have been rather few changes to the API in this version, which has focused rather on improving the simulator interfaces and on an internal code-reorganization which aims to make PyNN easier to test, maintain and extend. Principal API changes: * Removed the 'string' connection methods from the `Projection` constructor. The `method` argument now *must* be a `Connector` object, not a string. * Can now record synaptic conductances. * Can now access weights and delays of individual connections one-at-a-time within a `Projection` through `Connection` objects. * Added an interface for injecting arbitrary time-varying currents into cells. * Added `get_v()` and `get_gsyn()` methods to the `Population` class, enabling membrane potential and synaptic conductances to be read directly into memory, rather than saved to file. Improvements to simulator back-ends: * Implemented an interface for the Brian simulator. * Re-implementated the interface to NEURON, to use the new functionality in v7.0. * Removed support for version 1 of NEST. The module for NEST v2 is now simply called `pyNN.nest`. * The PCSIM implementation is now more complete, and more compatible with the other back-ends. * Behind-the-scenes refactoring to implement the API in terms of a small number of low-level, simulator-specific operations. This reduces redundancy between simulator modules, and makes it easier to extend PyNN, since if new functionality uses the low-level operations, it only needs to be written once, not once for each simulator. Details: * Renamed `nest2` to `nest`. * Random number generators now "parallel_safe" by default. * Added documentation on running parallel simulations. * Trying a new method of getting the last data points out of NEST: we always simulate to `t+dt`. This should be fine until we implement the possibility of accessing Vm directly from the `ID` object (see ticket:35). In the meantime, hopefully the NEST guys will change this behaviour. * `gather=True` now works for all modules, even without a shared filesystem (requires `mpi4py`). * Added an `allow_update_on_post` option to the NEURON weight adjuster mechanisms. This is set to 0 (false) for consistency with NEST, which means that weight updates are accumulated and are applied only on a pre-synaptic spike, although I'm not yet sure (a) what the correct behaviour really is, (b) what PCSIM and Brian do. * The `pcsim` module is now implemented in terms of the common implementation, using just `net.add()` and `net.connect()`. I have just commented out the old code (using `CuboidGridObjectPopulation`s and `ConnectionsProjection`s) rather than deleting it, as it will probably be mostly re-used when optimizing later. * `Population.__getitem__()` now accepts slices (see ticket:21). * NEST does not record values at t=0 or t=simtime so, for compatibility with the other simulators, we now add these values manually to the array/datafile. * Fixed the `SpikeSourcePoisson` model in the `neuron` module so it has a really fixed duration, not just an 'on average' fixed duration. * Created a new Exception type, `RecordingError`, for when we try to record membrane potential from a `SpikeSource`. * Renamed the `param_dict` argument of `create()` to `cellparams`, for consistency with `Population.__init__()`. * Created default implementations of nearly all functions and classes in `common`, some of which depend on the simulator package having a `simulator` module that defines certain 'primitive' capabilities. * Created default implementations of all `Connector` classes in `connectors`, which depend on the `Projection` having a `ConnectionManager` which in turn has a `connect()` method implementing divergent connect. * Added a `ConnectionManager` class to the `simulator` module in `nest2`, `neuron` and `brian` packages. This allows (i) a common way of managing connections for both the `connect()` function and the `Projection` class, (ii) a common specification of `Connector` algorithms in terms of method calls on a `ConnectionManager` instance. * Added `weights_iterator()` and `delays_iterator()` to the base `Connector` class, to make them available to all simulator modules. * Moved `Connector` base classes from `common` into a new module, `connectors`. * Moved standard dynamic synapse base classes from `common` into a new module, `synapses`. * Moved standard cell base classes from `common` into a new module, `cells`. * Moved `Timer` class from `common` to `utility`. * `Population` attributes `all_cells` and `local_cells` are now an official part of the API (`cell` is as an alias for `local_cells` for now since it was widely used, if not officially part of the API, in 0.4). * Removed the 'string' connection methods from the `Projection` constructor. The `method` argument now *must* be a `Connector` object, not a string. * Standard cell types now know what things can be recorded from them (`recordable` attribute). * Added new Exception `NothingToWriteError`. Calling `printSpikes()`, etc, when you have not recorded anything raises an Exception, since this is quite likely a mistake, but it needs to be a specific Exception type so it can be handled without inadvertently catching all other errors that are likely to arise during writing to file. * Moved old tests to examples folder * Added Padraig Gleeson's modifications to neuroml.py * little change in setup of the rng_seeds, re-add the possibility to give a seed for a RNG that then draws seeds for the simulation. In that way one does not have to provide the exact number of seeds needed, just one. * add `save_population()` and `load_population()` functions to `utility`. * `test/explore_space.py` is now working. The test script saves results to a NeuroTools datastore, which the `explore_space` script can later retrieve. Only plotting does not work in distributed mode due to lack of X-display. * STDP testing improved. `nest`, `pcsim` and `neuron` now give pretty similar results for `SpikePairRule` with `AdditiveWeightDependence`. I think some of the remaining differences are due to sampling the weights every millisecond, rather than at the beginning of each PSP. We need to develop an API for recording weights in PyNN (PCSIM and NEURON can record the weights directly, rather than by sampling, not sure about NEST). However, I suspect there are some fundamental differences in the algorithms (notably *when* weight changes get applied), that may make it impossible to completely reconcile the results. * Moved the `MultiSim` class from `test/multisim.py` into the `utility` module. * The `pcsim` module now supports dynamic synapses with both conductance-based and current-based synapses. * `Projection.saveConnections()`, `printWeights()` and `weightHistogram()` now work in the `pcsim` module. * `pcsim` `Connector`s now handle arrays of weights/delays. * Implemented `FromListConnector` and `FromFileConnector` for `pcsim`. * Changed tau_ref for hardware neuron model from 0.4ms to 1.0ms. Old estimation was distorted by hardware error. * Fixed a bug whereby spikes from a `Population` of `SpikeSourceArray`s are not recorded if they are set after creation of the population. * Optimisations to improve the building times of large networks in `nest2`. * Can now record synaptic conductances. * Added an interface for the Brian simulator. * When you try to write data to a file, any existing file of the same name is first renamed by appending '_old' to the filename. * Modification of the RandomDistribution object to allow the specification of boundaries, and the way we deal with numbers drawn outside those boundaries. Numbers may be clipped to min/max values, or either redrawn till they fall within min/max values * Added the possibility to select a particular model of plasticity when several are available for the same plastic rule. This is mainly used in NEST, where we set the stdp_synapse_hom as the default type, because it is more efficient when (as is often the case) all plasticity parameters are identitical. * Added the possibility of giving an expression for distant-dependent weights and delays in the `DistanceDependentProbabilityConnector`. * Harmonization of `describe()` methods across simulators, by moving its definition into `common`. `describe()` now returns a string, rather than printing directly to stdout. This lets it be used for writing to log files, etc. You will now have to use `print p.describe()` to obtain the old behaviour.`describe()` now also takes a `template` argument, allowing the output to be customized. * Added an interface for injecting arbitrary currents into cells. Usage example: {{{ cell = create(IF_curr_exp) current_source1 = DCSource(amplitude=0.3, start=20, stop=80) current_source2 = StepCurrentSource(times=[20,40,60,80], amplitudes=[0.3,-0.3,0.3,0.0]) cell.inject(current_source1) # two alternatives current_source2.inject_into([cell]) }}} `DCSource` and `StepCurrentSource` are available in the `nest2`, `neuron` and `brian` modules. `NoisyCurrentSource` is only available in `nest2` for the time being, but it will be straightforward to add it for the other backends. Adding `ACSource`, etc., should be straightforward. * Optimised setting of parameter values by checking whether the list of parameters to be set contains any computed parameters, and only getting/translating all parameters in this case. Before, each time we wanted to set a parameter, we always got all the native_parameters and translated them even when there was a one-to-one correspondence between parameters. * Implemented the Gutig rule and the van Rossum rule of plasticity in `nest2`, and changed the old naming. * In `nest2`, fixed also the meanSpikeCount() method to directly obtain the spike count from the recorder, and not from the file. * Great improvement of the distance dependant distance. Speed up considerably the building time. * Moved unit tests into their own subdirectory * Fixed a bug in the periodic boundary conditions in `DistanceDependentProbabilityConnector` class if they are specified by the user and not linked to the grid sizes. * Reduced the number of adjustable parameters in the `IF_facets_hardware1` standard cell model. * Reimplemented the `neuron` module to use the new features of NEURON (`HocObject`, etc) available in v7.0. * Added `get_v()` method to the `Population` class, enabling membrane potential to be read directly into memory, rather than saved to file. ==================== Release 0.4.0 (r342) ==================== * Added a `quit_on_end` extra argument to `neuron.setup()` * Added a `synapse_types` attribute to all standard cell classes. * Removed `Projection.setThreshold()` from API * In the `neuron` module, `load_mechanisms()` now takes a path to search from as an optional argument, in order to allow loading user-defined mechanisms * Added `get_script_args()` (process command line arguments) and `colour()` (print coloured output) functions to the `utility` module. * Added `rank()` to the API (returns the MPI rank) * Removed `setRNGseeds()` from the API, since each simulator is so different in its requirements. The seeds may be provided using the `extra_params` argument to `setup()`. * The headers of output files now contain the first and last ids in the Population (not fully implemented yet for recording with the low-level API) * Global variables such as the time step and minimum delay have been replaced with functions `get_time_step()`, `get_min_delay()` and `get_current_time()`. This ensures that values are always up-to-date. * Removed `cellclass` arg from `set()` function. All cells should now know their own cellclass. * Added `get()` method to `Population` class. * Default value for the `duration` parameter in `SpikeSourcePoisson` changed from 1e12 ms to 1e6 ms. * Reimplemented `Population.set()`, `tset()`, `rset()` in a more consistent way which avoids exposing the translation machinery and fixes various bugs with computed parameters. The new implementation is likely to be slower, but several optimisations are possible. * Added `simple_parameters()`, `scaled_parameters()` and `computed_parameters()` methods to the `StandardModelType` class. Their intended use is in making `set()` methods/functions more efficient for non-computed parameters when setting on many nodes. * Multiple calls to `Population.record()` no longer record the same cell twice. * Changed `common.ID` to `common.IDMixin`, which allows the type used for the id to vary (`int` for `neuron` and `nest1/2`, `long` for pcsim). * In `common.StandardModelType`, changed most of the methods to be classmethods, since they do not act on instance data. * Added a `ModelNotAvailable` class to allow more informative error messages when people try to use a model with a simulator that doesn't support it. * hoc and mod files are now correctly packaged, installed and compiled with `distutils`. * Added a check that argument names to `setup()` are not mis-spelled. This is possible because of `extra_params`. * It is now possible to instantiate Timer objects, i.e. to have multiple, independent Timers * Some re-naming of function/method arguments to conform more closely to Python style guidelines, e.g. `methodParameters` to `method_parameters` and `paramDict` to `param_dict`. * Added `getWeights()` and `getDelays()` methods to `Projection` class. NOTE: check for which simulators this is available. XXX * Added a `RoundingWarning` exception, to warn the user when rounding is occurring. * Can now change the `spike_times` attribute of a `SpikeSourceArray` during a simulation without reinitialising. This reduces memory for long simulations, since it is not necessary to load all the spike times into memory at once. NOTE: check for which simulators this works. XXX * The `neuron` module now requires NEURON v6.1 or later. * For developers, changes to the layout of the code: (1) Simulator modules have been moved to a `src` subdirectory - this is to make distribution/installation of PyNN easier. (2) Several of the modules have been split into multiple files, in their own subdirectories, e.g.: `nest2.py` --> `nest2/__init__.py`, `nest2/cells.py` and `nest2/connectors.py`. The reason for this is that the individual files were getting very long and difficult to navigate. * Added `index()` method to `Population` class - what does it do?? XXX * Added `getSpikes()` method to `Population` class - returns spike times/ids as a numpy array. * Added support for the Stage 1 FACETS hardware. * Changed the default weight to zero (was 1.0 nA) * New STDP API, with implementations for `neuron` and `nest2`, based on discussions at the CodeSprint. * Distance calculations can now use periodic boundary conditions. * Parameter translation system now fully supports reverse translation (including units). The syntax for specifying translations is now simpler, which makes it easier to define new standard cell models. * All simulator modules now have a `list_standard_models()` function, which returns a list of all the models that are available for that simulator. * The `connect()` function now returns a `Connection` object, which has `weight` and `delay` properties. This allows accessing/changing weights/delays of individual connections in the low-level API. NOTE: only available in `nest2`? Implement for all sims or delete from this release. XXX * Added `record_c()` and `print_c()` methods to the `Population` class, to allow recording synaptic conductances. NOTE: only in `nest2` - should add to `neuron` or delete from this release. XXX * Procedures for connecting `Population`s can now be defined as classes (subclasses of an abstract `Connector` class) rather than given as a string. This should make it easier for users to add their own connection methods. Weights and delays can also be specified in the `Connector` constructor, removing the need to call `setWeights()` and `setDelays()` after the building of the connections. We keep the string specification for backwards compatibility, but this is deprecated and will be removed in a future API version. * Added new standard models: EIF_cond_alpha_isfa_ista, IF_cond_exp_gsfa_grr, HodgkinHuxley. NOTE: check names, and that all models are supported by at least two simulators. * Version 2 of the NEST simulator is now supported, with the `nest2` module. The `nest` module is now called `nest1`. * Changed the order of arguments in `random.RandomDistribution.__init__()` to put `rng` last, since this is the argument for which the default is most often used (moving it lets positional arguments be used for `distribution` and `parameters` when `rng` is not specified). * Changes to `ID` class: - `_cellclass` attribute renamed to `cellclass` and is now a [http://www.geocities.com/foetsch/python/new_style_classes.htm property]. - Ditto for `_position` --> `position` - Methods `setPosition()`, `getPosition()`, `setCellClass()` removed (just use the `position` or `cellclass` properties). - `set(param,val=None)` changed to `setParameters(**parameters)`. - Added `getParameters()` - `__setattr__()` and `__getattr__()` overridden, so that cell parameters can be read/changed using dot notation, e.g. `id.tau_m = 20.0` Note that one of the reasons for using properties is that it allows attributes to be created only when needed, hopefully saving on memory. * Added `positions` property to `Population` class, which allows the positions of all cells in a population to be set/read at once as a numpy array. * All positions are now in 3D space, irrespective of the shape of the `Population`. * Threads can now be used in `nest` and `pcsim`, via the `extra_param` option of the `setup()` function. * Removed `oldneuron` module. * Added `__iter__()` (iterates over ids) and `addresses()` (iterates over addresses) to the `Population` class. ============= Release 0.3.0 ============= * `pcsim` is now fully supported, although there are still one or two parts of the API that are not implemented. * The behaviour of the `run()` function in the `neuron` module has been changed to match the `nest` and `pcsim` modules, i.e., calling `run(simtime)` several times in succession will advance the simulation by `simtime` ms each time, whereas before, `neuron` would reset time to zero each time. * PyTables is now optional with `pcsim` * Change to `neuron` and `oldneuron` to match more closely the behaviour of `nest` and `pcsim` when the `synapse_type` argument is not given in `connect()`. Before, `neuron` would by default choose an excitatory synapse. Now, it chooses an inhibitory synapse if the weight is negative. * `runtests.py` now runs tests for `pcsim` as well as `nest`, `neuron` and `oldneuron`. * Minor changes to arg names and doc-strings, to improve API-consistency between modules. * Added users' guide (in `doc` directory). * Renamed `neuron` module to `oldneuron` and `neuron2` to `neuron`. * PyNN can now be installed using `distutils`, although this doesn't install or compile hoc/mod files. * Added a `compatible_output` argument to the `printX()` functions/methods, to allow choosing a simulator's native format (faster) or a format that is consistent across simulators. * Temporary files used for saving spikes and membrane potential are now created using the `tempfile` module, which means it should be safe to run multiple PyNN simulations at the same time (before, they would all overwrite the same file). * pygsl is no longer an absolute requirement but can be used if available * Changed the behaviour of `Population` indexing in the `nest` module to be more consistent with the `neuron2` module, in two ways. (i) negative addresses now raise an Exception. (ii) Previously, an integer index `n` signified the `(n+1)`th neuron in the population, e.g., `p[99]` would be the same as `p[10,10]` for a 10x10 population. Now, `p[99]` is the same as `p[99,]` and is only valid for a 1D population. * Addition of `ID` class (inherits from `int`), allowing syntax like `p[3,4].set('tau_m',20.0)` where `p` is a Population object. ============= Release 0.2.0 ============= * `Population.tset()` now accepts arrays of arrays (e.g. conceptually a 2D array of 1D arrays, actually a 3D array) as well as arrays of lists. * setup() now returns the node id. This can be used in a parallel framework to identify the master node. * Unified output format for spikes and membrane potential for `nest` and `neuron` modules. * Added first experimental version of `pcsim` module * `neuron2` module now supports distributed simulations using NEURON compiled with both MPI and Python support. * `Population[xx]` syntax for getting individual cell ids improved. You can now write `p[2,3]` instead of `p[(2,3)]`. * `v_init` added as a parameter to the `IF_curr_alpha`, etc, models. * Trying to access a hoc variable that doesn't exist raises a Python exception, `HocError`. * If synaptic delay is not specified, delays are now set to `min_delay`, not zero. * Random number API allows keeping control of the random numbers used in simulations, by passing an RNG object as an argument to functions that use RNGs. `random` module has wrappers for NumPy RNGs and GSL RNGs, as well as a stub class to indicate the simulator's native RNG should be used (i.e., the `Random` class in hoc). * Translation of model and parameter names from standardised names to simulator-specific names now uses one class per neuron model, rather than a single class with one method per model. For users, the only difference is that you have to use, e.g., `create(IF_curr_alpha)` instead of `create('IF_curr_alpha')` i.e., pass the class instead of a string. For developers, it should now be easier to add new standard models. * Added `neuron2` module, a reimplemtation of the PyNN API for NEURON, that uses more Python and less hoc. ============= Release 0.1.0 ============= Version 0.1 of the API was never really released. At this point the project used the FACETSCOMMON svn repository. First svn import of early stage of PyNN was on 9th May 2006. PyNN-0.10.0/ci/000077500000000000000000000000001415343567000130175ustar00rootroot00000000000000PyNN-0.10.0/ci/install.sh000066400000000000000000000004051415343567000150200ustar00rootroot00000000000000#!/usr/bin/env bash set -e # stop execution in case of errors pip install -r requirements.txt pip install coverage coveralls pip install nose-testconfig source ci/install_brian.sh source ci/install_nest.sh source ci/install_neuron.sh python setup.py install PyNN-0.10.0/ci/install_brian.sh000066400000000000000000000003251415343567000161740ustar00rootroot00000000000000#!/bin/bash set -e # stop execution in case of errors if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then echo -e "\n========== Installing Brian 2 ==========\n" pip install cython; pip install brian2; fiPyNN-0.10.0/ci/install_nest.sh000066400000000000000000000027361415343567000160620ustar00rootroot00000000000000#!/bin/bash set -e # stop execution in case of errors if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then echo -e "\n========== Installing NEST ==========\n" # Specify which version of NEST to install #export NEST_VERSION="master" export NEST_VERSION="3.1" pip install cython if [ "$NEST_VERSION" = "master" ]; then export NEST="nest-simulator-$NEST_VERSION" wget https://github.com/nest/nest-simulator/archive/$NEST_VERSION.tar.gz -O $HOME/$NEST.tar.gz else export NEST="nest-simulator-$NEST_VERSION" wget https://github.com/nest/nest-simulator/archive/refs/tags/v$NEST_VERSION.tar.gz -O $HOME/$NEST.tar.gz fi pushd $HOME tar xzf $NEST.tar.gz ls popd mkdir -p $HOME/build/$NEST pushd $HOME/build/$NEST export VENV=`python -c "import sys; print(sys.prefix)"`; export PYLIB_GLOBAL=`find /opt/python/${TRAVIS_PYTHON_VERSION}/lib/ -name "libpython${TRAVIS_PYTHON_VERSION}*.so"` ln -s ${PYLIB_GLOBAL} $VENV/lib/libpython${TRAVIS_PYTHON_VERSION}.so mkdir -p $VENV/include ln -s /opt/python/${TRAVIS_PYTHON_VERSION}/include/python${TRAVIS_PYTHON_VERSION} $VENV/include/python${TRAVIS_PYTHON_VERSION}; cython --version; cmake --version; cmake -DCMAKE_INSTALL_PREFIX=$VENV \ -Dwith-mpi=ON \ -DPYTHON_EXECUTABLE=$VENV/bin/python \ -DCYTHON_EXECUTABLE=$VENV/bin/cython \ $HOME/$NEST; make; make install; popd; python -c "import nest"; fi PyNN-0.10.0/ci/install_neuron.sh000066400000000000000000000017221415343567000164110ustar00rootroot00000000000000#!/bin/bash set -e # stop execution in case of errors if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then echo -e "\n========== Installing NEURON ==========\n" export NRN_VERSION="nrn-8.0.0" export VENV=`python -c "import sys; print(sys.prefix)"` if [ ! -f "$HOME/$NRN_VERSION/build/CMakeCache.txt" ]; then echo 'Cloning NEURON sources from GitHub' git clone https://github.com/neuronsimulator/nrn -b 8.0.0 $HOME/$NRN_VERSION mkdir -p $HOME/$NRN_VERSION/build else echo 'Using cached NEURON build directory.' fi pushd $HOME/$NRN_VERSION/build cmake .. -DNRN_ENABLE_INTERVIEWS=OFF -DNRN_ENABLE_MPI=ON -DNRN_ENABLE_RX3D=OFF -DCMAKE_INSTALL_PREFIX=$VENV -DNRN_MODULE_INSTALL_OPTIONS="" cmake --build . --target install pip install nrnutils # must be installed after NEURON # compile PyNN NMODL mechanisms echo $TRAVIS_BUILD_DIR cd $TRAVIS_BUILD_DIR/pyNN/neuron/nmodl nrnivmodl popd fiPyNN-0.10.0/ci/test_script.sh000066400000000000000000000004641415343567000157220ustar00rootroot00000000000000#!/bin/bash set -e # stop execution in case of errors if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then python setup.py nosetests --verbose --nologcapture --with-coverage --cover-package=pyNN --tests=test; else python setup.py nosetests --verbose --nologcapture -e backends --tests=test/unittests fi PyNN-0.10.0/ci/upload_coveralls.sh000066400000000000000000000001711415343567000167100ustar00rootroot00000000000000#!/bin/bash set -e # stop execution in case of errors if [ "$TRAVIS_PYTHON_VERSION" == "3.9" ]; then coveralls; fiPyNN-0.10.0/doc/000077500000000000000000000000001415343567000131715ustar00rootroot00000000000000PyNN-0.10.0/doc/Makefile000066400000000000000000000111601415343567000146300ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyNN.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyNN.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/PyNN" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyNN" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." zip: cd $(BUILDDIR); zip -r pyNN_docs.zip html; cd .. @echo @echo "Packaging finished. The zip file is in $(BUILDDIR)/pyNN_docs.zip." PyNN-0.10.0/doc/README000066400000000000000000000005721415343567000140550ustar00rootroot00000000000000=========================== Notes on PyNN documentation =========================== PyNN documentation is generated using Sphinx_. To build the documentation in HTML format, run:: $ make html Many of the files contain examples of interactive Python sessions. The validity of this code can be tested by running:: $ make doctest .. _Sphinx: http://sphinx.pocoo.org/ PyNN-0.10.0/doc/api_reference.txt000066400000000000000000000005311415343567000165200ustar00rootroot00000000000000============= API reference ============= .. toctree:: :maxdepth: 3 reference/populations reference/connectors reference/projections reference/neuronmodels reference/plasticitymodels reference/electrodes reference/simulationcontrol reference/random reference/parameters reference/space reference/utility PyNN-0.10.0/doc/architecture_of_PyNN.svg000066400000000000000000005605311415343567000177760ustar00rootroot00000000000000 image/svg+xmlimage/svg+xml PyNN Simulator-specificPyNN module Python interpreter Native interpreter Simulator kernel Direct communication Code generation Implemented In development pyNN.nest PyNEST SLI NEST pyNN.pcsim PCSIM PyPCSIM pyNN.brian Brian pyNN.neuron nrnpy hoc NEURON pyNN.neuroml NeuroML pyNN.moose PyMOOSE MOOSE pyNN.hardware.brainscales PyHAL BrainScaleSHMF SpiNNakerhardware pyNN.spiNNaker PACMAN PyNN-0.10.0/doc/backends.txt000066400000000000000000000011271415343567000155050ustar00rootroot00000000000000======== Backends ======== The PyNN API provides a uniform interface to different simulators, but nevertheless each simulator has features that are not available in other simulators, and we aim to make these features accessible, as much as possible, from PyNN. For each simulator backend, this section presents the configuration options specific to that backend and explains how to use "native" neuron and synapse models within the PyNN framework. .. toctree:: :maxdepth: 2 backends/NEURON backends/NEST backends/Brian backends/NeuroML backends/NineML backends/neuromorphic PyNN-0.10.0/doc/backends/000077500000000000000000000000001415343567000147435ustar00rootroot00000000000000PyNN-0.10.0/doc/backends/Brian.txt000066400000000000000000000000231415343567000165320ustar00rootroot00000000000000===== Brian ===== PyNN-0.10.0/doc/backends/NEST.txt000066400000000000000000000127131415343567000162610ustar00rootroot00000000000000==== NEST ==== Configuration options ===================== Continuous time spiking ----------------------- In traditional simulation schemes spikes are constrained to an equidistant time grid. However, for some neuron models, NEST has the capability to represent spikes in continuous time. At setup the user can choose the continuous time scheme .. code-block:: python setup(spike_precision='off_grid') or the conventional grid-constrained scheme .. code-block:: python setup(spike_precision='on_grid') where `'off_grid'` is the default. The following PyNN standard models have an off-grid implementation: :class:`IF_curr_exp`, :class:`SpikeSourcePoisson` :class:`EIF_cond_alpha_isfa_ista`. .. todo:: add a list of native NEST models with off-grid capability Here is an example showing how to specify the option in a PyNN script and an illustration of the different outcomes: .. .. plot:: pyplots/continuous_time_spiking.py .. :include-source: .. literalinclude:: ../pyplots/continuous_time_spiking.py .. image:: ../images/continuous_time_spiking.png The gray curve shows the membrane potential excursion in response to an input spike arriving at the neuron at *t* = 1.5 ms (left panel, the right panel shows an enlargement at low voltages). The amplitude of the post-current has an unrealistically high value such that the threshold voltage for spike generation is crossed. The membrane potential is recorded in intervals of 1 ms. Therefore the first non-zero value is measured at *t* = 2 ms. The threshold is crossed somewhere in the interval (3 ms, 4 ms], resulting in a voltage of 0 at *t* = 4 ms. The membrane potential is clamped to 0 for 2 ms, the refractory period. Therefore, the neuron recovers from refractoriness somewhere in the interval (5 ms, 6 ms] and the next non-zero voltage is observed at *t* = 6 ms. The black curve shows the results of the same model now integrated with a grid constrained simulation scheme with a computation step size of 1 ms. The input spike is mapped to the next grid position and therefore arrives at *t* = 2 ms. The first non-zero voltage is observed at *t* = 3 ms. The output spike is emitted at *t* = 4 ms and this is the time at which the membrane potential is reset. Consequently, the model neuron returns from refractoriness at exactly *t* = 6 ms. The next non-zero membrane potential value is observed at *t* = 7 ms. The following publication describes how the continuous time mode is implemented in NEST and compares the performance of different approaches: Hanuschkin A, Kunkel S, Helias M, Morrison A and Diesmann M (2010) A general and efficient method for incorporating precise spike times in globally time-driven simulations. *Front. Neuroinform.* **4**:113. `doi:10.3389/fninf.2010.00113 `_ Using native cell models ======================== To use a NEST neuron model with PyNN, we wrap the NEST model with a PyNN ``NativeCellType`` class, e.g.: .. doctest:: >>> from pyNN.nest import native_cell_type, Population, run, setup >>> setup() 0 >>> ht_neuron = native_cell_type('ht_neuron') >>> poisson = native_cell_type('poisson_generator') >>> p1 = Population(10, ht_neuron(Tau_m=20.0)) >>> p2 = Population(1, poisson(rate=200.0)) We can now initialize state variables, set/get parameter values, and record from these neurons as from standard cells: .. doctest:: >>> p1.get('Tau_m') 20.0 >>> p1.get('Tau_theta') 2.0 >>> p1.get('C_m') Traceback (most recent call last): ... NonExistentParameterError: C_m (valid parameters for ht_neuron are: AMPA_E_rev, AMPA_Tau_1, AMPA_Tau_2, AMPA_g_peak, E_K, E_Na, GABA_A_E_rev, GABA_A_Tau_1, GABA_A_Tau_2, GABA_A_g_peak, GABA_B_E_rev, GABA_B_Tau_1, GABA_B_Tau_2, GABA_B_g_peak, KNa_E_rev, KNa_g_peak, NMDA_E_rev, NMDA_Sact, NMDA_Tau_1, NMDA_Tau_2, NMDA_Vact, NMDA_g_peak, NaP_E_rev, NaP_g_peak, T_E_rev, T_g_peak, Tau_m, Tau_spike, Tau_theta, Theta_eq, g_KL, g_NaL, h_E_rev, h_g_peak, spike_duration) >>> p1.initialize(V_m=-70.0, Theta=-50.0) >>> p1.record('V_m') >>> run(250.0) 250.0 >>> output = p1.get_data() To connect populations of native cells, you need to know the available synaptic receptor types: .. doctest:: >>> ht_neuron.receptor_types ['NMDA', 'AMPA', 'GABA_A', 'GABA_B'] >>> from pyNN.nest import Projection, AllToAllConnector >>> connector = AllToAllConnector() >>> prj_ampa = Projection(p2, p1, connector, receptor_type='AMPA') >>> prj_nmda = Projection(p2, p1, connector, receptor_type='NMDA') Using native synaptic plasticity models ======================================= To use a NEST STDP model with PyNN, we use the :func:`native_synapse_type` function: .. doctest:: >>> from pyNN.nest import native_synapse_type >>> stdp = native_synapse_type("stdp_synapse")(**{"Wmax": 50.0, "lambda": 0.015}) >>> prj_plastic = Projection(p1, p1, connector, receptor_type='AMPA', synapse_type=stdp) Common synapse properties ------------------------- Some NEST synapse models (e.g. ``stdp_facetshw_synapse_hom``) make use of common synapse properties to conserve memory. This has the following implications for their usage in PyNN: * Common properties can only have one homogeneous value per projection. Trying to assign heterogeneous values will result in a ``ValueError``. * Common properties can currently not be retrieved using ``Projection.get``. However, they will only deviate from the default when changed manually. PyNN-0.10.0/doc/backends/NEURON.txt000066400000000000000000000117311415343567000165150ustar00rootroot00000000000000====== NEURON ====== .. testsetup:: cvode from pyNN.neuron import * Configuration options ===================== Adaptive time step integration ------------------------------ The default integration method used by the :mod:`pyNN.neuron` backend uses a fixed time step, specified by the *timestep* argument to the :func:`setup` function. NEURON also supports use of variable time step methods, which can improve simulation speed: .. testcode:: cvode setup(use_cvode=True) If using *cvode*, there are two more optional parameters: .. testcode:: cvode setup(use_cvode=True, rtol=0.001, # specify relative error tolerance atol=1e-4) # specify absolute error tolerance If not specified, the default values are *rtol = 0* and *atol = 0.001*. For full details, see the `CVode documentation`_ .. todo:: native_rng_baseseed is added to MPI.rank to form seed for SpikeSourcePoisson, etc., but I think it would be better to add a `seed` parameter to SpikeSourcePoisson .. todo:: Population.get_data() does not yet handle cvode properly. Using native cell models ======================== A native NEURON cell model is described using a Python class (which may wrap a Hoc template). For this class to work with PyNN, there are a small number of requirements: - the :meth:`__init__` method should take just ``**parameters`` as its argument. - instances should have attributes: - :attr:`source`: a reference to the membrane potential which will be monitored for spike emission, e.g. ``self.soma(0.5)._ref_v`` - :attr:`source_section`: the Hoc :class:`Section` in which :attr:`source` is located. - :attr:`parameter_names`: a tuple of the names of attributes/properties of the class that correspond to parameters of the model. - :attr:`traces`: an empty dict, used for recording. - :attr:`recording_time`: should be ``False`` initially. - there must be a :meth:`memb_init` method, taking no arguments. Here is an example, which uses the nrnutils_ package for conciseness: .. testcode:: nativemodel from nrnutils import Mechanism, Section class SimpleNeuron(object): def __init__(self, **parameters): hh = Mechanism('hh', gl=parameters['g_leak'], el=-65, gnabar=parameters['gnabar'], gkbar=parameters['gkbar']) self.soma = Section(L=30, diam=30, mechanisms=[hh]) self.soma.add_synapse('ampa', 'Exp2Syn', e=0.0, tau1=0.1, tau2=5.0) # needed for PyNN self.source_section = self.soma self.source = self.soma(0.5)._ref_v self.parameter_names = ('g_leak', 'gnabar', 'gkbar') self.traces = {} self.recording_time = False def _set_gnabar(self, value): for seg in self.soma: seg.hh.gnabar = value def _get_gnabar(self): return self.soma(0.5).hh.gnabar gnabar = property(fget=_get_gnabar, fset=_set_gnabar) # ... gkbar and g_leak properties defined similarly def memb_init(self): for seg in self.soma: seg.v = self.v_init .. testcode:: nativemodel :hide: SimpleNeuron.gkbar = 0.0 SimpleNeuron.g_leak = 0.0 For each cell model, you must also define a cell type: .. testcode:: nativemodel from pyNN.neuron import NativeCellType class SimpleNeuronType(NativeCellType): default_parameters = {'g_leak': 0.0002, 'gkbar': 0.036, 'gnabar': 0.12} default_initial_values = {'v': -65.0} recordable = ['soma(0.5).v', 'soma(0.5).ina'] units = {'soma(0.5).v' : 'mV', 'soma(0.5).ina': 'nA'} receptor_types = ['soma.ampa'] model = SimpleNeuron The requirement to explicitly list all variables you might wish to record in the ``recordable`` attribute is a temporary inconvenience, which will be removed in a future version. It is now straightforward to use this cell type in PyNN: .. testcode:: nativemodel from pyNN.neuron import setup, run, Population, Projection, AllToAllConnector, StaticSynapse setup() p1 = Population(10, SimpleNeuronType(g_leak=0.0003)) p1.record('soma(0.5).ina') syn = StaticSynapse(weight=0.01, delay=0.5) prj = Projection(p1, p1, AllToAllConnector(), syn, receptor_type='soma.ampa') run(100.0) output = p1.get_data() If your model relies on other NMODL mechanisms, call the :func:`~pyNN.neuron.load_mechanisms` function with the path to the directory containing the :file:`.mod` files. It is also possible to use NEURON "ARTIFICIAL_CELL" models, such as :class:`IntFire1`, :class:`IntFire2` and :class:`IntFire4`: .. testcode:: nativemodel from pyNN.neuron import setup, Population, IntFire1 setup() p1 = Population(10, IntFire1(tau=10.0, refrac=2.5)) p1.record('m') .. _`CVode documentation`: http://www.neuron.yale.edu/neuron/static/docs/help/neuron/neuron/classes/cvode.html .. _nrnutils: https://pypi.python.org/pypi/nrnutils/ PyNN-0.10.0/doc/backends/NeuroML.txt000066400000000000000000000002531415343567000170250ustar00rootroot00000000000000======= NeuroML ======= A subset of models specified in PyNN can be exported into NeuroML 2 format. See https://github.com/NeuroML/NeuroML2/issues/73 for latest status. PyNN-0.10.0/doc/backends/NineML.txt000066400000000000000000000001121415343567000166200ustar00rootroot00000000000000====== NineML ====== The NineML backend is described in :doc:`../nineml` PyNN-0.10.0/doc/backends/neuromorphic.txt000066400000000000000000000001021415343567000202070ustar00rootroot00000000000000===================== Neuromorphic hardware ===================== PyNN-0.10.0/doc/build_examples.py000066400000000000000000000074261415343567000165510ustar00rootroot00000000000000""" """ import os import tempfile import shutil import subprocess from itertools import cycle from datetime import datetime simulators = cycle(["nest", "neuron"]) examples = ( # "HH_cond_exp2.py", "Izhikevich.py", "current_injection.py", # "VAbenchmarks.py", # "brunel.py", "cell_type_demonstration.py", # "connections.py", # "distrib_example.py", # "inhomogeneous_network.py", # "nineml_neuron.py", # "parameter_changes.py", "random_numbers.py", "random_distributions.py", # "simpleRandomNetwork.py", # "simpleRandomNetwork_csa.py", "simple_STDP.py", "small_network.py", # "specific_network.py", # "stdp_network.py", "synaptic_input.py", "tsodyksmarkram.py", "varying_poisson.py", "stochastic_synapses.py", "stochastic_deterministic_comparison.py" ) # todo: add line numbering to code examples template = """{title} {underline} .. image:: ../images/examples/{img_file} .. literalinclude:: ../../examples/{example} """ example_index = """======== Examples ======== .. toctree:: :maxdepth: 2 """ image_dir = "images/examples" examples_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), os.path.pardir, "examples") tmp_dir = tempfile.mkdtemp() results_dir = os.path.join(tmp_dir, "Results") for dir_name in (image_dir, results_dir): if not os.path.exists(dir_name): os.makedirs(dir_name) def run(python_script, simulator, *extra_args): files_initial = list_files(".png") args = " ".join(extra_args) p = subprocess.Popen("python %s/%s --plot-figure %s %s" % (examples_dir, python_script, simulator, args), shell=True, cwd=tmp_dir) p.wait() new_files = list_files(".png").difference(files_initial) return new_files def get_title(python_script): with open(os.path.join(examples_dir, python_script), "r") as fp: while True: line = fp.readline() if line[:3] == '"""': title = fp.readline().strip().strip(".") break return title def list_files(filter): return set([os.path.join(x[0], filename) for x in os.walk(results_dir) for filename in x[2] if filter in filename]) print("Running examples in {}".format(tmp_dir)) for example in examples: new_files = run(example, next(simulators)) if len(new_files) > 1: raise Exception("Multiple image files") img_path, = new_files shutil.copy(img_path, image_dir) img_file = os.path.basename(img_path) title = get_title(example) underline = "=" * len(title) with open(os.path.join("examples", example.replace(".py", ".txt")), "w") as fp: fp.write(template.format(**locals())) example_index += " examples/{}\n".format(example.replace(".py", "")) # handle VAbenchmarks separately example = "VAbenchmarks.py" cell_type = "CUBA" files_initial = list_files("VAbenchmarks") run(example, "nest", cell_type) run(example, "neuron", cell_type) new_files = list_files("VAbenchmarks").difference(files_initial) files_initial = list_files(".png") timestamp = datetime.now().strftime("%Y%m%d-%H%M%S") p = subprocess.Popen("python %s/tools/VAbenchmark_graphs.py -o Results/VAbenchmarks_%s_%s.png %s" % ( examples_dir, cell_type, timestamp, " ".join(new_files)), shell=True, cwd=tmp_dir) p.wait() img_path, = list_files(".png").difference(files_initial) shutil.copy(img_path, image_dir) img_file = os.path.basename(img_path) title = get_title(example) underline = "=" * len(title) with open(os.path.join("examples", example.replace(".py", ".txt")), "w") as fp: fp.write(template.format(**locals())) example_index += " examples/{}\n".format(example.replace(".py", "")) with open("examples.txt", "w") as fp: fp.write(example_index) shutil.rmtree(tmp_dir) PyNN-0.10.0/doc/building_networks.txt000066400000000000000000000001761415343567000174670ustar00rootroot00000000000000================= Building networks ================= .. toctree:: :maxdepth: 2 neurons connections space PyNN-0.10.0/doc/conf.py000066400000000000000000000221441415343567000144730ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # PyNN documentation build configuration file, created by # sphinx-quickstart on Mon Aug 8 14:55:54 2011. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys import os if True: # mock NEURON import mock # mocks are only wanted when building docs, not when running doctests # is there any way to use them selectively, like this? # also, the matplotlib figures need the real modules. class MockNeuronModule(mock.Mock): nrn_dll_loaded = [] nhost = lambda self: 1 id = lambda self: 1 Section = object set_maxstep = lambda self, x: 10.0 sys.modules["neuron"] = MockNeuronModule() if False: # mock NEST import mock class MockNESTModule(mock.Mock): GetKernelStatus = lambda self: {'num_processes': 1} sys.modules["nest"] = MockNESTModule() # 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. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ----------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # 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.doctest', 'sphinx.ext.todo', 'sphinx.ext.viewcode' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.txt' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'PyNN' authors = u'the PyNN community' copyright = u'2006-2021, ' + authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '0.10' # The full version, including alpha/beta/rc tags. release = '0.10.0' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). add_module_names = False # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- 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 = 'nature' # 'agogo', 'haiku' # 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 = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. html_logo = 'pyNN_logo.png' # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. html_favicon = 'pyNN_icon.ico' # 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'] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'PyNNdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'PyNN.tex', u'PyNN Documentation', authors, 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pynn', u'PyNN Documentation', [authors], 1) ] # -- Options for Epub output --------------------------------------------------- # Bibliographic Dublin Core info. epub_title = project epub_author = authors epub_publisher = authors epub_copyright = copyright # The language of the text. It defaults to the language option # or en if the language is not set. #epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. #epub_scheme = '' # 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 = '' # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. #epub_post_files = [] # A list of files that should not be packed into the epub file. #epub_exclude_files = [] # The depth of the table of contents in toc.ncx. #epub_tocdepth = 3 # Allow duplicate toc entries. #epub_tocdup = True # -- autodoc options ----------------------------------------------------------- autodoc_member_order = 'bysource' # -- todo options -------------------------------------------------------------- todo_include_todos = True # -- inter-Sphinx mapping options ---------------------------------------------- intersphinx_mapping = {'python': ('http://docs.python.org/3.8', None)} PyNN-0.10.0/doc/connections.txt000066400000000000000000000522151415343567000162610ustar00rootroot00000000000000============================== Building networks: connections ============================== .. testsetup:: * from pyNN.mock import * from pyNN.random import NumpyRNG from pyNN.space import Space setup() pre = Population(10, IF_curr_alpha()) post = Population(10, IF_cond_exp()) Conceptually, a synapse consists of a pre-synaptic structure, the synaptic cleft, and a post-synaptic structure. In PyNN, the temporal dynamics of the post-synaptic response are handled by the post-synaptic neuron model (see :ref:`section-cell-types`). The size of the post-synaptic response (the "synaptic weight"), the temporal dynamics of the weight (synaptic plasticity) and the connection delay are handled by synapse models. At the time of writing, most neuronal network models do not explicitly model the axon. Rather, the time for propagation of the action potential from soma/initial segment to axon terminal is added to the synaptic transmission time to give a composite delay, referred to as "synaptic delay" in this documentation. For point neuron models, which do not include an explicit model of the dendrite, the time for transmission of the post-synaptic potential to the soma may also be considered as being included in the composite synaptic delay. At a minimum, therefore, a synaptic connection in PyNN has two attributes: "weight" and "delay", which are interpreted as described above. Where the weight has its own dynamics, a connection may have more attributes: the plasticity model and its parameters. .. note:: Currently, PyNN supports only chemical synapses, not electrical synapses. If the underlying simulator supports electrical synapses, it is still possible to use them in a PyNN model, but this will not be simulator-independent. .. note:: Currently, PyNN does not support stochastic synapses. If you would like to have support for this, or any other feature, please `make a feature request`_. Synapse types ============= Analogously to neuron models, the system of equations that defines a synapse model is encapsulated in a :class:`SynapseType` class. PyNN provides a library of "standard" synapse types (see :doc:`standardmodels`) which work the same across all backend simulators. Fixed synaptic weight --------------------- The simplest, and default synapse type in PyNN has constant synaptic weight: .. testcode:: syn = StaticSynapse(weight=0.04, delay=0.5) .. note:: weights are in microsiemens or nanoamps, depending on whether the post-synaptic mechanism implements a change in conductance or current, and delays are in milliseconds (see :doc:`units`). Weights should always be positive, *except* for the case of inhibitory (see `receptor_type` argument below), current-based synapses, for which they should be negative. Inhibitory, conductance-based synapses have positive weights, because it is the reversal potential which makes it inhibitory. It is also possible to add variability to synaptic weights and delays by specifying a :class:`RandomDistribution` object as the parameter value: .. testcode:: w = RandomDistribution('gamma', [10, 0.004], rng=NumpyRNG(seed=4242)) syn = StaticSynapse(weight=w, delay=0.5) It is also possible to specify parameters as a function of the distance (typically in microns, but different scales are possible - see :doc:`space`) between pre- and post-synaptic neurons: .. testcode:: syn = StaticSynapse(weight=w, delay="0.2 + 0.01*d") Short-term synaptic plasticity ------------------------------ PyNN currently provides one standard model for short-term synaptic plasticity (facilitation and depression): .. testcode:: depressing_synapse = TsodyksMarkramSynapse(weight=w, delay=0.2, U=0.5, tau_rec=800.0, tau_facil=0.0) tau_rec = RandomDistribution('normal', [100.0, 10.0]) facilitating_synapse = TsodyksMarkramSynapse(weight=w, delay=0.5, U=0.04, tau_rec=tau_rec) .. include figure? Spike-timing-dependent plasticity --------------------------------- STDP models are specified in a slightly different way than other standard models: an STDP synapse type is constructed from separate weight-dependence and timing-dependence components, e.g.: .. testcode:: stdp = STDPMechanism( weight=0.02, # this is the initial value of the weight delay="0.2 + 0.01*d", timing_dependence=SpikePairRule(tau_plus=20.0, tau_minus=20.0, A_plus=0.01, A_minus=0.012), weight_dependence=AdditiveWeightDependence(w_min=0, w_max=0.04)) Note that not all simulators will support all possible combinations of synaptic plasticity components. Connection algorithms ===================== .. currentmodule:: pyNN.connectors In PyNN, each different algorithm that can be used to determine which pre-synaptic neurons are connected to which post-synaptic neurons (also called a "connection method" or "wiring method") is encapsulated in a separate class. .. note:: for those interested in design patterns, this is an example of the `Strategy Pattern`_ Each such class inherits from a base class, :class:`Connector`, and must implement a :meth:`connect()` method which takes a :class:`Projection` object (see below) as its single argument. PyNN's library of connection algorithms currently contains the following classes: All-to-all connections ---------------------- Each neuron in the pre-synaptic population is connected to every neuron in the post-synaptic population. (In this section, the term "population" should be understood as referring to any of the following: a :class:`Population`, a :class:`PopulationView`, or an :class:`Assembly` object.) The :class:`AllToAllConnector` constructor has one optional argument, ``allow_self_connections``, for use when connecting a population to itself. By default it is ``True``, but if a neuron should not connect to itself, set it to ``False``, e.g.: .. testcode:: connector = AllToAllConnector(allow_self_connections=False) # no autapses One-to-one connections ---------------------- Use of the :class:`OneToOneConnector` requires that the pre- and post-synaptic populations have the same size. The neuron with index *i* in the pre-synaptic population is then connected to the neuron with index *i* in the post-synaptic population. .. testcode:: connector = OneToOneConnector() Trying to connect two populations with different sizes will raise an Exception. Connecting neurons with a fixed probability ------------------------------------------- With the :class:`FixedProbabilityConnector` method, each possible connection between all pre-synaptic neurons and all post-synaptic neurons is created with probability ``p_connect``: .. testcode:: connector = FixedProbabilityConnector(p_connect=0.2) Connecting neurons with a position-dependent probability -------------------------------------------------------- The connection probability can also depend on the positions of the pre- and post-synaptic neurons. With the :class:`DistanceDependentProbabilityConnector`, the connection probability depends on the distance between the two neurons. The constructor requires a string ``d_expression``, which should be a distance expression, as described above for delays, but returning a probability (a value between 0 and 1): .. testcode:: DDPC = DistanceDependentProbabilityConnector connector = DDPC("exp(-d)") connector = DDPC("d<3") The first example connects neurons with an exponentially-decaying probability. The second example connects each neuron to all its neighbours within a range of 3 units (typically interpreted as µm, but this is up to the individual user). Note that boolean values ``True`` and ``False`` are automatically converted to numerical values ``1.0`` and ``0.0``. Calculation of distance may be controlled by specifying a :class:`Space` object, passed to the :class:`Projection` constructor (see below). For a more general dependence of connection probability on position, use the :class:`IndexBasedProbabilityConnector`, which expects a function of the indices, ``i`` and ``j``, of the pre- and post-synaptic neurons. The function should return the probability of creating that connection. Divergent/fan-out connections ----------------------------- The :class:`FixedNumberPostConnector` connects each pre-synaptic neuron to exactly ``n`` post-synaptic neurons chosen at random: .. testcode:: connector = FixedNumberPostConnector(n=30) If ``n`` is less than the size of the post-synaptic population, there are no multiple connections, i.e., no instances of the same pair of neurons being multiply connected. If ``n`` is greater than the size of the pre-synaptic population, all possible single connections are made before starting to add duplicate connections. The number of post-synaptic neurons ``n`` can be fixed, or can be chosen at random from a :class:`~/pyNN.random.RandomDistribution` object, e.g.: .. testcode:: distr_npost = RandomDistribution(distribution='binomial', n=100, p=0.3) connector = FixedNumberPostConnector(n=distr_npost) Convergent/fan-in connections ----------------------------- The :class:`FixedNumberPreConnector` has the same arguments as :class:`FixedNumberPostConnector`, but of course it connects each *post*-synaptic neuron to ``n`` *pre*-synaptic neurons, e.g.: .. testcode:: connector = FixedNumberPreConnector(5) distr_npre = RandomDistribution(distribution='poisson', lambda_=5) connector = FixedNumberPreConnector(distr_npre) Creating a small-world network ------------------------------ .. todo:: Pierre to write this bit? Using the Connection Set Algebra -------------------------------- The Connection Set Algebra (`Djurfeldt, 2012`_) is a sophisticated system that allows elaborate connectivity patterns to be constructed using a concise syntax. Using the CSA requires the Python :mod:`csa` module to be installed (see :doc:`installation`). The details of constructing a connection set are beyond the scope of this manual. We give here a simple example. .. testcode:: import csa cset = csa.full - csa.oneToOne connector = CSAConnector(cset) ``csa.full`` represents all-to-all connections, while ``csa.oneToOne`` represents the connection of pre-synaptic neuron *i* to post-synaptic neuron *i*. By subtracting the second from the first, the connection rule is "all-to-all, except where the neurons have the same index". If the pre- and post-synaptic populations are the same population, this is equivalent to ``AllToAllConnector(allow_self_connections=False)``. .. todo:: explain that weights and delays can either be specified within the connection set or within the synapse type. Specifying a list of connections -------------------------------- Specific connection patterns not covered by the methods above can be obtained by specifying an explicit list of pre-synaptic and post-synaptic neuron indices. Optionally, the list can contain synaptic properties such as weights, delays, or the parameters for plasticity rules. Example: .. testcode:: connections = [ (0, 0, 0.0, 0.1), (0, 1, 0.0, 0.1), (0, 2, 0.0, 0.1), (1, 5, 0.0, 0.1) ] connector = FromListConnector(connections, column_names=["weight", "delay"]) Any synaptic parameters not given in the list are determined from the synapse type. Parameters given in the list always override the values from the synapse type. Reading connection patterns to/from a file ------------------------------------------ Connection patterns can be read in from a text file. The file should contain a header specifying which parameter is in which column, e.g.:: # columns = ["i", "j", "weight", "delay", "U", "tau_rec"] and then the connection data should be in columns separated by spaces. The connections are read using: .. testcode:: connector = FromFileConnector("connections.txt") Specifying an explicit connection matrix ---------------------------------------- The connectivity can be specified as a boolean array, where each row represents the existence of connections from a given pre-synaptic neuron to the post-synaptic neurons. For example: .. testcode:: connections = numpy.array([[0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0]], dtype=bool) connector = ArrayConnector(connections) User-defined connection algorithms ---------------------------------- If you wish to use a specific connection/wiring algorithm not covered by the PyNN built-in ones, the options include: * constructing a list or array of connections and using the :class:`FromListConnector` or :class:`ArrayConnector` class; * using the Connection Set Algebra and the :class:`CSAConnector` class; * writing your own :class:`Connector` class - see the :doc:`developers_guide` for guidance on this. Projections =========== A :class:`Projection` is a container for a set of connections between two populations of neurons, where by population we mean one of: * a :class:`Population` object - a group of neurons all of the same type; * a :class:`PopulationView` object - part of a :class:`Population`; * a :class:`Assembly` - a heterogeneous group of neurons, which may be of different types. Creating a :class:`Projection` in PyNN also creates the connections at the level of the simulator. To create a :class:`Projection` we must specify: * the pre-synaptic population; * the post-synaptic population; * a connection/wiring method; * a synapse type Optionally, we can also specify: * the name of the post-synaptic mechanism (e.g. ‘excitatory’, ‘NMDA’) (if not specified, PyNN picks a default depending on the weight parameter of the synapse type); * a label (autogenerated if not specified); * a :class:`Space` object, which determines how distances should be calculated for distance-dependent wiring schemes or parameter values. Here is a minimal example: .. testcode:: excitatory_connections = Projection(pre, post, AllToAllConnector(), StaticSynapse(weight=0.123)) and here is a full example: .. testcode:: rng = NumpyRNG(seed=64754) sparse_connectivity = FixedProbabilityConnector(0.1, rng=rng) weight_distr = RandomDistribution('normal', [0.01, 1e-3], rng=rng) facilitating = TsodyksMarkramSynapse(U=0.04, tau_rec=100.0, tau_facil=1000.0, weight=weight_distr, delay=lambda d: 0.1+d/100.0) space = Space(axes='xy') inhibitory_connections = Projection(pre, post, connector=sparse_connectivity, synapse_type=facilitating, receptor_type='inhibitory', space=space, label="inhibitory connections") Note that the attribute :attr:`receptor_types` of all :ref:`cell type ` classes contains a list of the possible values of ``receptor_type`` for that cell type: .. doctest:: >>> post Population(10, IF_cond_exp(), structure=Line(y=0.0, x0=0.0, z=0.0, dx=1.0), label='population1') >>> post.celltype IF_cond_exp() >>> post.celltype.receptor_types ('excitatory', 'inhibitory') The :attr:`space` argument is used to specify how to calculate distances, since we have used a distance expression to specify the connection delay, modelling a constant axonal propagation speed. By default, the 3D distance between cell positions is used, but the ``axes`` argument may be used to change this, i.e.: .. testcode:: space = Space(axes='xy') will ignore the z-coordinate when calculating distance. Similarly, the origins of the coordinate systems of the two populations and the relative scale of the two coordinate systems may be controlled using the ``offset`` and ``scale_factor`` arguments to the :class:`Space` constructor. This is useful when connecting brain regions that have very different sizes but that have a topographic mapping between them, e.g. retina to LGN to V1. In more abstract models, it is often useful to be able to avoid edge effects by specifying periodic boundary conditions, e.g.: .. testcode:: space = Space(periodic_boundaries=((0,500), (0,500), None)) calculates distance on the surface of a torus of circumference 500 µm (wrap-around in the x- and y-dimensions but not z). For more information, see :doc:`space`. Accessing weights and delays ---------------------------- The :meth:`Projection.get` method allows the retrieval of connection attributes, such as weights and delays. Two formats are available. ``'list'`` returns a list of length equal to the number of connections in the projection, ``'array'`` returns a 2D weight array (with NaN for non-existent connections): .. doctest:: >>> excitatory_connections.get('weight', format='list')[3:7] [(3, 0, 0.123), (4, 0, 0.123), (5, 0, 0.123), (6, 0, 0.123)] >>> inhibitory_connections.get('delay', format='array')[:3,:5] array([[ nan, nan, nan, nan, 0.14], [ nan, nan, nan, 0.12, 0.13], [ 0.12, nan, nan, nan, nan]]) To suppress the coordinates of the connection in ``'list'``, view, set the *with_address* option to ``False``: .. doctest:: >>> excitatory_connections.get('weight', format='list', with_address=False)[3:7] [0.123, 0.123, 0.123, 0.123] As well as weight and delay, :meth:`Projection.get` can also retrieve any other parameters of synapse models: .. doctest:: >>> inhibitory_connections.get('U', format='list')[0:4] [(2, 0, 0.04), (6, 1, 0.04), (8, 1, 0.04), (9, 2, 0.04)] It is also possible to retrieve the values of multiple attributes at once, as either a list of tuples or a tuple of arrays: .. doctest:: >>> connection_data = inhibitory_connections.get(['weight', 'delay'], format='list') >>> for connection in connection_data[:5]: ... src, tgt, w, d = connection ... print("weight = %.4f delay = %4.2f" % (w, d)) weight = 0.0094 delay = 0.12 weight = 0.0113 delay = 0.15 weight = 0.0102 delay = 0.17 weight = 0.0097 delay = 0.17 weight = 0.0127 delay = 0.12 >>> weights, delays = inhibitory_connections.get(['weight', 'delay'], format='array') >>> exists = ~numpy.isnan(weights) >>> for w, d in zip(weights[exists].flat, delays[exists].flat)[:5]: ... print("weight = %.4f delay = %4.2f" % (w, d)) weight = 0.0097 delay = 0.14 weight = 0.0127 delay = 0.12 weight = 0.0097 delay = 0.13 weight = 0.0094 delay = 0.18 weight = 0.0094 delay = 0.12 Note that in this last example we have filtered out the non-existent connections using :func:`numpy.isnan()`. The :meth:`Projection.save` method saves connection attributes to disk. .. todo:: finish documenting save() method (also decide if it should be write() or save()) need to think about formats. Text, HDF5, ... Access to the weights and delays of individual connections is by the :attr:`connections` attribute, e.g.: .. doctest:: >>> list(inhibitory_connections.connections)[0].weight 0.0094460775218037779 >>> list(inhibitory_connections.connections)[10].weight 0.0086313719119562281 Modifying weights and delays ---------------------------- As noted above, weights, delays and other connection attributes can be specified on creation of a :class:`Projection`, and this is generally the most efficient time to specify them. It is also possible, however, to modify these attributes after creation, using the :meth:`set` method. :meth:`set` accepts any number of keyword arguments, where the key is the attribute name, and the value is either: * a numeric value (all connections will be set to the same value); * a :class:`~pyNN.random.RandomDistribution` object (each connection will be set to a different value, drawn from the distribution); * a list or NumPy array of the same length as the number of connections in the :class:`Projection`; * a generator; * a string expressing a function of the distance between pre- and post-synaptic neurons. .. todo:: clarify whether this is the number of *local* connections or the total number of connections. Some examples: .. doctest:: >>> excitatory_connections.set(weight=0.02) >>> excitatory_connections.set(weight=RandomDistribution('gamma', [1, 0.1]), ... delay=0.3) >>> inhibitory_connections.set(U=numpy.linspace(0.4, 0.6, len(inhibitory_connections)), ... tau_rec=500.0, ... tau_facil=0.1) It is also possible to access the attributes of individual connections using the ``connections`` attribute of a :class:`Projection`: .. doctest:: >>> for c in list(inhibitory_connections.connections)[:5]: ... c.weight *= 2 although this is almost always less efficient than using list- or array-based access. .. _`make a feature request`: https://github.com/NeuralEnsemble/PyNN/issues/new .. _`Strategy Pattern`: http://en.wikipedia.org/wiki/Strategy_pattern .. _`Djurfeldt, 2012`: http://software.incf.org/software/csa/ PyNN-0.10.0/doc/contributors.txt000066400000000000000000000024761415343567000165000ustar00rootroot00000000000000================================= Contributors, licence and funding ================================= .. include:: ../AUTHORS Licence ======= PyNN is freely available under the CeCILL v2 license, which is equivalent to, and compatible with, the GNU GPL license, but conforms to French law (and is also perfectly suited to international projects) - see ``_ for more information. The choice of GPL-equivalence was made to match the licenses of other widely-used simulation software in computational neuroscience, such as NEURON (GPL), NEST (GPL) and Brian (CeCILL). If you are interested in using PyNN, but the choice of licence is a problem for you, please contact us to discuss dual-licensing. .. centered:: LICENSE AGREEMENT .. include:: ../LICENSE Funding ======= Development of PyNN has been partially funded by the European Union Sixth Framework Program (FP6) under grant agreement FETPI-015879 (FACETS), by the European Union Seventh Framework Program (FP7/2007­-2013) under grant agreements no. 269921 (BrainScaleS) and no. 604102 (HBP), and by the European Union’s Horizon 2020 Framework Programme for Research and Innovation under the Specific Grant Agreements No. 720270 (Human Brain Project SGA1) , No. 785907 (Human Brain Project SGA2) and No. 945539 (Human Brain Project SGA3). PyNN-0.10.0/doc/data_handling.txt000066400000000000000000000134401415343567000165110ustar00rootroot00000000000000============= Data handling ============= .. todo:: add a note that data handling has changed considerably since 0.7, and give link to detailed changelog. Recorded data in PyNN is always associated with the :class:`Population` or :class:`Assembly` from which it was recorded. Data may either be written to file, using the :meth:`write_data` method, or retrieved as objects in memory, using :meth:`get_data`. Retrieving recorded data ======================== Handling of recorded data in PyNN makes use of the Neo_ package, which provides a common Python data model for neurophysiology data (whether real or simulated). The :meth:`get_data` method returns a Neo :class:`Block` object. This is the top-level data container, which contains one or more :class:`Segment`\s. Each :class:`Segment` is a container for data sharing a common time basis - a new :class:`Segment` is added every time the :func:`reset` function is called. A :class:`Segment` can contain lists of :class:`AnalogSignal` and :class:`SpikeTrain` objects. These data objects inherit from NumPy's array class, and so can be treated in further processing (analysis, visualization, etc.) in exactly the same way as NumPy arrays, but in addition they carry metadata about units, sampling interval, etc. Here is a complete example of recording and plotting data from a simulation: .. .. plot:: pyplots/neo_example.py .. :include-source: .. literalinclude:: pyplots/neo_example.py .. image:: images/neo_example.png The adoption of Neo as an output representation also makes it easier to handle data when running multiple simulations with the same network, calling :meth:`reset` between each run. In previous versions of PyNN it was necessary to retrieve the data before every :meth:`reset`, and take care of storing the resulting data. Now, each run just creates a new Neo ``Segment``, and PyNN takes care of storing the data until it is needed. This is illustrated in the example below. .. .. plot:: images/reset_example.py .. :include-source: .. literalinclude:: pyplots/reset_example.py .. image:: images/reset_example.png .. note:: if you still want to retrieve the data after every run you can do so: just call ``get_data(clear=True)`` Writing data to file ==================== Neo provides support for writing to a `variety of different file formats`_, notably an assortment of text-based formats, NumPy binary format, Matlab :file:`.mat` files, and HDF5. To write to a given format, we create a Neo :class:`IO` object and pass it to the :meth:`write_data` method: .. testsetup:: import os import pyNN.mock as sim sim.setup() population = sim.Population(10, sim.IF_cond_exp()) population.record(['spikes', 'v']) sim.run(0.2) if os.path.exists("my_data.h5"): os.remove("my_data.h5") .. doctest:: >>> from neo.io import NixIO >>> io = NixIO(filename="my_data.h5") >>> population.write_data(io) .. todo: check whether we need to close the `io` object. As a shortcut, for file formats with a well-defined file extension, it is possible to pass just the filename, and PyNN will create the appropriate :class:`IO` object for you: .. doctest:: >>> population.write_data("my_data.mat") # writes to a Matlab file By default, all the variables that were specified in the :meth:`record` call will be saved to file, but it is also possible to save only a subset of the recorded data: .. doctest:: >>> population.write_data(io, variables=('v', 'gsyn_exc')) When running distributed simulations using MPI (see :doc:`parallel`), by default the data is gathered from all MPI nodes to the master node, and only saved to file on the master node. If you would prefer that each node saves its own local subset of the data to disk separately, use *gather=False*: .. doctest:: >>> population.write_data(io, gather=False) Saving data to a file does not delete the data from the :class:`Population` object. If you wish to do so (for example to release memory), use *clear=True*: .. doctest:: >>> population.write_data(io, clear=True) .. testcleanup:: import os for filename in ("my_data.h5", "my_data.mat"): if os.path.exists(filename): os.remove(filename) Simple plotting =============== Plotting Neo data with Matplotlib, as shown above, can be rather verbose, with a lot of repetitive boilerplate code. PyNN therefore provides a couple of classes, :class:`~pyNN.utility.plotting.Figure` and :class:`~pyNN.utility.plotting.Panel`, to make quick-and-easy plots of recorded data. It is possible to customize the plots to some extent, but for publication-quality or highly-customized plots you should probably use Matplotlib or some other plotting package directly. A simple example:: from pyNN.utility.plotting import Figure, Panel ... population.record('spikes') population[0:2].record(('v', 'gsyn_exc')) ... data = population.get_data().segments[0] vm = data.filter(name="v")[0] gsyn = data.filter(name="gsyn_exc")[0] Figure( Panel(vm, ylabel="Membrane potential (mV)"), Panel(gsyn, ylabel="Synaptic conductance (uS)"), Panel(data.spiketrains, xlabel="Time (ms)", xticks=True) ).save("simulation_results.png") .. image:: images/release_0.8b1_example.png :width: 600px :alt: Image generated using the Figure and Panel classes from pyNN.utility.plotting Other packages for working with Neo data ======================================== A variety of software tools are available for working with Neo-format data, for example SpykeViewer_ and OpenElectrophy_. .. _Neo: http://neuralensemble.org/neo .. _`variety of different file formats`: http://neo.readthedocs.io/en/latest/io.html .. _SpykeViewer: http://spyke-viewer.readthedocs.org/en/0.4.0/ .. _OpenElectrophy: http://neuralensemble.org/OpenElectrophy/PyNN-0.10.0/doc/descriptions.txt000066400000000000000000000074551415343567000164530ustar00rootroot00000000000000==================== Network descriptions ==================== intro to mention finding bugs, literate programming :: >>> p1 = sim.Population(100, sim.IF_cond_exp, cell_parameters, ... structure=space.Grid2D(dx=50.0, dy=50.0), ... label="excitatory neurons") :: >>> p1.describe(template=None) {'cell_parameters': {'cm': 1.0, 'e_rev_E': 0.0, 'e_rev_I': -70.0, 'i_offset': 0.0, 'tau_m': 20.0, 'tau_refrac': 0.10000000000000001, 'tau_syn_E': 5.0, 'tau_syn_I': 5.0, 'v_reset': -65.0, 'v_rest': -65.0, 'v_thresh': -50.0}, 'celltype': {'name': 'IF_cond_exp', 'parameters': {'C_m': 1000.0, 'E_L': -65.0, 'E_ex': 0.0, 'E_in': -70.0, 'I_e': 0.0, 'V_reset': -65.0, 'V_th': -50.0, 'g_L': 50.0, 't_ref': 0.10000000000000001, 'tau_syn_ex': 5.0, 'tau_syn_in': 5.0}}, 'first_id': 1, 'label': 'excitatory neurons', 'last_id': 100, 'local_first_id': 1, 'size': 100, 'size_local': 100, 'structure': {'name': 'Grid2D', 'parameters': {'aspect_ratio': 1.0, 'dx': 50.0, 'dy': 50.0, 'fill_order': 'sequential', 'x0': 0.0, 'y0': 0.0}}} :: >>> print(p1.describe()) Population "excitatory neurons" Structure : Grid2D fill_order: sequential dx: 50.0 dy: 50.0 aspect_ratio: 1.0 y0: 0.0 x0: 0.0 Local cells : 100 Cell type : IF_cond_exp ID range : 1-100 First cell on this node: ID: 1 tau_refrac: 0.1 tau_m: 20.0 e_rev_E: 0.0 i_offset: 0.0 cm: 1.0 e_rev_I: -70.0 v_thresh: -50.0 tau_syn_E: 5.0 v_rest: -65.0 tau_syn_I: 5.0 v_reset: -65.0 >>> print(p1.describe(engine='string')) Population "excitatory neurons" Structure : {'name': 'Grid2D', 'parameters': {'fill_order': 'sequential', 'dx': 50.0, 'dy': 50.0, 'aspect_ratio': 1.0, 'y0': 0.0, 'x0': 0.0}} Local cells : 100 Cell type : {'name': 'IF_cond_exp', 'parameters': {'E_in': -70.0, 'E_ex': 0.0, 'V_th': -50.0, 'I_e': 0.0, 'C_m': 1000.0, 'tau_syn_ex': 5.0, 'g_L': 50.0, 'V_reset': -65.0, 'tau_syn_in': 5.0, 'E_L': -65.0, 't_ref': 0.10000000000000001}}.name ID range : 1-100 First cell on this node: ID: 1 {'tau_refrac': 0.10000000000000001, 'tau_m': 20.0, 'e_rev_E': 0.0, 'i_offset': 0.0, 'cm': 1.0, 'e_rev_I': -70.0, 'v_thresh': -50.0, 'tau_syn_E': 5.0, 'v_rest': -65.0, 'tau_syn_I': 5.0, 'v_reset': -65.0} >>> print(p1.describe('Population "{{label}}" consists of {{size}} {{celltype.name}} neurons, arranged in a {{structure.name}} structure with grid spacing ({{structure.parameters.dx}},{{structure.parameters.dy}})', engine='jinja2') ) Population "excitatory neurons" consists of 100 IF_cond_exp neurons, arranged in a Grid2D structure with grid spacing (50.0,50.0)', engine='jinja2')) >>> print(p1.describe('Population "$label" consists of $size $celltype.name neurons, arranged in a $structure.name structure with grid spacing ($structure.parameters.dx,$structure.parameters.dy)', engine='cheetah')) PyNN-0.10.0/doc/developers/000077500000000000000000000000001415343567000153415ustar00rootroot00000000000000PyNN-0.10.0/doc/developers/adding_backend.txt000066400000000000000000000144001415343567000207760ustar00rootroot00000000000000==================== Adding a new backend ==================== Structure of the codebase ========================= PyNN is both an API for simulator-independent model descriptions and an implementation of that API for a number of simulators. If you wish to add PyNN support for your own simulator, you are welcome to add it as part of the main PyNN codebase, or you can maintain it separately. The advantage of the former is that we can help maintain it, and keep it up to date as the API evolves. A PyNN-compliant interface is not required to use any of the code from the ``pyNN`` package, it can implement the API entirely independently. However, by basing an interface on the "common" implementation you can save yourself a lot of work, since once you implement a small number of low-level functions and classes, you get the rest of the API for free. The common implementation ------------------------- Recording ~~~~~~~~~ The ``recording`` modules provides a base class ``Recorder`` that exposes methods ``record()``, ``get()``, ``write()`` and ``count()``. Each simulator using the common implementation then subclasses this base class, and must implement at least the methods ``_record()``, ``_get()`` and ``_local_count()``. Each ``Recorder`` instance records only a single variable, whose name is passed in the constructor. By default, PyNN scales recorded data to the standard PyNN units (mV for voltage, etc.), reorders columns if necessary, and adds initial values to the beginning of the recording if the simulator does not record the value at time 0. In this way, the structure of the output data is harmonized between simulators. For large datasets, this can be very time-consuming, and so this restructuring can be turned off by setting the ``compatible_output`` flag to ``False``. .. TODO: discuss output file formats. .. TODO: discuss gathering with MPI The NEST interface ------------------ The NEURON interface -------------------- The Brian interface ------------------- Adding a new simulator interface ================================ The quickest way to add an interface for a new simulator is to implement the "internal API", described below. Each simulator interface is implemented as a sub-package within the ``pyNN`` package. The suggested layout for this sub-package is as follows:: |\_ __init__.py |\_ cells.py |\_ connectors.py |\_ electrodes.py |\_ recording.py |\_ simulator.py \_ synapses.py The only two files that are *required* are ``__init__.py`` and ``simulator.py``: the contents of all the other modules being imported into ``__init__.py``. [Maybe just provide a template, rather than discussing the whole thing] __init__: list_standard_models() [surely this could be in common?] setup() - should call common.setup() and then do whatever initialization is necessary for your backend end() - should be in common run = common.build_run(simulator) reset = common.build_reset(simulator) initialize = common.initialize get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector) set = common.set ?? record = common.build_record(simulator) record_v = lambda source, filename: record(['v'], source, filename) record_gsyn = lambda source, filename: record(['gsyn_exc', 'gsyn_inh'], source, filename) simulator: class State run clear reset properties: t, dt, ... standardmodels populations: class ID class Population _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly _create_cells _set_initial_value_array _get_view _get_parameters _set_parameters class PopulationView _assembly_class = Assembly _simulator = simulator _set_initial_value_array _get_view _get_parameters _set_parameters class Assembly _simulator = simulator projections class Connection class Projection _simulator = simulator __init__ __len__ set _convergent_connect recording class Recorder(recording.Recorder): _simulator = simulator _record _get_spiketimes _get_all_signals (staticmethod) find_units _local_count A walk through the lifecycle of a simulation ============================================ Import phase ------------ [What happens on import] Setup phase ----------- [What happens when calling setup()] Creating neurons ---------------- On creating a Population... - create default structure, if none specified - create StandardCellType instance (if using standard cells) - check and translate parameters, translated parameters stored in parameters attribute - create recorders - create neurons, determine local_mask Finally, we set initial values for all neurons' state variables, e.g. membrane potential. The user may set these values later with a call to the initialize() method, but in case they don't we set them here to default values. Defaults are set on a model-by-model basis: each StandardCellType subclass has a dictionary attribute called default_initial_values. [For now, these must be numeric values. It would be nice to allow them to be the names of parameters, allowing the initial membrane potential to be set to the resting membrane potential, for example]. This of course causes a problem - not yet resolved - for non standard cells. These initial values are immediately passed through to the simulator. We set initial values using the initialize() method, which in turn updates the initial_values attribute - we do not modify initial_values directly: probably it should be read-only. Creating connectors ------------------- Composing synaptic plasticity models ------------------------------------ Connecting neurons ------------------ Instrumenting the network ------------------------- Running a simulation -------------------- Retrieving/saving recorded data ------------------------------- Finishing up, or resetting for a new run ----------------------------------------PyNN-0.10.0/doc/developers/bug_reports.txt000066400000000000000000000006461415343567000204430ustar00rootroot00000000000000================================ Bug reports and feature requests ================================ If you find a bug or would like to add a new feature to PyNN, please go to https://github.com/NeuralEnsemble/PyNN/issues/. First check that there is not an existing ticket for your bug or request, then click on "New issue" to create a new ticket (you will need a GitHub account, but creating one is simple and painless). PyNN-0.10.0/doc/developers/contributing.txt000066400000000000000000000227361415343567000206230ustar00rootroot00000000000000==================== Contributing to PyNN ==================== Mailing list ============ Discussions about PyNN take place in the `NeuralEnsemble Google Group`_. Setting up a development environment ==================================== We strongly suggest you work in a virtual environment, e.g. using virtualenv_ or Anaconda. Requirements ------------ In addition to the requirements listed in :doc:`../installation`, you will need to install: * nose_ * mock_ * coverage_ to run tests, and: * Sphinx_ * matplotlib to build the documentation. Code checkout ------------- PyNN development is based around GitHub. Once you have a GitHub account, you should fork_ the official `PyNN repository`_, and then clone your fork to your local machine:: $ git clone https://github.com//PyNN.git pyNN_dev To work on the development version:: $ git checkout master To work on the latest stable release (for bug-fixes):: $ git checkout --track origin/0.8 To keep your PyNN repository up-to-date with respect to the official repository, add it as a remote:: $ git remote add upstream https://github.com/NeuralEnsemble/PyNN.git and then you can pull in any upstream changes:: $ git pull upstream master To get PyNN onto your :envvar:`PYTHONPATH` there are many options, such as: * pip editable mode (`pip install -e /path/to/PyNN`) * creating a symbolic link named :file:`pyNN` from somewhere that is already on your :envvar:`PYTHONPATH`, such as the :file:`site-packages` directory, to the :file:`pyNN_trunk/pyNN` directory. If you are developing with NEURON, don't forget to compile the NMODL files in :file:`pyNN/neuron/nmodl` by running :command:`nrnivmodl`, and to recompile any time you modify any of them. Coding style ============ We try to stay fairly close to PEP8_. Please note in particular: - indentation of four spaces, no tabs - single space around most operators, but no space around the '=' sign when used to indicate a keyword argument or a default parameter value. - some function/method names in PyNN use ``mixedCase``, but these will gradually be deprecated and replaced with ``lower_case_with_underscores``. Any new functions or methods should use the latter. - we currently target versions 2.7 and 3.6+ Testing ======= Running the PyNN test suite requires the *nose_*, *mock_* and *nose-testconfig* packages, and optionally the *coverage_* package. To run the entire test suite, in the ``test`` subdirectory of the source tree:: $ nosetests To see how well the codebase is covered by the tests, run:: $ nosetests --with-coverage --cover-package=pyNN --cover-erase --cover-html There are currently two sorts of tests, unit tests, which aim to exercise small pieces of code such as individual functions and methods, and system tests, which aim to test that all the pieces of the system work together as expected. If you add a new feature to PyNN, or fix a bug, you should write both unit and system tests. Unit tests should where necessary make use of mock/fake/stub/dummy objects to isolate the component under test as well as possible. The :mod:`pyNN.mock` module is a complete mock simulator backend that may be used for this purpose. Except when testing a specific simulator interface, unit tests should be able to run without a simulator installed. System tests should be written so that they can run with any of the simulators. The suggested way to do this is to write test functions, in a separate file, that take a simulator module as an argument, and then call these functions from ``test_neuron.py``, ``test_nest.py``, etc. System tests defined in the scenarios directory are treated as a single test (test_scenarios()) while running nosetests. To run only the tests within a file named 'test_electrodes' located inside system/scenarios, use:: $ nosetests -s --tc=testFile:test_electrodes test_nest.py To run a single specific test named 'test_changing_electrode' located within some file (and added to registry) inside system/scenarios, use:: $ nosetests -s --tc=testName:test_changing_electrode test_nest.py Note that this would also run the tests specified within the simulator specific files such as test_brian.py, test_nest.py and test_neuron.py. To avoid this, specify the 'test_scenarios function' on the command line:: $ nosetests -s --tc=testName:test_changing_electrode test_nest.py:test_scenarios The ``test/unsorted`` directory contains a number of old tests that are either no longer useful or have not yet been adapted to the nose framework. These are not part of the test suite, but we are gradually adapting those tests that are useful and deleting the others. Submitting code =============== The best way to get started with contributing code to PyNN is to fix a small bug (`bugs marked "minor" in the bug tracker`_) in your checkout of the code. Once you are happy with your changes, **run the test suite again to check that you have not introduced any new bugs**. If this is your first contribution to the project, please add your name and affiliation/employer to :file:`AUTHORS`. After committing the changes to your local repository:: $ git commit -m 'informative commit message' first pull in any changes from the upstream repository:: $ git pull upstream master then push to your own account on GitHub:: $ git push Now, via the GitHub web interface, open a pull request. Documentation ============= PyNN documentation is generated using Sphinx_. To build the documentation in HTML format, run:: $ make html in the ``doc`` subdirectory of the source tree. Many of the files contain examples of interactive Python sessions. The validity of this code can be tested by running:: $ make doctest PyNN documentation is hosted at http://neuralensemble.org/docs/PyNN Making a release ================ To make a release of PyNN requires you to have permissions to upload PyNN packages to the `Python Package Index`_, and to upload documentation to the neuralensemble.org server. If you are interested in becoming release manager for PyNN, please contact us via the `mailing list`_. When you think a release is ready, run through the following checklist one last time: * do all the tests pass? This means running :command:`nosetests` in :file:`test/unittests` and :file:`test/system` and running :command:`make doctest` in :file:`doc`. You should do this on at least two Linux systems -- one a very recent version and one at least a year old, and on at least one version of Mac OS X. You should also do this with Python 2.7 and 3.4, 3.5 or 3.6. * do all the example scripts generate the correct output? Run the :file:`run_all_examples.py` script in :file:`examples/tools` and then visually check the :file:`.png` files generated in :file:`examples/tools/Results`. Again, you should do this on at least two Linux systems and one Mac OS X system. * does the documentation build without errors? You should then at least skim the generated HTML pages to check for obvious problems. * have you updated the version numbers in :file:`setup.py`, :file:`pyNN/__init__.py`, :file:`doc/conf.py` and :file:`doc/installation.txt`? * have you updated the changelog? Once you've confirmed all the above, create a source package using:: $ python setup.py sdist and check that it installs properly (you will find it in the :file:`dist` subdirectory. Now you should commit any changes, then tag with the release number as follows:: $ git tag x.y.z where ``x.y.z`` is the release number. You should now upload the documentation to http://neuralensemble.org/docs/PyNN/ by running:: $ make zip in the :file:`doc` directory, and then unpacking the resulting archive on the NeuralEnsemble server. If this is a development release (i.e. an *alpha* or *beta*), the final step is to upload the source package to the INCF Software Center. Do **not** upload development releases to PyPI. To upload a package to the INCF Software Center, log-in, and then go to the Contents_ tab. Click on "Add new..." then "File", then fill in the form and upload the source package. If this is a final release, there are a few more steps: * if it is a major release (i.e. an ``x.y.0`` release), create a new bug-fix branch:: $ git branch x.y * upload the source package to PyPI:: $ python setup.py sdist upload * make an announcement on the `mailing list`_ * if it is a major release, write a blog post about it with a focus on the new features and major changes * go home, take a headache pill and lie down for a while in a darkened room (-; .. _Sphinx: http://sphinx-doc.org/ .. _PEP8: http://www.python.org/dev/peps/pep-0008/ .. _nose: https://nose.readthedocs.org/ .. _mock: http://www.voidspace.org.uk/python/mock/ .. _coverage: http://nedbatchelder.com/code/coverage/ .. _`Python Package Index`: http://pypi.python.org/ .. _`mailing list`: http://groups.google.com/group/neuralensemble .. _`NeuralEnsemble Google Group`: http://groups.google.com/group/neuralensemble .. _matplotlib: http://matplotlib.sourceforge.net/ .. _virtualenv: http://www.virtualenv.org/ .. _`bugs marked "minor" in the bug tracker`: https://github.com/NeuralEnsemble/PyNN/issues?labels=minor&state=open .. _`issue tracker`: https://github.com/NeuralEnsemble/PyNN/issues/ .. _fork: https://github.com/NeuralEnsemble/PyNN/fork .. _`PyNN repository`: https://github.com/NeuralEnsemble/PyNN/ .. _contents: http://software.incf.org/software/pynn/pynn/folder_contents PyNN-0.10.0/doc/developers/governance.txt000066400000000000000000000111621415343567000202320ustar00rootroot00000000000000========== Governance ========== PyNN is a community-developed project, we welcome contributions from anyone who is interested in the project. The project maintainers are the members of the `PyNN Developers team`_. All contributors agree to abide by the Code of Conduct, see below. Contributions ============= All contributions must be by pull request, with the exception of quick bug fixes affecting fewer than ten lines of code. Normally, pull requests may be approved by any maintainer, although anyone is welcome to join in the discussion. In case of disagreement with a decision, we will try to reach a consensus between maintainers, taking account of any input from the wider community. If consensus cannot be reached, decisions will be based on a majority vote among the maintainers, with the caveats that (i) only one vote per institution is allowed (i.e. in the case where several maintainers belong to the same institution they will have to agree among themselves how to vote) and (ii) a quorum of three maintainers must be achieved. Maintainers =========== Any contributor who has had at least three pull requests accepted may be nominated as a maintainer. Nominations must be approved by at least two existing maintainers, with no dissenting maintainer. In case of disagreement, decisions on accepting new maintainers will be based on a majority vote as above. Contributor Code of Conduct =========================== Our Pledge ---------- In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. Our Standards ------------- Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting Our Responsibilities -------------------- Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. Scope ----- This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. Enforcement ----------- Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at pynn-maintainers@protonmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. Attribution ----------- This Code of Conduct is adapted from the `Contributor Covenant`_, version 1.4, available at http://contributor-covenant.org/version/1/4 .. _`Contributor Covenant`: http://contributor-covenant.org .. _`PyNN Developers team`: https://github.com/orgs/NeuralEnsemble/teams/pynn-developers/members PyNN-0.10.0/doc/developers_guide.txt000066400000000000000000000020661415343567000172630ustar00rootroot00000000000000 ================= Developers' guide ================= This guide contains information about contributing to PyNN development, and aims to explain the overall architecture and some of the internal details of the PyNN codebase. PyNN is open-source software, with a community-based development model: contributions from users are welcomed, and the direction that PyNN development should take in the future is determined by the needs of its users. There are several ways to contribute to PyNN: * reporting bugs, errors and other mistakes in the code or documentation; * making suggestions for improvements; * fixing bugs and other mistakes; * adding or maintaining a simulator backend; * major refactoring to improve performance, reduce code complexity, or both. * becoming a maintainer The following sections contain guidelines for each of these. .. toctree:: :maxdepth: 3 developers/bug_reports developers/contributing developers/governance .. developers/adding_backend .. _`NeuralEnsemble Google Group`: http://groups.google.com/group/neuralensemble PyNN-0.10.0/doc/download.txt000066400000000000000000000015641415343567000155470ustar00rootroot00000000000000========= Downloads ========= Source distributions -------------------- The `latest stable version of PyNN`_ (0.10.0) may be downloaded from the `Python Package Index`_. This is recommended for anyone using PyNN for the first time. If you need support for a previous version of the API, the packages can be downloaded from the links below. Older versions: * `0.9.6 `_ * `0.8.0 `_ * `0.7.5 `_ * `0.6.0 `_ * `0.5.0 `_ Latest source code from GitHub ------------------------------ See :doc:`developers/contributing`. .. _`latest stable version of PyNN`: https://pypi.python.org/pypi/PyNN/ .. _`Python Package Index`: https://pypi.python.org/pypi/PyNN/ PyNN-0.10.0/doc/examples.txt000066400000000000000000000006741415343567000155570ustar00rootroot00000000000000======== Examples ======== .. toctree:: :maxdepth: 2 examples/Izhikevich examples/current_injection examples/cell_type_demonstration examples/random_numbers examples/random_distributions examples/simple_STDP examples/small_network examples/synaptic_input examples/tsodyksmarkram examples/varying_poisson examples/stochastic_synapses examples/stochastic_deterministic_comparison examples/VAbenchmarks PyNN-0.10.0/doc/examples/000077500000000000000000000000001415343567000150075ustar00rootroot00000000000000PyNN-0.10.0/doc/examples/Izhikevich.txt000066400000000000000000000002761415343567000176520ustar00rootroot00000000000000A selection of Izhikevich neurons ================================= .. image:: ../images/examples/Izhikevich_nest_np1_20170505-150315.png .. literalinclude:: ../../examples/Izhikevich.py PyNN-0.10.0/doc/examples/VAbenchmarks.txt000066400000000000000000000003461415343567000201170ustar00rootroot00000000000000Balanced network of excitatory and inhibitory neurons ===================================================== .. image:: ../images/examples/VAbenchmarks_CUBA_20170505-150538.png .. literalinclude:: ../../examples/VAbenchmarks.py PyNN-0.10.0/doc/examples/cell_type_demonstration.txt000066400000000000000000000005041415343567000224750ustar00rootroot00000000000000A demonstration of the responses of different standard neuron models to current injection ========================================================================================= .. image:: ../images/examples/cell_type_demonstration_nest_20170505-150320.png .. literalinclude:: ../../examples/cell_type_demonstration.py PyNN-0.10.0/doc/examples/current_injection.txt000066400000000000000000000003341415343567000212740ustar00rootroot00000000000000Injecting time-varying current into a cell ========================================== .. image:: ../images/examples/current_injection_neuron_20170505-150317.png .. literalinclude:: ../../examples/current_injection.py PyNN-0.10.0/doc/examples/random_distributions.txt000066400000000000000000000004731415343567000220160ustar00rootroot00000000000000Illustration of the different standard random distributions and different random number generators ================================================================================================== .. image:: ../images/examples/random_distributions.png .. literalinclude:: ../../examples/random_distributions.py PyNN-0.10.0/doc/examples/random_numbers.txt000066400000000000000000000003601415343567000205620ustar00rootroot00000000000000An example to illustrate random number handling in PyNN ======================================================= .. image:: ../images/examples/random_numbers_neuron_20170505-150323.png .. literalinclude:: ../../examples/random_numbers.py PyNN-0.10.0/doc/examples/simple_STDP.txt000066400000000000000000000003021415343567000176660ustar00rootroot00000000000000A very simple example of using STDP =================================== .. image:: ../images/examples/simple_stdp_neuron_20170505-150331.png .. literalinclude:: ../../examples/simple_STDP.py PyNN-0.10.0/doc/examples/small_network.txt000066400000000000000000000004021415343567000204250ustar00rootroot00000000000000Small network created with the Population and Projection classes ================================================================ .. image:: ../images/examples/small_network_nest_np1_20170505-150334.png .. literalinclude:: ../../examples/small_network.py PyNN-0.10.0/doc/examples/stochastic_deterministic_comparison.txt000066400000000000000000000005161415343567000250730ustar00rootroot00000000000000Example of facilitating and depressing synapses in deterministic and stochastic versions ======================================================================================== .. image:: ../images/examples/stochastic_comparison_neuron_20170505-150418.png .. literalinclude:: ../../examples/stochastic_deterministic_comparison.py PyNN-0.10.0/doc/examples/stochastic_synapses.txt000066400000000000000000000003251415343567000216410ustar00rootroot00000000000000Example of simple stochastic synapses ===================================== .. image:: ../images/examples/stochastic_synapses__nest_20170505-150345.png .. literalinclude:: ../../examples/stochastic_synapses.py PyNN-0.10.0/doc/examples/synaptic_input.txt000066400000000000000000000004561415343567000206260ustar00rootroot00000000000000A demonstration of the responses of different standard neuron models to synaptic input ====================================================================================== .. image:: ../images/examples/synaptic_input_neuron_20170505-150337.png .. literalinclude:: ../../examples/synaptic_input.py PyNN-0.10.0/doc/examples/tsodyksmarkram.txt000066400000000000000000000003361415343567000206250ustar00rootroot00000000000000Example of depressing and facilitating synapses =============================================== .. image:: ../images/examples/tsodyksmarkram_nest_20170505-150340.png .. literalinclude:: ../../examples/tsodyksmarkram.py PyNN-0.10.0/doc/examples/varying_poisson.txt000066400000000000000000000004441415343567000210030ustar00rootroot00000000000000A demonstration of the use of callbacks to vary the rate of a SpikeSourcePoisson ================================================================================ .. image:: ../images/examples/varying_poisson_neuron_20170505-150343.png .. literalinclude:: ../../examples/varying_poisson.py PyNN-0.10.0/doc/images/000077500000000000000000000000001415343567000144365ustar00rootroot00000000000000PyNN-0.10.0/doc/images/ac_source.png000066400000000000000000001006321415343567000171110ustar00rootroot00000000000000PNG  IHDR ,;-:sBIT|d pHYsaa?i IDATxw\U WLܣđ{Ά rWO-s{ Ms/Ds "{.wq|hҤ ^xL2/_?(]"""""%VZ_4QF@ؾ};Ke4kL4e( 㑒ܹpQ{7V\WW\ZH@5j___xyym۶~Qv'-[6ԨQGѳDDDDD.˥`yyyo߾hԨ|||p!ի(QpU@ѢEiH"صk&ƃÇ@;%&9sӌ(DL:u~ ={D@@ƎM6ƍ0a£ݿ#Gg3gG?7W^@@@-[oLdjՀka8^68}}^{s-ZA0>H+n(Fw۷oGƍ-zӧQb ^^=!** zjt;wDxoΝ{n?ϴcccQP!Gܹܧp!pP:m1o5CQww#c1>ii`U\ϷE1%Jٳ44+WXbwHG ^^E[V^J$Wr 2AZr{V?1ZQJxzzر'%%!22]vUDj3% f(kJ5,]*%Kjz*J΍A0>HKNqqq|/,, hٲiӦXx1޽-BBB:uKq."pE@IƸqV}` O SrR2AZs^zҥ {̞=F۶mQdI5N07oDÆ 1k,|ТE 4oܠ߀(crRoJN ]XAA@j*P:m s»uS=2VPX֬N{ tҵkWDEEaҤI:t(6oތ,Y&Ñ+W.1sŀzjzOdފ@@ٲ괷l7/+U LYl2՛G2Ň:1>\ ヴ ȸq[n1} Uկ_v½{p5ORku/ez.괧([oT&'su+xpA0>H.`Y,n@;Gq9i4?k@WP" %`M9FcùC =0!rB+V/ *΂)EK;J (S H]a|9۷剒Zӯ@4mWMuO7P=2a|^90 )8`ߐ޽^ 8L~eu@  0!r2AA@Z7wH{&yJպ:푱ҫ~eO2 A0>H/L@ȃ2Үzmn T/^d;wdDڧ&[li s'& DNd6]dvqc#l <|iN{ @FGb|9"'v-P O~! ӯ~arT.N{{ IÆO~dIGal{wϿ:qRRdzޛo>}77/;v J Mrn2AzcB$bc՟~U:?zm1➽=͕ sb|9"'*zmn]EH$/N{Ip 2AzcBEZ.&'$DJ)ط/c1> 8u ڷgi+eO:{>ق|dヌ ^^@&굹?PL"&O.[P:u 0AuFヌ [l ̩^u'$D*ͫN{p%2AF`B_͜meÇ@DKIƸwWzͥKw@06ǽ{5 0!rp6ȰuVܖ!cǀ/"w.!@2y5s`|92 "*si VOOfM$c+@ɒ@" sa|92 "!y3ge\lOQ7սsk`|92"kpN Q.Dbb2meJ   #1!r`2mnON+W#l2bm|DGTƇc|92"*>]KǏ@I ٳfdV9y0> :^F*,~e͐ѣT^XkGŁme5˫W 1!rP@l@fgHx;:16l׌3eM2S sd4& D*4h@ɒ 9Yg}&Gf<}–!!R(݌AFcB՟^*O8$`&OW0>)>>0>\"mK76I;wߣヱd"j i*{B!r" =-I$,GF^^@r s9EDq/NRm>X^݋^<<2~T s(<=鯿dwZ-SAY9`T>u*jڽX~*TAj|TĥG@ϟwwtbccx믿ZYRHE2ݠ埱tIreu4J2e'v]H3'и*@XFJ J9۷3F%mdǤI<4Ğ={pIpssCQreԫW5t,0n8)S>{&Mz{ŊӴoDO ?ri''ӯAiϜ݁m%+߀`R%Md,ܽ+7yZR`@`B_!CcQQssS9(}2Usg mMR5~:f̘ ŋٳ#_|HMM۷(Uzw}~SV/f>___t]Ӿsw/0{m?/-K"$ɇɗ_:T>]Ou| 0Pn zz9 f*@.@O?qθ5d+LJnSRHII}))){!ȦM2uk>gSU %K"6"kP{횶}q56-< *~&ׯC}>yVǎ2ft`bd+GJr=sG㓺TK@v؁ŋO?/^/^%K?TjԨ|}}其m":::={^^^AѢE1zh$''k7BC5]m'O>>@Ѣߖy|г'Э[ɟ ј޽9!!R:o)[29c? dpt0>WӔ3glkZo>knV]vسgZx3f ((~)n݊z!&&/__~%/_Enݺ?~\L*7.DJ$JLJ)9}Z>T]VZCa7gΜN:SN-ZA0afΜgs}=z`3gu=f^={v@n-ǿDطySq@.ժi6R͇Z6Ӭ0l[o%Kj7WR{b@n0NZ&3g!C,{<ȴuk`JO3?W*UbLT={6.]ٳgc̙(]4w=zw*W["l\D IJ͛7-:.= %/_ %*k5ʔΝ-_ce֭R"O{S W[bt`H?U0>d09!j2p@ 8/_Kbĉ8q"jԨ=z[n6޽{ϣPBE%GL p(`KA8KMV{o-e~-|F.]dWQdj>E௿ѣ%sϜrl_f- )+Ve5}'UEq믭,eTIFSRG3d'ŋ㣏>Çq)|W{.>*U8tɺc14I@ҪTƌgb:t(N>d:U^C.]cdWVr' i*U e˖}"11QuQQQ4i͛7c8xӪJ. `ڵ_#))Z"-=x h|Ē'Jі_ }BcTgպ=mX `zq5 mWQ;>{|,F)IH_V%//<ǂ ТE /^Æ C\\FGɸq[n1}gt.]+Vq=ܽ{5) rPA7l(WxUoY?[%I|?T9mn >åZ~[ iʩ0>#}Y)>݁^r4I@vZt ~~~۷/>bΝ8<Əowe,"W*UM))] xRVU嚔bg& ͚5 PvmXbڴiOkVeR"@Ktjd)wش1IL峲#GW$>ݥ6mm-Naǂd]YDzb|T"#29.U~/_Ɩ-[ЧOfKS,(,LJj_T$ Z\ m^zI] 3fצ |}m+o'Ѳx3GxjXvW~YvYDzb|2sS51bZ 2Q*k' D@l#c4hn9rb߬,$DwVTu ,!۷Om2wߗ;+6b||ƇȊQ|qӴ Ν;ѷo_^:UjժV'rh M%3߲l9߶v 8xPۀma]dںw'Y*#cUrhL2 6ʕ+q˗U@(P@9mdqw-]^=۶T@XV-&.wsPQ{LP:r~ wOAX̍g|< s,Q~}\rؾ}3_V'rxrj@.ʩ]3֭ڴ֯^yEhUiR;>e E nVlz٠>G֍U%1ʪɧ,IHH@Ϟ=!*6 RLu֮]NAT_;JL6oڴ8.kٲƒ'ǎC-o0˕ sjw GempNY=>Wkc}Y/qe.T27)˶2nn@Pq;n JsM;|XԬiS,6`$gh{GcTƇsQ,?|ضϓ>4K@OM6aɸyV!rJaa2OY R(r Ց#bSpq4Sʕo VD=wnu}Z~8&8a Ƈm9c|!Ӱ86%Kb9r$ *///ɓy䁏ϣW(4T6˙S\ {tO '\u}Z@2",F7^ܦ6 tӺpAa""ONX@ :d1>=#GZL@fۓW0aJ(ZjMD-`_k*[/n#"dfg dmm;Gez'#Ç;tnkӿ?0utu1 0>,Ў#Nj/"\kٳo`ݺupwt"q#̦`}ɒըL';7k$ 2AUv7*O THsf`|hǑV-;ԭNH]eIIIx7|=%8XN="Ez@S+۷9֯ٳ^FsSSՏӴ5mל$|Yca=Ƈ5>VzNr\e[Ν;j)%%6h_Gm=p<[Lc!}O\&hmFȎm䱽o)z?·XΜ2qi|78q Ç#..7o|(+ٹS}H i׀ݱiѣ={6ԩ???,XB iux" (􃌞(ٺHfON'Tj [=q~lpM IDATXzmf>ɓϫۦ%??^֯uP&u0>T<0{WH]-B=zta,JE#^ ^xA6-Ѻ5'(MZƎU6^SfnW"eӯ@lƍSmƇc|<^=:phX6I=% |VM9夭܌.Zr0dmZ"[6ORߕl*I? KOW#pSO#WuLd|؆.gʕ%Aڳ #b*"\_x.@O/_ 6֘'з/p&dnP"P>dzBZzqℬawxZ@rk+`|؏gwwڳGH]% &MB||՟}6&MV7VpGTɽ9Lr'L_٤HR>jLJ9'O7:&o_+rzvgZdǻuK֛5:fҺ5Pk1>3K/ɵӰj ;zH>|~)͋{bXd ߏ >áC={`˫/سzzI#@jư DTG$9ԍ۷e#4=I_X:TƇsȓVMFȱhRf͚YM9x24q6wyU ٳh%9\X>>-0>,*Ѹ$ W!cd$<<7F޼yڵkcʕϼoϞ=xW兢EbذaHHH0*֬VɟV b#F6+W0Zo̖ngE2{v{b=EVڴa|harh~8uJcuTM@nݺfs D-#GL4 ?4h*D&M)S`_ѩS'zN`zcG폥")XvѲEvQe En;&Mы+Ȃcg'Zb|~}ٿjAQ o"EЪU+ȡuZ .СC1e5j (۷Pti 8[lAf2-[Ī-W϶6 ̚L et,l,rĥO0M M L8 -|9Ck9k| 4l(k2 8d9UG@:vpt~~~۷/á4n֬YPcǎܽ{7ݾܹsٳz ootkefy}Ie(K8"R}s:"7oO'Zh6tHNY0>0ϕ7dݻ,jdbɒ%xװd4oŋLj#pa5p#$$%J ,ѣG?_HNNFڵ|lPF q d䆸cG}Lͫل0#*V^{͹Q:$]s&bצZykz aG@Ru!˨=w֭֯_k׮a̙PM:uƍyv¥KЯ_? 0k֬AV0~x|wU@ѢEiH"r}%ײq<]cuH{1 ;&b_.*mtO,|Tzu#Iq:;7ylYÇGeLѰf|8>Ƈm*TPEӴ V1x`ر/^w}\r믿F +Xܖ(HLLݻuƎo۷ŋѲeKL6Q@kVr 2g%M,] TGXL/N{TɑC.zcG)g 5SSzMl^|;uEtiS[V;;SnƇsx I@Xx-Q>,\m۶(ؿş߱crmٳgr傛=5NٵkWܿԪ\[gܹsgڿE!88x 8ؘ@P( rd:S`lNvirzMBv MS|8l٤X2=ґ \ou#W7ߔ??WP:T/bҥXl?Wzaq+W| o)RPX1;w~O=-\0eMSLSҺz*+fq?D`` <hdlBL嗍9=%4ӣ(K}['OՂus|g ղ;:G$..+WҥKw^xG(mxzmgj׮hĠL2oZQ[:WR8x :ٴ!)) $9KeQtɒ@9mRs"Pmj饗dۼy$$惟|bhҕ+zzwl$܎~/es̻w%>>7Ƈe=>eڷV&MruUCݻXhZjŋ> 0|p:t'O_|aua.]~KS'55(PjժEӦMxbMSmѢEHHHfdXCWO@^}`zđpe {5k&g)vQ;>vႼ: & yԽxQ^!>:uQ ݓ3>HGF2 k*IR5iҤ /_ׯ#00M6V0tPcĈ)0`Y&Ñ+W.1sŀzjzN(0P*,qME]s :tf̐Dđ\uyU3AxQ*ͫ^Zɓ;W6s$-1>0US+WTѹsg̙Sf)Sʕ+HLLDdddUׯ]v޽{v~'xyycrVGJe͛Jtbv 6l0'OZ݋r횼1E/m|*H ޽ʆQLO"/QBg& ['Oa917=mzU*D>صqS!C9>I'b|I+e.1RS|A9aC 5h5'(H?g^ P9 DΒ=3fuȗ^u2j(mJeFKZ|ܿ/I jb9 @V bo2,ڡwI ycq* e{OuMĞ=6,\oQÜ;l)P6>n1nx\exsx9Y3 1!\&'ў=͟Le M2Ց?/}5jy9׮; fgu=άYclmtn5GG^1>D_ǬY@G :d\*& LiJFC֭y\*_kWv Txqԋ2wϘ>$$ӵ~WP3A5@8c| L.Oa }2> 1W}HE & (9-'vKsȗO2b@  XMDQdөd缁#emΜ9.MGs}JEÁ?ԦoXX9?W)Xis'T(SFM, &k5p2:V #}mD OQ54B:]ܭ[[>q~ |=ТP>̬?Zhv{- t|~OMѲ%1>F||$XSݓ inb)ugϓKesq=&N&9EW(cH9a*b] DF苣0Ňe4(V̾_ѣgp(RDRO*==& ;^{MvP7ؾgaԨ't|P=QPe=ѸqSK))RY3XhOLJS(Bʗr~ ܸLѼ9РDz#s~*?`tO& UdGv7j{W6m1iP(\}^c8K'O&h{kIeZ_}%7'j{%KSΆQ2p޸>& PI[<<$I $D_BI얗9=G_\ߗRuhs [p^&ĸJ|.,OΞAo: GY#>>\6~tAnݾ-k6xCv,mThƃ1m֔0]G@ϥk~}mK2UHs9jcw6?[wpNq4e @2_d=^m6m)Xٲw\sL'X[ߗ# ICCeΟ?Q>_Ww$+Gܲܶm2NMɍ'*۶=c|׮#G@zR}{㺘f6l^x(YRǏ^yE "5֕)6&6VrHzڵ[>@Qy*Z0괩6Wzm:-Νei]RS%>d#b|XGww%/bBOqcϖ X\KNZ EFWz00s#Ol ̙xyߞ RMS MGe>}9L.#sStG~sJNcBzҹZ*^\ٴIyqq24ޫcFLV UCbP_/qߗQJipblr`Nl_[ǎСR1>3@36޸& ;U/2!!O)#7zBejY'm:6m3bqmmLXQo;"77X+5U)~aͥhAA+ ƇqYʺ{Z&Sҩh͜oy׮~]vM XMOEhdU+S'A>/1; ;Clx୷iwH|$&J|a|Yxq`25rѽq-L@HT%UT2MR_?u½{5@C..Uzm,܍ UWBf{ /,O7lsqq22{挌>69S|4n,kA~:޸& x_Aww % R[;bԜ{\ -s_yEn"&N!+9#{%j/5ݿ5#W.)ܴ<=x Ԫ%eUkC0>С2{ Tw䈬0Z<ž{WZؚ$'={OxSMi#5uD)e_xA*e]$/F.RpaaH>L=;nY%>r֮ƌ&L9SQQ޼ Hi@@Dh::Ƈq[|֫0oѽq~L@Hu rec_׌=;]$7!!F m& ˖MnZ.́H`lѧ]=paG\}PZRqNbE1+P@~9#7&vM3a|؇a=g KF9|^]B`~YW0 ի}>Y|W @l5T['DsTqxѢBVJPM*WVOVM֠1>c|0>qpwf̐RÇ'O>.ٳ3Tẇ5Z~0` 9v~"(OF֭ok~.ilB䩥3}5:y:y^m0>̷8o|Iuw@2F̹p *>8{qzy6n IweG bb\@Lq#-_{g3O.Ja|9g l.{T&19%rL@HU莒4o.}۸QF@vjGS.Sӧ{}dCdp-2W^u:ɺ_9N"U;&SZn^oh 2A0>2#/v>P%Eq(}Yb$<<7F޼yڵkcʕO_3_Z2 Yh-=:Z-Sȹit1>Ƈ7ѐ)SdSJ ĀмysL4 8}4bbbys=I&=bŊUpF"k*2%Lli2*2yVM*qv-jt/K' .\{gCbʔ)ݻwסgIQ$idM@9j'YD+c|9/ݲW);liNUYKڳf͂(;v,ݻP̌)ܽ{W.XI2A0>Ƈm<<-e_X`|yX8z4P&P о=RM{N@(Q>>>(X Fn"rYxyyEѣl@ϝӉD_JT IDATZu'TYVu c|9 ɶ۷ˈȭ[_˾1 JTÆ? g Fr)XQQQD~0rHT^k֬㑜'>zoѤITZ XjƏgb U=ɚ2-1:dC}9sʾd JDEvGNԭYǟ˓G-*_ ˦O5nlf)I@E,zoΜ9o7oĴi0j(x{{ΝD=z1g >u5{̺u{3;Ypiuɓ5e +&da|hCninSRΝ ^k md#hg4;v@c SӧObŊȕ+߿nݞLv튍7"22>œ9suLEPWz|eE݃vmCrZ <5SKժ2D $0>Ƈ<<%+3;87ܪlrulڴɢk>>>X"Ν;DdK1ƍѺuk[mڴɰ{#F?LƢ \ӌwV}vڈFLL ʔ)W\L[>"""""KWҥ ~{T@U >>%`pssC-4 s[mM4IZj ݻ믿>uat ݻwGrp}]{QF """""4k@l/+V͛7#G>0… 9r$s9\ d%& v`BFQs"`ȗ3~j"&-y-n;.C0dxeҠפEQ_提/W>Lm1*w)>^;6|vbPAb*re˥AI/<9<؁ !!)cPAl2|rfgOQ>yeUǤ{ap`chGX"Vw^˫cd& v`BzvZ/i37`N9^=vXe-njWzikqV8vZ/w[WKj-??HOL@}3-FTqnݿ:b=Xq%TjqH]oGE͐ =6h%+c_>,{k8.??HoF{{fȗ+ºuh=Lc:NŝByvkysņf7ʷ ͎E;ppy4lߜg; yg))K-kֿ.%뜿u|9a[m(UX㦤uoc_˰*^ux sx 0Z̝<=h?95tC`t Art=>ϝZkpξ;٠dt_Az(By<9<\j VBBkl燻;,X`gϟwwtbcc59ѳͥo"%5[{o<=4-WǑGt΃;h%S+\q|4/Vu±t22K% qqq7nΜ95juǍŋ?Ew2nk% KvXi*6JBn_ƞQ*o)#w\㍥o0>d\*)V]'OVZ{O|a~O"}#lވUVJ*Fwƺ憀e]>16Foʎ+|{#{ͥo2> 9C'N`՘5kfϞ5kɓ6={v., l]ڢ( 㑒bs?1?r>ퟆZPsb)?>}S148s ya??a(:۶mһwo%_|[_Szl۶([nÇe8q/"Ə-[ZuCᥗ^ѻwL߿j*lܸ5:Cܹ%J<V"5Nsj+ߋ\rݥ 8 3Į~RNp{cO=C7 ŬCg?rN9/// 8-ӧ1k,ox& ٽ{74hAa̙j3~ǂv HeVn] X?u56 >m&> I 21??門(`[nY(QQ?p*>ޭ.3;+[sE ;2q'^Сrc|ïm~@x sx GpexrJmEqΟ?ϵ$\|Kar3JG}_?mQFw$$^(4oݱZ;`8{q9<9<#s$55[lo ???tC59޵kpi$''?^\\3 CDD ,5f~ K:,AlM"EsmO\Ν;+}2k,eРART Kex;/TǸ+`tW^c2Fwe쾴[q㮌1?17|ctW\dtO@1c(+VT???eС777e͚5v_tGɌM o+|J͚5y*ٳgWJ.{f & dJeWS*kcra$QN+2aC/}xT\=btWd t+ /28\r]vٳ'5kOOODGGbŊXz5:tpRl5tP̍rUիWǰaPHt HIIyb4U%&'p-Qۻs[o%RLd',5FW,ca Y5_KEe ٲdK $K5T[~={>syݼ}9wf byr~~8rS¦O>k&q ?"[|Ǒ;ť Dwg6qO,(@\z=¢Ep-?,‹% 9'AEw:N&LҲ?qc*n}  :;jSK+ăUb=@ǣ^555?{!&&(L:ǏGQQiXsu zC|ǩ3vθWoy~~O~BSg>bUw_2"bZ/DϺPq FOYY.\={χP(ě7oj7ozjDEE!::999ؾ};&MTsrr`9rի֭[nݺsԔL=/.<믯G8tlґ8Z}b˅׷Πq2qo\ܚ~ |GsԜ믯oA{㐏P/nD***ϱcddd $$CYYY_KKKu%bFBHHzjdff>CV]YlsئWj a(^︙~JZ148荈~@@|Ԇ-t[`q?KOqq1`1v q۹sgݿ{dee1===W_s?}@H g3j"sG0W\wzˇLL1(28W-7z1ZfwÔï`1|G!S^z$''#;;Eٶm[_ƍիv؁'~p{\riiiڿk޽PS5E>L\)A8hjIfmqpw]kwz1`[<~V;yy"41&¸1q/#4hw$ݨMăH&to`9s&&&5kq\xQY`eeUgϞ(((d(~zJ9CW@ӎOKe-n.$_@}R~x5fwzg8ޢ o> oyoFNN+=RRR,,022^RbdfxaF6;otu`plJKünֶ|McaclƑ{GSodg`eF;oz4ru ao ;w+DQQ44^اYuaa_ ppp@HH3ʃWVς!(7ʡ+ûamCZ `橙Tĺa;ΰokoO}ܢ\ Nς@뇯; 8vpYc3\QH-6133C~~>_D(J{}HB_#88aaa 㥞Q -/ ; $'; 5^B&Č3~<41GXؘ88GnF( wGă`Z|ǩq  ydٲeؼyNz##jOJOOӅ5AqAw޼*|? _8PGnF_a8p8.318vp;NѢa 67G|MNQ9 F W Wzǁ DiӦر#lmmabb*mذγXZZ"""[+[EEEA[[m۶  cډi;.Դ%sߟť`W+ov Nuz~{b5Ws݃ǧ׷r _Q~TB9 ͛qmɓæM<>JKK%mNNNÇ%m/^aoo_*HKEo#\plkQ `㈍(bs"s/]ZrX82w!n`\9q%?̀f@b߹rӦMɑN'O b׮]HII H>}0yd$&&B__`aɒ%Rϩgͱf /¸NгYODAIA-'q-CCn;&1(#DAIV6b58Vh"X L=>[ĴSoQAGuhD6R֭[ooo89rXx1rrr=m)p)76l дiS\p|Tq ; Ls2Xry R_bV.n35qd7`@?Z@;(KhP5K̀ruorr2b1b1PVV&sl+\QF@VVq…joNHޭbTrBbV"V]Ysu{75Cqނ\,88u.Y __%6K`،87ob|ǩsQQXjׇPAf@ -Z%K$+KDjj* H XZbAX Y<&[2܏SNߏ )&Xe [bR%Qa4ܾs#7z7y}aŸu8u>vw};ܠf@ ~~~طo7oA'ODbb"ӑ8q/FѲeK8pI>3 yUV6e2խˑ_쀚 -XP_L`J|,i}j$f%"!.KKlUVPcE /@@3 Gf#445aoo 4k ͛7GΝUVA__HLLbOTD} "S#xɛ7"b~u;p:Y3,8Rwm,?XKCKۿ.9EI͸+z!v;ܡT1EEEK>ڷoݻCSA.]l2ھw;vã?MVgffI&άH;w5%sߞ;no+92zl DA]EHrkX=+̪iť =%<'_s̅ ?\c%*W@/KpsMO@ j(xDѿ;BCCw۷oȑ#q6l(| lW3p;ↈ 1So;#b˺1- 1\v A՛ʛ% !+>\Jf@¬~êU0e\p-[Ă5[%cĈꫯ*=44~Mpv IDATUw Hug *VlZ,VF/S'bմ/ceJ|ױ*j+Շhia6\{z "]{z ] Nݔ?]"fr!bڴnडOOODFFٳg| PV;ֵ̀@}z~+bbeLE:Xd8 aW,^q'q>ZAI&=/*+Zq3&q>Z(LDf|Q(7 fҿ5; 3C۶mSgAoݺ G&PFɤks,:U?A{/=3Or`瘝 qa}3?댯B|#=?2?:K($==FFFU+޹6&O ___=z ,@xx8:ˬh>4*2)9)Xp^9pAqA8b\ [hj`~$g'csSkbKhoО8 G]E{œ'{VpaǺa膃uqt QHaaakTUXo۝ &K.ř3gK,_FwssqoBd:6鈵ŦMM A2xNLsGgLwաIl7p qj,9;SOsGgLwΠ~[cȽ#|ǩdxSG'L>8 D3 GaB!Iw8|Vexk2Rᛞߗ恮]Ҽ2ƇG2zKZyvĹ0T4 S=ģ=ocSӒ^k#LPW46F}GC _8heeU파=*==`ll\c7o<~ʪ3 @yg[3"=#Z{gG̳\v =qq:z+,Xbܡqs>/Dlz,"&Gf#(dC3 G=zj{j"Unp%AYZ.?VYڨ j! ^vfaϝ=GnF}4j6!CnO|_lΰO>|QzB=v9A}1z[!wCb sh֬; o?++@ԩSy&fϞ]gMmf@*t5 Q DϺ`fuɸǧbb׉cqNw7XYߑ*~SOĮ]6eհ+o7n3z֯i.07_"k(?2k Xh233ѦMܹO<%-\vBJJ LLLw ͛ضmLLL?YfES <F[fX4; o_LL2q0\X4!|Gdg`̾1hjQTn7>:6%L{D>efԍ2?]CD 9; !!3@]Ex\jT\ #n8,G=S9  =o2eT\IG'bEu=Ju$T*etg(;.\1c4GPl4"hBcyqcw;|923*>< \rCCpŹa#RL<2`zs.9Q#\x| 5DGN3vٱIPC"5:RTeKXoFJNʧ"|u+ć`ؽ~L|s}s\vů1p@<}Z,.-Ƅp0 ;SG:?&8mI5'O2\@sGQQhD(?>}N9996m4i 2qqquXHcB&p L muzaiNQv!0r>Nuv,"m}fzorZS _aa8z(8cG:;s}s\t EEػNeK =GK(rC3 G X~=ܰa`ȑz{5jBBBիW#33666x?1Ku)zqe4nA}^v;w+dy×R?Z#3 aJ N!G-0p@싗ũY8v~ R7P,4"bDZuIڊׯ{ݿ?8Jڲꫯ޹#33߄o {_@T1 64[ɯY&.c~1~Ls&ו%LBR‡Qs=+1O'X,f73eׂCK+l x>Pv۱/}w R 3r!bڴi6 xzz"22Ϟ={ニ;v...8vJJJ4`L:׀PMcv? _ &~t띌;cfI]'!3fͤ kjB~c =-茮]&']h: P}w֭.]<4h@|羖STTѶmjۻw/Ə/w@MHHy'>T}>ESeѧOYP~gt 4i׮]<999033jpuuX~/^#F޽*B!y07n9mڴΝ;q @wڅ(51|9oB!0`]/ 88ڵ+N8!|kEfyNaW^صk >!B"!B!o9!B!DfhRK`ll ---ϟ;Co޼ŋaggƍC `Νn{=١Aĉŋj B  Ѷm[lڴ.#1119s&:ulƍCRRRm>OBBѦMhkkC__Þ={lKA`ܹF˥K }DGGWVjZquuejjjl, ׏+WԑdqkժeܜmܸX5nܘYZZ2HTi[qsvvflĉ8j*Y-"%ؘ͞=e˖1CCC%Q}(SN1;;;tR6l8c˖-lGA+---:w\9rEq3g۳gONj/$cmqlݺu"fffc2RYFFc7ns2c ͞>}*i;<8mݺUVPP}'LtttXvvvR]JJJ*%%%1MMM6aIPVV,--0ظq㘭-aQ>n'AZ?>SSScyyyW\8c<%#HӦMٸq㪴k׎J~>y$8>}v8{n'2geez!ꃼmѬM6>˗*g 2B5|* b_eWy жm[Tjٳ'֭[|"gϐ=zTygϞ\okee@@ucȀŋx֯_3g`>PVVYfaԩԩSFɓѰaCB 26> u-==FFFU+dxJJJtH>VPWW>ՑسgҰl2T;w.n PUUņ 0m4TǓ'Op…jQNprrȑ#a``] k`ii)AZ(,,FvMMMD9U>PXXuuj_GCCHݿ~-I& w}aϞ=9s&B!&MD^| ooox{{C__mFS߾}ѷo_ϣGtEr[4P*EEErI}BDj_H=F:P}]vh׮`„ >|8̙qQ}(5k;!ڴi9r1 266u$ROTL}Vӡ555ɶeeeUDxՑň#k044ՇKJJB@@f͚T %%EEED￑M5B*iѢD"޼y#AZ֭bzYfhҤ bbb<]6uUqb1Ց***=>|'N} Vq@ PbϞ=X,Zn-yDGG055 ёڐz[ > k׮Uo߾<&#exg̘]{˖-z7tWYmT)33JH$bVVV2ƨ>Ջ/ѣGٱc$G2 ֪U+v1 MFOuǭ[3fMk "-[X~:;C7nd>>>lƌ8|||es'R333ɝHX׮]܉Wr'ҀɝHW\#`ٌ8<*P}(1cưC%KڷoA/2> TFT#glԨQlٲel֭lΜ9LKKKڠH-3###zΞ=w,RZj8c1@d6|p7nܪ1X홆377gdvH@Pi[o>3CCCȑ#lKA*T!cT#fÆ wL__f͚'GUVjc1ٝE!BQft:!B!DfhB!BB!Bd B!!B!DfhB!BBpww)18pQPP cE1 !DB@ 82,^^^^Ғq0o<,_2;.!(:!!({VyΝ8wv]7c jjj^G#RSSadd$c>&O,cB!(3gb(5_ ''/_h,B!$%%֭ƍajj mmm 6 O>X,7o---|ήOƀ]]]=STT3gֶsfѱcGhii_~{.fff 2dJ'%%FFF hѢƏׯ_WW\ANNN~BOBٽ{7JKK1g|WƸqпDFFbѢEHJJƍ#((Hopp0aggիW͛7󃵵5вewfH$U ̙3ʕ+aooٳg#((3gīWzjxxx << 0|p HMMɓ' ]]]1w^QF}쯕B!J;37==IIIhР W\BB (Pž={555 SN&MvaŊزe;ܿ޹2WRR߿O5k )) ڕ>y&&&HLLDJJ :cJ^_~r֭[ݻGB:B9;;KЫW/dQ.3s琛 WWWxBի.^|@:C >$|c@Æ  ߛ/^xvBj B>?[hQm{u III!CiӦΝCVVVR>6)Ν@UxmybBWt !RQQU{Ňv CC*۩!}}}ccckǎٳg兕+Wh֬dA{BB3mڴ4iC ۷$''SNRO?Hl СԏO!ʈN"R4O5.VX*躊ݻC]]111RyyyUXXX@ @$Ujq۷T3BB!H 4`eeWWWɓ'8y$qwaÆXdpu IDATrc̙pqq9JKK UUU8::Vܹs~Bj d8{,nۚ?߰fy0`<<<>x8::"55͛7Q岴?gϞAKK 8}d,Źs%RǤU!"eb;v .]*cXv-=z B]B!^Xt)|}}QPP 㖔`iA!RD3 B!?)Rަ!aIENDB`PyNN-0.10.0/doc/images/continuous_time_spiking.png000066400000000000000000001106601415343567000221200ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxyTOU{7@#&" DJ\3&FI1FsԈp6qKP d( i޻~TKU׭[}uN ݷ.>O Ffc C@81!p c C@81!p c C@81!p c C@81!p c C@81!p c C@81!p c C@81!pLno=mۦzJ{}; ֦7_jjj};4S .SOiܹپ >p}iΜ9پ QG8:K^zA~?E]ou~}l>;CsӸ"vay~Iw -Zȶv[z%I/yx㍒;S\pJKKu衇fk-YGBqBI~':Yy^7<_;viTG?3utt y}YY9Uii]d֭[%IZ~$_$D_UQQynwL3*mپ}~>w߯Gf'v 7=p x}dZ˲]Dz$e~-nx~dF>3 NX9#%eZ:@fLa ]l І ܜۀiܸqپ Lw'L+Qe6lؠg6A!ؤo}`-w@u>u3'v2/]3wpK$CTNs0X7o֭ު'W^ IO5}tUUUWssnFК5k d՚O$ь3[o.Suun6uQZv{o&!؏!t,G?ɓm֬Y:蠃h"$]wujkk믿1cHLc=Vs.?8j͌gժUZzVZSN9EtghZ`.]O[I3 }1xy)NT{oZ~}~p ChZbc O?iӦR`P'|rcUMM=P{Q0Խkg;<A}̱Z3YjF>$Fgq~auuueb K2LhT :蠃$I׿uVvatM:3}o͚5ںu:]~YCOԩSS?X3ټy:A_M6@0) }aX~?SӦMӹ+Ijkk$ D䢋.Reee͞=[g϶aիW{s"M:ݴi9\=˵|~_۱ccOH{{{:H[H86r V,d) iڲef̘*Z8I:::L{{{k.;jkkSkkko#VX[oUo{묳_|=:cz?{ŋ5o޼?ϛ7ϋ/Nx͚?qڵK쳏~jر:c`-X:J/:::tkҥ裏4{l-^Xs?kѺuTXXӧ.uiҤIzt%y%a:jHtkΝz饗-}[͛U]]+kѢEܹsUZZ'xBSO=~Z{,X^/;<瞒 &hz7&L IICN?XGyM͛7??tJJSNw})~sVҬYTXXYf^/M>8_ U3^G>b1:BIQ{{Nq9s=sαW\q>c?׉袋4eʔ!O>o#F$}]v;rssK//7!d53 &襗^R4;xWURRgp6ʼʔApXfҫ+WK_RN=T=cO{siÆ :ӝ]nIO~tM 뮻2ܝZ|*++ӟ>!f…‡$=jllYg5hW_H&X[lN;M zzm6\R'x;nyc6܊H ~GՉ'm۶sJY:rJ}~foM:֭[@ o#fܸqmݴqF577,衇oڡ3gwU{{LA?{' ?@ iuIh1c^_/\:tM7S;-$%޹^|vؑVytj̙=rȸ?VWy%vZ3㝏 OK/-ܢ6M2EK,Ѹq;MS,/|?\z/ ͛zKŋxߋ-{jhhD_OG=-[l{lZ3QYY;SwyݷQV3b]!xAow220ĉF￯O?T{WFf$o?ꭷҮ]}_b[9{6557͔i^Sf@*:w@***Rqq3 8%I^{mp8K.DhTַlytgjǎN~7d۞k̙Ҳe˴v~߻ꫵsN۞ qS12T!,qSN.L7p<@vi*..֓O>~[Gq.Ҍ=EߺꫯjԩڼyV\3f衇.7?%%%;4k,q5kF_~YoO!trC݃)/"ϼ-5n8-YDo$I?3(7O 1[^"uuus9Gonf曺5gI+$?Ϛ4iVX;C555zW^{˃@$D : \k֬Y5kV,X ޹瞫s=7=zA_O~"gVdCWU}_^n 7t@;̨L`M6 ?r-{v @2}t@ ;LƍmذA?$;T~~~n[eba>z衇?QͪK.DӧO@boMa, UW]*۷rS1:`3*.A,32En ̀bFe%@Lk MPK`Fe% #f12n `N,@a.oO?UccrrrTYYc*7[H$؆>i>3Yw$mڴI/"wˬ. b^؆0 H]ꫯ 1zё;Ɏ:ꨔ A}#d7 l  >hvmڴ;> `ُ]Ql޼95Pȁ;,YDNo d^0x3 𕮮.'Ձپ8 ؟ˌXk.KuuueN`Ŋ+4}tUTTX|-Z~kڪK/TcǎUaaƍn!~ꩧtᇫD:䓵~zwy {ݸq͛{OfR]]rrr/JJ<٩ _ /|AW^y%sHb U0d at@+͖T4e}͟?_-RmmΝR=?z)=˓椫K_״yf͘1Czu嗫]W]u:KŚ5k׿UӦM!W_>V[[{?S4gGyD{キ﫣Cw}zcp 7S: aثUׯwyw_W֢E4vXYFuuu뮻N'|{1x㍺+$۴i&L{NJǏׯ~+͟?/w^ZtP ?O_uZ{._\'hǎ2eW}~o`bag=}#G}D_z: hjjRuuuOz~,Bot=;aeOEE3[u56oެ@ {]ZohhD_RCYQQ۷+ ![lIn m;Pmxx #W: &8qѨA{駟jÔ-I/ҠڵKomo'Mp8 n ~݆7@ Kl:%I^{mp8K.DhTַ23gTEE.]:h+kwbC?O}v;i}@L퀰 6`7R ,ʞS. 7ܠZp~ f12[-Z˗kܸqZd~H~gwPo@o:,=:C?ITUUW^ywgT%+WkQ$o[=c:?)LJ{!0pC5kf͚?0,X1w;~_ ?~d=Lf/Kܯ+ԕW^9{&+$C2$''af~4 j ~Ԥ~_Fk'Oҝ @p#: T?$f@l՚5kk=Ю]+7رcdXycLI 3 Y tw}u'x uwwkw~͟?_555پEx\Al%Xx b`=Sw_o冥Au@J@x  D@| ``Za `3*0H$B7 /̬L@i*F*7ӷe6nCb',sS1h o02}3C@ a.vfywi3 cGCL4w\ǟrC1md : @`h4%Xƍ{7iܸqپ ].67w)dymxo3 , @@Q˯؆0 F2&v*7H$bx؆6sw@ RE,T' v@Yg %n p S; s F HĨm  0VGG8,`7`,`Nu : <dx86s|ώWֹ%0̩NN,!lFZE`7řN`s0]f@X%X~ ̩N@u.-$o#Hvu?$: sK1B͜ag*3Xx fD`[͜abe4Un mIY 0uuuK@L I耷[[]]]jooOz]ii^Fmǭ0`x eu" 5n πAJo0:zYY,]VpK a.C@)̭N-V`^,T'XY UTT,rK F-mxo#(jkkKz]II rK1,̩NKוI AY̩NUZZ*I,`+Ӷ(0x D@ /RϋXnnn ,%X,̭N%+KJJJzle2L f1:Z[[^+2,f@XO:̭N𝖖K~Ŗ_X pXp8{V?w "0.nylIu`7u@L ̀f1:R=$ep8l,cj :  6s|%%uŃ>a d ,,T'ZkkOv?$`[_Iw]b %a#> IDAT^f@"H,`7: V`s|-$i͚5,@ mV'p: ]vVXg qK1m$,MT'V4),,;Р?O[i3 7l0\mmmiF`y؟ 7S[kΝPcccҟ  Έ؊&s|c8Gss]CqK F-7"  |Hx7P(ߙ0\V?2<`s|j$;vHvt80-bNu/EQK@X`(nI0}؟ 7SKNz]@jjj$0|V;1idDᅧ g]}E0lnyC%Xl x6tRWWfasSĤ2P%X!---Z`;81B`P{;<Aop RO9r2j͌{[G`M@ΘHnݪ j=Є '|!)((~_p6}Î%X~vڥ> RJLd…k}ͭ-@ e6HF-[Nk׮ɓ^:+<^ڪu%},: @jR8qb~n u@b^̭N:I ]4U$Ν;5ԑ(--b֦fY^EHM*53h4fa;o-# B̭N.ڪrUVVZҒ24rH9R;wL8裏VEEJJJ4sLپ@ᰑKb =z~kĉD"z'um7?o C6 w=6[Dܬʄ٩h47͛7OG}]/5m4[Ncƌ-bb!]w]?q?~~hժU5kVܟ袋={fϞ{퀴J9pFun˗/}2C79uN׿uM>]?tlQ3X'PG݊wQ+R=\r7{f2XTSSӻ4n u4iҤ,ݑ=?p}K_ҳ>lQ+o蠚 /8:fU'+,,Ԉ#}lߊ^CC#GJ@dϘ1cؘۈ-mxJ@xAڶmjkk}+ե%KRq!|k먛DK`Vur .$wqNߒqJĶj: @lٲEׯWwww׶n:'xB֭sm%d0 p뭷jǎڴi$G,I }vz:묳>Hz)=:5s̬ݻ)'K F3gf/ג%KqF;V4m4M8Q&MREE֭[[cǎ[3 } 6Hn&}Gz^H|A= :sTUUO&K$ ޘyzOUGw-X%XI8be7@&fɒ%܉ QFY:+5sZxq-\wW)a mV'`0p.}gf@F,BaVu/tww-u%%% _cí}; ڱcG0`Hʒ f@L[Ko#sb&3P($ipNt@$H4U45,̪N;`%ۂW@FH7{0-E6se ̀;@ Ò : ̀dVu/صVyy {f @\n d H!t`DXYTRRP+IRnn>>])+Q V')5uC644[~%)1b[H$sXì㵴Xzj4˜Kxb H_o0:Hv K`IRmm-isCa@)vD:: bZIY=d%XHDPh d=Dx F}vQF@_f@MxF$QKKK늋| B7׫]C>@n ~$ t@o0:8>2! G0Bf@ Dɬx &f $ H}}@ /Ko3:8vUXX75J@@ /Ko"3e xojjj4c @n u@, fU'-gHRnn***,u@x9̀fnuQѨRXX!ihh;3b#2̀%X9̪N0V[[[Pu?ϗ`%RSSC1̀ _ P `(<`IRmm-H[: .{ q;tA3:Xv GѤK 2&2P0x ưkYt@X/-W5m HBp?]a&bX3@$@t@X@%X .<-Ko|ꀘ669 p-+$ ZK@eXMMMC^C@ :%Xw]YjmmMz]IIT`INC'!dfU'uV_uww>KR]]Ƥu-mxG+=m6EєHmm`{S6"W !:uuu%] 2,H 0>AQR&v H,0 7?$"Lb^, *))xt@`@qCI pp8lib˟B!Y޲WF-k@L 6w_9---^T.R;0V999@2`*<t)3@$@,`[H862 h_9 v@UYYI%n H73 l xc T`BH^^*++4u"a^繥 ,`YDx DҒbK "BP3 @@#F` K@ /#UZ[[-رcSHRMM %n ~逰 ]W(x'KBSSS6utt$< w:Ko2B25!)%X}f.H4Uwwwʏ ,gq!]W(x t@b?2,XG`$=o999,<WD$ @)O,/dtwOo2B3ѨRXX<ˏۂ7Oc?Vq'f@sWYmmm IK!t?~8ut@}@ .*<+;`IBarH$b @xtg$Jf@$e%vJKsWY@vie~gdb V4 %Xn ~ن ph4ji V~~-?nKKچ`@Rn v@.~U(xRGG@;PZCgAf@2B29!)%XTSS#i  Br}v:蠃*hT@@wuL;C2kI$رCqa i/f@ngTyiwWWV^mSx/SgB!$ V=  ƾ\L{%a^FIO~9s3۶m _&`xԨQdi'j} b^P'tv}P'tRR8\ق7TP3 : ,ɨC=ϕNKCeee)x[2f-$b9h]qzڶm[o)32a05>n ~0xq /ѣ5c -[Lپ%$ɾHUUK`$j} :`*SO=9sΝ#Gjܹz'Vs)TwϧcQ3c6a.ȱŋk˖-Zr׌3T__zW}#S555626Dt t@XxySO߯o]ӦM{ﭫJno BwdO 2jxS@`p?+r͛7O<>SW҈#tjZWWۓ^WZZXHl V$2,1SP3c01ü Ŀ/}ڴi7ȞLK HNNF!IjllLxi[ЙvFȆ l2-_\8]}:Ӳ}{f53[5TSS#IC΁@` jfz@a.c͛?Q˖-ڵk%Ix-Zٳgu/3@4bBP;a ^Foz 鳺 @~kٲezD4f]z饚;w:ldĎ-xc*++4sQ3}Qe\ TQQyiܹ>}#@`;`IBۊa<'j=@"3 ,a\YjN8%=ҩ%%%i} l V#$kp[C%XwWN9^H=u,p:LDʹ$u@=p?: w߭>@)I Ν;|ta7n:7?m+ 0?~_h̘14i***]Z pX۶m26;aۨsSK$g: @.p z衇,^mۦh4%XO/fyV3BF5c # , *))Ic@:;;֖:>.@N/@pXi3 Ї@e):o]@׿}֭[[j絴XzNwP($Hl=[{ooX= &=yNu@4,{$''GSN1[ZZ`wq1b`wyGwT]]s9G۶m~*P(d{́ uHgg'/~lxvءo۪Uii9ޣ,g^3ԣ>jcnݺU .Իᆱ &H駟jtK.?c=t"ZZZ^W\\xvFYf͌'hƌZ|.Bp B:ꨣz B%XgyoiرqN8c=Z[lQ]]֮]ɓ'ǽS[[^u3F4e{챺{t2@[[O~HπHe˖!TnׂY3YjV^UVSN$q?~,XKZ~L׉`mx .i钤7|S^.ol*rN8!I_W4~xXJCJ#fl Vee}!m-I٬ZJF TSS38Cwcf %~F;+K[na6{'O֓O>r$Х5]]]YY%1VL_mlD6oެ:jcuӦM8L %~Flikk$ ^aaa5E]~_={fϞ;͎VKץ)@25"tY=>˵|~_Kֽsu4lQ7f@$ uԭ /5c >)\{{n6~}݇}EEEnh?Pss~_ּyO7X2r$(''GjjjJx3 7/\K,ƍ5vXI=_͛󟪮mݦh4kƑ{ye =FtM裏$|P<9k̘1zt/WAAN8tMiNu@o~(Y@ 'xB^zniʔ)ZdƍJBq!]x9 IDAT4|ᇖ&WNt@tGCr!J\x/^<땕;uwflp8bniE`I: pHg@LX]̫Pph4jRTTgKKZZZ2@***DD *g竪JC] @2sW} HI O) H4e',ih4h4j @xqdԨQ7+W#۷>+ Il$vPi؛q?t@XxqNӳ>Yfiȑ7o}Y 8 N1H3P3aSx}e\ZtB.]#8BK.׾5n⋵vlߢo9uHmmmƖv^,x5Y4`1ø"={}QmٲE~ƍ_ך0&ijjR$oKh4o6fڃ +3 t@3.|+_ܹsuI'0۷ 2 y ڵk^ջkVL{%>be,g\yG} J$//oXo؝婪JDQ3bz/2B!ۓ^g3 Ib$S؆0 UUU:Ė`I=@L_ 8'HRb@HrOSx b 1 I0B? c ]W(d$UUU `f8qj V,TVV@HrO H P0;2JQAAǩ%Xyyy{a",-O؟cfkD"%xX/pX[nu4$ C`fkYz1>LHđ J566&@ DW0$tQ5NK?0&@:;;vxX[: π t@# ܂W#a@:;;y!ˉ: `p?T(dlDs HYp2  fV(d؝bKDbV: X  (+K)))Qnn^,\a MRȸl,v U r+^: ~O>f@3[8;@$k@8 6, E*[ :c0B`.7cmx1JQ]]]Nz]˯$ou@"ᰓ!V: @[(#nRQQ0HtAS1=Dx2jg)"XY%`*7g@8 0[J=0&RGG^G0: ,?U C%a@C0U  ;Hl VEE$7n ,J@<_CCl 3 @L A7J!c-}oPH#Gt=X)++S0 9: {̭Rp{>_r3 BS1NH 4V, / `؜>D n؂W|>q*3 J!c>D| @A@3@6̭Rp3 hUK[666ƽ%X A7J!ct@rss{ߤΝ;ҷ2 k)0҃P֖^|C$fDRYY y@L0*GX ;NAxa9`o c&T)dD@ @,>`f `XuHnnn}7 E${Xx2"P(:WK"~ sYxU )1H^^JKK9 ov^`n#t@ m{N7 B7u@L H[4)..U[JwIUU7; 1xlfgJ|xK*%X}; ,xJw"#fnBec6577.~@pS d}+d@Aڲ BY ;wT8t K),̭R8NAoD"jnntMgg'/a RWoOt@l{N7KFQuww;z_2M%Xf@v $@h4jRTTdq 1ҷ"%> eXY@LX]̭RȨθ3ٽV(Ruurssm}\; e0 ${yxT?I2LB ,  h -T/J}JEhx) OCkVdߤ d1Ld2_ו 8s;gg>BTgbسg4-\˛vv< ēO>DEEaؾ}{׭_]B/G2Do{`}Q9XjjF !jnܹغu+q 0֭Ô)Sc7,Y~s8JuC$ j$4}@B?wq:-+ ݻw=nBfg;Xb.\3g'x_|E[1l0)zJ@V,d(BTUUn#* ź4M@8.//aaa}l6ϟ]v… mC9 pQh2Oȼyp8n ӺKA9 5 ށal7x#1}t|j}lv1n8dggqTîZI"%!qN Ht%M@ay N + d=z8̪{ի=v]c!XzQۘӈlx(r@CxL@ܮz磪ݻCsN>/XTqݦpQh2n˗;t>r-(xZzh)^ۭ%E宻ڵk}ǜN'֭[ѣGgϞK.ĉ~˕߰~L)oBIDs@2k,DEEa̘1HNNDZvZ^к{][歀DGG#,,եx4LiԨQ1c/^bbÆ 8w֭[;7 |M9s};Æ p8~F>}SOi) !2& ۱i&@ee%q]wgA^ȩ-K HRz˛oz-aС裏p 7Α$كO~|ضmjjj g !X\78""}c~?:[5~\M@<h:7#$lX|9/_9֭%KdzJ@^is@ByVïs@^ H NdL@#wq)B @h B((x<V@HpUL@ Vv@Bd$zJ@V,d(EA][4lv+ DD=-U@ /Qh1n"EȩXVl* H!XMSL@A#j&& $AmmmEEE6T0p8 <"25 @M*XBZ(jkkeu%V*..nGLLLL$ay+ 恰Bd rtH@fL@8(7JQiw ^Qw7$I+ L@ 8(4'JQqy@V+bcc[wB,# /[СuDKz݌\" =r|YQ`SCCo$n7tmTL@H6-+ }ОInDh>n(//}4} nnfm6aBɩHp7JhZ)-- xW" }zcUUUDm~].Q]]y "U /"4M@N:=% ޱF4!XB/^DYY*++vcBhY)..jE|||פּCp7t"Sb!XۍÇ… ZwBEnDMCEUn!XD z<݋˗/k2& $ Rp444 <<Ѭ3,ˍ!Ct|DFFpjtuuup\ZwdbBmn?+ ě- QSb ĉ G\\߇fCmm-***PWWKBjjjPYYjTUUʕ+ƕ+WPSSp:=A8r]WWhP\ү_?E׭@!PD[\WyOVm Á8ߟQQQ~?xb '{7@DD0!::V7!<<* |J|ڤ X ѣR.[VVl" L@hXXQqqE-ItWوiv7O?>(ƏK(ZfBx4~& A'jPODw8-뙀0=% fHλ|2:EGG !ǎ;pBxw_W5kN<'|ɇA+JQ~ X!X_)M`V@́Cs@vw^TVV뺬,$%%u}!''wqpak!7ǸQBt%+ R"sS x3B8!8v]={vg?FR|'O{RjP\ !XD9 \W IDATKGҥK.55WWW㥗^K/Y&6{tLX!2 =% f4s+((ٳguM޽1hРvt#/FlllEQ:M]ЋCrj{hi/& D 8cΞ=v]v<ر#F}݇'ĉXl3WvSZ`HPp}U#)0KᗓҥK8rH󕟟ӧ㦛nnΝ;yfw1'3vNx<h3z QhӃYșN-+--uMLL F%g>(222p!lٲ] \. !9 O@Gr!B+ ڳX,TYY] nGvvH2WvzGII ![iEU9 4`Ϟ=p\ Gvv6"##[= R{;JQh.^M`544y DK ч`5 H{nɾb`ȑҥKq#A& "=, '!)a Hc. {װaеk R;JQh])..},\`eVS2ǃ}|5C A[=gٲeÚ5kpAL<3]%$tj bQl|gQQtS T)++kv`.& 2!pa\|] 8}i2+x衇`tLQ:L!+-hjr)2؉'pv]ӷo_yիr_#cB J B?i<KeBwBo8uTѣ222YªUpiҞɦtPnML 8CBefϟuMBBdkr ~_wDL@(0'_idzBd0L@exwe:t]bĈ`ʕ7ozn1t^XXCw3¦.=% f̘ܽc޽ܣ0j(!íyQZZ'|$Z,!D$/4CBV@8 ƞ={%b@vv,N'/_ٳg_~*& -++ |)^V@ :޽]%VFj(ף/H7;JQg ^J WNzSX_J@$IR-=Z]{Ammk$Iˆ#|Sm^̙31pt7"fr6mkZbHEm[Qc PxVll,$IJXD]m%x)9y$Ξ=ۮkzA-ǃ\L<#FhD-a ӸbZѥK5;+a&V@g!XӮk١7>C=zk֬iD1_6eHpФex͐vqv]ÇwB\`ܸq5P3r* 6 aa!##C XϬeP9>Sݻ۷oD1~vq\~pQhSb9 MR^^SN>n#;;˻ !d=7tSAV@ȏu̙fBk b!<:$pdgg#22mΝ;Gb0OƏT.zQ]]CT@ $t"fI@2F'ODUUs- F.]t\deeaʔ)QKX!?zetAXXog8TUUr͡,$7Q1,9$ (((}~zz:vک6wڅccJUz) QhSvY1!>,;r8߿]t):~흾QKX!?z!Wobp8\M@|0! MzJ@2,>}:%HCv|شi)~H;"?r* aaa6SQQVz(kf/*n[~Q1ўrJ6LKKClll]t)0sNߋ5FmmmEGG+ZTT$ü6孀HgV-!")12Fx^VN{1lݺӟ`|W@""" BO Y* rPv9>СAI<yg|00 ^܌ȸh!XB[ũ_زe V^ |ZU@& 93 .nDz]-QQQ4hPP~/(#j#ɦ @2!2Qb G%%.\˗e?dȐ|Ϟ=7|-n`B>r+ ņJ@U@pQ(bD}M Nǎ}~>}/_Á ~Dr?Rlr`Y,E!q:(//7V@ HpM@BѣGe eq݋/O.\ƌH!$**JS&F7{ Q[ČCVkC$%(,,}~fffZr%"##C~Dr1!WWސ՘+ 4񠺺8#'nUpݦ4%IR.[__G>gϞA{}|2֬YGy$("# Z~-mF9 DHs@?)܈dddUVbG =2~"Y`D$aXqqq' & ꓻb?^eeeXz5|A$$$DaHERTT]m|^4|ZJ@X! =zJ@̼ o r\8rw=zիW . =ڃ WH?k HV@Bq։'P[[+p <8h?GUUUXj~CRh0~"Y$ $!22R~jWD$BdL@jR9sF]w]PP]]EDeHE]722R7V@UVV<! rag\\q8|󓒒ЫW_SS+Wb޼yA/Q{?RQN'nw)=0n"w3BV@B; ]eyZ oRРAA}tb{ѿݗ#(]q())1CV@&  CdߵkW7}ذa.^z*%G*j^* %%%x}:|@$IСC=C~~>6nu#ACZKĨҖZK@c(t-12Mr?}~ZZbccǃ\L4 #G 꽉:+{W@}^LL V+& +?#111HKK z?>C=zZMƏT*L@1@,K@8xW( :wJKKe?tРWĉq 7D ɱw!P큅:}h)Jmm-kAǶm۰w^oMYTGO3$ J@p\jv:s@iZxtx^ɍQQQ4h"Xd FA?Qgbrz: H.]jv^}}}s)8n7PWW{higg|C$Iל8qBկ}U*a fc- .˲2d"I/|裏XQ']bbrr* ޽6!W{@y 8wOoσM7f$CıcdߧO$&&*җ\deeaʔ)ܟ-j(//G}}+ r`\ +jӄX1,Wѣ@kǗ_~۷#//-& &17! 4~ɉnwEmm-BKzb HSԬP ŵ^o] X@EE|ǍЀj\rվmMCCjkk}KއJz@-,mѣٳbu7gBcLs@ HYY_ǃZԺ{vQSSǕ+WP[[{K-22& uc\DD222ҥKYf)Q0011=U@áx[Z 4 @^__߬Sorq\8{,.^خ$ łfٳ߱hvX,UC7=v에zK@̺ Hqq1Ο?/+˱cǰuV Bt:o1doֺkW֭'Z hr{oh=Aj… (((}|7A$DFF6KbbbhDFF"::QQQ~oaBgWL,//OzC ]-U@,k_|iKxx8ɓ'yf˚@^L@g;Xb.\3g'x_|E⋀i%**JO\}Zpݾc* vBw}l`̙0`TJ7b1xI-kz8yyyصkpwfΜt<3شiS-2Q4ؽ{ 뮃nW/1| && CXX~1͆㩧… гgO {(ZmEEE'~K4 PZZ pK6k$InGddOKZK:(J[IUOg`^^wK> 113gƍР29 %w^$%%W^ܹsذa^|EE`b ==111~G 8x`H$ jQ--1i[mm-N>lxFZZnF 0 @RR9tzSjL|81tP ͛yfcjW:-l6UE=)`y?7VC`_RvJqԨ($222uuud񈏏Woѷo_U^8m6nVZwrrrPWW71hV+p#GСCUYmLn}~ߏÇև^8ګW/UKd%XV{GWJ޽{2q„ CKKzFD!=z8;=%%%uӦMCjj}p`ZwPQPPwRR!$IBTTbbb|hx)hz&M;"vC RDfD!_=>3TUUK.el к ӵkW$%%ZwC`p? 3q0++ 9~ ݻt:Ddd6 8u] (>>׺D'u]pXvĺu0zh]eXݻkÑ$ YYYHHHк+-$ ֭RSS1d;r &MqaСHKKC/]'Nr][TTc%%%_S}僨BѻwoL\\FɘHV@2j(̘1/Fqq1RSSa;w֭kxMVڶmL.] %%E46oެY ]m6FK.aƍlm۶aԩ~U f}ȍoo̙3ӧ ѣ1o<?~ܷ>ljG'OX$%%>LSVCE^~zL8QUmۆn ޽CN=[ISWW'-Z$z!v۶m x}o>{yԩS5iW훵m7kZEΝ+,8{2\$&&hq7wQsNnl_8c(faX|]!"҄8nݺḸ8xו"H5L@H5͵kM///5i[ڶ훵m-Ɨve|mkپZ$!4Z~lڴ gֺDd7nĽޫu7qb8& :QRRO?}Edd!"7|I&!11Q()qT-L@H5NDDDDDaBDDDDDaBDDDDDa1Ӊ'|)))ѣ}vUھr yL<]vb TiC= kYPPPxǎÌ3h$$$`رشimtRX,dff*g}cϞ=ǴiӐhdffbՊ;w?włBE߻w/ODGGkŒ%KTYq߾}o'O… [n;x_~ѣbm9r7pRRRck׮عs'y۷bm߿7pwƫ'bϞ=HOOWm5iKGRѫGGU%H3w$+WՉ41vXw:H!޽{$IbÆ +;w ~ ngVn%jf7|V;vI֭[o ѭ[7qwvK>s!IXlI{Gv{Rx߼wՊq7#>>ޯ &࣏>BMMbmEX8Lq>1~U 8p鈉;-w$Eo6oLR.]$Ezzx7Ļ+ϟ/,xWm{͚5B$1w\qqq1k,!$I6mR}5%1 X*(hQ1PFF曛?v옐$I]VhYXX(/QXXz^r555tɣZ& B|"CKFF$I|~ I[oXMUUU(1m4U/)Ņ ϛ7ODGGRE}/$QF$_ѶՠX8zYc)㨲Gs@4ԣGe;:)))jwIu[QYY޽f};QQQ?QPP_?0Ο?3g̙3C}}=Ξ=2oI^P__+W(ֆ[n~ǽ {=֪2lo;=uT(>G!77EEE#G`p`#KGRQe1  ]GUUqB%ǑA]]N'O⣏> A4w##N%\p<سgѯ_?,YD[riDFF6L#FpuƼIIIԦMХKL6M|/TMPe8;7Qwݻw`X8 72*q`.w+Vy׮3f}Q{ӦMO>QMfŰaDbbp\]RR"{=>{=1x`ѷo_G*~"<<\vm+${w-"""T6R\\,~3UBٳg &vm",,L!3[l$ezfB722 a86QFaƌXx1 6ܹsXn*}xWP^^{>s<bcciW>CL:%%%ظqϞ=[vGUU&L\t 6mB~~>֭[ժX >}z@wf͚(38~8֮] r0a|gSO=ڰwyn[ah"lݺǏC=]⣏>wPs׿{&MB׮]_b[裏*֮fyc)(΀̮N,ZHCv-mۦZ}MX,b~Yb(֮Wߵ(w.EBB2eؾ}&''G/ "<<\S?NRm!hhh>۷骮&cƌݻwWu !K?yd+"""ĠAIJe˄VSNI&$au]'^|f;h:-c捥FjPyV"""""2-NB'"""""0!"""""0!"""""0!"""""0!"""""0!"""""0!ꤾ}b޼yZw'''SNUr_+WT}" Mc%3`B$Ν;ϢII4UˮZlܸ-Rݘlܸѷ޾.DQyG  g~~>^u zղnݺ{ Tm7,, sOjD0iP"hv,<<\&Q"b O{9._C!11X`PVVڵ+O6k`ժU@dd$wx<Μ9OzjѸ[`ɒ%իp{ŤI(pG8J QN`XjgKc~a 77v믿Á?iiiXl>cKO_Á())} 6 رcG5c tҥY̙w.\>C\NHQ"cbB>}pzx1@rrr+W˪ o'Nĝwމg}۰~zw?DD-a%2&!Rj}\4Z 99ov듒TWӾ/ݻO?}݇+W/DtttED(11!A͍RSScǎnW]\l޼{/lU\Uc%,"J5]N1X0Ϛ5 nK,iv ˛?tP"j((+ D21Oc֬YǴipSj| &`Xl<GAA/;t~z꫸;пTUUĜ2e!88JH#F`ɒ%Xf Co} 8crk>|8O?4Я_?̙3ƍ 'JW_}-[ؼy3暠qqKxہt#''. >j%%%o1|pX .T}"b%R$Iعs'0{lU.//Grr2S"`b%R+ D~dffjmn{'";Q"e1!"""""p                TPAIENDB`PyNN-0.10.0/doc/images/dc_source.png000066400000000000000000000476451415343567000171320ustar00rootroot00000000000000PNG  IHDR ,;-:sBIT|d pHYsaa?i IDATx|ǵm6cl~mIǙ¬!1SJɯN9+DdJEg ?2 a X6v]]מm}sv΅=jZ CDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDFDDDDDDF6X,ǯjv9"""""%؍؍؍؍؍M RbE||| a۲e ڵˋFɅ LXDDDD9]EN:u*$&&ݻwӾ}{nf̘cǘ>}:>3zbZV$6lÙ1c ֭{!11ooo͛СCYn;vwbɓq85gV+'O~w߿n8p s@pp0k֬zPJ&M'ݻ,BBB\_L5kƮ]]Srr=bʕtڕƏ{^jj*QjURRRV3sIVB\r5m4F @^8s o&Ǐˋ וod-[""" UΆ,|2y+VaBNǵ]uË/ȭqqFBCC unbb"AAAxzz/ ׏kײk.ڵk'@ŋ+WxbMBΆ̛?.]*yҥAP6 b{Q:ॗgOh¾[ 4`:jժr!<@ZZpuՕXJMM%00VRe8wΟyp!#^t- /[ʔ1Pvs}A]s܌GaAaλfQR!+ v瞃K/a?,5!!!""Rq88a|=y2oלʕbż s*0t?MfW#RxN@.\ XlgΜ!88Wڼy3ܹԩS* ""ɐb?? C*WX#oeAρ#pbK ""d8v_Ϝ{'T_?VV< A0kՈmPe#D3>l|PkBPzժA@1DJEkΝФՈܜmPgq1Сa1cnԨu@ƣfͫzu!"s1JشI^J>M)%VHM5BƟIIW^Th ]jبS,("%Dٲ0{6tK@fW$rcu@DJK{a޽Ɗ9ϻBݺp]wܡOPEQxIL4Vs)@n-;ec׮Ac2eAhx!N l?CŒfW#r} ADD);ۘwcu.c[0G5ir5l4nl )=^}ƍ3nhjD r@DĖa#pyu!$Z46k!"ruBw "%mP⒕e ڲ6o6=j""`j 3JԤBCCX">>>|<| 2FJڵMVDŋFxcz`(pƎ[O%?{7W n'ͮF* =66(:uԩSquu%11<-]e˖ѲeKU!"r=Ƨkë22F M|yb߆6=D N=+)) 2|pfdGTpuu{۷hHt Ze>cǎqERSS9q/^رcӽ{w}]ԩرc q"V+,_ C`Ln,ao0p1UҺukƎKϞ=tn$''?W_}-[Gvl '| ӦADDDlO{w<&N4qFZ6(G` h /1*套`$=ͮFM.ûf"RJdfԩFcfXoW1п?Ո) UTO>DFFҦMuH ?#G /UnömP(;w;CvS&Lয়~*Η'/л7t {Į‡!XiiЯde]8 _c׮]4k֌H""" ,4: "j56?\9x B_!""vE#F_ܮb\QZ5{9vO?ĉ9##zN:-Z`Μ9h :buoÆ탈}{#x̘s]8}vX|9xxxn׌%**N:+T^QF?дiS:t@NaڵZ`HegÛo„ sBǎfW%""asz >W+GfrI-[laHJJaÆ >3f\ӧOsIi!Cˁ[n@D !00v46*)ls6K ֹsXp!;wZj9_qu3gVɓ'py [? @bbMqVV+̟͚FDCDB\4lht@5"qT6 .]bժUx vСC矉)┐@pp0k֬zPJ&MT`ǏPJ)N2V2ᅦmͮJDDnGr' ݺٳfW$X`}GoA=߿?]vͭ^P*TDGGӴiSV\I\\cǎ_.]ydff~\\g5 )_As-{q!M矃#)]\\_ByG(_|jYs=Sjeڴi=:nݺqFN8uƂ 6{>.]xεsΔ-[""" U32&OfL0j̮JDDlNW2e̮HE7x떹6l@hhhMLL$((ooo2228rիW}~ѢE 4M6Ѯ]|׿kDGGøq:ꀈ\be GCq"aar })bueY[[hРuUȡC󼟟iii]`cǎ Ç\=f|aED u oF܌M'e|̟?ÇgjbgϞBߟCBB8x Ԯ];xJJ @իG孷*kF99QՒ%GR"<~ JͮHJ:5f̘>ߩT+W}k <<y!66___Zl{|ӦM׏z%Kش.gp1_^k>DDJaW`d>jFԫW5kPB[Dtؑ3tP4iB||< ̝;(9BӦM|2ӧO7qiӦ4n8߽5DJCgOc %KG+`D3k\@lօ ߿ >> &l2,X@pp0K,ɳJÇ߱X,""@ѣhYT$: aaaԬYٳg%: R3ƒKc}EDDe¤IF'WB$?={6֭^̙3zlxY1 ~!""XNȄ 0f1KZ6L>1c`XMb䮂mN) ӡX<#-+`9 'Ndʔ)T^-[8ĢwHv޽""RxW:!0~: .]XBB̮HDDoOûMwf_tݻ;mqf:<DDđ=SŁUYlu_mۋl >5j_+|Hx1c8_j)l6kӺukY&Ϋ\-^.4KͺuЫs| ]8Mlk0VUQl@ 3bm q&+VNu~hL}[7|!͚]ؓL4h,qp!)cvE""64v˗C׮fW%b}@: ,LRHIp""`l̮HAKTbqqF8{OCDD V2Vߍ.]ZSrܹ"_o1u*@ RbE||| ayyiݺ5~~~xzzRn]ѣGmZY`C+f\]a,;cGc#\q^6QF$''A߾}y \ lذ˗|rj֬޽{|bccSNJbb"իWgԨQ???)_w̛7'x" V6r>DDdIN6G+ҿIq+U\\\$22]vϖ-[غu+gΜח`ƎKϞ=iѢEq|̙je?//BZjPFK+c^>DDd^+o3:6ojyxgԫ`͘1c=z4)))TTz_| r)8pcǎܹr;Wq$۷Ch(i fW$""r}VaO]w{TկovUR:TP777ӉiӦ\8Ǝ/Ǐ-Z`͚5TZ+طXg?1Vq{@>jLRv9LZdff\\]]ZL6ѣG>߭[76nȉ'=~ek.^Ν;y7;ٸq#˗:Ν;SlY"""S%%A۶ 7BJfW$""R4a3f5‰qaBCC unbb"AAAx{{#G^z-bРAlڴv]>[nm۶0nܸ|ϫ"%Ɛl:<j5Vn|߳߇֭ͮJnEMB `B{eT`` ?~~~>wl۶Exa8sFCDDbt?zX];0x9o@8p` $''Sv)))XdddOwPz)Kv-,^ ꎤXYG7o^bcce˖+WrY~,RL&Leˌ}]Hrs^06.<}6!+ʤ0u;]v%220K:;vd :&MOBBs%** ݻwӡC]w݅ }K,A|7xHID̮FDDĶ.\aLhҘ'ҤUɍkdդC^}60.g IDAT\ XlgΜ!88}dzi&;FVVkfܸqyVʺ4t ÇxYo`ؿƏמW%UիcݺudeeQjUGddd'g"%p=Ɗ | y""Rdfԩ\6&KbexϜ9Ç~H\\ZIdd$uԱKۅ#+ *V4"1yV0%_״n$''tRX,jՊ[mBDJx+#|]rr`"322_#L+oDw^&MիcU*G"%'CfW#""R= &[oҽ&t`vU]68rSNI&4mڔիWӦMz-{Z ^|X]CDD$a,ر*W[7cf_˗;*888wǝwi+u@LAV_˖i+Zᣏ`Xg<E`veKϳj*/" w-ZK b \\y^^fW$""8.]9s'*]YPˋ ʗ/#̹ͮ(~-lRd1ERL >14K眜!℞|N?T;4<<CZp9R@ bŊ˯{ٳgŅ+WڱR>ԭkv5"""CZ0s&= P1svs\N@bccܹ3L:ӧ|k&MDFF%0+34"""b_ p̘[{qnm|Hivũ$%%ѰaCΌ3 u?@-4i&MbŊ<#9 bkYY wk}r ;>oHH??cPՕ|N3gVɓ'F7[#GGG"7Km‡H aa}ЧN ֬1>D9uIHH 885kPzu|||R &M*0|lݺW_}AEֶl4 ڴ1)H0{6$'!aØ?2n1]rr=bʕtڕƏ܌ 2j(j֬iR"txqc|qfW#"""7SJ֮]wг'1i`b8*K7 (,Jf!g\مʐiӦ1zhzř3gx77n,+͸[mo-["""n>"?o|0JEDDb1jO>2668ʕ3nED@.ǯjqFBCC unbb"AAAxzz/ ׏kײ{nڵkGRRӧOߦ\rTŋ5 ]ƍ0k 2UAacߥKHJƒ<`+)-&4hЀ ܪUȡC󼟟iiinjxIJJ〱URRjҒbsOÈfW#"""ťvmc޽FY{Pҫ1\;#@8p` $''Sv)))cǎqAԩO>$`lNsʘ1pℱS)76S7j̙c%#;?dՋq@NN> PL6oӧ\w^&NHtt4wݺuO/୷EDDZ=V2?б#tj<*S;~zJ&M'!!su6l@hh6"HO7> Y"~n@|WU5'>>#F3j(N<ɒ%Kn>М__~s>DDDXwhcqӧa h-Nrec֬YFđZ N%u@8 !!0yZIk_~~jLl/@nܮlh.^;L+Ga ZfWts 3aغUCDDDXĦC+)<61ɑ#0a<4{Ո؇IF0vA2JDDDDGCDLgPՈ؏&MB[q"4jk_V{D;{58zXHCDDDJ$xexYc7SFCn`IQ۷Cb"x{]i]  """Rzrº|4UJ5CDDDJR1$!!P*V!!!,_<9=...]v5jq&s3f(|HCbccSNL:WWWINNwn5:ujc*UTZ  ͚]:$%%SO1bf̘q+Tc=fʤ42㥗̮DDDD|N=kΜ9XV&O єJvv6ϟWY`X09uIHH 885kPzu|||R &M*0߿///|||`ҤIdeeP8hQ̮DDDDdp!X͍MӦMYr%111dee/[^=ڷoOƍp~!111߿>যgTd˟Fr%,^ ʙ]H0ZV233 uXVMѣs֭7nĉx`CÇuV|[,g()}[PDO:u B*UhР 6,222pwww#ٿ?f>L2E~mPlY"""PDDDlAqtv _} ,O>ٳSbEzA_Rzzzŋs#GҶm[zU*ŋ;nzq>ꀈ-"n?gĉܹFOТE ԩCJZqav/^L-K.7@@@ìRSS ,׳n:>#rgeeΑ#G\2˗^J%u@DDGgһwoOppukӦ $&&2gùsnx͛aΝ;',|4k֬= #<﹔j׮̙31bč@?QDDDlAqtv G׷H3sL&Nxs{ә;w.=`K֭VǏٳԫW777ڷoO|||{YV ƝwiԨQu@DD6GgRFJ umVӧ?<'Onݺ,\G{رcYhIIIԬY5jPF|9r$nPDDDlCqt%nދ/|rzY಺7h"y/^ȑ#f͚5k.łb sȍ"""8:S"''/%Kj*Ν;wc=Vꫯꫯ^<9|p^[ϲru@DDxZ\sqD%Kp C駟{ť5hD -w: ""R4KȡCXd K,N6mGwY".w: ""R4K]H֭ٶm<̜9;Yͩ"""8:m۶Q\9NʠA85u@DDGgIgϦiӦ9U~JvvV؂: @|I6o̡Cxٽ{7=z 00۳锛KXlNDD: LYfvL0~۷ɦM# :O>/QHF""b .ꀈc3}ۖ-[op1֭[G.]Xl={EJ mD(""jq%ǚj5[bzՕ;`N8ҥKi߾e2u@DD|c1[Sbȵ<== ?.uDGGHrhݺ5 7/dEݺu:t(ǏAD+li8*SgǞ9sKra l%Ο?4h+Wg~ҭ[7+ڶm{뢣9{,}~:tٳgfvލ-|R"""p僭l?1L ֭G%==*UyZymƲe˘>}:F`4jԈ1cưy^;sLڵkX.]x={6/R~2: ""b ꀈ3-l\,.EJ(""r3ꀈ3-[oqb_FF{xx>_X6m_$<<zP 00Xti_KWvN_HSDis@Khذ!:tf͚em֬Y'_MӳPILLW^4i҄{P,^;˶fk;u@љ@z?WP0TozcǎѩS'*Ug}W^[ ꀈ-"δ!X999zVٿ?ΝsoYf7tԉ˗/n:!Mu@9<{&;;s$66֭[.{q=… t֍T>3֭k"""8: JOO\r6UVӇ'ORn].\ѣG=oر,Z$j֬ @dd$۷og㏹/_={RR"""8:5j0b VrINNfܹۜ:u/Z'xbhڴ)k֬ɳѠbɷ,ba̟??swyu@DDGg LIHHo~ۅz www^}U^}#R"RDDDlAqtv }GO>!66)Spҥ-[,:ubʕ4SUɶ"""Oqtv]Օ~~/c9}4Ӳe EUv: ""RGg> mۖmۚUM"""8:mu@DDG"b#ꀈ-"NDF[PD"""8:QDDDlAqt "6؂: Ν;|M- }nff&&MbŜ={&MCnzٳg3f V"##VZӼy",ꀈm"ή$$$H[, kРA\g}KnꫯnHNN_ٳgcƌח~z;vP^"-ꀈm"ήd6mXlӧOgԨQ 0F1f6o|kWX֭[Yb<}%((^x%Kجnq^ꀈ-"ήdРA6+psscذa2dƍ_~Zj׽jժJ*ۗ˗/SL.Iu@9$]vwsw5{HOOg[ ꀈ-"iHjj*_9bkEGu@u-edd︇Gs[S1LCX:[~+˛]8+NjiW%ܔOOO233xbmn/g6b싅)[\&.ADDL2x&fS 1b.GJ VK) JMM 00&^yf*W\rf ""NŕGf"rK&4oޜ 6p9ʗ:oYf׽Yf|XV,Kk U@DDDp;{8&޻wo;wnLbcciݺuǏ'11<מ8q>(ةSѣ)&Niժ}ɓԭ[ rQbccs;v,-"))5kFiݺ5O< j⋚!""""R\h"y/^ȑ#f͚5kwu5Œg }̚51c_ qK.5)CnDGxXVkɟ*_B]dN<wh,\lvRB!7܈r#pl """""b7N3 lN2)a.]įjvRB!7܈46y2zQ瀨r∈0 ).]\r#zȍ!7UDDDDDDI"""""b7 """""b7 """""b7 """""b7 EItt4+W֭[`vYbg۷o駟ۛZj΁O?ѥKʗ//Ծ1Д)Spqqq{tڹs'aaaEƍ7J={ 4ॗ^"###yz8 . /ХK*W .,ܢ͛G $((ٳg(V*V\ɳ>Ke!N/IDAT|WmNz֭[ӧM4!55ٳgsyni޼9*UbĈ;wӧSfMmF2eLI뮻pqqvٳ'sz>_|=ze˖Z+޽{{ o+Wf˖-,X0?J$ԩCZ]66l` 80yEy/;wtܙM6xb^yƌc*Z-_=vEkzmڴ12-[X/_؁n;v,XBBbXΝkz\:Xz!kF {t7nbXۗ?nX,ֳgZV?JL'VwY-u…+{!==kѣGo'OC`Ŋ1lذc 2[/X}݇[}<իGÆ ILL=rJwNsoߞ /_nz<6mbʕ̜9Պb#O\\'Odʔ)1"'''yzoN9^jU{fBU40 F H($mP`E.5hc1$Y:HXNưɐ(G7ۗ}fΌ<9|Ϝ=ss)0?87,M6с]vl[nxCuu5,Yٳg봯Z pҥ ڵk477 ?VBuuC$h4HLLD\\x[ݘ#ԩSJhllҥKakk9s`׮]0fpttĎ;?ǏR\.D DbYw!V8;;h׶:$=z---xwcLGGy T*ЀnR044͛7cΝxP^^Ltuuرc #₳g؞dwBkk+LMMZd=e>XXXhדqA||<֭[۷ fvݻ{L&s8ݼyxqA͛Vdv6mɁL&CQQGGG3?HO.AcYJ<~~q= !!!;w.{1NIIIGbb}#Il?(%%ͨ 0^}UDEEqA"}r jz7hpGgaڵpuuȐ4 "##QQQ/k֬_XXt4ʠRuVCKb P\ |r̟?رc1pmw;ta|}}O?AR055C=AwL6`ggBB BBB 3߄H^Ep\xeee@ك>ywNz}aAFF͛J^"72hoo˗6qڹs'>SDDDG|_~ڵk!J;;;qqq01R-[W*r9R^P(pDnn.RSSk3+g~W^KKKa͚5ɓ';,20A"&&&#D_6n(vvvBttOS4+VhgAa߾}‚ sssaɒ%‡~8s8UTTT*OOO!--Mh4:3߂ t/D 999`aa!,^x}p """""2 DDDDDd0,@`X!"""""aBDDxxxLw#|d5ؘpww`""!"$ɤӧO&&&F~r /ߏKD4EDD3ܱct~>rJKK;;;333C8B P(xg :6L$$$ ++ ʤL[H9 uuuH$x  ((FJJ `mm~#>ĉذafϞ T'xW\0~| N" 11ǏDze`mmuRxVVV@}}* aaapvv7n ę3g5!ot@DDahh{A{{;ܹsxסR_~ELL [nAP՘?TUUAVw?k$$$@!44wÇ#66eeeZ7bpprNNNhjjBqq1!J1V\ ApY?+ ""1ڝPTp{bxZZPUU6=zJfffy&r9T*۾};.]TdggKMM d.J̛70w\@RR")+ DDDDDd0 |UIENDB`PyNN-0.10.0/doc/images/examples/000077500000000000000000000000001415343567000162545ustar00rootroot00000000000000PyNN-0.10.0/doc/images/examples/Izhikevich_nest_np1_20170505-150315.png000066400000000000000000001206751415343567000244200ustar00rootroot00000000000000PNG  IHDR^sBIT|d pHYs&? IDATxy\T?׀hWZ]R\J3ޯ[%rKM=5_޼-Zվ.j+D 0?N30ss༞8~YuF""""""73@DDDDDD`@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHiB""""""b@HDDDDDQ 4!F1 $"""""(DDDDDDŀHy憶mܻpz:u~>|8nݺ@.HktFѨt&Tlnnnhڴ)N>mu/,, ~~~Bvv6,Y 9r5jP0DDDDDT1pht EEE}սy!// hҤ 'Dݱa5J,Fpʨ۷[lG}d-[ЧOs0ݺuC-yfWe4`„ =z4{1iiiO(((oLyJJJNg777:K=ш͍cXĀ._uaŊHMM | $''waXm۶Ŵi\Cb ^YbY!)Q^F#rss3R ۸5.]U)t :u`pAHmrssQvmAY2իW޽{'"""""Q! {_~5jvtVܒ*Yf(,/$ I򒑑*J# l㺎*@^^[_ã>7x#F#SW^ovȁTZ 9sp5ܹLj#̇Q2 4tQt:F#GHaJJ ̙tiiiXnӕl#F/^lu}֭b5k 88^^^ر#=t)uoУGcݺup:`rQ QX\tVHEnܐCTE|ШC9:;hԨϟ_s@.]퍠 DEE!77| ۶mJX@rr2ܰyf߱vZx{{>tGɁTHIIw}^xZ W%_ 2|E8t,l:v$SΆba=`ZV.Cѩq#AcժUHKK̕+W /`8{,6mڄ"22RM:o6ѳgO?~ qY̞=3f0&˖-ÓO>7o쾇`ѭ[75N0*PC5kjZW&MBÆ ++ꇜGv< "^/󵳳e&$Odzrt)"g݇U{YfٽpB :ǏGHH:v숏>(,6cfĉׯ˗#""ӦMC1|pDFFbɒ%Vzc"$$'OF w^q-67nΫ#ՖjBk׮wAݺu???ԫW~~~JgBǎCdd$BCCQvmaРAv{S.\^zN:_>.z383 Xo9 !I.oNRTŰC>Q-/;u6l؀:uz@h/KѡC?'&&"<3dq#-Z€жm[ܸqVB ̄uT<3… %Kٳ8rj(8wf@/*@]rGMH+*_Q,,tW3<={bʔ)1bս3QQQ6=`>͒c|||dϣ_Tӡ 4;2J퀔F9Rm. }A֭Zh͛7,8ezp濣RSFyY0I.h*Fvw(t:͑?!C`ʕV'OCa8u._[bgvիW#!!ǎ[oRh޽111HJJ5kw߭eҤIXnbccq;6QqsR; n۶B9 Ґ'x湧z ;wtu87TBn*C^!`Y̙M6Yεi>}:tшf͚aРAg.]7x]tAƍb 8qk7o̙3@`ذa妫hpufΜ ᧟~8<!t=յo޼+W_7_3͏t(..V0wlܸ~``ͳs M0l 8B)dgV.U\ B~~:`׮]eV``@;w^sѿ2_ի6J7ƍ𹲨9Rm. |7Ю];|UrS.\Hc<@͚5mUI]2 k%+ -. LNNƶmмysRiݻ7ߚ[///@AAMS(ew;p aPrQkI+A՝`0eMG|6ڵ+N:t6*-++ zBVVveݮiP&<:OOO?sC.j̑%6\NN| V𫐴DՖ*o׿D=?NΕ<{lV;!﨔"a ! K2JZ> 7G-U2UVXx1m߿;vѾ}{ܾ}[\V{dLSQ8eTUc@B i,i G]O#ضmVZSU޽7nA1bΞ=\-I4}C1 TN%QҊJY;-}/ "22C |iiiXp!A'0M h Dzπus@]F"غV#F /uVGѷo_4innnضm账8Bz!ԠAOlhwɿ.KGpsq9dֵt:xyyaѢE3f ֭kuيPIGMzaȑ?))g`U (nc@:GzU*Sg5d;HKdi|1 /c>gy={Ĕ)S0b{9993f lG} M{;y*?:%sUrTN4IR" t{h]*QXc'Sí wlQͱ  ,, -[޾}{?emذ!NJJtsiVpAk@-\ө=ɓSVw_@mmbB\QwV:ay!C`ʕV'ON:a5j|||p9aժU]bر#0exיz 1114h:5k`ڵz\|W^ũSGyܴyyV* $*Bg-N 1c lܸwE۶m\QraGitʷªbΜ9شiG6mӧK.0h֬ d~fҥx7ХK4n+V'^ހJvyf̜9111 DLL Vng;::^{ }YiY5+Gkزe &N͛cÆ x믿s.ɃBU%]F[TZ$&4*>cV֯_os-((v:`׮]eV`` viuΝ;V[l(߿}UkҞ}Yk S*cǎa?mݲeBr#G`ӦMXt)&N6lBCC{.ɇ¢" :O/blɞ^Ā L \nCUeTi;voAΝ~ΝÞ={Pn]P}jԨѣGլY#GÇ|zQ:︘g`!S9! TŰjVc+V… 8py"!!-Z@ڵ?SWr+~4SFI ds~R"W\A޽t8q">sX ш4;UjJI${\< MeH *]ޤ*UrT!;;ФI={p=sU<Դػ_+\PWmJJzmQ~8BHp !ij}Qݦ2]t/6m`ž={/[nJgϡP`gi)/ + UvQ6W;!ĀP5ܬSFI zW\,4kےc. \z9 >}:<<>2xJF!^^ H~ЏQP cʨ׃!FN%3YY*Ӳ#F 66 .{guV%Ux ?… BΝh"hѢ´Ng5pp0yQBшWg<==h",ZHo*]O5ɓ'̙3V?8yW2 +ݻITjG)AmЁSFJw4U>l h5j8lCE[nv۸N™3gqF5n׮]C˖-EOh @xx8VXa5[M6V4jsܸq8<<(laCQ& 4.cŊU8GڑɷG+|L$n L)YY)e. {Õ+W'N@@@4Qn˗/O?mugϞxgzj̙3|}ڴiG||yD0((o&JlewO{N=yۨUxU* eTEXsIUe@Һꔥӈ@ݺu`ǖ-["66V,t0< h8DGG[M>|8&N͛7$ tVTTᣲ~ abz2}S-,Pww}B*#kTܹ#sϔPzY ,LUJ#՞I&aĈXx1X,1x`s&^GNN4h`vCVzxx ,, 'OtIݓ9e޺Uc{//*Ȃ΍NAX"%y$+K8{S P: Mc]OuN=zcƌޤIܸqCUa0|-==:6"--%ystλwmrho Y&Ta |;6頃YS3Dp 2*S> URa͚5esҥKhذKb4QXX(ٚ5kڽo>̙3 ³>kWfZj;@` +caf>搀A@nBCe|߾ kdf:%_e~IJ ̳LULJNX5x{{#''Gl  ??usM 9s`N?'O+Ҽ۷?|t:$&&EV/\h۶-֭[gu믮킂7`0y vݻjKz[ +s9\[{$~ߺ4hހB"K{Ey !t:EU\\ܹS:uF4-9¥KW_C=<<쳸q:uy4/߱aQϖ=z۷oa4n'KOOGƍ%嵼q֬Yޫ Xܻ)ݾ-uH(kkXn(c'ȄH+*U[d*ܹs1{lK/=xB(u/ԩSA]fi>|twA=PTT_PԨQǎëjn0AIzO^^*Zc'DDz:ّ6˗%&4KOKe!;!2ܩj**ۉLf̘ӧᇁ{Xmb6EAA1n8{@aaꏜ\ HOOǎ;b9___DDD`ƍVBcã̟!-aCj)|)T眩DCo׮&Llʼ)L*<*ڔH[<Qnnڰ!|ѫW/^PPU@خ];aɘ4iԩѣGc.ɫܼ) ę,JApŹe{LF231m_=-aZL~9ռ 26Ǖ'"gqCuKbm5TeNd r=ڵЭ[7԰8׮]C^̡8׮]|Ν~'|YYn֬YaŒ @Fr(7n:7ªNNC:6XQ UZމ,fܾ-!a~SP"d0 HHH@Ϟ=Qvm=OOO4mNTw _9{g9(=L$OpB{MiEΥPzv:ks0Y+=NBY \]ῒwyz:+w /g;Aɤ FmUE-*ۉ,jVխ!|Gp … VZ!""BU/pQ&fe R"NMIz9v*bS-d't{pSW0iʵk@Ӧ,NHz7o _ukJ /MTjV0н{wt]T[7nu? YW<,֭^Baϒ`{ UlYJgTt hze>/Oh(o)#1Q(nRq%&SD .ȨT7evލ>}qqqJgZqtBקE %C\񳄤$]*PE53h`-w?֙H>.Ea3EpfDVѥr(N^zN:BTT|}}/b͚5JgOѣG }{СCx@DEEA;=_ɶDo bak3v zZ ˬLg,z@UvB8ymh{ QEWvB"en$uMU :n|rDFFM0?>WIs1|{?!!hݺ5/_ׯcɒ%|2oԼ]b={JteQ /^ KcLF#p挨#! 5䄮s*^ۋJgT  Ϣ '"G+(,g9٧ Ս޻wzޣGܿ_^{ YnaڴiG||<|M̙3WƮ]>EeS E&xt JH6RSNNII¦;-oQX\'&P:+2ۏ*,)"[.-T$% {ςU. ۷/~[nE>}ȑ<8w͛g~vv60l0X6|p`N͟gҋE5]Y{JO5Q|"} } :~hΕ \/#sGW+.4\CH*ƪY=T7eu֘7o~Wt oʕ+N0Al+''SLP5 Q>HbBA}yQ+ʃjըt6He:ۙI\aѕCl*,yyr7"G:zTXWlf-)15T7B&߱aQbϞ=駟? 990XTT<$''uA`` F#m^+==7Wңjf͚>gYGB$)"cq(j޽3o$lJby<dسX2|~~ĄD8qB:$DbC#طHmn݀sby+V() c@X >\)))t߿uNT`0aBCCQF ;v Y 8z)nnOs<}R.׾}[oILM;w6ݹp+K*oDO; 2Q'N~~#:tH^zDHLd{ʭwHw {7ӧO7_7{CM\ހ9B֭#3FMG_"""7nČ3>66z-ã5&F0kP-q${WXChoZ_239B(BA0Zfreg VbBIIx6t*#?-XNH/Z~εrN*t0ewwwﹻ ?"@ ?ls=** x饗ϛ7ҥ |M`ٲeٳ'w<^.\i}ܿ_ pF"/3 ;)j ,L#0Px{xW0isHLd4 22=mWWk]:-oD' cOTf:t(:uTz IDAT_|ԣIh׮I&?Fo8?!lH"9a{gmwʜRQB2k3K+3(n† iY!|z(n?u ~ZbB"e@D*/kX-eUnǰa0dKq0~n-,֯H. B=ecDŽ {;-oD+,VisDT^x]t^n]ܻwOUɓ2)_XP[ ;_!orVo_0+!36em-|E%Em;E#^!ٵK+z)[>Z9!`0#OOȑF`Z`؊#RݔFhڴ DN(dɴ=E?2,%%ڶ{d@MI 矅ހΩPs?>=gy섶,_'Hl Jܽ+ h:Ts6Utz0 c`0=z(*Muѣ> :iii8|0y̘1CUi"PD„Ԉ=/Awp8jh6k0iR6($|n7$l''>ᑇ^8v!a>vߍFaqQmѣ=o.,t/mq<¢Y:~p}pĄDDEkֈ8`u=&hݺ٨6~Idǧg)lUN@@΀?^|.9shЊj1uP~:ߡC-)ԁH 3xa_1GMnJ02'yI~vvYsR3(^zIPYTz,\wFff&JJJw(;#LWl!>xgKoyyr!Q2{9q/i\ x[x zUJQ?/ܲhy\C.B1c|DF#E -͞- 9/'3tW]:/Dr"_OǪTܹ۷oGxxY6vfJ n+2;8~xi۷kT{was_vULX:O|nvmK;C)08] KtPi SE'i;&磩GOx+oGVAE;cYeݢߓ5kMyDd"Xo/⑀ioUP l)wᬱDqrYc&LU2TΝ;3grB'qnN%&LN~gK{zuG~*YT^~Y8S{t ^yݧc[}q e!S4Z7lzpw+!Ҝ@˖"'vzN%Qݽ+Lus6ݼyFφGNƍAuLK.O?i۷*Э[7ԫWx'/(p2`raZҥ%+.up?.ι\t -sYSa?1[1j( ,;.^֭[c~:,Y˗/c·('xqZҖ A =aHq:a9RTیUO. #7oI89`XPhq4o<%KFsh-@@S+"Fa ŋ/Q኎6M;_Ehn!e c?HUT^XZn%9@d$p,wAeaUYf)JINNFdd$lٲr6m ›o8DDDT*/GT$/ݳGX`g_؎x Bpv0mT ;w† ¡mbwkqH[b{v\sn_oM_W^Ep$)2ٳΊ7nǩH0ȅFa3IQcǀfD&ӣM !xQH|a xL< b>c`_۟53;;qqq6l9Ç;xp1FFolv'$X߾jhaEGFWyWqvXj!ٶM-\Mi`OXlTxvi!21csMoO~$-"13'm@wFt莯{ʚwK{whH-{ tYObYUtYUQ.zBl5/'OIo _~ <<7'6UBCը%K`&hРs&)2:aK^}U~mƎ˅ Xf gRr۷/cƌ^zZ{ɓF5ٱc?矫Xqgbg,gTJDjN%&({wղF1wjeT k߾-ϫV˖&UEsϵ>Dg힔u][Pa22TJڽxs{ctցF0ĥ7l>>WĿ\V+U4HIE `rTxɒBA14h8>Fݯ{u=5 vV}Vu 0Aq(|-){t_ kתfq'HY/v~̟?N:һwoVJz?VMM)ҹ.KKKC4Nѣ֭׮]O?孷ÃLtyZj\ڦMK璕gݗO=f.ZP~zwY3wnz[cT0[йM* Ng)TE{&k''o!&6Ǿ=-3R }mX[:QQ`CbGd ,?['& qq4ȽW `@f$U[Cq6ǎrgφ틵֚t%A԰>KMIQ=-$f"bI_ԩSRȲaV5uJ{lcv\. nݺxzz O>ɸqJ4/۶mM6'?Y c:~NJn\,#GBNXM>׮U:{6isׯU?ʻS`7&WNrElUm۠v;w]Ʉl ۺ. X"фPjU6l@Æ ٻwoiwR͚5YhQεt "22jVPMHJJi^+::6XXی?w}4#MwτU٬Z/e`dR(,S[qF#9صK=ũN=үWzAZ$ܵ -1w>q}W[3ipu wG?gǥ ah聋 :ԈiYxh!7'Ú#9+ G䍇`㹍3JiЦCt,;Qz23U/նkj"G)Ez:Gn?Pf;vT[˨UQݶM=Z:uR ): m?ƍU.?(sRijdnݪ{[";s-G6&MNĘ׼y%s}]@ح[76mDӦM:t(?< ,ҥK9DO>}QFDFFE^ChGy$]?K+Ѩz,߯{U7jTF|QUW Q7AjT K7S23}jۻWv7n -[sƎlMx%U+6M}NǫE6=]}4jl7h7f ƍرc?R>k!n? ?3ެW^sNWNΝK1gEӫW//_΂ ,pB|}}z{{Ӿ}{-[Ƹq~ɒ%ӳgb] '''>DsŊ4V{{xFcd`ǎ[`뭷T֭G]^NIZ;}v;yRMZXzBTE8}g'1cJ),8׼H281 ELm?yoilԈݖ,y:|7Fw\t:{9ʹ+URRr?}RkED[oX:i&UM;@sҕNXh>,U\Q /"Mnw|u<7rKNy{=>#ڵkGF &-uggg^y%pbb@߾PGlտXWN3C'MSxy/]RsZ QQqr1xJhIXj+7T|QC?Nyzʰ[T-[Xp=R5rTwch2&\9Z55#}PYYKKIr2kJ5)yTT78._VUk TJe+x%pw^YGcwD~{ >Ѐ39996Cgwe`At:Ν+?K@NEi~:%3n_vtoxWrkINaˬk,?&MWbsU,VRkeu^:0b!v\u܎*OgRwN]VZ7G]vn)-M-9i٢ ~ө>[?^WE*YWq!ի*%X]Olڤz6nne-[*R-[|?W1 ѨUQ:]%wZ7@BQ?ZԤ+TwLj9  ׯV!"F*SÇ3Ci0ʸe_JjxF%#5}bEC(E l~n^~Y G$%Y#% , v;2q}l٭ϽG쾲_5{Dw0>B uq;RV\ͪ^0M츴N3kR W6+'WՂ5kVިD ӘIg,ワamK<f;~/-M}APF)T%5`*@F PT ((wsފ},5yG5^Y_W͌~7b߻L&Yh}[zUk)S33R&3jZn]/1d1u*Uď՗•+*@|)#;;11jĨ=KExXP9b3Ct@/6V}(Xo/x ܻYc[7 K]kбc1,jno?6/mg$g%0Z'ҽ<+{,l6) ƛoBݺEH>]UxCv1j(t?w?F%lhKk܊ D7rrZA_5R#Jαc3R&+XqyWV|*]9K˩Sdn2hP{@;ȑ08{٬  רC CX\bIny"wUd4M_~QEƌQ]oGhY$*SOݳ599ݪ_ZblY`X| x'ar|l +g 9s իWgĈ߿sf,o\\fYJ-Ds}EXxh!??3n:\Ń>H7nu g͂M5lEƪV¹szN>w DU,l/ 屠/.NU+T2^6ʞ @}<~乧Βr+|}nmbzi\ղwpv5΋/)slVR *qq-PLxDIG<\ pL.c< p-]Lzg炃-%4hTCaNUЧ` X>PTÐf0ﳂe=&FMO{<. 8c#+xqyIIjtAB;yL’cw;Ì3:t(uc׮]̞=#G2qRΡ}YW7K~LW2̝Y}b5R*RRU TPر#<+TR,]fU ;uR3ؔPdR_/`Rlc"2^OwĻNsCqm& * ,WH5DUH % x*8$gN!\pHGfnU +f)W lYgx1,YլZݢI5{ҥjq.tHtj4TG4UnVP`w׮^Z.Iy_#=`x#c9xd㒚Zr7 K˗g̙f~СCIHH('˛8;:[#Cp˧O|Z(uǏؽ[M>פT[BCoHh±$ܿ_<, -kX0Ԙ:K%-1) (*!^I'蚄IY OCT'ɉ_Gsu >qߘ]i9ix:翹iGb@@A´ߧjW)ZY_~7Gq$/мVjV+oA [_!;;w=."reEbd8M_^`Lwh,9Τii@Rޭϯid%f`LJŐJg{~D*8&K9OcYd$☚.)ѨnN\-,='=]x׫!0<\*U6uIݒ0&F >ϛ0,L%,[ΑtwׯȾ99/]ީzB[NwL—Dʘ4$ᖕs'.j+ln yJ;'%²e˲w^W]4i҄ׯR쓼Y퐖z 5ϞU7˔QC|ʕS-*..*Htv/R OX*&?,gI ա,F9499c9n&ܜ;pu4`U9 7cΆ4pJCoAstoot>>*>>:sϮoWq)Ȉ=x6zʗgTȾn&Ð{ǎ#|zmVMXy|%; Lfc7e )c۽,ۘMŭ d;%9+S˲Rhf18n/@db$gUW^|3< ZL C/Rѻ" ,౪YmVZX.QK\mX[Ƣ>AØcbLItt|sP44Ԝ,emaemYꃯ/?1/{zشX> ՇGᬦ|OBS BˆZ^iڊd>yZUn}5kVie.I@(D]+fRI,<yggVt_a tTd(J7A3'^9AYtt aW]y;͞{QFc[^Bʭ <6\4`rg96p&ͱc ram|O:|՞@ӑc=F5e m2GRS)Av]#yS=HHǕFnL{t-BZp% u>ϯp0 ZOz/C 찳2/~ɀl))tXց #.6M{qzi] INadTƈ#8:(_oZK uw6'-'f0c y~5ye'¬YqlN#L(PKm<;/ɝв P-ŇLʭ@gZPB9[qf͌ld6Gdc\s _XSVAtp;qx+7OrSRߺ??LFMBHE@ئM"ؼyEB! w,̥Ko4ؔ!?sچἆ i2;O~Aw0?MX0v]ŔSl*Vr;? c=qmyWyWqsRk}&ChGt 䶓VR® YMt:>vm]]߸:8ё+ٞϘg&/>"#$Lɚkq U}Zx$C1xuëK:&CV>Eħ3a^Lψ݃ T &g։T3fmGdb$+fhWιsL6$+N[1gfͥKx8yV*?g&ʻ'8D f>OU"EZ%g%b^54X53n- :ێ9s ppF=4Xjv*K/!3g"9fl>ꓫqutEH ZTjA=z,W%H,"Kt8qXvZߺ^A׿ :}_?TMc]@%.{?~<'--*Uп^y79w믿gϞL2",o!nzu&o-^Gy4Mc [7;ۅX}9?-h6QMYm)FAkwd\q<$fVcuդd0tP:=cZᙈgIe6لI31j(cXa h8oo&nWgDO6J˅-95=/ueBd(?Č͗2n8N$`@}Lكɗy,;-Y{f-qFd2Ypp3w`6p92wTQ4\3pvz\N̜}s`>zѱclV_ɜ}sHJ+'lcifZ|HH`޾ydv1Ⱥ3HNzgD>p|AY>/K'LfG'ѕJޕ 3 r'<=pwr/bl|ր$* *KQJ8 i>Ѩ&-#R-9fy愇ӯ_?Ynk֬a|s:D͉`\riӦѶm[֮][ɛE'>=)ۧ0g|jp<8ooy-緐mfW]ld|C̚+)W;`/#0M,?v}D|F &NBQs‡®]j]ŻqK=6l{4?rV4{_~ }YmիWHMMϏW_}z`Ϗg}/ד7B!W`~]F%V^駟iѢEigoiݺ5+Wd5 www~g֬Yӭ=zHFl;99Q~}>&M`$x%.y|}cƌŋ4M#;;H^jժtЁKrJ:w̐!Ck=/33 WZ !Bt0z4l ˖A͚0kǗv΄ƍE*Upwwg811m۶"8q|̚53g@i۶- O>$z775S``"++z Cz=z=!Bqh@M0n̟cƨHyQICBT Y99 YY[ޟ xnXv6;]@ػwo2eJʔ5khѢ"k9gڶmk -t«ʅ R hFttt׊&((XyٺǏw- !B{N;-9~o5krEwvooptG ύjM[zPn|ɓL8|t.;wk.x ӧXibcc1L[Zaԩ#{6:t^z]`!BQZʔ 2TP[=178tr~.q&coXb`0ܴC>v֬Y?ίJRR>>jqi̊+jժx{{Ӿ}{-[Ƹq~ɒ%ӳgb]IB!6[BsaMv!ܰa&L`ԭ[7_]J9+o^x*U0p@oؽ{7'O7ް{AZhAZ8p /_fƌnݚHד5ZB!ĽF%BKjN+;_y9~8)))ԨQ!Cп|ܹ1cpիSL)rYB!ĽF%­[xVJ('wy!B{qKx"B!5R-9v9yi޼9QQQ,];v$z IDATr΄B!aw??.ܞ̔)SJ9wB!Bqﰻcܹ̟?ߦyE8ps&B! O:E˖-/S ׯ_/ !B!Ľ€"##߱cUT) !B!Ľ0|pvލNի|׌=AvB!Xol]vdddвeK\\\=z4C- !B!=n!!224""",,%YE!Bk[r6 E#o!Bq:nɱ.}-y_}ΉB!B& \h4hiB!B;nf4hɜ?6mڰ`V^o+ 111moooz=۶m+;wA`` Ç'===yRJxX|yf2l(dwߕ"Dʋ(*)+8:nɱ1٬Z;wҩS'c=N+|mݺmRzuʕ+Ǯ]زe -[wCh޼9 8+W0m4ڶmڵkm}7:u*/7䧟~bٳHyÃto+M_QTRVDqHyE%uܒcWa^/^dѢE,YKmt e˖~gϞ;vȑ#:u ,XY~=۷ի/駟ZӷjՊ .p…"fE%_¢8"Cʋ(*2z#^NCӴRo*lٲ}ʕ+֬YhdРA61h \®]n/ B!0;;oG}p=ٳt]ѣG14jf}Ã5kڜۤI4M9W!B!et,_J*ѷo_[ʕ+W*ht:cs <TR!B!& ;w.!!!TR[u[j?i\bvfff\]]-v^B!B;n>}L۶mM6sfΜi?w\i޼y򺽿B!wEi$ ,{N4%K}vƎk=oɴhт-[2p@._̌3xyG3bONNN> Wo)B!BNERXLNhٷsNƌÁW^L2&S2o<^:o>=B!BB!B!OK;B!B!JB!BqP!B!Sޅrrr3f ӬY36nXl߾} 2:uIhh(z̙3=y$:t ???CBBB)Z؋ɓ'W^cR^ҥ ~~~xxxPn]fϞmsɳ>KJVZL4L_?~>>> >TMFhh({QVDEEQF z=+Wȑ#6Ǥ 6ХK6lH^ٳf>@ʊ+WPn]|||xe׮],\]zj@ŋJ*UXp!}98ec޼y 4=zc}v,YԩSyJWi⮲{nMi3f̰ҪUhѢs&Jۮ]4`̙3 /X 4HЮ\bݷqFMi/ ѫW/}Z֭u"RRR{7=OʊIppu_vgʕ%_amƪUO <.E|1yd222 \$eEPBz)+#''| E-!11ۤWHKKcڵ/ 2"<<OOOM4"Xʕ+իWqkҤ ,Rd66l vKy6mۛ˗/SfM<==fdggRVҺuk4Mo߾>|+Wb Νqss" UayިQ#5j^rTLe ̷?00MӸzj)Jثe˖ų> Z1 %GQz̙åK4iRǥ3g`0ڵ+O<V_~̝;}RV3i$~W4h@HH׿6lӧOlDGG`m䄟ԇIFe233qqqɷz\Pt 2-ZXm[ǭʐSeTDƏ;#oHyiiidff2h >cz)/8qaUreZjEeڵL<,eE8e#33s>\<eܬ]tʲ"66N:w}Nrˇ!1vX2dHHy?4׿żyصkj/_Idd$k׮F6ǃ(_<˗vϞ=R~QQQhưa #,,*U{nN:E*U4iNp,֖1:*T";d2o04 (eE8e~hܽ{b6wݻc4/rrrXh͚5W_f3={d|֙go3O?T6mӧٳgIeW:uzjV^͚5k[ڵ e͚5"gϞhƂ lϟ?'''ZjHY7@z)+pE-m۶ח9sؤ3gtԩ|/i-&$Z^Xf #FZj,Z}yfZhQdĈ̜9.]УG|ǟ{9\BÆ )S Ç'55ӧž={}M6\v#GXIyg…уVZe~z-,RVi׮ 2???~z ܹs)+>ׯܹsyiРÆ ˫XecΜ9 2gym۶l2L˜1cJ׼;ZPP5mT_K;[nZnyZ4OOOWӧWJ9uZz"F6qD-,,Lsqqõ3g;Oʊػw֩S'-((Hsqqj֬}d9OrʅQ.^h=8e/jժjիW/sIܚ !B!}J !B!}JB!B!OI@(B!) B!>%B!Bܧ$ B!BB!BqP!B!S !B!}JB!B!OI@(B!) B!>%B!Bܧ$ B!BB!z[nRM@Ӵ;vzիW߱BqP!]888mL8-Zwq̘1;t;v~1cܱBqiwvBY/_sikk'=vA.]]l6W_}Oʃ>'|ʕ+ &&Çۼf&M:um ! !GFΝKʕ޽;˖-#..777j֬I6mزe =zҥK,Z˗/QXn .+:/^$(((~MXp!k>}uoc ?%p+#!f5QuӦ?at2i8¬TZdُf´e;X FiDb0vq*?[~<9>k$Ǐ#< 6hݛ̜93zȑ#|1wދ.P:?I2bĈ3&gwؑL0!lܸ1{qcǎo߾o>f̘nm1bD&MmӟѣG3v̟??VJWWWˉ'r#8C޽{w{\UUum'NHyޗ[K W_}u_pM7e4w֝Qrצ6+VHmmmF]iӦx<3O_ $sߟ'oϖ-[2jԨ={v?套^:N_A8SNW_g͚5O۷/7ow}/ z-X ~p򗿜Ç?%8Ch….F}kt !@!( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (E ɓ'矿X橧JSS%9vOq :oی5nj;6>p>:wޝ[o5Æ ˠA2iҤ}RSSS9UW]uAku)sС?Krɳ/?۶[nIGGG:::rȑtttkIu]ׯ_vޝ7x#֭˔)S={vZ[[+ؾ}EgȞ={2Ԥ*W^yeʘ/}.ٷo_'<'|2_q\O?3gy x: 2dH&Nyeʕ.Ɏ;2|߿?_3<ٴiSvܙ5kdժUrVo̘1ikkKGGG-[/}KټyscG:::2k֬߿?7xcZ[[sZ*=P6lؐ$y'|lڴ)e˖gW^*nذ!ƍx驪$ɒ%KrGIGGG{1~?O{{{jkkhѢw|&LȜ9s{vSˊ+K/1d-SL?+"'O /Ç5ՙ5kV:::r=dРAihhȴiӲuZmݖ$ɴirM7ۘ'OV~_bEMV99iҤ̛7/}z%ׯ_ŋw{N/^իWg۶mg|~͚5_~y>gg|d<СC9x`z$0`@d˖-9tP̝;7v:um C=9 0 GT{f޼yYdIsС|q3B,X /b֯_Ǩ\veٹsE\ YhQvڕ'NرcַUlᅚ2eJw]裏g{aÒ$?|.WWWgϞ=ms_*׿ٙm۶e˖-IYf'ݻsر,\0zqչt477'If̘Kf=7bĈnPuuuYpao~<@mۖtvvfٲe9zh.@*}% ѣy]r9J#IDAT杞+裏;Hccc:;;3y3+9Cʕ+O}|߾}x|ʥgr]wϸqrΞ477ȑ#CgΜ#Gd̙=s}eݺulFuNk۪N~qq?{ ( ( ( ( ( (R(IENDB`PyNN-0.10.0/doc/images/examples/VAbenchmarks_CUBA_20170505-150538.png000066400000000000000000027657501415343567000236740ustar00rootroot00000000000000PNG  IHDRt&sBIT|d pHYsnu> IDATx{u?> D"y)TZ881g1Q)) u4[ArmLMj1F匃5lĹ85Ak9<}~S)VW:;N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(I@IJP@':$ $N% t(ISWWWWU}_9sfc58qnYxq?92w裏ι瞛}{YlY}+Eccذa&`/SkC=ɵfCw^>ݍݻw :4`lݺYmc+_Jƌ??JϞ=3dȐ?>s /1p|=ox/_ޢzt*J*J̙-[4Sjmٲ%g3~qy駳qlݺ5z,Y;3SLG>VG]ZV֓y /?'Oرc~&笳ʱoy'vl޼9/ry\~>ٳg/)q m(\2wKkҥٰagU-'ghw{2|T*w>WǩT*7n\,Xg}6k׮͒%Kr 7C[o+"sN+"E@ҭ@Wpw]oңGVkK^Zϊ+rmRSOw~eȐ!93gΜ^z)yqOCIQ{ط.3gL,]4wuW v& Ș1cr)(\{ٰaC=qT*Eӧkɚ5k:l|Zjo[>ꨣvzoq=~$K/{n}O9|CJQ9sf)h)NІf͚[.\sMiӦlܸqw}>Gyd/R7W <8GuT>oo~8EA֓հaÆtMIѣGgر;Y|y}{ԨQ-:~+Vhٳ$v[[^B?<'ONQ曳zswc˖-Ow…˞*mɶ`(p̟?߿~nݺτ 2a„Eswiјtv1cƌٴiS͛ע}hZ3<믿jO5֓ݻwOfm׭[=g?(Lu]?[nE]_׻wذaK6]ox̙3'I/[niјtv1tМ)"w_|%%INQF(<ٰaCKj'뗢(~f_n]R䠃ʅ^?uuuٺukL ?_$C=$礓NJQꪫoh\Fz왷~;gϮv9FU޴iS+`W:z=ya%I}lٲK.MR߯Ə/}K)"O?t㎝ ><#FHQ{k5?ϧROoqmsI^:7pC`& {\p)"wqG/_^풒$VJt=x`uzrIwy'?Oʕ+$'pBǜ1cF$7o^nݺSK/4Iq|_mN>O.5f̘r))"^{m jڴiݻw6oޜy8w_e{hѢG*J&N: ԢZO&ɤIһwEٰa.m޼9\rI.[=s(X" ,ةy睗O|)"v[Ν9ӲdɒT*\yyߪf͚꫹ N hРA_P]|yٴiS6n䣡˔)S2dȐ\xᅹ|_>W?SSOMQ֭[fϞ @d/W_}u7M;̟??+VkUVرc_*J%ӦMku`v_Wr$I͛(=_Tr]weԨQI/<Ǐ… seݺuon)FʢERT2iҤL>u5*gyf[ .-ܒ^{ɾEQdȑ:֭[}M]]]֮]n)7t.T*8p`|n PiӦL6-˖-y睷SJ\ve5kVړ~/̙33䮻ʤIy{ޓ~8SLwߝGy$/em_Mo|k9sf!ncvЙU*T*=ׯ_.uu'Oʕ+~7'Oѣӿt-z!N:)_}yy#-S+Ɇ.,[,]vY= 80ݻwO~rGgԩy'o~ؔɀ$e8 ./1f}3nܸ̞=;˖-kVSsɓ{hREQ":4@':]wߝO|0`@ѣGꫯΖ-[]{)kUREQ"_|;޽{&N>}E֭[g?YzQ2؋Xtm.˩}桇1$yW3a„<䓹Ko}ʕV:]q{,s׾F-^8ǏOϞ=>}RMUjЖ^|8ovx.*AХ|p Z` R6lؐ$ݻn'IwHMݬUJVR߾}$7nm7x#IدJ.\^zGM4hP<PK֬YW^y~ov6mڔ|34hPTֹZ:YLХzIUVϪURT6… 3ut}_]Biu*@XEХ3&Iv<:tN}}$ɇ>W^I//K ̝ ʁآ19$O==ztrU)chzkrq3[o%~챝TigG;} IDATg muԶ$#]x{k{tQ ]z:k[4p`k tt.ӧOOQۿO3G%KϬY]f*jW@[sMeWϯ5ҜT\<֊^5ءZ=qjekW]Z+u-OW;樳 .ˏ;6'9sUWL=]"4#S7NZho<wjZUrOvUQTx}߸O~uk4'<4֪~j>vpZƵR֮sC빖{׻im:[-QW:;N]PQTښk7hoܧ-cVWϮ~~ѕ椚ҕqoQ׬cj]+Hǩ:h_vkg OW;樳 JT]A:C]:#]4ԑ{s ;%/[-]ZI-߯V[C빖{Z޵VOR5Igu}?׹"ƍG' d4c*R@.`ab I%Р+~ Q2T~ j f{9~#1^7}hdžNSO!c)*+]U'y0{jЂ/uPT3PRy-寧_7JIRRE=K=o+2r=Jϩ{z9Xj[^N @,2A7sYuy.%qP޳zeFJSAkHKi)|!6t~pK@ܻy0{j70/yK)e:@ov-`IߩH3S+hdžN+$džNSl֝20*>'y0{jЂ/uPT3PRy- ]O)])e=%+g;3/_|mo7m_/_tP̜93ΝG}tr-${ja @+0O>9m,oٲ%=XfM̙3'-Z/Bs=$?+YЊ $}#/9X`A; /0֬Yx`s=1gΜXn]yqWQG/g5u'ɘ r`N;^Uz=K?AWgR^޳Q?3RJ[jyJRumSCn5-/@'l$5=\\2FXb , . .ˊЉz4u'JSŃMb,qZ5_.XüV:z^PϤ@Y+wqGK1o޼Xpḯ|q}O?]uJywan${7Ꝫ֪,|L:LGڼ`rQny:aC'֭C=t¯>1w܈x衇*V6t~h41o޼Ik ٬;Y;UU^1KTŬ%PM6EDČ3&=f̙w>9O?VtbZ,[,n_jժ8#Sk6՜U`y0{Cr2hkUxjkؤLֹ$u}&bC'FGGG5F#^x)Y""b͓]ϖ-#[kN|p w>Szx:醹a>寧_7JI{F׍>fUGFFW USc{e[TTr_9jdddܦB7g5?^xw\D6Yg[r)OjZT][lv>[wҥqǷ=Z b,^8OO;fs9qƭ_[vm\qh4⢋.{s0 *ܵXU΃˟Vk\5 ֪iȩ&9eU T:|^eK-˥IL*LgRZ43[^v`"_WoϚfwq1}86|_?֬Y_s1yW^;/-Z4ldc~~Jy 2>j _W__[ӄ=?e]ƽnwqUWM7wygL>=Ĺ'xb'C-tJ?AVy·'ݨܮt†N@nᆸzibٲelٲzM6w\OGg^U=Wz=K?nTΥ)UJhL;)HKs#3xryny:1Tw@_K5apt_j=,;<ifj% 7x Ccn6˱r0 *ܵXN:/s4ީ6%SV T:|^eK-˥IL*LgRZ43[^vl!:;k7;яΨLk OysO~@}l0Uoэ2(Uj=,;<ifj% `l٬;Y= OMhpz0/5K&}zFu.Hk}/  FgE~ ~6t":_l^1KTŬ%^4=z5@ͺkz:F&VJ]8Rޣ$u֫JAAղ@ )Gݙ~ԵyQ=:[/Z׽QV*uKQڽ\s{ YZVt†N#u?d @?/r\~qA̙3cܹqG-ғ|9<8CF~rL~tFzz~~(&%K R_<ӎ>7ZS[s :9sύ[o5O-c9&}ٸ{nٲ%:꨸ 'Ea{o|ce˖4kӿ/rX _|Y֪-=,@R`IߩH3S+hgZǗ?[_%K7'vm^xᅱf͚8{9sDDĺu#+2:Xxq~pfA`ΩZ3gjK}t\Sm@R_ZNLNY3PRy-Ğ:P;@kZujsszJ[jy^n/6t;ǟFDēO>x饗b޼ypq=#"~ 9<̘CF:LlLL]ҢQ*_7JIIR֪3ϴcFT)yK-OI^VZ}J;~ȭF +_:""ںu""CO̝;7""z衞'琱j @^3mU[J{JYvOI-ZSfVr Ў \>vyXh6_[~}47oޤk ` @ ?e˖Eш/}K{lM6EDČ3&;fΜ?|9f뿦29dnmAsZb*/~$֛t֪5SV T:|^eK-˥ulv \6Ԣ'"Liuʲlٲ:~ݪU#O=Twqy?˖-J)ټyd_|1bډRQ:4@g\3Uى~tFyOMzm4"F']ndd5+}:22ת֩ oiJ7::mK} ɆN@Oƣ>kF ~駟˿'|2>ķ 5kVDDl޼yҿu];8/y_Dr]r%q饗v>R@}|uO.J_zKeJk@R`IߩH3S+kc6tzjձz}c{,x<{w4x'&zꩭvkn_r~DD̟q7<<{aiD>q 7KΏ:K.u*PKת@l$}}#ızkL>}>x&cƍh4b]e{͘l4u' uo|V Gİu)UϟCCo4oV5<}z<f͚q_馛""=ܳlaC2B76S*g|/~$j ZrdrJjZklȥulv \)_H3S+hdžN@r}8cwوٳggf39ظq֯]6h4qEM)XNt5z 2/U!wysO~@}`{gqF?3gNuYw 'ć?m_jf͚oc͛7wJwyhѢem4S2P- bתzVJ2-<^/k7<ifj% Hγ>F#f˿ˤ>y?q\uUqM7ŝwӧO=qƉ'k6NתzF%b HΏ~)~ڴilٲXlY$Ѩ;ij<\S쨡{f 5=OM^+.UG)Q:U{W^ߠˠjYc ǣLuNڼ(UJ(+:Zj%(^uh}@RJTrt†N#u?d @U̹k?=ЂOMcǼV:z^P:flf3J lP \p s 1R/Ѭ Tu{z9Xj[^NGA IDAT zăTӯ`<׸Q6t(.ˣLlLL]ҢQ*ߠ_JbinjTGRjZT][lv>[r :LAQwrX _| }םZSCrw{( ` r 9dnmAd s?=ָz=k-ԝgpzPyy-Բ:s6cR.E@jSfVr Ў ِe<5dC'y:;kFӏΨ0/IoTYs l|_ΠIZE˭FhdžN0`<8 @;>+gꡇիWǕW^W]uUxӟX;dӦMqLJ>w}cٱN;8;h_~9.8蠃b̙1w8[nI?)8fs0 sNժ9T{8w (XkUZ)+1(3PgvuNR˖ZK/ٌnmEO-ODZ-/@;?7;N<F̙3'N:8s]zW]Q[o]vY̚5+88蠃bhh({챸~|zkl{lY&̙-^x!O~~\qSSsqͤE?:^t¼&KkԹ6inj}|Z7p ?x|_[n%vix{GqD~Gٌ 6/XfMp rG?ͫ{}zh m'~086_ c͚5q=s̉uőGW^yeuQxdm4S2B\_{ϊR_Y9ReJYvOI-V3 u+VO?wuW\zq)>XhQzꩱ|뮻btt4~Ǚ1cF_Ÿd#"<'>wu6_{bʕh4bŊ[X`A\pl6. 0sJ{R_2vd bŊ/~gyfmmbҥ5\SAޚ6mZ4i;#^z饘7o^,\pN>䈈駟$+!jѓ;ʆNPO?=:XO?o144w6_[n]DDzv}sFDC=uQfl9[l;S\}qamshļy&kxgc]gH+GQ{zU7e^:jMSkձR3rΖr-JRjK=/(Akf4Z@ה qWUW]{bɒ%|$vicMm?϶~֬YqUWŧ>qnڴ)""f̘17s̈x{"@;YRXo)c02oK-c\ĆNP뮻.83L],DzenuV#8bܟ[.""^xxGk_Z,Y$n[[>//8כ7G];q1<<\Q*RAD Gшt󺑑ԬH߰a⵪u*}2::mK} ɆNP<f?c|_ ƒ%K⤓Nw޹9FGGG5F#^xᅖ̜939Xzunq5+z̬Y""b͓=ϮQƱ{ԭ8䐉KK/}wmD,tݓתz6m::6Fkcu 0o_r,_<㎸;//)gb}yիWիwST\s5}od{h6OLڧzjwGDğYd;<<{LEʣLlLL]ҢQ*ߠKK#xGT(%UoXd~lO8!gʎh4N*n4N@Rץ$UV/[+>O?V]timK} ɆNPЇ>Їgn!VZ+V+W12cƌo͟|LƍhLiC]v!K-yK-VtbZظqc|[ߊUVH4cɒ%O~2v}#NꮻSN9%k?={vlذ!'h4qg>qWY&o8cbqw+w^,ZVYwzMO٘E?:^Uz=K?Aײ~Ը_юZS-<%9Ό9ԧO?Vt†NPqǭoy[SN3<3oy1{{cݺuaÆx{W|S38#8 _;Ǐꪫ⦛n;3Oy{sύOoISolfAPŜy0{j@GrdrJjZ+l{Μn\6Ԣ'"L}.^z饸[bÆ ꫯ޺uR$?):;53極FӏΨ0/ LM|3Kgsm537ꬉ~@}ޜ9sbu?0GF5pJZES#z t 'ĝwYwb٬;Y= @_~y<3~6~_ES$lT`G 4k֬?+Ww1mڴxӛ޴MVw"WViOSAsZb*O5@ cjSAQuO@5JsZzsZnwTRZ43[^v׿7{Gva1wܺ#?,zyihL/GgԋNԤ7`RkvU @_#񖷼8EKو<2Bю#eJY(I힒ZES#z 轧~:N>d9A<uk\S(:@wظqc1!>m01\jy(ydC'(瞫;Jrx#tFO@M^+.UG)Q:U{W^ߠK'RՍ΅j8;ugi/UrTjJ=/Wڽ\s7ĥ7lQciuzoڴi{Ɵٟgx;MozӸ>O֐h?raK?ټ( @~__veh4lT0\p3Ufҙqrf~ف**|H[jZEk4,QO 0rzXڱ tGy:n P:kUƲ`ҥw;o͛iӦ>SwO<F#Ƥmٲ%:꨸ 'Ea{o|ce˖U>-JN7 ļ3Tתfh=*6o{z9Xj[^NL;0u?pwyO:>XpaᄆGٌ 6/˸{??b˖-q'^w~ĵ^ꫯn^k֬<0瞘3gNDD[.<ȸ+㨣ŋWz٬;0*+*6o)ڎ&dԽ}{?ϸKSOEŋN˗<vZ?޻-mڴ)8?>srh4bŊFD,X .h6qe;v~w;scx饗o{,\pO>~:s~*Af}[ s?=YVճԇjZR JPٌÂE(ԩ!P:@CC=(Sr78묳#l{u""&=}'Ν>lϙg9|<Σsϙ{;N 6mڤ+WϧdI㏫jժ0`@HSLI޽{}NٲeCz@V5;d=zgϞnbo^{EA6k,h'#@PD2rH+,"jܸq7n\_wi֭V5jN(QB-Z< IQ~֭[֯_u)jU-IҙgJcf*U仍Hc( s,ſhn6L;jQI 9?kŊBIA?^Zҁ|g~^Sc!Y"ۄ} : zo} e[Ʋ~eH'LxzG>$?}Gհa\sUN |>mذA6l9 .$]s5;%I-5k֬ю;Tv|dZRD ,_)S_~ѷD"m9_4QAm `FW;U["Dkam>u= O<-Ȉm__ƪҲnG>$ )o)RD 6TÆ ?hzwꫯj pXw3d>hѢܹ:wAAa/lа X'檹DnG 9PpLö IDATUTIO=-[ɓ'L2ZhQU |?'s7PxO}#pN 2ؖnhXG[Ec}iʔ)=z[Ou("cWN`sѣ5uT%''L2jٲZh:uĺ{@ǺD\BC /cj͚5ue˖jܸJ,!(,xئİVPK4>GAi UX~x]گ(HNxF󺎦sџ£+#!~bיKyD;@' ={NPnԼysըQ#]@r_7(t/tƺ! .ƒbBA>Db"S oA|&Kcm Ku@]:_'7k#7xF/u 9? 63I.@'Dks䤠$绂VAbuk#-H뱰mź}m*,"֟3{~. Dc9˗o*yD;@'d" (<8ޑ7(Lߑbr$|h-c[.l%:J@(t ?=@@DHlx٦^z/lа"ς Yg}$zX׈x?I-2gmD[G_Nab݃%BE$Lz\+۬`[Ƕ,\޹c@lD(M>€Rt6L $Dkam>u=犷[-cO2$$JBu=#grh& / " w$X;mb)/a=]DS"  EP_#/WdY?_~uRca,cFX?OmD/@'0$7'B Kbc(oA- wXG[:!|@~o0~α j[02o6rY"Dkam>u=犷[-cO2$$JBu=#grhAP8RRRR?{o=t;OKV t5hڴiQ@dQ:@h_ #4/%3 \586x} X|a{;Ec|>=YK.U|yկ__ꫯ7ߨcǎׯ_L";\' 1d:_sZczK3{$-X@7ow;ra/lаp !Dk86dDPPpe'1S4kUVzTv,dI҅^OkPpP[xD;x"X-}|t~??eb@':vܥKիWOGVʕf͚Ŵ٩Vvܩ5kOCx(qy+D @AdӇ~kjZrJ*O?Ս7ިٳGTTl߷tҒ׻ $IXw,;w֌3B~o.q~'u֪_j׮Y#FPvs~yߓŋ~^*UTJ(*؆ ~LP(NM_/oCֶmueo`D4yo e/k+CIT4o'o^[Y<^Ε|3ֽtiw9x̚IҦl=+V+sŋWh~wy۶窱F|%ϋ5jE4hf̘T$[LIތUwvʖ-R5;d=zgϞa?_-.W.xIÇK}Fd驧V<:tH:UzX$J..ē͛u/BmyΡCҘ1rX\#=4twm!ijSXF/n5:mm"uWtڒstwYoI={.zmb6;)))ĉR+C:HKM{,0o^{EA>p߷b5OMI N[[H}H#GFpe[jLr`gRi^{-:m{N[6H:y?Z^ہVzN[~b d;9*^)=Wc _deP/Å?Do.k[bu.ΪUR̙Qhhi䠴)SO*~o#m+͘!ubl"%Kzt}k?+WK ĺyҋ/Jg^#GTh & D:7nƍijՒ$_>5jk]ULkjԨFK>wԡCϪRJm@N>9݈+[1eV(جY_XW@}Ê ˥Q [cݛbחxwtڵIhvՋαޭ[ywIޛ|P3+ ;~>D*V,vY̮㩩in]+ڢEKn;WjƊ,3ȷ7oԹtҐ!ǿBN;8|>8Pz?TaY?ۯp:$]:^zR$"l&=d7Ԅ 9vŊj`CS%'XSKz8xSۘ0c/~<NKZIK?~i@iʔ贕wޑZ/{бN3s̛yUk߱eT~p zͶMOca󽒕+[nV3ό~_퓺wиcF#cEښ5r.|mҳJt!]>P/V̯Zi/ҥv,wf+lsgoy hԨqcwmޏBW.Z#𯍖,?\?}Q5l0\@DH8۷o$P|K-Z֬Y;vva$kc^h@;B9^yžѼj^??H'J5> j3~[u}|=}Q{!C@q X"!%E'#no )@(+;wJ7d}Էoc^ѣGpI*YWj/TY ic_~)ihs[?͛[oN[cھbrĈ=d+￷ԙ3 "NF#FXZZ^+=7+7-rVܪU|sPhm,$|Oy@ٳ>秅ol`<2c8tvG6H=d5Gj6&H[mR8(ZTW]_uKi=zgQ1î\`-Zd;:ƨQI[b4Ü&LZN9Ec)<-[,\_I'Io_‘_UTI`-E  ~&O,ϧK.$o 4Pŵn:͟??k'L IS=|!h*|tyRބ9Ip79IVxѠtҮt"=>!5i"]|2}.0kX{w>/[d/TvpSҝwJfIw_zI+ >\?l+qZTlC}tunSJvV(1c sj+Ҥ\wnU*]:m9fΔZ*=ݎ3ϴЏ0SNy ~/PD~B" - /=CzoN9ƵNau6vB"\u4i|+vͷP%jg͒qÜxBZ:se; rr*T͹?ו>T~*}bO0֭I"NZ]Ü~XZRj69̑wC1.ԻrvXaժyκupѢTtM^[Ҷa}n~Ƽw9RS=u0ώNxqF͵o s*QY\ kvGBa w{1ܿy^XZ;xV~aN[[pVl짟,D(azgÜ-\-mƍ9Yg=x7H^hN=>sݺ i[yC9k'zlؽs; xiB:du:(=݂zvqt N ZreǷmۦ{WK.UbԦM+WN[?;vŋկ_?|>u5KQ޽V8pU%+\-p' 8¬Oo_ߖ~il |xu}{0#)S)s.- &}  p  Y@DKOo[3';-98i`i& »ZD)Sl?-1j sZĖ+WZ:ȷ*sUW_/g|n.LdTkZ+9ny 7X1Y$'[0sjU*T͛}Jغu6We۾ݺ=+/Ϙ7Ͻ&)c_oq @?qgʔ)j֬j֬38CJҺutR%''TR3fN;Lӧ~͟?_5kԵ^{/Tjj~iw}" V+WZѪ$Y[oYqD`EGʏ?J]EmNBF^>]Ԭip}K+Fs2D]~+ۖuKoH}|9x La~X5UKnZsZ|oF~i5kENo뭷r_~Yj$?~cbZw{VD-[,l)j !\e( (qbrOW[_Z.\]jղqwl0ixT)iaᶵu8b,@G3?7k+wUW@sZ?,-^lpd<(5k&͝>oF7u-HlhQ+*m*y<*'iS;oG_} vI[KgZwZ$K0^ho]:y06ĉO>ָgN=5Z Us\ʗ j G&Vl]q},}lڿ|kx\ٽHu$#rjo%G3ڵ˂>+T;%ŶAFfڝ{my|;,[}Nj6݊v ,RĂDŋ_6m;V믾b]ldI+i.X܊?&N YREZQfǎҚ5V`b?iiV엚j+_~T7m%=ܫ}D&i9IV؝1̠re i8cmfdq.uF()- {{pa~_nي֭lSr"ҫZQLa5i#rV4SX!/EUX_V<.>3qb I®͛mAbZѵyٹvm1=.JI:wPG}5:>vFI6ml-WdW\v;vH79|W=ǔ)vIv,͚e/Co,_mۂ_?uҕWVJ^J9"~+vC9hk;5q9vݻᅥKo֮uBrKzԲ jv~;''vBTx6VuggmׁO|[}ڴk4SرCdgyYF{|7`׶éO8IMFu4hP|mLOz]^{}ounp3vWj;jQ+_ڴ4;:޻WSWf3 QZ]szrJVKZڶm6! g9ᆼJJۧOE0rKnƏ0XٿߓB]}ۺ&&?ޠT-SF=řw~n]}];Y8‚o[^sߝ':)R{v'xeK匑m m\9iljF26nz&%5;x8xo@'w5jFa-ZT;wVΝ#ث0q}S pϋuF8v3` ͘I'YS9|x4ocrnRrn}0N"[HCŊ?/u|*[xnҟڎFuV|c. n$+ʾmEaylE=9Z*N8Y,){i1oU3NlhC&Mq)̙L$? \%+"2 ߊھΖ;N7.sA~a~:=jy[Eqt³sn U3kGXĶIK'̩jU/O~^nIg[^=0H{k`Fpd"w؏#5Uڹ=Vŋs._q:Z1c. ~n_SdUӦIGRS1Iv^ۮlj`:o Wvll)d~;# cVpc-Zrv\^\5W^qc 87D_}58{jl`A#GZF ϗ\x4gZټ&|m]k(^ܦl;)뇕0|xpoԗ<5*8, "H;[Pd!Sh&C[F؉Km9li =zXJ_tLn̙ʕmsϵ9c[8RS߽};dgͲ(-v:us˰a[qVٲutx; _T~M k?xO^龠SWzͥ|ճu[ݣZs3fXhZkX,QKڼ~t-#qyNvO#Hc_|ar}^/͞=\knPdf9ӇϏ>K[?~㍖uŇ70B~emGa}p,KuwouKKjٲWY̎KGj׆ z9Tz-Wv#C%l;[]ugm{' DGZX-hڴ ~ɖ-; ݜovT(=Ƣ-RƍVk>$[[n9\3E[-dv+U ٶזWJ?ޖy3ΐֵj<(~m[;)Ɋ{tC^.ƌn\z)5nϓVXܻ+GŊ]weα:,=NYk>cVK'oMj˭ZYaUEk^ھv l׭𿤦O>;EHO=eyN.ط w[oH{u%rȷt[#F_~-Ybtk[R۶(^`g6εsSdv:ps֭Ecέ۷ςx=E[7E_ z9>}qA"v-@H??x9fLօ8iR)] ş~:se^m ⋭P?}gdjK#ӗ.]0'CmϺB808̩{w 8V||m'X۹CH'%Yc.V bNMuVj.Zsw\ۇXSПgÜ"w^{] ,\hT uqǎc[[0E;6߿ߖ5͋sPFKHŊI kd-"%}Z5N6h ]xaU4KZi`Xwq/kYg հ嗖n}6>|R#>>;N!鑛7hK2mb9Ǯ['(}0'}3G=\x$l{ڽϷŊYH&):3gعsm8k=^T`_(pysa<ڶ[NђlvY%׿U"-.cPGŊa/hOp`6?߹,~≶o{n6Nqqb`n@lt9O2ĮS.8(c,þ(H[7l]9ι D@][ѧS &Y._~⵵kǶlo[aCy*,TZͶ|)tj--kE7`m<7Oj~vqV WUA>Ut8 -^l?eZB.v)SN>Y;V4_.v٪ŊMnjkָ/nſ>kžC JK>}:uUz巃1l9EMZc̎0Ai=^PϞ8DiP֦wҸq/js}{wyԨqIV4ت; $%YXZ:\9Gחk-N:-矟 =DXY%{S s2B $騣,̩fMړlx7cS-,uYvm+4No~+V;LDcپ>}S'ۼikЬÜPӦv=-[6v;>Ij4m֯~ao^] $1z!3ƝO|v|4(8Wl酿bm+~œm ѭ!Yj˖N:)vsvnpΣX&cO>imyn"O><9ھ]rxq`u]_C:wq:xپexm2O8z땴4>֪]{/⋣攞ns 'GÜ$k%l[VbJIqnNRfyy>C%؜{T<zOo-5o.KG-8M4"g@;W|jDRpҤh쪒$5TW]wCkWɷ߶m.ў=vohԨ_˨jU [7~m{jm.u-7hyWF}׮ၡ[onD9{%eHNO;obyOUczU~׳Ϻu뼝woJvZ~=4o$/q]?޻>Iև&M-Kœt.\hogP/@ PpyQ (- VzF{}>BlO? V/aNh $zu+i&6]ȶTwnS$y sjHԐj_cu$baN=d5+gźMVذAv W>߭80'_N7vÜ-}U%%U,;in[& :)R$xz:dÜ$m5S7 sj +߾ht[M$w瞳:䷽;qk9H;vVHgO#= :+8̩iSgVhp[V:vtOǝ:YO^͛1lXEΑlS0 Rr>rR4q{?^:4;mptnSѢ/.Z}&SOYd}x86ԯgYJaN^HIB[n,qkԵm#0 lL?kV.gu{.(&X<8PdÜa`f ysO:t@<ݺz/Izvl s*Q ֮kUaNkJ]g&;aNŊYdޟj쳥 'GC ;aNŊٶy啼}Pɺuޝ@۶7wرNB˖68`s0^,2}.?ظBU$;gAjyg5B7$WթS_{58#9^֬jPV-ҭo}i;HJORWۀ0n7eI/l)ʁ7dJ[;l*Ug*&pOKS$M>%%uݱAA%-zx v sJcc#lѣ-*0̩Y3i6;fuxq;.[sSpɓm%I*OesZ L*ZΩ"͜0l[bʕ|y3aB0']ʕm|?B+Ե'œ.X`߫wYԒM}Ƒ۽_ij7'M[S8(=)-y^ik`R є8ٷ a!aԮmGeE ˗[[})0jըIVGǶOc :pżV-+0Mz4uj5ݥ_n¾VRx/1Êܒ{m?l8+guY$%K#͘a N!d׿^ o53fbVsV4ݻAuy`9EN1ݥJӦm{9K k\cG-toUՕv9l~'CC ɋݻ j1YTf׏+>9E7^ST7r4{v޾cUZ[Æ%Ə`/HKV_NH|y  "x[o}Z袋so'T~f+gL^DCx6%Ŗ;tH|A'飏>~N=r%w?$)c!]*C6pcYmZٳgK=ۆ%0RFksgXر6.^=:ˉoNq9`t[T))wNI?L㎳a>G[~9!GU̙>6޽[j%54++f7TڵssU[R']:֓$Q>(q4ȒzJUBO_Tӯ$z>uYv[gY'+K[xZ|~֮Pش4:To^ s Iv/l~v XYӛ~H bU]8j;NJ]<[RSqnE+f0:t(m(Uvn@vǎ)c?ҵKINZ`b4d[?YmC' Zo$;_''"VMbEiӼ>RSn>S\S[̝kEaä .]L³T[ %X12K9(ZRni?oI,gd}:-(6ڵb|#[w~+ʕ0I}~]-xqKZ[2K!ٶ>*˪l[~_~8Ê.F?ŊI3gƙz jsuHVzrU9Wp_oͬ iﷂǕ+m-;-))ׅfnnƉds4 YZڵ+[YkV- :p~VKCK%'[!7r>wۤ{_8V-YAoDرp}v WiX'0먣zжmh\ᮇd7P mk7XfMO/Z 0@+WE. mX!lA~U;^|Q*U*q ,-^lתeuH'\y^bcmB4aX޶G rj/m'lW_8^| ; UZ5dԥ$67Uj|7pܰ;"vC! 'ۙ^zeT{wMwC?޶A^={!ZGmf  lT)k"N~PPͻ~vgLc-s%]@`xCv|'۽N? P's*+֕R O^_ECAM|6hndO,g} |Zzx+c-[t#uMfrl 7l~}m^}]sN>9RSUcl՘3B< {Iu%Iwܑ+ܶww#d-_^1v<ٸQ[RrҳϺI'Sǎ ׷{/+'r5GiN9'^MK߻t߽n|9::Tz=)ɦ f%v^(Y8^ D#Utxi~{lR wÿP߷g϶%,[w<9tԩc;[qln &E[Hw~w۶9$ѣ385wMXhCľ{>iԲtyE0bؕ.\|NUjロBpې!֭{ҭr2y =׮^پ=8OTɻ˖%+JE:uvɊ]u뮓ZMCmHwKLK/?ظjUsfSs3ΐ|0>H6 '=H~>{ ^`NqB|>;/kAv{۞cΜ%;ճ͆ ;w߹qCΙۧLcdSj?` Y 6jd0t5v9ڹu ;F{Y8>0?iCDCjtnS v䒼sCѩ]36l:(77{ܮXaAc\+ZӲey֭-訣"Lmp&'ӧK< cXQz=)3쵻wۍzSO[GWIZ&3عSj]V6H>H61#SɒTҩp ׭V`›nbkaXfMX ̙vذa޵pa]gj*խkӱZg4bO? t`O7ߴN]׶n…\}rOCiSpQ ߷nTnn|w#Cz7 eZ t͛kǏwgO<[$'[œ9\G[mhQiH[),~qmh# {liE/Evm>N*RSwccdžVYV;b4m$i&AaN[{8Ega/Z]ӦҼy wZA'G Wv8J֬ ku~~:o+uW} Cz#nm[RV txZvJz gk W'{)5LVت.yfx4O6mOԡ?#F6vP_? 8֮>Z2+F69|+=ڎٻ} ԋècG /p\x3vtC, ))V8Pݥ;{.*n߽ 8zȿo^-[fGɊ|ӛ@<_zq;$ێ7F'.loǽcw|޺ɂ\۶=!cf7(ӥ1d5+]g*;ΤkF[rm|yߓelU$B~Y~<Hw௖~՛垑oMIS) H^vY:?H6@>N `Ld ,vK+QB;nRg j;8d9QYGqa$/Nw(RE -ťXqwRP xR n#$Ǘ3& sogvgwΎ ;}~\O8"(m&^ͱWc~Siϛ^˒\W2W3kfDY-x =G R%%;8_p0W#        'q,kAA0 iTӇGDq˚!p>[.LIGTleNozQbI0_99K` F _yl˖][ϕ D1/Ӏ Ay[|#&.3fd"f0eFn1& aNf - aQ߾+@<9f 8-J'V@J$ eN!!J)8#>\måKwT2'=۷*V9T׵wOU^$_Tv8`6rH`_2h@S$&볍':([?c8woZ/w[91wnCs8dNοȊ9k)dKBOrǨ!|DO˜:/֬RV={f9I[cN+ܿLe__g ;slf8Tk2 4""2abyOl>''x(9̿rr\bXܫ[s0h P3`C7oervj  MЏ,r^Ǎ&Op:G|Tl:w FS h?TOx8p4I˖9ݻ<@Z*dL)@Q-Ᏹrw)\ص?{4i0g`}tFW2e\@H- 3Pp% EЫW @ ߼ jy;An*Wϟ3xQI T* ߱M˽81r%J{w`g,ɽޫcbE Mx"E#G$))&sWWt&@Ϟ@}ԫhY))]U@[ׯܡ=ss+__ÆI=\׺u}\~=G>^ ;x_ĤxUc]Mk_}:l~}X%K<zbHXVۉ(&w1/ߟ5au>IZ'|?,B(|mr ~cmet/е+eRܟȑP,Ը }(2 rP*&^0 yDž/2>4yg+`&lsɜda_"MWɖ-@ ĉ,eD?U+QS_4lυZ^^B$Ko{ps*.]bXFwA;1ז.=rX )Шܭ F||`h&Qgx^pK|oBO 3C(sܘwJ2<}v<څ~p!ۃo)CtDi4_;rewol{4:|8l6CoJ[\1!]=GAA@j}xύe3iy Lj)sҬY0ov K??w.' G6}6vqiʔU]LWcDX?+ݻ :fuEjy6nڕ;YW(~Ŋqб#d M99%L37oZL΢p9)AAAAAAAAA:  ?:U-Kg|ˋ!kfBðaѢn.t$$Och<] P$IΟ8nQi4`x0`ym*cLĬ_6nЀshsȔI7xu0Tu?? GNmVSK÷0͞J<3g2W 34={2grA5([FT%!b<$Ils'5e73@͛X}jn1gmЫo_'Nk^T%|aA_Xڅr Ǐ)Iʖ?z|1ã)S2tܽM >fU0JC>4v& =vuӧm U*Wb{eӝz޿o U4ϫTҿQg>tWЉ9ƍ s ??e:側ia0yC.ԭk˗|xqQbZwb6Sެ1z4`DD/c#OHiشI5x1ؿ}3PKv@۶j=M-ZpI!!6?m9ק2r]'sZ=6:_#=-l&D`Npb_ tBaQܬt 9nzq?P1v,в%UUr ЌMv5Z,E=)t 獨Ʉd/gN)@ԁ{Cb²e-,;SP(wr7۶P+"׽f[ʐ՛1~1c2{6֭|؃ʔφNiN03}7%jz%۷]yzNN<[X|2fk9'-SۻN=q֥Ps~         ?qAA ǏsHNla2͚ZIj[_ )N%3CZdk#nQ9s㙣o֭aV"o^Lv!7>~PƘnQ4 F]KO|;"q][5>ukeʘI&7%og u*3V' '@ ;FI-cDhh͛ *+.؆ږ;uNJ4~LJf^+k0ڼ(M[^`Y4LQN`~JǬh;w} xڗ&M *U=O; +fQt6[Up2A#8bz8pJHo֧OޠE3y]v$N秠jBv 7<ٽP=x '<߭Ϝ9{琢I4 RV)_6MϘAߑ1mi1+m5cȰ7NիJj)O'F "%e~!['-[z;w2qqЯw }LJp 5=:0BH4OׯSJfivecG1{Ȅ&X$dv]%&}oFDשC 35ib6MYeF cJ bZ IDATY3Jnjrq aNJc4r|, …|:|ɓ'۷g` <(ak5ӁO>a~0uj0[޼ի3S\VBBѣ9Lk:9iNAt U-_ԯ ?6&:\9M'ˠa;tNb3Q"l9 V1;fN;;֮rNDw yyܩ\"&ӧ_/I: <,2E;uR ;X?OUcG-T+]O@\<|3T{\\>TD-m˖|9v̺;={]u#=P7a%#.2cCoNx+4n &tj9Ph,@;kr'pTdhp0Y˗ˋٷ:}/ iR`$`'oPvHoۆA ʨ!'z'sHKoʔiS(ə?/&,]jU9x~! nN?)ck luT(p$P;pP-~9J^ 'OlS93iٴ +Ge{]sLx:_581S(]a^ش 4I'(`Pز?%eHL&%9옰t y{W[˄-׭Г:5g=\I+ܻR?IFp^lbJtWmfRwrW8ߩ xmٖL&`tu1Brl#4I]֮x<^ZKv>N@jܺ5GF[6gy;sZ/S|FGW)SԳ663I<}'?x bE`6Iѣ{ydO6dF<7"mz2~<Io2erSE|h)$\9Jl36[-s)[2=Fx8aa_oۭ^.6zjnffcntI Zr7 >LC]AAD 6C~GѮ9pUWgR] r,oe}X:=V75e7w;z>>1`Z.E'e* h{FgWaHFt)8wS_R6tjy#thT7%W/ @JZ1bՋ;'K.Kwo{7xΝ-u @HheK,Y|h{"?` Z;c,ݽ \b{Zq*3͞()x/ϛ6)t|_tTիd,%Kr"F2 5~fuk>iD٢X1KAlTQ^PpnzrS_sLIx6LH{P:o&~< [G^3ϔ ŋfsד1+Qoeۅ Ζ=2v\9a`^5V(99m3.x8%,-qb/Cof#J{`1W7nձcVmq }WE5t)7>"GN^Ksy lVVAAAAAAAA!  BlxMvn#pa?g];p/o W..}~bm@۶j}4SJx8Тpa=edl`|Kaǜhݚy3駱(T , 0idÐTZV <&ف]FH[q!؈)?5j1D_/\ i9_$9rim۪fTfS g.qZ'JDߑV/{{8y{1G:uN}To֩ ǎ1sl՗q_32$q`vCO֮e^ ʔjp<\c*׮_{G.ܳ7ŪUWie)vGܡCHeԋqb6O_,*ffvy MgpD /ܾ q#3۟= y4L!=i [֭:Bu|8ޡc/ལub$pl_͝Wh@@$ʐ5+-""(s*߶pa)F" }&Y<-uō&"xjt>5 ^yk׀իiۦ]0&$N|WڴGb7ťT)v(NHBmYQ/_[cLE54Tg/ 0*Wi`kWȫn:î]hҸN;H%J `z.޶v0% *9[?^ Cus솏-+Wr TZ 6޿,*q.h f pis}uլ ?jm=;bEĥK*Q7]3`!2gq._B3h<|bUÍGNuu 3.l&&aakkQڑ=;GP6r$:I|l<'ZJ_PRN//<4mֱ굂xr|$ѣR*Y2SAAAAAAAAA:  G!51Lt4W8>/\^4 XB9`:Vmרz<@GE]Б_ę\lӰ?^4 M+Yo>@fOQaJ$B7a @H:My,@Y01вqv#Gc8:!cXpz.4\?{fDJ۷&,>~{Bi0v_W,ć75-hp8sQ4i/'Vo~%#QsvOwM)` FpX @fswiUQlfkz{OF kq=}z`63N/c'`8 Zi Xڴԩ59V^N_ԩc YG|j 1gMDڄEO`E6qbr̟ Ĥݺ '<qHt69ʻfqnálFZ5Y*{d?6ug;)&Lu9Q)a3Co|`ĉ/9֭\ 6 1Bms>j\Æv͕}I uk?,Δ'$D ٞS XfԷ/sm+>.Sdjn= j.S/X`{̘aPP⻈pW+        IA!+FA4kYʕ;3;YA~?| м9Ю+_yO 9'MRm`x][7f6Ua6f}vi{y1+ f+Wj|'bJ=v+9HtC/f`(Fz[g5dI>aPǽwo>dm?PU%AfDVi35mн;Oq|:I@RJKfMB7n,+cؿP!CSBիG&f_ϟ?_d76mv̭<|]wM;vp9yr-XEËgDV]&ca60YU.j``Zc~lؠ1_|fʕ??.'N8qb+GPPt+tNŋ\Nb&u)|: o*ݚ5i~uaÔ!E `n@Q_U+Mu7oq Y hZ$<~ $Њ7-^=JUG`kWƌ6&jMScØ k>;M {v(Y\ PÆ1@k?d_{ÔGM䥧5ϒS[x8eCڇճ6UNAسg߹~ط.0r(5n~sg,Z?\ԩ)vD'P/}ݰk\U>}G͚qQkDbʵk@Fq ;͚YW;o6o[PAAAAAAAA#7ڂ  q;Es {Dþ}K;g۷g`rCۣ'OK2D0|~ZdJ H*@J,y-_0g„j8|:v(?P}{|b}ULea]cd\ {X1.p8;GO҅+4i#1!4'RR&!Am2סSF'_S:=Q80ضmؒJ,H9]X q쩲fM>@YvsʗghM9ºӦpq~ 0pgy0=tAfzE @Uǎ.Yy\M"if;I7n:Gf3}Ç+T͚ 8ҥ H#%ƍSk`p}^Kc[;fO/2n%ɘ%+|avc צ1&ZaI(tZܴO!v[s,N] Gh$iReKey;+lBÐ;ɓ I+:%5ׅ0_+o\<dM|qs,OM)@q*5\R4pcetJRqN "܈<_[֡!Gxӱb6n.7w7Ɩ/9PW4 IDAT-{ݐ?d[Ik罊ENJiСm3gmڰ S'Jm0%1b0_tw.% oӆbŀo儮^Rrj}SxBOP6T?Cl%`ʖ׭d+L&J4:u0q1 :Ts,d8/{c_9!RzDoz׷v7\NL"J$˂td XuܦM̊         IAUE:p@H]G]7иJk<} T,XԘQa*&/^DQҤ|sd("m,ڐ j/[f^p0s=gr=W.yě7  4jĴ@ɒ*ϼ| @_ݾw47Š=-||gCq?ժ1oЗ0u#}d g(O|p%){c:ѳgy ;Fԩ|%CB~`]m1)SݺnFwL ED t:d- n(0xj"" Ԇvhndx i2~fzsٲ@4j=S&`&Kr[Kh H*`9䦎UXDӴ `;9 ˖G)^ 93ڊqlliNc"' gWvN,V_OO 4l~[Ik5S֛ԩMUɓbK S}/ں5a j—ُ*U:z ;G+Xe7ox-:鹿/?9v #֭n NR1zR6.]\HFgdlW*cG=/_Z۷-"D!0 oSÁ>qti}El8fݿ}9'QuP$ߏMǜ[YV5ynޤR%W뀴iF.gxX6][ή66ʜsygt>L'clE2ZQpc8Í݌9]oHԩׯ~v)CCձ#pUn._VʑÎ `cƿX./R2% ͛?]w"&>^KBAAAAAAAA߀AA\tZ)XLh]9_jb8F{bLEjnтV )[]Rv#FyDGvӧ3L};C:o3pr,Pq۴i̘acNp0P*$<֩_q:ѱ޺UI qbKO` sX&og;cmW2=wғyg8ۉ. Xf̠H"G 7 C[}H{h,\hx::vgP_3EGs_jբLO$}돱cJwzy")Stƍ4S Z."eJJP[ްAy62%}nOgCP_`zhC%ݱBıE `8ƄO6WQ=Dw} i^ܹe֥p%S֯τo.\P5k,HGzZ#$׹ G_n kOJf5KK3ׅ )*r'Ms(``l.][3g ~JNCaXV%OξɻQHS`Z8sL lYlvٹSYfuʾd;psiav_xvdlެ:Qر%j_!K+c`pN;^;7UdVk"yXIZr%8?=pG 5=K\|jo)""(a-t %͐E $ZenRucstof pĮdbݨw*6eѵc*wrlIa|IZҧи,ƶsh譍)v֮"[6[|Dꬠ=cǁ)STm5k/]ʕ|͛{^X]yχEڪbl%H[x4<} |yBGjD ܮ\z$l֯oFע=أ~}v9cCtR/ԱfU0ɜa%Q##m0!!_d8-;z&4HIb7χ֗2`|YQy'^=9Çy 9o 8ĉyΞ3Ŝ9sr.N׬@k"fGyIм9pE,ݻ'_5i[         x: VsutyAҤI2eJ(Pڵm~͛77n .dɒ!uԨR m!"\-k;ܾ e//|H>E >ח7>M K X'Ho= ۙ|z`ܹt2W?_Om+r]iQb|AFl 3]ǎe0>]=z?߻S` 5(3GY4ҥWw`o[5VuoXuBz4ik8x̛xђ/_:ΜYu͛z)Pp IEm[ ev͝*WwX˖_;ߧ/ 4b4,_/@ nU?^=D'#Gr zZwgvʕU_z.%?Z ӭ[kvnj*4o0&<$ɒO\A?nە׺`Z8=XZ5-V>$_Ӥ:5i{ƽmɊj9.u|֋mE,*P#uҖꄤ-t*dzR|m+…> tԵ?(^޼:uFa{GStOw -]لj2$oo vKUeR~?&B0b4'btݻՃjc+q>)%L0t9Ԩw~XN{K:=:'arNyAZ-        "B'AAZVX|aH81֭ʕ+d2aѢE8gPre 0Y&J*Cq۷o?,\B-XI={h 8yiY)sspKf$ Sd $Jyicu(!(Sf:uhkșS>A*/qU$O~ٵK) , `L/h>* P'K C}&uk7~c>k~Y6l8B` GŋU2?hQO>ʕS70aZˉ ʜ& CxNT Vƍ],h߾z<<$D|"7ڹRY?t%3\JƓ'3gMTӼ!!0YzJvg'O2H\"8+Ub~QuK5q5wѝńYrtͼ UϮGkOn4WVU&8FӋwN\) SÊD?5s(9%b.|щʄ('6Flhw7+Mc_{ɏ;@!BFذ!޽iS?[]Æ|'=z4f"0FK(pu<<>6D=!!̅h y X1OBS b@tI>[@??Jؖ-Zd-8} N ^`-RA;?YhhIiՊƎmǀ;/vϋ_^Jr؟!Njb@>({L̲>Ob[۵x&]wB#MIIRcEO2gl*eپ\Iyڼyf9^iEHהV]u"((˝9ÇSĨ}ӢC!_OУI)Z} l܈MS)0 c~ݻ5+0b[vi̙yiҠ9pꚥw,ǡC%2U#G\S^^R gۗԣY%K[L-ZDJkȲ߹s+\ȸD_؍/V͉/vQQ(tսJ(wL®[> R\{qYJQmo];_m{~r+~C"%Z'}S]vk_Z&MXmMTNG~X ϕ6Iv=ƂfʜvRTubeKʱ>ۗcp-Z㹪]k֨ixZz&<8/AdF' n4y/cD[&^?\ʕ91{.y:w}Q}I9qrdӥf *!CsKyplWq{wT.]%v^F ~a1cXm\3|Cj;E<^kQfڶ _>=_ qOܖ&ӥ8۔ֽgҫVпo         $ [GDDڵkٌuYd {Ȑ!ᵧObΜ90LdH"ׯf3F%1"x?z(3irnlf m t.~^`%)kV{z, @ƍ|/W 'O27e E#DD0I[o:U"Gif6 =~ټ )8l~=&c6ص)R0P,uN2&Kŵk ja-I:dT)`A71S&8%$ Ÿm[;w(I)lzʛ7JP! OgπձvJ5ZJu 8@o|lnZ#ɒ("h\ 1=49@P͔ha08Ü92'˙}ߟag\*mZ7Ӧbuj'_c jˆ)Y>g4`EI>bM~߄_1Xm 5i2'ujZ%JDofi˕ ۷|8ߟ)ڴ5v-`CbAoLڞ4 S٣G,2v{%T@9jӆ&+)*hxѾ>-QYK&LP[8mS bz)fpvqC7iB1U,y$ Zv0*5OFtJ+KW?V@PR=A8?M&BU/<걍WBQnt8`Av,0tq]W ׭˛ `_Vk֨I5؟< Ok5ΞՆ0{)֭S]͝=yOɓʱPe;vIL;8 WP8uuzVNz"Kȝ;t$g*dQGT1/0d)SaqrJM@K,V ;:ҭ#7o+K<^VsHн;?1xDfbswf[bIPQkO_q[ IDAT7L&(smaah;mÇjȾyk3^tWȾQ 9qܹX //J{p@vHb޼jD96n^=;A`giHvY_k#3' X`H1; ~iዛ76-=6$        : ֱyfܸq˗G)cmٳtV7L9r݋qLe8|Bc&ϏKݞ0MƮmeJ5ތ)ӧDa̙֬͞}??رؕ% uP]6]+zn_ "2><>=/S810UH1qb! rγecx?~SӤaeX45ĉm~uXwm.EL4˗L6,RĊ篿ٿ!ah޵,;;6tfiLrqr~.܁)qRa".v׉Cǒ%jukֹ}qɔ QfF ֭2V#:Ŵ_ڴml!8عru_?<;1(eʒBJM"*(%%"T*EJh!Y2$}99x}^>r];LG/k`A2 hF W\Hd[iɗ:u EFS4YiJLpG2Gp}խ+}ҳڴĤ}OY3ͺMx~[U,_±w?,{g NI,8|8H?tWQ QK.N$ޗtxW<$lx]w:)KvHw[Av+C:$:||Yg8>;=SXJ% 3M?$oω]MVRZ+T H'L:x:`wFZ{N!@o$v#׮MG!laV#$s3ƌ&ӛ˘޽yɓRϞ5 t A*Y!x4%i*{RI1koߞr{Ax05k:5{}gXHٳgPd(TC^5upt_լiY+!y4{~mvMfy&Ovs;_?ck7*ҨQmIvz}ѣCV}Yrǻ2x(xkN}~|BlSO>6l4˳Úұ{5'}߉v wTx׮VVV駔n6 @ؙ7o<5k͚5K R߾}5b ɬYFԨQWVMђYl|SރGZ3p`6d޽Ҁo$sZͪj#kw`?X/@z{7$O>u|iw|$ѣ7XgG*;ϛg}%e߶N/N{ݔ$ڷz*ϞmEXϾY~QQW] FX3e_gYG? HLt@5[__U+U,ɛǏ1^4k֕ZO?P_{2dyYm|qJr|0o%լr;/w/_?u^X<.٢Hih͒СٸAٳɛTo!,c}2.MtDV-W/;ճajnUI={qGlۧ$u7̝9͐6{p|fHLYBMsc:oݪmHʄ /cbuyvFknc޷{pMSR$oKhܛcgHC{7;'#;wC!s$|Ia\ʗϒBZNxo NRgP@r26j[1c,/aX?l\ɲ+[O`|iqzu䈍wUu2gU8l%\,X,q4wVtXԦM×vtaj׶x #Gڋq't_~m ҦM?aӾaՒto/R%-]h&3Vv3Zp3&˔)pBժYV֭s+ٺս#"AyǝG"҃ZZ7\3GB/=qSc$.;w쐮΂) ۦM h[4йk$ IǨ(V{R>ҠAt]A|eHV 0DZ+gSN&$ u][;yR1îo>L"EI !YR3dy*-q3Qqn!Q+w'uRv(P>TQCon|7;t}[+^o,l]a7${ mj)ɲ,ܔ;x2y;K'O^{*gwz!}Pޏ{Ha*{u=W\@'v֭['Ie]N:W_5dթSG?pn*ǣ@\rҼVJIig>we+~CޣZ^QTɿ3ϸ NKK kXojb}_]9`PuӷLf&I0;%7%9rf"7$"_>[(H3o&XH{u"YH-H;[G]s6=ƎuӅ$kCmR@lk n5[DDHիFBMˎWz[_xѢu& /cn'-hgtm⧟v?PP2s5ФI;z츼zœ$-KgDp;v͏*ڮˇ ٓ=۹m^{͎mr Z'oӭػ3wW_v0]<͖ؤݓ;w+Vۤͻg=|SCR|1c,Qԧk`%+jHDFEh{m;su9:_vzKSJ<ݓ?C.zYO+LUs:ԾȔe)Vzظve';aߞq'0xn5):@شipΗTf9l}ݻ c7m`A;v~9 K/(_f~ {ƍ֡t\OKJ%ﲆT}Ԭi؃+5jH[ 7ꫥW945u[>w};wGa8}TI*ZTXTGt~qgMLG3E)SRӺu*You`gS6SKLR6l;JgR>+}w5cqvsB+f+SӁV]WIJ&%?'8J]tyFsIŷj[e6YEs=oZrRٲ:xнNWWϒ{?{]oGKݻS-$Yc'~(p"Ѳ'կ&p%{%{zFvO|M{xi&fgk`o&lb εl Ne$o.cy">PpC܀J_‹ƪY3+;K^!9EjTKt m^̙n222CZVeЖ-/L֭֮Ӈru UdTd_ɒ7[Oػ`^,) 6lSɪCD%KUix@" $^xܹS}ۧ;vhԨQѣRNERI(Z$!)ƍe+ Y_^y%9aѷؐ!Af3F߹:$$X]w=%icx8,kv_߿~zth]BDj&{ f϶xjR./2UH'gYg#G,̨Muo@,5OCRŊ.eԳSD|k驧܎x;~)b!r]g9cX'@7w:-!6:{>>^Ivd{J>g>OȧDK֚WnM|Þ~Z:hnlyMyo@Qzk!`CX0˥ZdJwߝ;JWs(9⋶ sĘbR.S .Ybԩ$YGil+LZ&hH"7ϖ-ndD4|xEvxχ|/B7ݣpazx޼mMX^Z"{ ^PIUZM7[4kdž[;Q8|xڕ.]s;JRҼyv" M=*s+rH:)+>C[gf>L=k\9ȹ 'oVb\nzv|S_}ՂARذg ;Jv^|^*~FB(uٱcvWo/'>﫽:kok)[֞6d:p[dǡY3xmdI޶j*n`$m՝42@ݿ4r r玚i'N`M_wJ81[' ~omTFt}_4#꺍s]PYl[b&f*e99Ț5v|o$ejִV+X0S۞8nC{d z׾}vo {!ۇ{RS)o>^Dye~y/+6ֶ5l;kL7=<@ *h$+?)2mե;|؞|g\&Nx+ܿڭ wbml_ތRu|3BO6oz!K&Nf7<"%ٔ)R)-[&v=>$趼_}՝֭6P!/|o}$:Bvr̙Ҹ˴H%IѺ,ݭ[C vY8'Nd~=>ILtMs5͘b=jРn+]֭?O$y-'::x9;tRNHp&Mx"Xȿ)9NѢYڽ󯊤 ~ǹjy1ӮYRֹӡ@Ǒv9>sX'f0'FrH$'&&&K]j[N2e3qbv)WSqʖ ~No>î]ӷ3ejtF:t{]IhW^?BY2kǦՕdyWRnOprghqN^ߟ]>]8*[|:tp"""z(hիDDD8ofQ6?O%(@5^m\链uE%_~`z| Kt1…/ 'N8iWrTIt. ]'xBٲ:)Y3&)8۷Rqq>q>G+Z4xGEΥ XgL8y::.ݻgaB1ǩW/e`q t ҿ I'X\bh;eس'I XU(9Nҁӻwh\.W$AQ;δi> ;b&%w*ҥ_f&⳱'QΥ f>,#6ֹj# IDATl&4e!QQzLej„-9uVZ5;5k2#bc1dtE /l}:g[=ddj[Lj:N\WhR8%J3~+?xЂ\OCN{~7w;3w0I-psjyi vr*ƎM6o(jժYۭZ5!!*emvLq?>Kʕ9wnh8uVǎY\c+ dS2jUrjmq oqNp b~qw?|0vʺtgsz X-YpZ%$+@e9sg/O^Sr: >[1HL4AMK5w':NN֭CZOΈ`,_5*갳Sޟó>bRӨQ}7q5 5h8G6p=4ieg ~r1>Soy*ozk?SN^hț<8#~f̘kV|Myʕ+?TΝ%I?^yuA3f\ҥ?W۶m3TիWaÆ2eP+V-*^\j\*P+T *dhy`+TnQ OwEEIÆI>*ED]7J';`A͟/]uU& -￷_z(G8Pw知NȑDWi.[kUǏK}&8NZ;gO:}]eK;iiR&J_}e"~چsվ} 7H+W=oժĉ5פ?Kf̐:vL{̾I:k+V.)w(]6!:+fY8g 0;%$عc[լj]XM:uh{#ً]{$.lОz*{cmjDwZٲ̈:=!1QZBzil^"ZdIi7FB6Kܢ5f]cT]BI(mNt(a-\8eccckl^P_m#iR׮]uj֭)ۺu?|y<_*J@eyutp 8̯kpEI| 5lҕW>.lRRxiؔoA/mgzIoq,d:0_9uul[_YuokUZqɓ҄ 6_ֳm[,`5nl غ%I#N˗(լ馓t_q,8o&ZԨu5y s)o:zJR:%@H/`'N^}՝w0 ;k"PsL1 c7=X]`ͥ s3mؙ|I5iD/p|GܲpYcpԱ:doGEwڵ⻷_e?w}u ݽ;Czεt;^{̆ΛUJVy9vL*]Zcc3~}Ԯ j%}uˉ[:zKR6mt6\u6,Y2ʙ}6=fiG,%X+r\aZ=j㑑|׼|K环\ihgx ݳGЗ.\(li%JH"+eke0..O±zVS-qq <;{yAӦZ-?[96ƻvP3g!yCw@ ,L&+Wyt)y]L^~Yza.\!- }6 4 Ç-_uk{'*YI9WN{S-X1W%-itwgBwK圶?v @؉$wk׮W^tJҶmSNId%DӧQ߾}uV^#FAF#i& ߘ1Ck/dAMu]$|#G|P:xP>ʝj 7weKуJL s{Gdz s+œpa7l~ Ȩ7pJn]7fi޼-=;y/Ic痞 s$-͙cFgS$կoJlIOh7̩jU;r*IOaܲx}~xnœ$l)aGœ+^ڽ^/h%_{HmڸaNkʖHw[$I+K"]{-aN䦛~ߋqtV2%K,q >|X^{V^RJ+ǵ|r=zTׂ TD ԪU+-[L%JP˖-uQ-X@NҠA4bĈLijذ!7/2E.%߽BB[ #o|>H%XF1N>w[xIRSfLt=;͛ .NUHAgp4kԱ ׫'\)EEɓ76~6^bJ۳'8yRfvK /LḠh@mޜyi7qc ),PpkUa욕'Vnʮ-r!q(s5ҷfvoVεv,Dl$]|׭sk@kt)|oa_E,hCJqͥ~AQlx" ^񧟖z*g mN;[m|Tsg>k4p8X4GtV @:qFiӦi͒ZjszT@˝:uJ>CmٲEWկ_?z뭙.;:~:%iiy䈅8!wD–-RÆ?_ZgD>DZdsJŋ[vcǬy\Ʀ?{ l!CȼDO6vO^Z_ZԦW`){eB%6B~=gQQv ΗLЪ={%)&&x' -$1Q{Jݺ9WN{I-ZXڢEmٲ[uI /HjeKѓ;&]uvv4mqv+/O{++xq `+U*{ˉ[JJ)!ƣ,@i< ={so]o%ի{ wo-mÃIFgV;{aғO\ K;JmZ@of϶A^ssI=u']r=oRS{][ze]ЕIcZS ٽ$H008RǎvrĉRr9Z,k3 ] W=Сt]AHЧ9IqkЕ5m{0Zw% gnps%&JO5Əw}ÜjԐ.$ yOrK.ҥ9[mxڴqIZ/:ZZYZSOI3g0'IR||}nSrvg̰{dzK6<Kƍ9֯n]YscA"5h`AY_J_|Au^@j+ygI7NL~EB駟/`-J/Ξm쯿ʕm R{:{_65sJ/dyRǎRnRB]2gOmG={,䧟lvmi A^ 7X@$=ݼyҠARÆ:$Y R-7ζmۥ:u@ym&!" 些nFN9R% s,I^^ZqBz1ϏzvG*TȹrVT4{RRrK˗p^yE?_V颋fͤcYWz%iƌmիA es%t]wI^j?$}a=|86|Rdd )S߶S04Ig\)" EFiniH`.d޵Z?KoԦ}~4dtmң#u`|sΗ<@' DDH?dզMҤI6\u`Jƌq{4. w*XІ=)#J4uԢѣ%K8}{7˥_RpV N͛K-[ĉY_СRb ?Td %ݺI˖I[ڰ#TF %KJ~+]+5jdoBZ¦U,}?<@' =;Sұc_תUҬY6\ԿE4r{Rj6-"BZig-<^=iRJҡC6\RŊWF8֤t6k4cF8ҀСR"Y/y~1ud _pԫWu-*%rNK?$oWvtrBRZ6/dl#GW_]y#խkAN vi8+Gw|ذ-Ү]6|R۶+ tr}Iei[1cl`Ai) trJѢ㏻<2Kw?TFԧT /X Y|xۤmliS # IKƹIIeJH[K gKSJT iw))b&H3gJvI-ZH_~iN&-Zd.,͞-UkE@'tڷo߿@/.= '&J:YXƍ)-UB7Nsz9p9p9pF{)wpxv!q"q"q"ftH dR׮){h4cҶmRfZell F-Q;Q;Q3Kpdz sss6@;Æ SDDD?5jɓ'K/~*Zu5h֬Y9'@*"#>LFW/i*iScGm[H.)V@v_w9sСCjٲetuiٲe*Yڶm#Go~X#FH]F[is IDAT! ۫}?ɓ%I={LiٲeW[,YRf5o\/ZhveڪHKDn #&Nծ][W^yg[o%ǣq%u G8zr<*$fĉx<{R|_ĉZjtwJ/_ݻwg{YgڪtgŋWTTYFԨQWVMђkf_Ag ڪ" 1&L I馛TtoݺUGUV ʕ+' @VVNp͜9SG={ 8Ͽ+I*RH-ZTtpV _r oyGgex &I&A?6m=J*J3->>^dɒt_ti)S&;ٴi߿@EEEnطoߟ|vnj[v*.;Q;Q;Q䖼V7B*66V[x<:rH{x<޽<Oy+&I:zhxSx Q;&I7nƍ停ҵk.*Q;Q;Q3o)ն*T)xv!q"q"q"܅k[@Dɓ'k!]篿˗GA;8ot;w&͛Q;w$.\X HsҥKL2}iicǎ%M^mNp6+mUyN M0AԢE U^=|^z$iժU?ߺu<(ǣ dKV~2 *THMBB&O,ǣۮ];ϟ_۷oײeR|J7ngKyymUBks՞={t9C[D Go߾:x`gWֈ#xgwymU/ &HN(P y\R˖-S͚5ղeK=zT ,ЩS4h m6 h 8Nn {rJLLԊ+԰at-w)+eϟ_W~t뭷fsymUC@Evt:dNYD@3fEVѢEU~}9RN!8yΝ__~*T B j߾T?ڵk2eʨpº Oѣ.zjuI˗WBTzu_ !zG=ADN;yƌMTR*T;oS+Vu:r*U *22Rk׮|iԩСr^Y%KԦMũN:⋵amذAEт tgh"Vř*mU3ڪUI/^Yvm8uu"""b |NN%KlN|g~YƉpy%Ms:8:uJ]v9E?{UurA UrE=Vi*Z-xJ=Z"E{<**bU"& HH\ $$3f23$dkZk{͓%X,>xYfa'N<-ĉСCgiwMb :DW7o|GLO? F+yΝM0̔þ(>h+֭[Yf}{uu:t4hiXy/ia:lhhH0+0WEO\ъ*sUDND$tcb1[vig߿jŏ~#0 sڴig̘aZ,;*S^^n˴X,֭[g?3 0/철r4gZ,sժUǶ!8!{ヲb1W\iΞ=;]Wi9w܈*զab1n3>>޴X,YQo=˽:/^la~[ *x̳>۴X,3<i3OHU "0WEbh\ъ*EW_iƍo1hIÇ_WWW'cJv䋻P9dM4Io[| Y.!!AW_}$786 @PwG|\./a~Q])&&&c$r7pCP90_i\т*sUD+檈vUS1W bIRJJq ۷o$ 8зm۶m:t萤qҸqdf@|JJJ$Iv~ ??;v/_C)+33+oTSS~[[tmuw5hsUD '` AB'$Iuuua9x$o߾]R'<>=sJMMUNNN?Oow}髯ҢEQ]?vn+%IeStөkVz7}OfW\w}WzK"F:Seٓ sUt'檈U0WEc*N!dddHv]v0 ߱hѢEJIIѪU4jԨc1o߾ C"r8y-_\VU/ԩSg}VSNՍ7(D>|xȿ<4M}גSt+VJÇ׸q6L'Ntt!71hqʥ)..F0WE4a*sUTU$taرj?JNN֪U|qgxIG㰥70LJJgf9F30rp8ٳGT^^.á 6H">?|!I yw{bb$]˻دo߾alI(CwġoxU"0WEO\ю*z*z:0x`?^tҠk׮ծ]+C=JNNVNNN߻wo]y2M3d|PtkÖo-0~-‰Fn;YfIynرC7`eeeIrss\.2 C&LDk _̙3r4qDedd裏>Riiku 9s^E~]mկ~^zi„ :SUTT2_w2)/[nE.Kiii?~z7jիz)vmQKŚ;w Ð$رCUUU:4x`q˗/׀|8,,,e]CiĈ:yfm޼YIIIu8Uq1WEO\ц*sUDND$th믿ŋO>QSSN?t|ͺeZz^xr-m;vmO< 6NC ь3C)!!!kZfNӧk޼y߿ڄÜ9sd=#!J'^nn??ԁtꩧK.у>:+d]eӦMӟ$áݻw4M 8P'O=ܣqƅ,GXy1aTC qXZZGyDڻwiӦW 0W\=sUD#檈fUݘ8 ,]NDB'N"@' HI$t$:t :NDB'N"@' HI$t$:t :ND(##C]6*..NK,:<󊋋Syyy+ #5|oXdFתxq5kVa֬Y6l~u[ 8L'|r֭[{uqڧXoxnb߯Ϻ.]Naf{-E?JHHЌ3*ŋwwU;: iΖa={,,;&##C+T~oQՒ^xAcƌQ|| 'x"d***tmiȐ!ѠA4gڵ+6x<nWBBB_EK,s=QF)>>^ÇדO>)I:pN 4Hqqqtw}W_| 8+кu۷=d뮓o[q믿97o֔)S9sh᪨O?{O}j~]xayꩧ7nSJJ.]'ݻ{9 0@/o>=+UZZxIҚ5k4}t1B>RSS7ߨPŚ4iR233zj}'3fL";La3{LEEl٢~֏c-^w瞫뮻NnVIx63ϔ$mڴNf 4( $}ߖԜ0ɛIbcc5qDmݺշm˖-*** 7 4U]] /P^ػw$)5551z/}$?i6[W_jN2MS{iXNtÃH r_uu[l$=߿SN9EWVeee1MCum/99Yzwkܸq{K/T ,PYYYu1 #D=]^:HN]{)")0S{.d}ZnrssvZϟ^zI3f(뭋n'*:pDFYg%9Svvv?gȑ2MS۷o?VU kҤI4i$i׮];vA o.04r^'d cҥ1chӧ막'GU]]]PbӫW/m۶-94c zꩊue IDATj޽uǎ={t͙3G;wt?N\.=1c4zh=ڴiSc$''XWѧ*h[zz֯_d= prc ha 駟u9 .PJJ*++~z}Gzp8tꩧ3fƌ0 l{5sLn?^Æ ƍ?Y֭[Ç[n.2kĈris%('믿^6ojhĈ5j^}nUЕ,]pbZ}I$|ro1h_BBjIo 7efP9N$sN-Yd:\]=0 ]s5AM窩fرc5}t%&&/Bahܸq!9n8*..wkDCukfϞٳgwk'7j$t햓K񨲲RׯuG :0 ;zw|LӔfӓO>o92߇ 钤;w>^111mGmݫ6;|:nAiii]PN*T'8Wpvw@իW/ 6L233uE馛n֭[#ZHFO=ݫ?P&M$%%% %I}mkY.oe#l2s=*'ᄏ[2U@d{ AB'pLL0A{l٢7&99Yr}嗾CB#F*k.IRFFo[bbRRRT[[ 92l4E>I$͝;7:n&K:s:@bюE#Fݫ6[n~i߼)ZsU橈v<QD;bюEwsU'6:c&!!AgϞxs-]Tڷm6͘1CE]#FHvڥ_~9d˗na馛t)\=쳾Gsվ}4aM6-ٳ5h m۶M 7oH8p.]T...Nuew͛yf%%%^SLLq/\=È#`v޽[VXݻwK._NIOOC='BV\:M>]rrr_>C]wuڹs/_.ǣ{G| \ff>S͚5Kz7T[[ٳgO>KsUDkwW iiiz衇CE\&%%E ,yǎ^{o:7 1W@4twz::t :NJKK D*@H(1hG"1_;.D;bюE#F3ifwW'(**\?X~wWA@x̙tKwW#@' HI$t$:t :NDB'N"1rIyy?;TR<<Rmm<(Y.Gyrymuc]d?.Kyyyrhnuq^K;*>~H t/9G~|צ+%ySbH}i_x5\->-(E.@j%ҶՍ`G FWX u hPˣW(OjkR5W܍Gpa)$(6=]5~\p:lgXr76<ۿpwZo9eŢ!Or,\U.F;_Sj##߭ȑnw[+T\FPt$M6xQPٖ2,hDѓ_ >@=IOnp y͏ı@9U{r`S#t+->ME_]ޱ6czV ~.tx]PZ4ܠ3"n!GY:6wnrUNn;CDj<.V,'$I;%rRGC}Rb:UGV|~/B PNt7}k8X<#ǚ5JX~ 8^H-9nF}}]Ud%FjjI}b&蘶1m6U55n\E҂t}pf6K@'~C[S*S}4Zy\#uaO~H t/9FgٴpumT^^\_*pۉ|IlNX)4<8m>gk55]+\aVPE(HXw*/O0vnQVbV˵}]={xlo{kC,okT7Iq6xL Pأ#;'5EG8NM>Ee>j܆ sʓ5V3@kv$eesg? !vNF]Ñ$kˣ9iN>bT {qqăVGAxۚ9z\xwwjjlP_m65ijjw\6^6MMUMmaiAh.L)Z vTIdI%%czkjr~mC'_U/XeM$ܷY}f&lvU%cܥv e\©#g;Puѭq=+cp_9[<\CvuquAsvGI2defigNeefi: jQKUԤD6u%ɓ!h壃#v%8$HƮHQȑ4d2ZC0N6lPYlV.^2dGPspjTCeyu{V433 Hg!=p:Њ/ {\\'IL)gC}HXWIwB<ΩVMN<sx?=;=sW'iqȮʒ Bo;#hc\+轿e~GGmkSYzMڐ6{wF5S~~qIHvٓU^]gO5y3V;_[tY^[Ӿg]-\[)9YZab=+=S[s5yؤXWͱV>¿~As 5: r%w"ԑqܭ!VY!jk߷Z'OV)0c%I :[C39;NPPVz,(\ȣQ*9#_jց 8$:ߙɻQ敮BL/⏜T*9m2Fq;'M (PUN/[-e''jn*xy潿?|t\G~ߺPòeX#ꯐ:*eg7[`Xuyӵm%4i uM7(ujqNS}d)@PUcjּ&|Bo}dn~<'pS/o^֞ňEɲX2bjU}մ H rw@'/uhǦ4嗶OrVNw:5n[be5l{Pwe:9͑o{YRN}[.NIS ,Ҳm g"JP4U}vOӴځs%٦L/q<TUΎ's8ae^P$]arT~[.O{l/ tSsr/wNNMUƨ&e8mۛX#>~=RS[Z5?'ft8eL2YLUl}Z||߁@nIRVG;uݘ\}cX4vXY[&=ػzrL 5 \qd/.VYFMIN ~w%H X4-D[qI2юjC=$D]oV{Mpa551ڳtpl B&ii+[gMjUν7\HߓL\MMGg3b6? NB]VιbrLWlkG3*QZZ$>۫87[} wu)l27FqgQXt^)\[Ϝ:Qǀoe`;PD+Jc˂H_߶b<W};&H:FG*.nyڵ)Yڏ'7AQm%*\[igLS€n}:/ԨX55_kk޽Cp65UZԩVЎ|?]NMO$C*|P}KFL?:/\ޜXIRpb#iЯ_OK+.ө~YojW*❭/zZ^85 /{?uJj\vܕw\PM#(wn>XW>X%M|rT.VCeCd ].yrTSP{Zo_s8JM* jumS{r/}bů%RedgLV*KAꙶrJ :@+ ZTWrMzi^bDy뛟1MMm]k歺NWڦrS$*K U?‚% d]]MtrvTfV֑;g%m^ p '9L-#?lvn#sy6gfm̐BO4G564&H`BCK+Wu L[ CEz;7kT\߻եwL?SeG A%dklh ƖV]drYb{8H,XOԾ|2b9ҤOب^%Śjjm~/*ۧ>s'iSY\gVScrFF5Tpt߻豝LxN&1"_j{rZ|_IU[7~C_|?0m,.(Uj]`)Q\A[%Hk23])nsXrٳ*3SȰk|FFuWPRPJ=Dٚ,sdl/HL{ǥG{d*xBQsDlؠĶߑB=bѴ5K'O>k_B.,ݞQЀua5_PuorQxWm{g`Ѫ?_.t8A%6lPIjeX< x}~iirKXy IEyy/x o3 m$D[ry<ʩQNuo57oCr׬V6(&evY)b2b0nW)6]b䌝2IKwp:2zA㧷}bwjȫ5i:{22UUU% UG^Lkp8m& KqO]@omf uݽ|ӚSI_[K6]"mٖ ']M9]rFW*47K/sul$=XccwH눴_ޯv~>Z|~v"[g? q`dO!e=?VMYϏ y~]PZ> Lbm*>XGvW ]}'YBg\.Q9O @B'{tSyі|l˒qe!`` x̶epΐ̴i'ɤisڵڼiJSҦ:u欞dZ'/9I$MIk3 31Hc. 2 0ٖll {kc{Җ/v"_~Ȃ VE=V:™ jFeƝJ*s"S0$`i: o;pt[P4RR`F7-I$Μі%=wc#L(si%:GJX iXݢzhoW4~|,iOM1HYř3tt(uu\,r_dD*Ӊ>°Dc|xG &ë jY5Ñ#}݊UÁn [2oJzw Y^Šnd<.omQCyXS"f^eP;ΝKj+FsnnNvlZqigFs?As4B cR!o`J*.(~ S%d4 !CDdA?0Z"'vT O,FT#`X+E~? T "%x }Lk9-eTEeX{ZLtÒB43Į܆Ab1ϟĥ{?G{`|okyt8\O)/N$.0qp4eZ'LQKu@PLlQ[\:3Zͅj3,t޻_s'Vt*YSx**ȼtlXQMH&8+ۨ,hh+BlnSj"rhq<,tux ,,?.*՗l58l,c=Uގϑ Qy;IY#oj0ux|w86:F ==-rca\.TG˅ oG~YtTpJ4{lp܋H\]KݻlJmpO twhXE۷#R(--B!ZZZH$< ˜^(Ox=' @JԴD&S%\@`';PTa,;D#l7|)9Ń3fkPyKLr~Fb6{O d+qlh,%(s#|]t"_Wfq= -3[P5Vۢ#V/0u{Y|@irL)ڂmCo2>($<fÔñQIS ַ?{ ^,c3 }YA]rM>OA1Hsomk5Q )Qt_x sF)ٗ;rjHvĊVTJx'/A@nRRٷ赢LWRHQfMn--R% Å*/.Nl'A8p##8xfy,Ԝo("cl2O2P`01Ww3l^rF>56&}o^4LsNwOc<|dQ[Eu.n!ʓ=Z^p!ާS6$ޛWBG{{0w :G^LГS=dq+?W(#<<1{ c:>^OG,' %ދ(2yEB,|aw~GVI- /$BC!E//j \ [ZT(RKUX<߻|Yogs7oIЌDK1~&?78NұljEa--t_覥T:Bl fp8s̖TDxC$ǒb_>zaYJG.vwK$L`bG4ܿ_ڵ;|s^O;1A67R[  MIgH%toP茩G|Rϔ &I4ӼֵLvA[?,X` ,X` ,X` ,X` ,X-fDX` m[ ϟΝ;j*~a~~O~TUU>{1y^{5|<ɟIgΝ;g?Y(T5kO444|8~8ݣsؽ{7gOf I̪ /xOp׾9Xh_/I h| ?EsCys'Lrs:\(<,#$Dx,Ftz-{Uz֗)gvdӯjsdGq4c D)±ljMM{O?M2sst\%f!Ja~ P{#;dbN3FJC=S:^J-D{H۲&´o>TL ~Pj!b-_,rF{6so6!8k~s|=z=U : 4eԾ4w1zRU~c:E:$dAs]spUf!vT$^`ussF9w/ee>**hSG=(,+/TGSj&KG\ҡ{1Y`g+i>O~UV,eXv*G3qD_9:>mԱğ7yCt Om | LylKmbpK-<{,8K_Fiq(W>O_ n,aQyH${UQ² ۷!%BTŮ0ev` X;=a̍f`g鸵"BB٫)?ܩ }VEUD#㳥cr\㥪DoQPT9-q.:SUR^&>F*FڧaJpGh[Fv,kxx(nZ"ZLB|H$u3u;>~ 'fݺC%}VUTcĈ! jfak˓"y0fdnڃkWڋt ]? H9L}9Q$= i_ɽ.7M ΥUS=(RVx_k8=="};K!Έvr̲z>tz[z.vPQ0=?ǺgVW $Pr)^s?3sAo 7{٥d1.3w/ד-T,Yi2ޗYG[W-P\.CXKk2R=˥lNZ8{7Ccm#wޮ yjA.,?y(>@yyےtoߞ!kʱvvWvY}XnI*vi XcnDž4hlR=cm' e-' 2/tc#hk$"I^-kuT'9)xbi }X@Le'.PӳL>J**qǼGyߺC wh+O\zU ,X` ,X` ,X` ,X` ,Dž3g~DX` mw~wx饗pl޼Q9w/2O?AKG?Qy;ݻz*G?+,:.ҥKlݺwݼ[ IDAT+=˿dYūʓO>ٳgijjI׾Yvs_WΝI dwԌ LMA{v*bk}83$QzIltԵױ*)-^'IZLI&VT l6UŪU j{̏gp\,nݎ'QD"Yyb"wv!xС D'O6itQr4>&R<׋ Tx+x~=ޡ{:cA˻?3ML%. Et(D"|MT;~=6pelisSϋoCH_rHL'9-#0 NOFv;ǏG'G=yPP؆4[MFyʘ6п**|w}ˉ.?ފ .޿(p}lF݋dٜD"<=ܯ]_dG ?ϳ|;xK\Kfd( 66INdD"xhmUPQ٦bY~.J"M&^Af.q36`m-[[B)M޿y=&eGC\|9gbM d$I2Tt۴S{ll8Ωn.,ZIYHPw UU"x7oF]#$2.Ϥ*nt4n\m\n8s |!nސqap0ϑɨ?`>P$;]^+Nv&Vʰ+똽:YfͲmgȽjOYs A2 ?o1Ac Ԭ {U[swj N$jU>50$$o`bF׏؅#/xE/)|ϻiZDB^ElҏT@oaȇYaߚ| \jnPCa3H<k2'$C·Y?[zNɤ,o:=sKB"/ql1i lƍna7H6&89s#Ѩ6+^7nl!={ۥ-_"Z稽Y˪ |Gݼkc{TEL.Z$G3nf׶mE & gq(d&3&8e;CkAB9QԐ cxΜ~X;XKEkpjz LN^Үjj}DIOduCALF۽%8ٲo_667jJ^&8tk0uݾFKWeSi_O`)<m5LM j,\0D<t ^Lvc@NM[ggxv_{O_"8vK˂^ɏ׫ Z6nB/g~D%U,akK"׏3$|Վ߿`៷RaL^)6nlajl\ǍDfZoXU?'*[9Tx+I\mnfҗq:ٲ\zgU=pD}`[m(\HĩTWm,SG6 E!$k+B ]DJAV344il)~nA*W3=ݟcx 'vwawEޱ;> X^=Oۉ H懚9Жae",fP9cx+A@yܐWC9K݁\p/$95TIurJz]ZRNw-+~`lxqP2 ԯ7asةhٌa7d$I4m(Xl?H#n(O5ϥW` ,X` ,X` ,X` ,X`\/,X` ϓH$p/7͛ru>Oy?C/p^xׯo>._oou~7~L&WUN:??r>я222G>fffЇ> J,[X??ejj}C$!J"s9DIg "7#(2NMNMpS#rG͠V(vAn*ttQcB;|9\e6ozt@,FbnIs4RrMbR*EPOq|LNhpb]fqR.*c((Iut㩩^l:IMQT8(4ֺ)*3PZhl`~R]x)Ǯ|}R2eeR)Mw*q55E|S){˔g_(DBcYFvQa$Ibt4$B$o"PʿT$2nɾ#Q jիCҏؿߎ޳^ Ҳ&\%I9yv$Q58fl][-7?6d:Ҭ2DN;sԔLM5q'JҔ^{ogD V(s|/ϋ(bq-|uk^D \rʚ~E!ѝW$Ӊ\?˱J|^ݒtb.EBEU:Յg`/ Q<(DqC(+pEKgޛ 9OT븪],?ݱcA_=bXvJHUhfȼ[9ݦR267 |[t;UEanV^ -?h .)B׷p]FQSX댍5:eNufywFgtPUnÄB*w!KAp{FBɎ"R:|FG]KΈ;l>cM!TE%q,AjޝW*YNl4yvaL;G(HɥjS H~NcC#,O3REu7}$nu\.lu  ;vr1iC, JuEnnՔ~╓Trx M*hg$O_ f~'h\J tA%BMMMQo]7fSw_aG}/PRNp芤*'bȾ3SK}LނCF!"GJ\ֵZV|^sa*Kph]iORʪ2JK1\N$9M(Fnj2ebҸ˿G#RE2L{~7 % _}kk^,grw(~>$Ib49$'?` ,X` ,X` ,X` ,X`eϛ ,X`¿}8qIpܻw/}C|{c_ ܸqדfgӦM1x{/lxnjju111/Lgggw_җ]~a eYy^ʗe>sα{nΞ>ͮߖ\#9E$i@?+(±qb)~oT:Gm4<^L÷nqpZ,,#IaIhp/H>iY Ϡ:e((};R}=k?L~?=8BPI" * /6*dmbeW`لh 5$t쩀nEU|7|1׷MUWq~۱IEQASqvubH> yXUtүGD"hc2\PFI !mx#/AQU.zo_xYiimwj͙ 0:Of e YцR'(EQa$Qy~S)#=&fn;-UUB-)%EG=^OٸvS@Ǯ2BGcȞuO{'! VUIbvFmߍ(lT(?O!/]nX*Vq\䵉~@ڤ-QY,?Gl[|rq1nnYˮ u *:Ж8̎mGܸ"TΝt0jPw}CC!N S|R3ٿ?=TNWd{3fEC}PXs>%o 5PQPU‰MOR'33v<4F`{AKBoo_Wkշ̠Dc:."_l*EҲtg{d}8C ,0W(Bd(̎:k#:a3Kfg8jZk͇*WKBETΎB!}.D\c{TjK/pp,i+w*C!*9ġb2qBz;wDŴ{Ov%snBnSzK\9Ri/3|_cӦz6n4 " +#:ơwvx-FRtttj # -9{oR4 y뭪D^mA|]fb?N~o A]BxPz Faw]B <6jo[[:ʗI{CgDRRX7 S{&zP2+2jjc.1:Mʕ7b;LCI&) }[Ϸ3fŠ|+C@ 6hKؑbo}5xLV^7wك6t}NAneL&CccvgvHEB炐VR,3WUk'n@ILF&+ALAř,[/mU}9u?F2YE0EtCp(3#^YIs:M~ XfF8oe'BAHrT(KY핺w+KQ5]ʼnNE/c*3~ y8R W^r-hk˩aԫZ` ,X` ,X` ,X` ,X`7g>&‚ ,Xook_.??`nnBQ>lxFE?΍7Xv-/a$"T@٦=~ ՖMݞ"]em PA5kg_-LJ xny8#X/ޟ.*=6\ c'Ml|x^$ G?{}a``AQs4( t$65sՓ|h~җT8Η<PUgxX`b^x[yu8DrT_eerUv" ]|sMq=fI^/ .3[léa$$& _agIU]PI&(+¼ޔ5^"j}NPx+*蟞!PKr|5 $k.!ߐR5[*B<}S|Q^ h\~^({M>(#~x6 ֮Λ)?iK+Ji*ެŮ{Q)7Ԕ!?NxE/ー톳(E%ɋyf|׮п};˅`S]='O\>6n%cbdMO>Uyklh?q֬Y+{͏ZE(fTWo|TϢOp|xE/$p[;{7˴mAM4zL03%j-f"^&l k}>/Z̧(Gl*<22Dvx>^[ArNYdy6~}F|%k !h9$>ee-k,Ll R{;v /|5Ch_PX/P]c\"$.OEj \ۚ۩ڈ](3u9K+*vZw| 6zI^,kŵ(βkHjz c%rF%<:A3~=gv^=.DbOi95IZ .җg̹6+^1f(ѓGnjp"fqވM0yt ݡ/GrTPSqnD|-E-̠*IOVd3׮M"I##hDZa#R"Q_+u966)#=Ż οEC(^?x( ^t rb8~Y*W3=.Ο')$/3j[5+| # M Mp$aj^b@In_~1Cڛx_/6ȩk윸-GUF UZ8;3s.5sO>̌i_Y(jP3{z$>>d4[ڷ2SSȻoO(;4N\sYej*ϟbr;5sPK:aڥvrwE? 9̵"/Mmk{۫ͻ R ׯ__2~{ƍCL&m;m_v{!MX[Xr{hJ6#] XW˽ȒDfm Y6n[Xin-oݼI(nnE@eITMnF/Wfh MSk(DvGl rx(oi/}<U~+WP5 F'hmWZT:U_ծ%YI שFla(Z*d aL9ɍs瘘EY>^%2k:R6[2TQ P9z4iMS5,|9 2!3 yDE!6qQD"h2eżo3Mi ]FnmF9ο=/#ɛρJh^hb~%4p|y] 9l%fQUM%63H5$I! 2?G\Fw/UU)$̮ '6U5X6i66ڱVs0mvg@}zE /V_8,e v$ $(5XˬP^|B0tss\U0W41j^e8>T˙tQ9rw vQQhklcQ_OG-́ Ei:ʸ y,"~ 4l,#,]KF4r]V$U_IZNV6?}*PxT10;5u:=>9^1ORTU%:Hiq~/8$U띱kqjcD̈́?T7)]e63%APʱBa:nk}kJzM{)3 iDM>) "rPl㩪ՈmCu@qdI"vepkeF9~Z }-8CbB)=aT奭ښ/Dՙ rUmmRr9mi80z?ʌRtP 8uU璬Vâ pDZӚ[#dma8@U[#f^dա2(DŐt_=_laӾ>h߯bbqZ|iqr hG=1esmPGF[[X{3ȍtЙAzoi̲.,GPB|͉U5s'W팫 ߾j1[|271w\{^Rjً:u~뭸q{+٥(D}xhk(-ʌ䏾gn.@+EX r *$3pa %YbI~յ.................................... :6h__3 ?8;v`ttÇhiia[7۾ NߵkW]~͛UYT2wjt,(zIm*T yWxD*X g<t=J4 ק{bd2‚Rv IG$b~bietk/u<ܩ Ru(kf!'Hݪ %Y;wS ,5 ⳍb\HG÷ZRt$ːNOqlQ S'B" \7»b"U ;o$9\6>8u j+4>AW ZHW Lf?>pٿR+˰(24aii܌ 0 2 e4S#řcfA a9F橿b"/Tjcc0F5 S-4v.tTP|oĝfO,rIeUɩ(Fqb0V8U+vRߑ2rXFC,j۫ @<hxmJ>#@npmm[9vlJ^+|eI"69I8 W(\} xIt\Ebqu562r+EaP'V]htx|F:h,Ynr珃IB (n6) Ig?ZZg9?/#1 2!n)dJ(]+ < <'il^};WJ.GGr/Z~rtxsׯ 6.e|kY JcƸ`.jZ:/0ڔ5{-g7E7Ӽc5^ߠn?w ƪ*$&ظ`B36:Xʬvjk1DdMI]2[s :6Ν駟g!ru>OWW455[^^X ب|iijו^{;Tć;D_[/nFCE&#U,^\R5Uc.:ht~tl`ucVsA( :H,/¿UI" TeB : Ջ  ɢBlƭ[Qw@/( @l۞QJ8ξ>@͚KqSP.G JBwrN)j"j?e Eȑn׋EF$KΜei{RUĄB]y(bSL`g)kY [F鼺БmZif+݌>]4ssFG|s|LPvN6K/@(3 lzVc-*% eDDo]U 2ȃ$)ʘ?G4W^B6C.esa} ȑ#"cÖf a#T}k+Uc]^ж]ſMC| ׉"AQIJ^&J|ћ/r#uul FuLҤ1< ž"DdQb[矕x.\? k\=GamYh;Že;:UL%^ xО5M9}s>'*3MW8p*ʑbᯫwU%)Jymؘx$1o#0[)/h=&P_~TWMF|ads`]z"ېA"GSC(r!&2)9nX c_$QWWM?7O<}oqqDUUu=Vt~K>uuRQJ8'jNq`k#o&{=qߤY >~i W?bSQ E$Ifr1M(V26]6|]E}x 939VRUjM'Ǒ#( }-$-yr9?D_՟eU(4[ ~V˗. :qss ?Iޫ2x¾WUUQt_8^TQ -մW-S)/lU4k`Jt-m[: $oZMPX}YgqwCH@̈́?l>HldA{ 󚪡i*녲/P1tZ?FFCdr 5:@@?*bcظ#FՋﻎ,If;==e ߡV2{ cfx"ǂPMSyJfF*gɱMmZ+zO+Ί@w=p IDAT4n汈f)xQԅ;zIPij:bo?2, ~$I24;MB48~n|`xٵ6/\QOQY t_TC xq$n*+ rL um47XXl2DN٨Ft~Q۹w~cs =pJkv5m(4( }$ h g)yj= lz;1 [~cossl6ƣ,(aB.ڂaN/3I­[̳$L^(TGAWQ.PWel{{<^M#m 5&6ncZoȌʌB5Kra2>("w䖋5i̶ijYe&&246>Y*T&Vl\*e1At Ob[XМd}{rxG_v_^#gYG Jc[… lJn7$,R`#?r};6ڤ&WE52>trqqqqqqm( ryx 'Os7ǎ}I}gfΰ:'sr|k4q/xw*ޣTǩ(ilPRsJzz{Ӎ"p4 IVZ-FV;Y"ȲXY#%)jy;a䬌-&Xچ xmE V4M6vy Aŷ`@r"g<m.~rؔcJcV:>i-}H|7MXn, ^%|0L{?'o@fuy8 r AW_6 dqڂT \Yab>[mkbSz-vwG/qpW?/,=4/(,l*@ck z0o\CrUͅvymT+W!g)n ~jg`Yk)bv-,yY|)-rVh >؊l6Fo@4maloT#U ^=C^Ͽ_0( 6-'绀ښ^TYXƍs5,WNeձc@[CEk1nтB4 T(v0b.'`Wݟ"[`G5c$K4p"w*XyǎYfTܮIW1yemm[9vLd*,Uqp [}֞O?SZJtH[ͳ\˽ܡnj)A4C8 1z rK\C%p[T$~W1Yah)䰗cL?:v-;/ ZD(lV/vwZcE3=(;|Z磦r $TX\WJ]EaW:MCYfGRh;)cA?O# verSQRdY)zK ]ͱWCbaD\#3:Oln,5 <¼^{4"$!^!s,ͅqQUx@av"Sr n,[,C&ᑍ)C⸇I/f?B75[gi.+m[/2\>umc6$ΞezٳLNN299wݪvoO^/}la{ e۫&,'kS_kTmj" x4U#,CͶ<+_*wdg/E0 !NX\Lq萗v.'z+8QuU%)F;m(~^ϿIok Igr >_M-\+03Z pٳԲ 6 k;(Ga\N'(!z%n5ֈS݇z:rgvdߊqYH!A/b 0}t>G!c[;|{EfG.fy-_!Pi  Eӷ9l=sEN`i)+SrkQ/ j\/r~n+W{h\[!)sDkۭZB OS4ilaFPų8Y61R4Uc3(FnE\Hз!N*zpXTUEQbȲ]\QT㌎* x+^\8zEзoih~H8,-Yؘ̆.h'%#=;AZE7.a`v(`qP.\i</k$۾!/|evtۥT8uuAcǾB"\bՂ66- (mA27Ƌ7<ADw61n#H/[2|JY[NM\#yQ1s+f;do.>;D0hu/k(l钟)V]zªFV{W<t18k"N*'P.Ǫ(hG)f.$s3-,bJ( |0>o]?CmndzaF@_XgТ1(lLe 1R^4GD昽[2^;C"glO?{~wN(?'`#ij311#v|W}zo/Ys|ٻVY[L}}'egc%~hq=H2&X,F0$TSUG~5dbYRK^]peoo:ɋ|rw|9? <mNs"?0"*ǎMqp7(TNdcYl<Ρ-▽)0\0zͣ5JۨHسG!賉Z,!48Do{5.Mg%_{?́}'{%ͩu~fX4J4&#QX{;"ܚev[绝F1 hhx:5M f}֠lN1:X1TU)$({ڥɍq bz\&{іkLy ֦6%+6~ #<Ƕ6όr+Uϖ<|xsO u!Pb==xDw!KC\C612gL m6daEX[F^dISPWB۞~^:) u'3?JV==[q3=3Yg?{jȀ yޜPU9Zp\+T5G>Vqij>5p)PzNY9@jƵ𝛿//yk[ל ږ_U{xߥSdNk,{ ֑͓a9ol+B8lww 9s}7Uow;;jf9_)~qMɔw58z[D]H&0(-򫫌;K #bylTjZS;Za>ڇ1}EZz߷'t"A34Xyc!ec0?֭4[tqK@J!G逾mm2ں^BC.0R 0%Iz{ϰ޽]ӾuJ>B,WU<%xi_yt%H*G;88|קʛy/3]]D"ۚ'Nme?g\RzbLjALo_ճwYG-}OT1ͽ-b&h(8=ædcY;Ö%\(yFц0|tm[Omm+áWiIa}?Rc(@_/vgucWvl,^^[I`PdvX/u/~^{uV`˖6nޜk2t(qSr˴"l;4H<@E lvYt-i[8Z gvᎦ;j<,Fxw?B]7W3LynߪṈeq"NOiTyՏJSVT x3466%&I/NrdB:ھwȼa˞[6yN-ɓ{!ē|e#\,-Z =/rG3z5L!v#y؅8 xö5&>C;xҖ6rfuԲ~ Uo~m1Ym[ث1>2cQ<,\ x<^֓ f;+͏Rarka40m|!MK(=Yq5_^ln\\\\\\\\9p?/_tvvʊu/_}K.|]cc#5] oKSO6/>}+$όy|ԩW9s 瀍bG)FAUY]Uy$7;\$[+a}ĦcL6DDQD%ӓTU%(C!bgQ!]"gR)$A$% eFkQBXL42>w`e|v/ۚ%Yb-VxKfya7 h2E\pIY[˘EONyÞj%:;́=b>b6pؤF;w$>B8L,DFXq,*hjO<ʞK]ahe&%۸pSARcv6䥡,͓,$u{* 7^Bb_8C r4:ʌB\ipTw)\-򲌰8HFoNUUb~y;@@vYAT|>~t/>hVD~5_%7nP:-[`0(9MkEH$@&!-[(AdY&~Σ\7qUA(B%IY"% hס{ElUY^ʟ"¼gke[|!ve f\/ y!bDQTUd ,;*@li "i{XM Јbֈe46;!u:7D m<?}Y36NSJ&˺ހ,MUSI'_SP@ѣ6[|"GtcFܱ_2k<= OöUi65:QF($⼒gDTzy} wl9:ϒNI@qMklXQa&c}͢gǮ_bJPY0[#3?ʙ}櫜.mai g-bb"JccfNlKx`}]a2˳Kb"*176l7~Ua ·;/,9pRKr!x¾ c ?|}q&s+( 7[N(5O>$2O&-O>$gΜ̓Or̓OO=lf~R< {Gk?~Sŋ*_W*uZ-}-͈E_.D_&zI!jqs2q;aG*'O{q9n.U{V#.F B=Xd8]HW!rl;2d>LD"#hc+Kcjإl6Z!_6Q2{Mjf݃昔Kx>e[뉅B"xg5t7n (W㶦jdcY4u4z[K'Nzz:mcou(/^%{9h]3Sr)\J!< rs*BS̪>A~5o/R%>7WW,8mi$Kԁ0LE7sJ( ?HrXAU]X"k@h?N5rw(ĖQ߂hFV\[k{} 8w^J7kliEǮ(Z㌱#/?YLԆ0Ao>7Wvarל8dIc}~bx`g2}] dPqܟH#vyEqFÎAr+gq=4׮͚}6bkkZZ%u [_T~VM$Woѣ׭* E[%TqoQ5que"r+3N 0@!DA9ĥ(-RV n$K/*++Uu,%_XC ŜvXD~|гHn>V޹3*}wݼ 2l(9xkFmi9[j0>Y9#RXHi1}(8嵵IQh,eeGv&wkV~hAJc_]瞃u~+]ab.%ͼ6tuvm"v*T*ف0Wb32+x޲ôqgRg1ܧz ̎ě92_U$=|WOԩWǟ46g'trqqqqqqm6]=֭[RN>㱽k뀲nU{>Ni~CA$<{^@]4VӠ(;6E[VrQDh-~,µYTMEvQ$x"΅'_u,Tu*P0\n/W*RU^yAdC˯Ţd3"9b QYwMK!c!sIeYio%~*ē'_" O$BOY_Gr 2\Q7-pjsE SL[#J}yI4i9Ora<]|>υ@ag8N|uiIJ!D8I6/L]wV 7},d-ܼ IDATk(v$dcY8Lvmӿ/wttvv;{1+#˒l>O!Dsөx^Q.65˗9{"=T-x6z~3QqLF^=< cc㦯T+em^PSr)?/#͘sxn~U/8׀TˇLN-mhjN2%^n)|!"M#ka~ǹ{e>:YЫ( ttZgp-IN1R 0B allz(pЊ%/B|Q\oo>&MQXJ.G:⢿7hBI,jYk8vYZ.ȉ7 tPwn6fX--aSWQ ?;\QRnܝ 2Q$E6#E G08 ]?6W46{|1n&^Uf(HĭkyֻVFtn(xoF5RPET}=&&ʅS7EA3"Cjk_#с22!3smǯ僇`긞]+wrދ,3 (ņeJS5&xQr9NhGxk|qE>D܉y:DVV}I9%r~<ռ-;7!2/E p#}wB"=Xf$K씏 z ߩ8 6APBƁ@|Ec-k%)#יؿ\r9]f^Fg/ŒLNث 1Rmf=#~Di\5nd>7_0vyoRngpUŶ,"MrKv 3ފ#o$>\GAKdl@%ޘ 㙼;'Nc]0tuclj+#!kpwPWWLJ>!׿^v zQ?qۿ}x ?<O|}(n}}o~x<>Oo^1$E5fK=<YnfgonРv@P 퐉ᑎ0'_7 ٗX_.M j&!(e3g{?b6}Ɔ rF#c4u){S1Qq-O$ S7޺фYXPHJ&"B"n(-T`Ǔ"wd`mSX k(MmThkS<ɥNP'y56{J,V`jj.En0G 7^ųtn.-HEb,tvv٩ϗ\ $ PiVRܫThmS&a1IdVo>6~i&9bQ8E#GȊ"yy5|gl1'MS.+<Af}l1$O|Ęm-NmE (4O ŧkkWȽ_ϫ|_8jHf%f)-bdN:,<%}351O [OU fgQiddĴ?2k8ǁ-=9g-tޘ`z:^1RA ʆX2P>[sj皦rJ U(vNb,H+zO/?n)kXS,8|xEb||\8OVY͟O^˯p<׶WhjH)EpZP]_loiEU{upI$)2%RSLDIdJۤm&uW׳Yf25#M<&E2DLR@lS$ 6ܜ?^6^[rSnQ HOǟ@g+ e@{%Q45|h2h؅[X޻QIMA,&OdfNVb~) '1;d3'Lգ:/NǁdQXJf[p:iE% YDBdbb-e~ b F$./={7䬕j~Ⳟy9Z0S/ILܛȊicp#=**4Vq,rOeEP9`!XGse n#1—..-ۓ-2gu oE'#21c26k\re%N4*ofa"7W"NC*w^Bl3)^y筍hyhڐ+B%!3}me%?hZͺ++EsQsj8DIpfW$PXXkkv=kd2֦@F=sNgxxn~ἃfn޼ooq-^/g;w&sNԧ>'?ν{}7o}v8(|sҥK:t??{ꯈD"ёO~B}}=77<(~o||m\n M}37+#Q,. P\pė:h({wF"6цA0n`x< G'ANF=D#c.'"!Ֆ(H?vcbun,MM_SM\P|<\.È AeN'=̌Ǹe'!gM15؎(s- e" $ '"W3c r UUQ> JJLKY`zi >fѐݍ1XO b㶦nS0(B42k=58hllm2h! y.`0YkK YAЖ(RKr)Woj FBS&a.Y.r{S^ە?2DǢM}(F1/v m{,^N',8%Dįٵkݞ ͛HtVk :kev q\Qٺس3g??񇜛e`2x?FM!v8=(&\&EjH12;>Kծ*|_^Pǽbʕ4.qxaͳa[i0\m|4[\(kjep̌-9 g~nJgW(ϗkϩǴlv_پc.>2"ܼ`4|sss<1vN_ƽ ,n͸Y2&3OU^ޔlc6}~Ak_~/.ׇI&En_Χ0 Ƽ>)f@fܹw'##Yy kFQ?Z55YyGZvCGmKԇfcLHosMENݎ!olts))#S{wyf*Cq6tp,DU o"8A{+o='2Vx8OD0 6e吹cSrC9}(Ƭ5#E9s{/#|88N>íS嫢 hꡱly`Nfo~;,gd֥Oi*/ϼesqu$\Rǩe[(eP5<&kfoj̖]:>`vw^44I9~N÷}7eE:7q쬔ѡ!bPVNyY#@:NNsq1;÷üx[<3m ~ktm? Gs+=ZEHn,V7l"F]A;dٿ:vy<H$BMM O`Üxb= &2ܪr>pHV4r!c,:O?`9@UyWӵ:WMu>jj} gHQ:[8-߆5-3Z t9Cm؄]ZN[x=45d2%ef.31:5ʉmgV͋Yr 3KnMU"$yq4"q z,$\?<{Bng\>;>DNqi*"j|ǏgttQ$ ˘P4DC؛3ycA!( \`8:c%1ƛ([snu=d?sc xf3gzzx϶hWnqxFcbze0 ѼjJJA\.W7cZ'$lUPZkSr>ڽRf&=Œ]ofU0D"TT32:gӹhĻ{7&h =s:47dՙ|$Rwr納 \1̳nVoКlin7!)RS#d/fSZtd+a (n;-ˌma:4Ɠ>~ .]lHCVbqݜ3:k_t!L&ٝOoo/~rώ;XYYa||WL&inn楗^bǎYvuu9rxwؽ{7}}}lݺ5o,s!|2P:Ν;I,..g׹~:[l_`\z?*yr?8/7e^$hI`5:M ${ ͦyS2HX6FP*I<1ڻ,+ R,FBQ_GD)E̔ ^"#Eԁ+}*/aR7њ r33`$te[} iqSqd P FoNȓe14\]n_pUU$f;sۯ~VT`"O >b866 6>d<9mMK3=3)P@{}F+t29I!KM?~'1jyn[U6ճAPn?{K\60DIЌ(&S Msz,YrTdEAzHNiϟ1pޱy;H rbNnͶ'{.OnXLC>g72֑uTl| l:UPH·770n=HK^|*Fն hЎS>cl|^7606'~R6\9zSO;dqժT{SjN-g!9s0m^H oBBZPڳ}jkj~<.KԾ*niu۷p.;fa)J"~9Qd;hl_/Vojw^Y ߏL,AeՑLJ^{jfZ'N'y/Y.}ld&>+ٍ,HH4SS$~1~v綕U+SR$L(bR9Lfz u $~IC$;Ǯ34yPc.δa?El n=h{D#SS08{ۋZ$'٨g1R Ɨ'`,{5x#Kp%g⢢LMx-忹k圻{nCwΝPm[EAv!Ec C'OgShͣ O4:X1`(,MO417x[4|@QO@j~hf 44_kk^{=l^>nZKiVOlibkk dܖ}\V@#zwˊBם;l! XNaz_kX rEzW`Pg6_?ri1_lc#YW?=o$k)˸&PysTplKJ׾},'=L.NsJS,?zc.6iZsϥW_kU_m=sN˧ݎ``ll^{dY}/WUxOpa߿ׯ_|[¶!$f>355 TTT||;l߾]9ŧ>)b:W\A?w]d􏘋,pFNs8V/{k6h+Aοqk7{Qi"V^2$dΜ&sl0 ,'hSKpCuoL}(v7LZCأ8N?Re%5d{u& =AiUEZmɼM&P!In.:M$XH&hR_.TS S:\Jj v(2hŕw[fȽ==%al GTmMV(-Y=(a U*הCִth3x. 6ݡyniO2 <6az媲sԞZof;,+¬ehh2dz4׿Nll1x_'mi6t{]NpZEݝh0#i )xC}g!ue G]?Q:6eYm $hkt˕OZ>&fUt#exafzM7^0ѭ Ý!ֻ?h{v^ '<[CUu]&dENjT,y>etz4˦dY& rdWWe0YPr U{Pl-+NG̙kU5 qV8Tq1ӟh4dqq8Ԝ26h&ZrQQ)TX^P32:]FSK wLWo/۶1cӗ`0txOyg۴^~kt}[BTpZ7K䡪U5r-(뿹s}.KNMj3ip zp wjApeOy‚A<\ \H#ׯC2 nwv~a2 4ǦYU~gv ;8QЂvp\t9`G9³^vKMvlАf8kj45:A؄F^p#ΥrV&zcZG /n0dxxX5Ȯ #" $Vd,FT Mɜ>=՘Lg0)Sװ|ǫU\dr/Aȶh0[!{rbZ-QJS条ђab"M:a}NN'S.ĹS[%\.,s2:=Lφe0mu:1t_g{öFXT^^ȊL0v.-[{/20ʱmor}~ݻE.c"{7ޛʁ5u#^_ &"85O_زq&b9m*.x ry$!// ]4W73:5nj xrY9{,PvFG97;Ormj+j3ey˩КG[[;֌Xwcrиq3wlB"woZxh FPPʛCse9)>{[ָ  TGm3+ ]攻Phs;M׹6Nnenjsث|uF"!pz ߓS޹悤l=\^7Rq; Fmӫ4m{(/[]|z&g/s Z*-[ۨn6M|舁^Vɤ]lz2:ۖ2q+Xcon~WkHyF X%ݘu.?s} D̙kR1i-5mfdz-2;K¹7WOj/'>tBSGZ 7oⷾ Uutttttttttttttttttttttttttttttttttt~1$/::::::::3pU<ȅ .}޺YZXKoKV:ڷ?(7Yi;};aVTG*+ۧqw-ZVqD$h܈-۲ w8¸?JggSq6սU ok0V~a:zbLn3ϴ݃-l#S/3\J%V[ӁiBonsBi݊^| [3H3~BsTd(h@g~f3tmO뤻;Ag~~{xLۧ0R]N٘ӗq&8BmԮuyZ&[/tYD}oOrbZR[ĆR}3=BX>w`ހ %7...塿Klp)YAطLNO.B~ /W >|D (O;6\uvAtj*o>Q'r /MiixG;7׃ĪBv\znm 1Ol3*JKcI 7588烵( ]/&Y/.T;ֻ7j{t>72גZaggiH بu y6s_Ɗ[sZryRu_gr/l{cV;mOW51_(=33yss{ea l[%# 4t35#h>:3Ӄj4w$${.m\b,wӏ`*D<|WmWXFmKdEAzHN%2>Ӄ'٧7'F^{\r@0:ʕ+f֪-I֒YO S!ҊS~UD/; u DڿfE!\I.XԸa[*fYdrqσס[+ 52vpO?[Zu[ )"(q0Z{ sw{p"+;{o/?HiIb*dB WIJLhGkFm2C[-u:F1:UAwEV &m]_S /T&WZ`)ɗeEA:d +tm')5R u;+XjAlF^3YQLH-*v]mt޽ }|}3&rjc,F+97?[0̿֔xۉ/0ϣLMP)Ɩ87LU [zz*"0d[0p-< rUk۝T0϶B׌%(k>0Yn*[j:/v>& _փޝ3ܼ_K^\!|K3꺟#Yҩ8v%%zOF`r~p33í{`d'W݄a?\0^:Huߞ'c.1kOD37W_*L`U1 {̖ο̵G#gh~Ufx:E̥냐ɥ]8+t2QPG;R9!k֍h'0n=Hjkjl7Hp]NnN1;eԁqv/ϱ/gz :N.)k圻{}ҫrZUGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG{ x73,=N'#ėFDI%s;hb 7Ͽ !NY B,,,n$SDGGFw? \vl?Ic{[pZԅj K4`s>t:D]yRd4\ޫ=;T b Ȩm᡻5T1M %uC˕Kyef4eI^`4ǎ#7 (B4fr9wW6/1n>*cآ6nrenF!q PS]0MxVd"t $Y,S.ռ &ma]&p'yei5qsef{mgCg#C傷\0ZlCW 0u[Z!mK_⩨槷sdwk XBT!mm涷x/+2o-bcX_nq#.qa'>.2k0i00zl{+?Sǟhإdꛖ-c˖P(/oP^*-`8ʍl,:#\. FVGӗYOΰͽQ'0-dkiy*go]\b$Ԕn0i0\[bRLh|D'&1_Oj8des!J8N.Ζy*$smu|QLm2hژeaqxJz々s<]LLrYQlL&eA-eWU{Fhy*Gv}fZܪ7F8vFyZBd!L']Ը/FhڸcWBl1ȓ4`c!$Zlb0B .ad)_Iۙ`4 \x}*k3xv6%͠ 1ڽ;z{9'j``4β닼5acn$qoqBl GC9(;kT1˗9h\[vJ]T>`4et|<3@8:ˬDCMת.%//x]]OTuʝwBez]r`tK|7W+i,+KMmbAse6✯}ŢQ4B:xƖ&js+q}I?SWpl_Ñ&jM=Mb\Z*vg>ec^~k*}AHR՚#nvwahMFZiyB:SDZDQYW-o͇pWg2!%^OSY9^ :a>q2:ꏢDA, %m˅8!caK{Y}_2lLNrƲ2!3k *3O007iFs ;oI,w&7JA>r0 F^R]`06[ed=g\6W.SB0]k-QÁRndd[Wp+Yc|&Efj -,D!Mz8cO kpYsL``9LIMIѵ48Y]Pflq;>&`4bEʭ-=p 䲧p="oUQ3h\$zqGI^^aC !&nw͖ F!y1rqwJ#e^zLL@{LʚJ W\Vzff4㐭nl^>RƗe"j-@IC/~|3e뱃6::|PT;¼z5BifX_JW 2===(<- 9%g0IBqؙ{+cha9~-?ի_Χ2XM8l]5Ïۄ/fޣFHKt]L0_$Vr %(lLqa?(\$00 ORUf2wy<4- MOkʻ|ҮPZ7uUt;vX6&:>%B"skYg7;\ J0s_6w*+otwCw7bEEFֹG=]&g =7JL1?w>L{Z[vvd+G))-I[xl@ڭzyŁL AL^ɚ[<4SL9ǐPH.RfzfPdEsa-' ϵ6 2H||(&0(oT02m09@QRsLp9xj 37 Y#ڌTxm\^e a)`ZϷh1ށ`JmI~D^E۵lk?8%#}n 410hJ1BCѬU5eʫsOgg'5TZ57'͝wt\_UacaH%C20;{~.vqc $/,Ԍ'+09>! jJ3sV9p+ƭ57̧e*y-Qi$kʬK=!*CC+,0G:fbpz SmBP-ѥßyזs2M^YlָX[iޜ ӭp>\Yia'F0N>Rh=4hSO3xS2INb1h ƬI~:<‹]s{ۙxk-&œ%.mÊφ%Ʈ26ěB%?DBD`XLZWyrP#Io8w73Blkc!W9P"̋?5VZRn.hLÉX,)"u>&?--,,,@j%ww;W,1\-~i}9};^mm*8|5HuIII'h*G0 BV\M%sst.J53-\sT0$o33ZZ"03SPFtFp85ڗT9B8*@;r}.ԮSs<'OPOc} KY'@+ȱgDdc'eOI=sDYt@ |h|=uFn(3̉A6EV xʶƔbChT%.*yzI1flK+ ]AZԜ1Ɍ%W[drn1H\9*֯WNsײf =ŭedR}6Xr-ђhQtnөZfTNCi ѧ Or8Xpa5ktK$ =Nuu F3FSmm&3uWdd"26Ѧ n`1}${t˛^ˌII( &'n5W,jX w@e󀌾tڟ8ꨢeؑGv# ~{ Յe Ρ!M]_ot (Jm@Hcnz,ɉXʣভr$A0!~('N28Og_T N'?;K֭fkȻU6J.MmngNU#"J=_8sf6r/f$&QltLO} uz#En[ZG dmNı%@7$,o<\ļ} ؏l+f57Vd$2;UJJJG4kzÐGlwyU0 r\YHLa_,CiݹZ{4ٛ{ =QB6xa!{ZBu1˗6_D+|Y!OE'Dm_HhrC!3,3[2uS08۩gB{yr"u ]қ_04n mEGb:8),{0g &(qZf6 Q45Ey>đ͊<4[~ߋs70(r<(HN'ٞڤw-6`÷[7vy4C!j^1L[)$kup̩̼8WRZ5D0ȅf6rf_ &7t򠛙5eyXh'mpaL}}Yr\젿匓H|eW-4?8 D +[CذJF8|#GEtƇ c5/MbLawq MQJBsA~={JHDdȁ5A@I,\L=fHҏNt3]K9WZVjz&?G)kUTt5?3~fJyb%foN,_ ^ep2ܥ)=PI>CwO9wnu,؏a䦭 >YR~l%͉m*e_%0O?FF66)J_Cw՗U5C=^(;>@α]p̏$KrMrWsĖjny|F!aUGV%8 5#CU-=bl-4$;sRV{ w~o#$Or9I FgQ'vRV赧mG_Ƞ˧p`o}[NMS{oا77=>ʼ>ъrW;7+Ru Humw)7=z7J ?wx-l<\JG E6!r͋|&8U=*8}zPۅZZD5ueFOX!PUMS5f&4 ߏtaIS@VJ_z6jx<~n$1nmpk+BVu>.KZ׉e2YuMɻonBWCt]CSA4l՚du^ԎW2uQvR2sUQH-_f ЊT@ @ @ @ @ xx~B@ m;v秞;2:+ ܞQTP67(Vgx { jxՏN `J[!@.N{{t:M$)yUC,DIt;_>=ēg3?{BnwQ2$&;0Ĺ5FOW_yo=$=#AU9 ˋE*޹<Cn@:h;ĔL8#d3ydLn~Fi -.26?3p=1 2[lop=Q(t>/2?}13\nKvϜgf|S'ߛUH_$K_a嗹ivw{7 $jlRv,jynf &&f_~ۅd}|O:Gzzv:`إV~QM#x(-&chC]אm{rZ4&ws&J]漎26( 0L2lm\$\^(gg9p拋 ܿ`/,)O`s~$Jwѹ(C>Hׯ׿sCMML%5^Z[6(\ǎXxFtdGۋ8셓ӴַC/ōlJ؞ؽkR}7sYoL{Klm@`$PdgQPXZ"lb/򫄻K viNmյh7qc[tiH֛C!ޞB4/oٛwIqMr Sd+h:ec HmlYQj\wEv?/<~I߂#ӅhʰZZZYqKs6/Άm+?tEsZ\V)NոO9þN1Y>^j`d2-J冊E3/s~/ku?+i(r<i" xnRv +@nj7+hf utXRuc5ﹰ}ЮeڛP2ܺᙚlu/ :k95Pa+vƯ"Sش6=1ȅ|ɻ!ߞ!ghoj'w~##me/6)c ֞kdT±b{{,Osח!GdzΣ(% /8yc`ih+ZKd  w╤*!=e5壹+4r[%.)Y.\9So5mUkLo㘑5"_mlU,s-\IlIQnb)DbtO~ǎNGQ iېd 4~VhjiĉN*u/A"CPLӲP;?3fskGŝ IDAT=8A%wY2003;K_K _X08;O!B_'WnFRPx. okߑ[zjDA Kjqm V,c k?0 bsqh'l/ׇgɥZM07|(.yw(rN]{#ȲCŨvÎMW5\T6d}Ay `c,~O/ٵ)A/0~ (@*Q',+N⛅8r/F# ,Dv0FO_FQEn&[ӽė⨉7K/qDӮN $Ozx 4Ojbj sX%tr\gXvRl.[28iled5O4 x߃>(+$P5o6#~lzF,';'AW˥j6S?fksWbe#'.eʗUT4UAcskEda&=u&ّAUIF"\8v3̕-U3,S_=ȧz{ { b;PP.eI_h $ JU:['$iBPulίyV}ͬh+>QȉZ[C֝${I*B_}z\H|.]"ą-:uP!E]$FTAD[zr2ȋ}}o$2]mM:(߲W|$Cx:Aga6Sêe˩54 kkUUeyq1X_s\5˒̱k'zظBcjζZ4u-H Ե5c8LG3tm]\ @ @ @ @ ": @ IGHSVb/<Є#/[$YB:1H.^$C74 I^ CUtvv211Q,IBlyB3 S0}0D`uqlC_u 3ߋr|_MF] =ϡ7lNO-EQn-4$)ZZ|=(J5Mm5%hV sY~IIyOh 1tS_S''KYTE8]Rlqb;rTb[+5ݻy){.7YѲEڼF%H'h-ż]cJ92qf^c%V@ juجZ#1iylՕ=f =1A҉.NJ*\5]=OA%FW,j'7soM <ɲk֕plO% IoDI|~1ͭ&Qcc\^CuuDnjxơSMzk UQ0Ƨ p7F;#|Ibf|mo|X$OޮƼXfv}Aoow6:8G:tĵ EU6kN.)KWqkL-adžiB*#aj3o\̳ᱹ8cr<ܙgq1d:d,煘p6}܇{C95M٫6n 9޷I|aQ4ג躆 ~lAUUFJ](Id* q}1E$}47?ZQ/%.AnFRoY{E9BL->ִY5ٙDޏtv vQqIM©48{=N˝HU%'stet?$ʉM0 bC7WzlkVr=Cˮ مMsCy[u,X=5[ۖg0|4\%_å[ʬ-#Hj}[_>cFQ2[5kaj-3nf/m/QnrMztֳk6+*9Pk׬X!dz2 UC@U1$$fT_?K?ĭ cw0M*+sx~6Dv2L'ᷞgy^jK8/J&rg #kZ4 I4`]gHw՜UF&#tǚ6s-]>] al~ޚ@hMM*.ۗl+} $8~;}/xfvuVtI#$.$qO+mK_wnTO>o&/,_WkN^ZQK.nJG~MVa jF-'V FK zǾ=sje`]^o+~NM19azx%ytW}{ەuon's߫DxTj ͩra.y:vR_㼦i/Wm>]uY~0:?x-gV|DUU?2ъsWN^IIJ7:lGy[?wFӃ<fl=1QeKA//7֩6!I2)^6cHs|> r9n.nO~3MK✠g7%#mս@ @ @ @ @ E : @ ˗~i\Sܲ_b:;R|u7B{PBJįixٿFѷ&$`mj!i2*5>y IDAT|pؽYA-؋[dIb@KĖta8kSk%&2`Sw%_|9?O]'E҅DpilSaݓdf^Y͑Z4[?%Ifh#[Wu<}ԭpmb wFk@ɋoTעO۬ƻpebwA sf;s)×Z-;1>4L:ףr9Z1e6k,ˌD>Sija-‰`zw㈇QšGʹ ^S74FNz9lDU-{OB}|iaMZ,+ܷb?8vkWU\I&eId HWuB\qks Ay /7\W֡&.2#n 7=؃zm.^5]9++7Gp4LD]9b7Y vo{d EU g!v4LϮ֓x<戣FSo&Ba[iԄ9x9KQKTERCαmm{uSҾo8ٍ a@t$=!3^qA#GBO:v}Ed];Fq~Z?`j\ghzm6R`\\UUSXk缟O|g`3r?u^F~F">ݬ 嬹7gČs٫~ƿ;1"%7?F%P^vey; eWDD勎ښgٛhFv2՗oXlEKjib>ϱ?:/߭QiR)RT\W$"mNm\+pe-'rŘ]%6T{v!Ww묗{eϒ>?W8s&J6kk}pدoFln֘|~hBC9U^FfmU鱯[ڼIH9¬k:uC,.-yφJX@OR!qf_b.՚ 3D"p#'3mܶi {lMCYŘIo`{(jp)pirR(Y/Q0|Ĺ5uuL([ HONBEσiZmlg\^b/)nfadEG]^wܹ *l{<\W(Q\衪*t:!6pd*N 7篵7Pt6:=v5=f[ 9T4tw#q$5+-wTriE}}ōm{ڼF!HB^X3|v\5 a'0UTQUyZ$B ̿?W 2 &KӒZA8Q+W\ZIziIն*c;}R"7NɛWyWf3 _WYXt5?;,c;a8䒵5(Bݏ/ty [>11lR$7(I{M̒Yʏ^w9=2vMqnG7BPUh4M8A]N:5 9\u-%aU=^u߯%64 WUzkc+ٌשN Q‡L;f6ps\GDI$Su;l>HUU \isuح6xſm< sr=x^W_6Myx[Ѿ?^=ej$1 Y-4ɟ_eޫ\ܼ%:q5&g@}8F>&M$tAe~~|6$o3_~*_~-Ub?{MmY|ki4pj5#Doo7s5yceS{ G%6muȣtuu@h|ϟ*.NePrlK(rRf' n90:j2A&&43g_v'пS7nSJ]W̭52>um>У %5:X~`F5}"A ibJ'=D%nMmӯ %= 0h11Տd6srY1N1O-[!b%ENSAx v%H\m®[II&펦jəir]@ @ @ @ @77~B@ mw9r=_!m$_}w/qQUw_iM_!~o[Er| n?~z_C~*O\70;: sAЙi,/ihd} qC-uw[X*_Y'4n4hy8v㡣XMC^Meiy!gzxaޓC,;en$J? WHih(ѥah+˄>P:\yhiK/__+~޷T$- W9i>ɾ+\Y_GU$[I 耟4~C9$ݘ~x` y^Ys}MdW*xʲ++t*xxffߐiID`}[~^}geuznqGʞ˿"}}Cxe/a0>>>1x 8>pY9~9R*x~ $Lk_"ܾmw~[~@O9BC{[k)A^NF/| HT-o<5;+K~]iGy2uٟ>2!WǾ}%J^5-v8|u448|yC](l۱^wW^e8|+/{u'xpqA| @u i6C4yveNw{f2޴۵7S[;3[ɔl3LӮtĴHREz$!2%>%%g2*?yw.cݿʫM'|q,8*SѫӔ -駿y\ĮM{}TTf &i5 o!C䋿ֵUǞ=UUGnlD4oC0 D!؜Lh5 }IcCjXv ,-yRWZt,|Xcb|TUWD"HV+Қ%C QBxd[=5& $Dl/r]7xbAYFF&)- 3}>pptEuoZJn#~&'.S,5b6%BUUJiNoB#s1{vGՏ:gݝĹCx#g$-rHu';;l?wnO1]Gn"u @kqᱪXO+_UXE^~WX}ϸk։z&hfx.6KiQ‘Lz1-F2PK5faaz-;í:5iJ$B<ŕܘA˂S5d 0\ $OL5p5FP !x03<]$'Y%k*z/'Nb9!jmOBU,aHXY]qRR#k1ϔ$q#FKm{j.̱aNC 8Ucˡ舕u^X`svH,ҕuVˤUiWI$V[W-Ma-9?OZnA%Z+33ٻw?Ls'(9\VӅ|!j2r}G#ruS`۹X}C c5N63-^rVޕMX(:ʸ5.E˭O@/{w$:'˨/y Ocv0ꢲ27'e)V LP<\&Ƥ0;Nvztb?T&ނ c򆋐@ }J__تrgI*W+u" Yy^^n$7D)|go2w8v`AQh=ZZC cJoϝxnsڪ P\TLsʼnˑkjR;"YCm[r<մGq k{5㐬VB2JU&ϟV,3^oZbG`tWSF4'`w2Xeux{[!.BJއȯ?Wšb̃ K7 í(%Eܝv瞽ŦdNGy֭Q&pd+n%jWQϧ5MeiLO50ϾѧYb9{=DOaΫcQ:Y m6KK##PEeL.Yە{K?vJB%x $@ @[$ιKUu!f2jU_#FC''$G7xyح9ҋ-.cf<*-T* j=L3>PֽӥdCqzR=wAS5"\_O(d\Ko %nCq(ʸܕ}#ޥQrSaܼXIT_}R%%yѽ@ ,˺e lc6 HV+zg#\ʾS!@³u1'uMPbVkf㟤 WVƈF=2H*.Ob`fmȠ}]YMA=& ݉,IȸR/5ʍ2KYPb2.> (`ظQ] |բO8>Ɗ# 2e"I%GB0Mg:Z>E9[RoDiBNo\eLB!\ խKD)eX_*Y9sz=M|k/mWdNO:&;ܹ ’19åܿ_JggWJ3lK(;@Y͉.'t<¹> EL_TQvט%[w#m$d\e#NJYfrƣ`1YM]n*~I<7ioSN=;ܓ>n\] U IDAT 2D*=Mb^C^!+kty/+UEImxyz}+Uؚz=6L9ù&qĊfW {}4ӻ5Ӄ&=VwnbZrLEI4&Fxۍlnʼ,GyXq+b:=wڊ ٷ cAPB[後fNy58"<|47/м qWʊD4䉓/#{d u/P_ /t~{=%L*`6O$ä 8S Rͯ${׆M˅t}Mfsqv9ߟhf{2VW.!_MSism=LD3.C̔xQ5,H'+ݩX~xli+.f6Hvt{(X;b*o5.j<|d`jQ/pķ~xrp%W%Zgˮ{Ǩi՞Vա^nLi Io`6:MEyEFαcepKZ+&KhB_Ͻ6?|JCxRµX]e11%eCK,Gn9-N/N_̬Zyk`+tkZ%+M,vײsmmwQhkJ5PaxGMQuF!J Ss:3ţÏ(r {/fvNV1J5H&R։iND`|;K?}xl6̂9Ubd$ KZ3ݚ|>IffB̬eoN~ůм[ӁU;D pmSq?&@AË#CBuNo?9Q[&+mRU?3{zu=MM;)pp +k8_mB"?q:q!݄|!稜z}YF.w+cKe+"w;vk"Be9$B}=6x\K50D#Hqcf5*NU4Ifmw? ωE_~e5C4*9MdvE~KyrS3ޯq: дyo,`sطHF56cQZ>߂lNfSo9sJ_Jվ}cΦhjr M#9'=}Sݦ[P3٥kzs'|3d.$6Qj^\gݗI5ξ:7VfS-go-Fo6lRfM;@BϖnHK+_*yTTd5vnhfjd QhX޴ݼ.=v:qY,LqZy3}WpX"4,pO\ofn)S7 ΐcii?n_S(?oXl%B<7?OkJRrS V$[ mΖ$U% "d5#Jl笽*ѓIHK%gxEhUKACI=ѵ1v|[$+Y%$@ ]]^ќ(";fլHr1{.)jr@wKbxAKaIHcFu]TʈK4u$ fsN<$7KrioʇQGW̕3,2i>ma‚Dh&y]]+7-n 0> M!YP u>~ kۊ?FZqט񱶀"j#Ype=eF%YN\  B)/=.Z[Gt!%.0;FCuom{*DȿMt~(+u1ŬXV{=LOءb=HqwҮR""&z976TFGX_+ï5M?[40)>}}1-Ѽmh KwL 9n3M{4MeiZd|ȧN!bC}x3xێf4H{۲cM)I!ZZJiUS?i+_R;]W170,)6իvfF lřc'kp8Nw> N4019 1f>j $d_Vz8*g(Ox$}w];8gDVJI[*r~şjO޷mOɸkE^/9sYY^*%6SL=_d g޸VC8:Erd.()h7o\~.y'ZcIO9n7 q۟*Ϫ}=5ؼ\ull,-ua_&hRtKepkK 8u**'N4/)GUUldh* Ӷ2gi(34NKjzKTWoq#{Ot Hu/26OUUD/5ĩ/.g$xžmΊ*!'{&&yf.0wI 4lRq7חjuYuHzXs!NuNG)J|p,x: vhti* Y9J); ~"2l,OrֱW؈ޢNޥ 33Vsukk};q GTcǝᇊLBk/e'MmD(i=_;{KF,dT+!%5|-F)jIigfC@'~nF>:_~)dx(Ρ-օ0<FRl$I4.,Ggtƅ A /XⅧN07/y]Jz}yέ2֣(?~&r :SZOK]zAw,m$n.ѼTѱUG3O?5jU_?x<\r#Go!V㵣! "޹b!ك wV‰Fh(#j?HIT?7=6N;AvAH\|~=} u>a=GO깪1m]TMC vĬɟu9=/ްa'̂ 5m#RA[tƜ,lmqxΥM\?bnQ--s\=S5 [ɘOP \̭Ͽ̣G"ǎ ~H\2izj Yٖ[RoX\7?:V+fAe0IÑ7 -i*y_bޯUSQ(h=wt3-QĬ93ׯ7|(89s+*jcp<,#bj`6?֩n:vKEVrUAHk'7n`yBkkXهoL7ct'%֮!dۍ~p9TZc7 8ْIJ̐qZćϣu^>yz10]|A~]=~p_GZ( Dv_w*'9OȻp"SSP[ Ǐﰆ-=\!SFJζ>9ȘU-%1l]}n6\C؉j3b~34Ucѷ(W*5m( T撔&#G}_bH VبB2??_So UPBG(st!bgM.\Xq8;~v\`;7^\RXm1淸p<9񢿿===9~07O:+/|&"NFr& m/H'5ߏc07ǕR9Ç?ۼ ~iuk͡fF@pU΢&VM':fzя-A,cniŎ.~;u~[/h^^}wY[Jkߘ#?ϊe4`wv7q0nKf1ge"b{~vَg<=iN,`vv/}c$#[v_UU;W=60P~l}?ژ٣/ʎlTi6^J4Y[y'N4Z_O&pt)@'pFȘ{gw4~FHt+&:\.};~MhaYwUk);^ w\3ǴG5U"Ԛ9<[yȶ:r_jgO3<֩!<*wL] RX~Khm<3M_.:Cyyw")~티utLM]WwέwOxW=RUVViJx~+pNcz,5=_J=zI=wZ5SS7OСg~\S) $2O>l^~)JxeZ+.(1]%cmϾ~=ŕ:#unetD17]Raf|ZbX\֗p(ͱhynYrއj1j+39seS=R2LXY]U{"l( _}?5'-34nځ p9/"&9Oqy׾[iO)]rӭpXbqa2A<_Oj*JN+B<g łNAUUNPT* 1.//ZB~[ e;˳L. x38wB끄wu8ssv5CnI儝 ɔWۙDۓB|nPm  /!Eg4w]" "= =t}|\Es'[IYFGQ9_ ‘#~l6gm?Abɧ'u;9$֏o. "=];Kq/X*B"r>TM=q)(*7kW wCarYW P:Fdp%gܤ, 5M?*0NMMmor|=G$u~q`"gϴt%}¶Ln+)v?}g\ױE{QsttƐc9~&V^?{Sc앢G(Ōq+灄]^dvʥ8W|柭 5Awϻ\ff۟,6[`dMV.s4XX⟽N$)2; B/,.=ٝDۼn~m` +lN~Bv[^ۣ^7vUSSvt#/?z'ߛ5^|.⋟Ѕpwuﵻ9f^2Ϲj9ؼ-;yX&2sss[ޟ..X_dscI IDAT~ÁDj%3~*yb7y޺vBe~b7]x6p_j[ێ3VM98Z{0i1dk>~"[ꉜ+M4O{{[J 7! qaί+MǸ84FC9A0++9DqTN(+\.uǼz02dL<6Lik*>I@3ϰiccxȫ#SP#PU˔j*nSQyL$7M4|JaA uRRrě N7?ݰSx: YIR$u.S2 PU rd$| \L_5y@ W@S5B| H6+Udddeuv$򅰸,,XnN^V/"k4sa݁+/1f;j1{U %\Y>jX]ݞ$`Hg9FY-rzV-ўcW);x~ S-x3;mm(U>E2ʷ#sk}c>,L&<6ɔ?V i*%J靚kk]]:45M_O)Or4.^ƍlIf /!_;}.Q $|YtL!|A***{vas[w]NkϯXek"ϕCrmh J|S>7&H\./C,qqvg݌ r{5vJ.ntt:JLY>)3kԮ> [$IAe?%rjm{O`e|#JkK ٜ$A $%}Ug5 qọ̇2J7Ӷu(݄6+%` I :#8Z(~&oXddc/ Zx}.*M`BKEאvD)s֮R")]yIjzx2%Ckx९ -P*؀/X01:{y{y8tcɸ5J̸](+mza1%t?N<5`yDAxb؈nNc~"ϟ) 3檋$Aoo84TlԈ5!+aUfL?%3U;Þ=DRv'>[%K7ifE0R1jeehC%lwh6q]buE'3 U8޲:<6f'(ǏPU/eoPXt|7"2tcVAx<)?I]e$M|oe溾PŒ[9Qc%! e@(GXáT6{2˅b WG#SYcoqvmm[bJ,-'r'F4 9w;>Feyۗ=˥kgѢՌ=HIi(_WvjĈ2^褸0&Ob2 .LSo/W?׻{=˗[)\ːu|E=:1x\x r7wt70%bњ(~Sʮ퓸026+SoʇdR&<gD`ax;RL:rR:;erP 6oJn0=zI uf|#n5;zqG vJgfhzx|L.Fz)zy;7;߭5_b:N ɐuqFFdjjDfU$bi҃m+y7D}xڙW(dwhF{ߜ޺ჸlm?ɰl9??<>LU2Etro]]wvȑnl-ʾ}'Bξ463w2g'kcQl#gPPYWڷXԥo*m#Wۊz8k ,&LރҒlTMyqQxmCbbֶ֮,3t.A1*J0w;3d-hgod[o6g7aX T/|.j}*tuR5ً\<;A[Y&p))=Q۸e|>uCC ȲTݝ>{{ o+WEEMS /wr08s83?SsQ$0@?hrŗ=%4SEQ(CC"G jquoe=U4- eae0+{e|݂(Fy~p.C?8z Uݶsy{/>w=ǁמ?t!߽Mwii:O6(`*&CTAD=BFX o3w߀Fr!N$llt>n $}js'sss{Ӈiqi/]{2.$m7{ޚ؟-K%eBj-՞㔆kSoU}&9DW82* >M Roa9l`MM[E#˟{2seģ:q82eK%?C'ŲՏ {nB 3:rorvшWa%ʴj4y!5ڋ47ǒn<= ~ @$uh2)ٲ?$q'iDnqIoәLvf'ݙLwtuM7u(n4ءE,Q-J â@<8?@PtfwhdK9<ͱ^C##gٞf0cI"Ë|2Q/ ˨V:L,:J&!QA9;Cޟv/aᥗ Bfii%)Js{*Ho!~ F7HTNjr14c@QAJ7Dl #)xG^k9v,T^ڰUULFDʾ~ssl??rwJ^i _:OEOE^르ΟSj2)?EkCTWSgƽ.!oc~܍kronF"8K*v)i]w536JM&ux:{3sc~ }3Eo}g Nߙ搣>NrMG4ee&q܋9^5'ݒg-qU!!%7,)?L2\aWM x9/LAVy@ڿm?&2vH4Ç3?+C f#v3İ[|JK #76Gf,I.&ye lE+|sDF~Uv:040xx}zܭ>0/`zR"ԸGFnAE}NT-9Sy{۰;ooBۦg3ù zZ81zUT3OvLɚ %ys<<2K/{ݦ {Ô\E7}륒|ܘsz ]u/] U"ŋ[YgSZ/ʗoЋp5YhT1oy:q:{Ļ c<7l( CC !0:t[b0YB8lwCE^Q.N3fhh%[^/rK?薺91,V%72<}"`'z=Ȳ׻E03:fLy^S!-+`8qY6ygA/w1Nczs~Y_p8@:?bٝBbp޾<3LL `3"ˉ}ً5guN՜x/RiW'&re]'2*apzm_lm=P%o-]s( ;}> @̃Ka"! {.glAy|]  ȻD< vy+Z㱽\9ѼyuoC^Ly_ZcIVn4܆̦%ҜPk~yXv{0GgU;A2fċoAsq?H qxŬuʇϏ6z ʄiLEEw~|ag>4[F8Q'/gΡ{"Պ ?3 x:w/y{>/'F}kAhhhhhhhh-?NH&cR[eǻhiƿxqwI̘Qw]n71m$eyk1u^.ÁzNSYIC ā8/\iݭ933JFŠ6ĕ*]&-wmوH`W rMDi3dԁ'":ԍﲌް.,AL(){`蓁tmDMQwȲa"yy/| t _sg'xz$؏y _Xǹ2[b!׵syy8I[\8yVbrDZZh bxU!ƍ۱22tϦ[.]fv5:Ů܌-8rQqZBJ >D1sxr݆#t/`zL.oc uIZ8y$:Y`1-EEs#ɺi[=tm'(pℛ6ӴZcj20fDM7+p/PQMzM>v?UzpLOs-b2˕>MDgkef% 3k[FIWң(xQľWBn ɹk4ό 9&rqw[b%{# =_K[琲n s~*(X2mm>/̒Bs=O>{c/| ˅w֋e%p>@' Qw>bnkH2$-jj[JZOclޑҾ4,FZ\`3h۸*,R)IDɉg/wv.07ʝ*TV!b;7`dcumD]Z|m(b8FqJ6bq?eb&;l*%UUB!w&Vd!Kq8]:+X`noc:T{%|ݩ%#g/ka2d톊=w}?29> IDATn\ (8M&#;^G&ssLgY,/ EU 2Tċ'Z{Χ'(FΝ"9xih棣É(֙h f{@]P W;0O!&iu!Lđ]D߻d4|ٌ(_5]1R]B\g?GEv#IRQ( [7,k}PGyKy3CC \^OlZp819./cN#8{f=1+P/2M+WUuhv6D2~a0rrb]!{5Z7{"aK*3$ׄBc,Q.uȭ)d*U=G9k!fzf*I|[qX$\-h㲺R~x2,3^UUh>_ տL,/g#bNiׁӉ5a \0Nkkj uw#'tua3ZcgLt(xg Bdڒ@h]_?%;yc9M{d1Niu'SS5,>=_neg"1eXC,|ߘ[FyDIhjTxs})qΏݣ3&"m9{˭H.'9(uVJ̎Cce=}<_"8u\edN|\/,[UQ(T"a6֭_сoO׺ӣ <ȫ˱#Gt9CT'B{W;:]n[2^^8oSzuG6O|`;F]۫$sL P'8?vqUpjQnT21{CPSԛS^O*E<[{$ĕ+x:;)`n~$2+Aq{ A ޗQ=׎P~X{Q~__.Ψ*L噳P^ |VtmϐdYsҜطod yDJ&Byz5[gy6\V&'XVbu5HIXDžu+;79:]v OЀrȬkQ/_ooGPUHKc2S~= هq@"©#ROS9l¢g/ Z9)ǎ±ZNG"HFcFgnb#q`發6|\F~pOUY{dgG$ꩮOF4X QB j&项;Xiۻ7ᐘ ME&GJiEy?׉Uwa>%&u<ҽq?sI]ԴBEԄD]vjj&Ź6m[gifM;$I?o0~m3k5\\/7|HO֛?I}".2"-f/F:4ygCUB/va#3JF+t}j7*>V&WxKa{YZ^UCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCׁICCCCCCCc|4.,67 6iAlrഺPpniwd=2N5C%m[nl6'GO,Xl"LL bS7CCq'fч TL5e`h;iTH<rcf2U]vjEoH@(HF#j2|Ա(B!k܍~7q86Do;oY쪢tUŰrJ-livAlEQ6\!E4G3MQ+R'%.p33E$͚3Ij, ^h}"cEU3+ۨ2QT.c%.r*--}q(fK_@d;tC_xPV$Ĺs\#feږrLWDJ6IĥPj5QJ# mkzoPʡ觮m[ xvJJLXOӅex)ǩ2͚+%u~ih(#>QoxlXhm ~Mm RX*N//#(hxpތ6 qwe$ĕ$2͹v^Kݭb'-uc-"tW)8){n@R̺Wv#uKbNlHT#;uuq]ɔ^eQ2rYV5>+f*&LrwݵGhn6kFiWXR9qKjܑ.4eF(+k 9ĵ{gGJĢ$шI9x܉úkKo`(ál6*DBCAR>WR:[3Ż:=Uj,!A=VD ࣽ}slu5$e09MHVe˩1 b^q<[v [ySti]K窙""̀uU?dưH-:++= 80Ayz2$F#Ք18`m[`@i_TSm6[nVN^N++f3O~Yu'h$U5`3f33 |Rۊa̴H("`}NiRwe>$ Nŕz}|>f~uⵓVqtͩjRX[N{ao ~m@ jf_y6n"IKP_SSjDhCov]+ ۍ2-e{ʍzH-jLYKyd2q2D/qfnCIUUX\">ZQ~/FN\XE);Dr,SQʫntC=Cµ%ݵ 9|E-NN^>gxp|W; <4Nd%Br8zy luM4ܿnɓ!:s}2^le1o|}_a|ڀ9cjD{{oΠuZ(ͺ@"a/uKx096՘>ۜPHGٺ!J%$WhGvV2|rr<$ M7l:!obAS$p(P rI]Ĺm|}iH~<^P֖~Id':7[5g뿩TÐܡ{y{l*ڒa1l67JND~XNCq#Zs`SM[zΞw?hj꣼egGEpD\F *{*x=4JM8JsFﻍ,?|]>EQ5p=<3arbr[">꺞 q.gذX5F/st3\o$^hPF*^r)N,8UU TTclq 9 rPȍ 8{և$rlC܆@(h`7_Ako8d4 z 5ON;$[? wd q$/fLFY5w|ɷ饣Æߟ*VWx UνU;x a3(oi^ԋ :3 L fE"wɐԧL%~ɮ.L&uuVV- TTe=< p7\H|_Mvfan\/DZ>vyg O1^(cߴBE٪N_R\FLqW3륾Q/_9S9UQx",h3ZǞR *?xFOQq\.WNLsd*8M6x;jkn]z6笨*w=җ*h-P.I$T<̽SK酜.g^1P`,.H5}2 xgE+*(^m iXt:N޻ex=fsU'04t LdS;Q ng?BqG= &'E::blh)zt4>ϟq:5ӷXenWtU H5F|o'*wPFc)7~p6֛WX` %aaIjQ1ZZ߇s U}?}BͪCK"jDPH+&W{=@NܾG`j2bmAMy_ O'v4mKJbM8$7DIK-8R ,J+e3QY&N1>>MUU2W%Sh3X3?Iajɹq2q l1֡g60T$N^@o/VWX¹t!{S+ :{OJʸ4 %ΦX,r_^sLSuu*r֑(`rsn[IIÐߨQ+<5N xmuͼ~"~p~ f[v6X t M *4U;r% Q)Y4pnD1HL b7֊%&1]6;ys?j v5p'i;TNLNٍNGD6[LnaD^Xi2ITU0]!r./cah^;Ĩ9Nl%d0vCc}*x^( Ř6# S[|©TPt%I⟾]|'?p x`XfLl1%+~l|krSl= t'9VJoh`rqxFtzNr|YJf z5]ׇIRёiHn6#`iMl%8ƒyn{oGzؐ;n>^޽ւy痨<4_%^+|9ڏNn:.w(E0plyMO=k<ƾ#vo_p=&cMxs(zpstSPN{$).m,սhes@aW)6<  ήC6_viSkK~]-F([*;`1ܑއL&yix=2 Q|Vk2!u6S9} 6nͦe5fV# 8*ni, -2ZG2lC<^OꂋI:rLIUgϡfN?{^ W+GMWYr/N Jya!B؍vӄH15 ΏG9t)wbzJywgJ{kxtt{KoBlg$m|_Ur\BǍ.^/%`0m6͖Wu]UƢ{1=zYRdi}( ~) ;ws鮪%U`qxc,/ Tl^,^gDQ >8_yCCx񯬀 ;dcs6Dx/344!ذ7LI] N,8E!03`]\DWVƥ@MEm|OUZZ0nezՍq^ofgje~}lVp^fxN-cHODQ8s !z, {Yt/2/o+۾6wqrP=ӕ_pQ%T%%3 өӄaI_>ziW;30'{ޚNkF $OM9TK4z}:zaBH60< o=7Oif r|?  xaÇ=k&jdd(IEOUf|[0fn6U,sA Ç'x);ZH*Bԋe3-vUd^04=*P(D#W^~3CEZ{㍢:,4-r0y=]mGZ=jA" ^DOmʲ` 6~:+$Dñ~XpM8+/" 9r^ sc7of0;}l1iܕ{>2rMC" z*2WO#N1s !x(-s({%;sƐЭXs3Np#>ʰ;?,GygǗ.]b 2MN7g#af/lo3SQCC1 NͲ/b۔E; IDAT& 10ƴ&&2b/;l\0syUUfI,#>â׃ܧ[Xt5޷CQ|Ҋr/2F`Ky(gRv42/=aI7|*+xZXdlf~bcc23zt t3'Ìope+DoO$ z{P2D( c5q`zQ+k\ qKem @U7uds9'Nqg su5oy('5X2.Qd("KCKT wy8OqA \֣5],qx޸ ̥yt򯔟+$ "wW p8A[6?]0=~!0u%%D(( D9_xa/g|"3o7/gNZ'}N 'wQwUUOY}_^Z<>l5fۼ^3f 3aFno_+E.dmfgx1\o=kL?{}hawcӋ۞G3?ڰDY&l0rjs%fVW]2 #\][,j' U̷eZ[z^>ckl{A6^_ 288HwE QӵW`ldbapzx<;ܫBU\ 89x']׾}n7>,d'|}8}4?{.ԧrxx<߿߿??'?I***g}Ww^>яr]z-}ŒwyC{Doo/ |Ge_,=p-}EDC $R7t"R[%Sk+D۸Xfiͽ[U^}Z&'E^v4B .-bc#ü~1|hh`sl,A%sKr(oI.gF.vy=>E)ڹ8m6i»vZVFu5zQWF4b~N[o.4-b*.//Н;kj8.WnƜ[#:& I|Ȳ!]Mϙp4 ޠq#%DAD2By\΍ג$8~`FiFoOW]H~d9Ml=\SmzIdS<Bs.Clb2ɺ(Y\whm松Y>a2m.B2 Ci>KmxlWUSȕ t\+fWIFĕXFW1JF$8uC`DA$c$7oO~uFN?nTbV&bTqA!ҌPFڽy,Wjd- grn5G?Eԋ4M.P!b"90wú[1 WDZcpzD@n3e y&Pxou.Y;[xgGGƞ4[o; N 7KVVMk}vYnlC-"l%F.Ǣ,DAL٬U??QjF3 T?:k#_pBX/ye9|V+ŒoE%kSDZE|r%7.ǭk]Qݘ+[N'va3 rc4J[{DO_"ӜѯZ:bJHƙL,z NF~04L0rXcr| *3n6>\V'J;ŭlGLWLs%irB)/Eb>f)÷Mȶ'Z=UD1)ii^~jNܑ"N>wԈ[,ؐBUPVvDxu,5Vǝ-63Wk?dm7*u5P$!L9fgtQH'ٹa߼S/yYO2|-v͝;|jUӒfWnL[Hی+Pa/oQ3L]LOxuG̫?Dgg\fM ZK\ L10܌;[QTiX(6n_2zcV=`YMz|m)w1^Xoq9Sۃh^r d$Tv}:n.ۙ4EqލcօNCENg&N#Me(=Opv||:뮨1n;Rp`Bе rjq%$HW] f .jz-Cq Ä́k]LǧiqoN(tafzF ʲ5?Q ^t:Dh|޺{cbn7)F"Huu]%S&zse52ؽԎ{< ڙ93$Զr3EFcm<ON/9{ح=ab˱dg@::, !Geu4tvBzI!\t\Fq4ڭ_,ʒ{XYJK'n{U5ҷ,?r7R5\rv87oP~dPj7\\2m?aS>'k3sYVuٗo27+Y\۸N=~?Lcԗ3<440Uwa UZzϵS S5˸ wA19'K:sц.ݳX-ꦓQƆL nvu1~Ie\|o;gE囑l\'NlgA ezzP$BjreX\6wn:"KJ'6sXbL9, 'O5oT]_gq {kֳ߽e-thL}^?,E(m|wA|}7}=})goЃdPCX&Y;^ :ZRĝD6Lyv( sZY,蛛p.O0ҬbiɑI^* w|`Q9ncqˈjx,ta!Yܱ3:;S`OK9܋"aLX_2Zi{6JqĬr~f7^y_u񫔝ihhhhhhw(<󌌌pm^{5~2>>ɓ't|~~};ߡ.|_җH$|fttLMM/~1{+++| _`ee~LLL կF,S%:xo!pX,E,^Gx~-!ꘙ͕ܽʲ {u0$ږ!XIE-08$ µr$}8I{zrٔ'qWsfޞV{[eoMO4Z8mRSoH `xf >nRoodeN^ؾ5']CMno̷̓&%7/k0w;z dq brӭoX Q ݄6oF E1݈n.JN/E磪*+8=& ''a|L$-οmIH.3־iH0DPX[5dґUBμae3[nh^"XrLb_?9ٍ4(=r?ʜ<-eE$(jl a4ϸ^]T“aZニ:VrA:Y<gcHkq+4geV搛e'SWHG#$!Ovc#go3SABIL+LogjMN _h73k]nv8ͷictwM@UU0haL2'ὶ ѤSY21BJeZnmţ@[ ZZĎ_ k0߰PtPȈ?(V,!'2 7n`gM㩢LGz++ (> >LLU5`$20b(7V~_58 FK]5T]> 2YDޞp+8$CÞyeKu5+h[3t 4y5΀H,>DN"b~f~lL+t?.x6J7#)XnF$?4mȍ( fG5/~3c1N&N&x'D1ؑ"O_?Ksbg.Qz!D&&"HiGZTn]Gq]>\aJdwcscSs|˻?@U)W mq2vO6w^+E79}z_( e˅# O|ԏdQ|?Wʴg(TM ?F[I3g“a"@{z sD,g%dmQ| L:̋w4A4Yb> ܮhNɤŒ(LVV3u4o_ywr5e$I&al, NAMظ&csH1Kcb9Q1& |b Ƽ(6HӜ:?͡C$Ifu5F](VUHI̤rY&gf!c3ρ5Q_UM++$osR.b#%Y!M[4׮2}<ů=̤I#-2חq  "qw7"(Q#+weZ:Rycj~[6q#'F ^V50oҴǙ{y'9@y4j; %\<b]bt vvc'9{h3tXsAHre5KV*,xdYV 3BM~#pr߃ E:+$$HIY6֬|;;YHpftz74Nӣ_bI^7gycw~:͞:~-O$uybvbqݽ,(K4dR$4s7g-mp(|WenzCyw,\6o +ұ*_ɜ:v_3;n yL1dT8+M뵦jɱc>CSC%x(%YZ6;>zq;6W00(|u'*y-2---x{I{Ӭ^cyfOu>wޣK/ٞ?-w xz8o[$|v(_~N,Pd.$1_Y<Ϭw?ScE["tBT|6=Q GD#\I]!XʔZĩi|-]$ɎR+*/ ̱#lu74r{޶]:Hln_5iWۆz+Ijx\sbw;�no9U1hK=2?ׇOq3X+5jgG]@ w&''z߿nTWU{9.dnzv÷^)΋ \ȳ:ǣqU8[8U[hz7RWUyI&8sXR4_wSս˸]c0F7P}O^fjj+~ahs,_EV{kQ\S˛4=[Q2+NGs-Uƍ^Uyϻz5ʵMUԨy;I 6p2=oZ$V!K4=ݚ7I`SQAMr08AL7kiӧjhޞ8zHj\Φ |Dֶ6EY~=˛.aC{+}oG{݈BI`eoŧ]Tx* ;.+yM&s\;Zimif< BAnKr7'O8)Q%!WOŹgK37c";m&czm$nV]JSGK]Cn ҶS`M;px@Ʊ#}KKjxoU!w9]'w*_3G7ii!_9h}j*O1g.N^d{؃sK'ޜ`g˅ >0!RT\3ۊ,huy3Ό=5oXJw` ۤjAcYG_f1GTONr(/2 V5=9]GfTm uUnߞ&HA0ޚW~iءL^z)ۓOċ|Z>^˅ \عm'{GBXӛ73v$].7fSӈRZ7f;yT rڶ*D'-_\SQLUغ.|!' 4'lvsi"}Ag&}]2$JYc nO?hIzpX2`۽=N=.жcҽ޻!c9?|>EVV0iݕYoy{U}.-˼p?%H[ׅ,F"Um mq_B*eO &ܗpS~}!EMk ѩ([F}qs]~:Y$3{=٩ 4ߤ L.Jt*WˬL.y;_*ZuWin |sCL+7si"==LۦM$O(%9 ]in4ˋ2Гɓx<[M@~dQpP8UU^nߞw֌6[x 9YE::X^*VSͯ~3Bo{aTo 7gAxǥ~jiWqx3T@MM+C g=G>H4 [=R5p*qE^u.*z<[״֠T qA8,{ޒ1 L;ZrW}$5>tW_gU&qZ tWWilCwt*w^hJ4":EܭE1Wf?i^bpckOzߡF`fjG*dnO'h*Q术lb).WNۯ W̎Mp#9lE8ӿJ!u{y`S-АO?uTjht;[@Y+gZzr>zr~[g3v2G ,&m*4op~.x&].R%,tԧ'qhZb9idɬ"N}Ó6c57OwrvH%L=&OV6?*!:YbgG+p8(c&@|ʹٴBHi=u Umlllllllllllllllllllllllllllllllllll6qdccccccc{E9)`/g-z?ާ?iK9y֊w7JzlTucvP!iPTx*H( $NR糾XD\ ` ̽~$K8?xB2 2uůJj__yau|"PQg*  D#RmkB+KwA%~3zkE`0(%GG'rXZ-v:@,#Y&>S,P!ӭVXYs sC XYFY@c#m55ϕIȌ(~;wiБ$Crϐebct~*La i-uܭH^t$Kt4#$`z_mm14ACDB8M6($"=3Mϕz;IPkfy˲l`&̌]IYd8Q%:*;Ht$L7ëhi`qd(L䍟>Nv# " 'FӦ[-m{6ԊFxaU -Er9'=7`4UME"KE6*p:_˳) |zFdxX$$ t1 ٹ^EI||9]WnM~%Ii+Whko7m+.&-TTƪHK%͛V2S1;?#)6Vj~n$>?{MtI8Wc<hIиtfH>?9d8CA \VbpRn?uܥbu:NWgzmP!ZZI,)m] [p{z }ڽKN|%c WM#A%Q;  ِ"'%6lfy^tVK8 @FOZ\({)_\qϟY[ueZi۝9&  m xW%{sjOӆu|91?3kkN7x7o2ΞAMzM35ςEngnWa:5v`u#k56|~|s%IgYq9}Y|^/ Lj%7HFVEk50?8OC L~8c9r\6mKzRE=>?c1“ jP]+B{?&3}3~"ìN׋rka|<};(L$k5lr֘$r M؇V]Y ՕwTA?? .]vmm~=Ϸmnݺe[nqe}Q<裬166fz]得(Mq[|O[:22C `OwL$I d*OFLDzE87I;H[[.2m/mUEJ:HafƜ'7e\A#=$2I>~<.i_ŠQ3?)rI HP}=/"} JsKw|e_w/0;1!4 AzQ<6H1KTY!03eJ#)$_tvܳYKsE:5ȀPUPr}^a <$I'~0ɧ#"2Ѩbؘ(SQQˍs ~2r5RR^}4\Ar@0TUDA e3 y7tt m ]{@0 :5HH.KEn% .R6 %QH:jƑٿs : {3 `i'Yy<!ž=uY[<^VI|FѤ9!JBA\!!܏+DM#86ʌP<<2O&9?O0.6R@YL9AeIP<53Ȓdk틂H+#F2Y4}}4 60[SMIÙ~S ==}[ %kXݤk $0;=b.UЀ&7w-CO%~ɮقn??"}ll=!F]-]H*:mv0ĝJE/\Z<*԰弱=T*p:=n$frjgf6oHzb,͔?o4US&ь>_>X^ɼ h̝^ATou^]G/r>m JD@iW-Y |,9po<9P?Gj 1r}xMUUEAeILhCs$KMa6}-R)1" !pH?n EQ1埍KT^5"p?~}@tC{U%3uqJkQrwf0VK(  @}"hN5NL,#pFDAwi TjGp8s>?7IPrg\!Ґ]DmN,S~ad} ŅgdT -Bx5ɢ#M2qGpUZXQ[Ae r+!cG %AMS 0B$MV)bwy\L"k<̏о; ɒD|mÇM,xE䪢@<C{ YF=}AnIK 7rVS,_D[M}`e@T"c3Wuc>dTK3ͷ1O7W^ r=GZ,(o'F8#Dr)K4M%x!FNCE[ȒiϩL+"YH%q1ġ{Yý9{(\O"2o|߭L+5!^'D3UMI_>rxNK:\W- ?YϤM!JeȎbiI[;:*j|u-^͋_4/w<8_D|&eg֗RJyC#IƗǐ+E^ Օ"+k `egH)EE EnŃrJLXo*5'%-;G4b.iγ.֑[f-ZpY>2-WV֖mlllllllllllllllllllllllllllllllllll :q8/RW_}??k_/"gzߋ>?55e۷[z4^usss͛7s:X}=n$I;۝_ñHN.9sdb)(VPh"Qzy/sc$="IS=jSҎcWX;fv8r?^$0u%mbuk@S8=Y^DS(w@$(;NM"+''&%% pn-`Qjwu IDATB^&( cU| &l,?r` KSVR,"lsp"r%o]L=\2cT MSY:ҏv~w oê?ǥZNEn=OU;}, |*#h.9̸˲ io_Η[|JJ*3!&7pE% ºt9HyW7ESVُ8GM>Ta| mW 67-55n4$\.)8_n+?4 s4*惲)Qݙt)Rɥed.3VՕ!iܻU&>:e0F֚tyɤTVn`RUDE!(%Ǩp0VzJj-YHDOK~3>rNS,*Rs:x#벶KE{U##]d꼘XQ? qxb/Ѵx۸1ƚq#z' #2X,[ۧB&&cu\^UF!d, eנd#"8t>Sd*T$4Uܼ29^)22?,ϞK,/+l\~LCƑX ٜ,&FGP ).<#=!\[B*ju<, *cj|fZcJ G^Z*MՙJr8JEbEp:aOC>i_T)"|\(Lqc@+Ykڂ&[$[p:EJQ*&Rp4/b=@IB;Im+؇ ($olL{EA Y goP-rwCP6DSumU݋oG njߪ3jg޷7Y!EAsYm%d..,,hh~=s.L*8n7r]G}j GY|#6Q,"2. 4#$iqCC!xbx FS8y/wFNgSYgjk߇3(*GYCu"7˄O x³_ȬNkK4^=v'L6/7˙eG #K=Aq"kW)Vs/Z~Mh!@b4}&ܧvgb3UE&s<^4S>Q ?Y;nrȑ=KL }?+%ɒI.RvH{߬cdaE>$L,nc/}eR[QK$Kjecǘ!B@FJ'?;ZֵSSMxtb26폂qH_ŻB-b_Xb*yG|>^|֗V},M*oL-[w+?IM7O13;,*^dw/tQ _fS_ 8B@(;Qm"9}~?BZR8nПZCy+4iRgwU0Ʋa YO[Ɨ-ː׏u].,t -,uP2&7NDLw#H*e]Y!k+UU+$' "7\͜Fi׏!}g9zlg߽Þ6`'?'=;CěF]I!Y-MlllllllQ+fKUS^R]oo=9ڡ$HF~5Ϣqڧ`x1gABYM5Mk?F]I=Z`Qķ:?שzs\00pT@L aM%#.`$IJmG}ѣG^^%A4zK>ɟ} JZK3skζ@ySv|DUa?MhIJ@C:+hW:P(h`,]f}8/2b;),,Dq^,-}RnDA 8>X)Ƽc5 ?TjU'$pS[8 (20Y``8Ƴ[EQp?bm2;|&v}< 0xe7LX0aVK%dkE[cT_ce݂{2ZP.0_"YY&Av U~ߟco%/ "ABŜى*1npiihN.r C  %M#GpnY(&~˴7K&990}^q5XzԜJmaTTر97M1Q0 !YWW_WYg~-FGGRwOangΌ3::ʥK_T}lpYF5ꌽꛧO85hZ.tvcK+}3h9Q_|٢J5jo((LWWV{ԕPUYݴk?cYg'(|T" ?y+}<+RP)=;KԊfz[Q":`껱>X!EcwH.J-*:s8X1\HtO~f9F U%0>ɕPp2}2v3zn|x:i-_BlDA^"gfnDھì׊)+8beQP((fDki *7Cbs E@(307{H.(;MXn+h)~qOD2={rKTOq ΰ&[9YI1yO/iMkYpƭ|e yAV ܮ.FSE{ %`/>?D=:K"Y !=R9G,7KyǙ_:^Z/A~0꜅]Cѩ}> VSi#30dmv?j=( wQvlӱ+T7̜n%e:kC M JuP} A]a7o;~Ne}6]Q$%y#T}$SDh,]eZYQKKfET4H_]!BEuEx0_ohC4o\02J?.3c.@Ms$ziO[3g'!\ATEQpL#й L{ׅE\r=-g"9`8& 9q74:i•#u׳W]7 n7/t:y&N2^H&-n}wg?Y{1xy<<<zz#W?f>s} ʡ\PINOI"AQ7oFP$I&<7O݆O.YT" (b˜ثVXz &RPT!?q+zENrRb0ro!ؘ4@ᣦD„|.Qd!d˖r]yם4{# 6Snݸ==4nJՕtqQI cD55TYM}(,v%Ke?=*̞ B$%0Ȗ1l,Z+iړzӄ&uۓuҮϭčڴuʉFsFF@llj-ts@ B ̞ ˞9m; {f>>syc:!rLNOiRU{̈́qFS5NcζF|u7EdFG#lmp@`_k7_% jCQI#T1q7⿠vbߛRoClu=,uӘc ȃ2flK޽$8ʹU΂f63D!MVWNԳ?S_0&:&x}^oy +~1 3bsP!]R.Ka$eox\.>sKB$Brb`3l\Z !!=M/sx–zgޏ˯|?a5XVM@A ߀5VQ ;a0ݘ(7E$Ws_'_w6d4),tH< !Weþ:ݻ9>c{E eEK' Wt_ZeXnS<=|VVVX;p2mPQ4{-QG*Q u/α26p:g.:7e˅6t-tHď߯tt #xȖz[B![y07խٌ s`>4Q{Q\sH~g؟q%NbW{#h,6[lBz_?䞻fϞ=k=߳gOTcm)سZfjսϿz_O6Z>7yv(q%'dUR℀f'`K]gfpmd67u*1|+IV޾LO53_,w3LQ=87o`Cp_1_e" %R^I#qgNv³+MJ(DSU;7p+TA>oD*0SEbD~C*ۅvj*7[*GJE {lKaVÙ8jMոt{v8_LȒnÁit@fHT ֕#? ]µG bBG4l؜6ϥ㎑7-3"ErITM#Lc)~C:cWS7yYdy.'@> L8=G`2wKؕ"$S^8WI*~\/32!XYX;LRèmB-=C)Jh,7/`-Zݶ8'˞krVNO*h|(ٓɚdl 瘓??=n\?β12|K8l?Oz.D)=!XYYa,"/9$< X:D?k̳ Cv=+([&r2O~8.sESfXUQXtoczjgBH ϗ%Jͯttv%Ef=ٲ} m5[tΝ{Xin[|+?I\u;#8:}ffq(72\ӱƒle)e1 IDATq {=r,}ɫp:ح#}tx eYf&:C̼ We]Nj‹@[v/|1'}~cវCDBc_F=Z/ ,X` ,X` ,X` ,X` k"t` ,#$Y2amv'e_"*Ӧۘ(4YaQz[[kjo:4 gï5b@ܙuqo3D@0aU>j_ pEDd26l$2Uk{YоU6cA:|,XսMZ L mvX #Yjbu uyx# L`p6]JQ\ mãMHr  űr>$K47oȗiXÑ&46ME#=̍6 q򾜆LS<[}8BB=8ftS p^ffaƉF4UCQ<^TJ/#8>s[~0ih(rXJ2Ɛ$MM *{d&GU䬇!l`2z$ʙjDDG]I "~={{Jj?*EEȘ6B<,ènٔȌ{޷gES5675w,bO!yܺdE;7d]"6GZe%67_UJ1zџ\Oi^m044@hl+忁jtf6!(J$TxO"Q 9G0^Dh,O}xGpߖ$@4`˕J&K65N9ffSQfMsAOĉӜ>SOo<>%:F}'O+ǎ*>,6XHj<ᏑHfh*o[MH1D0D1$`TM%8P-iE\fU:<,>i{q%HxM*ҕo͇9VSA1r<2 wMj:B`wm?fMJE:5UmUlJ4Q7N(N:Y\0RZ&[R&gVqJ/I6MzkmBXj$If91C}pB zzZn*.\`OvY5rӾ۹zi\ xqWeydY&27_,9C(P-;83.Hظ"tUWzEQsH<3LjسpvKL)yc|~m":ov#:Mu`VI2La+'QFc-WMɫ$id 1,J lU۰1EdY%8Bԭ,߾ʷlaee3^oGfHj@tUӐe~Qnx 54~''S}D?3=^=6%W]Q1Ɵ/ z=Lh`՞؍Z@09?y2W`v 7A1s~mQ]U%32giHVsGM#_`sM;)!{1R̾E>tWq(;w~{wqjlľHG?x8,;$K*  3jQSWS#$K$9gJq,ܝ&=UjjU1<1Kq>~}s'r쟯Mmʂ ,X` ,X` ,X` ,X` #a:Y` GEhF|[nSNޛ5_}׃[oW[)k||߽z跸{7yCK#}nLa)N8TF`v6Mso4[<8yn=oڀ"KsNEMx^ J6!N~^{﹓7N\ 17ׯᖑqYx++n[k()e9 YÕ+E͚:\:G4-JAfʉ30KVYZURc$./ӉDNPQN=m2aFü×nL͚A!#3vXQp8e"3LoGh5i@ܙu7fN(ေ>")d-M￾@Ce E@o%\ZOmDڞD2J0VlHB Im{/Oroݽ&RL/L%HmuX^Q>h!LM7^ ]-x>Q~+MgbWec=Z 3bd"K=lUFFlB])/\9o4c<-uQ,7GuC4Ȓ4)jkmFfwۺq MǢ 6oL\#2sssA ߍ\IY-'poߑEh(;p F#"N>v}<Z}RԀI67}5ei C+!c}}9{,3 fXT`;A,hd$t*{ a&oI/;#\Q U,W()VElH O c:,H2__~͍݀fa l/ua&KǝlkNWwߟ5 ȩj}QE$ȝ2. F"?§/g1\7tލ Z;RŰcAxMt]2WfIM䒺e)2i2cuc>ȧMx6]\Jk\ow4:VZ[[Zu2Bm\u^5jc˷sATKmjP$;eUL)X]GKM-o]yl|xr 'fGJ*YCM)knLnJ7C $D^"ow ꛻Ɏ1m@@6J䓗'0tk{++U{?IRa"Ȥ۽K`:L3DNB|JyUmUZ(+*lV  \!TJm~ $n7>p.qX 欫 Թ uu.졐l32Ks~uzݤfX1H<}Xc A}CxIo˶j:GO͕lZeUxGxue?(B3qVs44٘riRZ*8GV&H=ģ )5,L)/u갟s2k%(I*`>F6d<,3u%R4 x)0mEcμ eI"Ѵt㵦FWu*hfEf諒TM(ĚA'%a3fhSm{5{o,M:m5P5ȫe,#9)CCm.þ'N2) cfV PHr 8!t+=ch`nV3[[risI pˌ [~b6uEe&Mq?øߏ٘/"jg'CFTKGZ}eͧGn3{ݦ3:iH$4%6!Rnc %,!gWqm.GP2s9z-b[ hخ1tݗ=(w{zd"dMdr*8W99憉Dd =syd"Dq;;׈dndٓLYHE61' (>Ԃ)Hy;:!B{\ˍ[LK{L̍(XuhK3tE&XVscJƧ @Sx{({erzq-DNXe~8g PZFuoc_>g쯴6m`ssM,IG縿1$]~maCqaeI%gg ,=򽕅r@c *b0HWut*@0`J:rSMZ\Ml{o nw O{a!fq笓 ]yB{*`ߺǘznM4ffJ:X )C>)R$DA=ˎ!9q2;\qwj^ ]EIpyJ^\^\\Wq%Ny]9]( _,hHa/,zFhLڏ7} 6[3ZН(8K*5H7zNQ5䦹k/]e('#=c3:ݺWǏdBm()TbeėA)eA3IQM M0`_U jZiԌ?}^mͭ1f.OjNʏ^%MSҦ^^ozv( N.L| Qb5gӍ)?cPu: ^#v#Gl% ,X` ,X` ,X` ,X` ,Xn ,XW^OTߵk.BϮ]??d?l6}֭Sֽ3<'Mw: >/_WVV k|J|#fނ]o߾]A?Slz}}L:>}ZqU?9?)=dJO&udoi%Q=J\SO}OiA]?{V}>7dJ?tp^?3}j\I;ggTP&C'}zzrJTOw\[\>3/->siyY?x萾KK~` |Ѩ~vaAE oTl]%:^L&AW1 QAc~A}9cMu=Zu]9#C*ԣQJ b&{W\xuߥ!08j IDAT3/ʶ珕L%kSDYVSgruL`guT2_Ft̬L{L߄O?s~[e9_0 ghԗ3Ǖ=zЧ//\{cZەeUWWhP?{~u>Yv~Ru]׽A}hi[&|˧/44;ꩥ\43"LSS5elKK:l?&q {N:M&uu`@}YXZNO렾Ҡ\Y1>$ێcxu7SKzj9/V{=6/0y'}>}0rEsP_ZsA>^yGFOTR|ӧI؜s%}.lKg/6˺e}!=ls(w2FӄUԧ~gOŠQf㝸{z.Qj ɧNf dJ?}'{J\AuúѠ7W*ҽџΰ>Ut/>Oj2}yL*bY2u2ח_пO~_Z^:ba#X.Yj)]VckȌk/ꃑ/W}4Wc%Q}ʲZc>sP?k{s0}~ud|o3~p$k|E|,/ϭsV]/MsMGxI9R*6_?//u%GGEAI]?꣱++VA$kyM>sdF?O'ό̣V_EȠkO3 6g^ڹT^y hpHw0 \S&g_yɬ-Og栮Ҿ,ꋦ}$%U:8K|Go6bۡt`Fd4r'~м~:ff b\){='B2>} w9;GOGX3)],Vk\ MLw.vΔk6r^r횪Kӗ6c<$eN߿џu}0~_o&l]'͆uߏgGO?Ӻ/sΓQ2:tK%S~񈾲+g99O dtqZՂ ,X` ,X` ,X` ,X` au]ϥ` ,h4JGG w}tMTTTH$x"Ǐ'smqQN翫 wyGlu]vm׹\. gBUU---l߾%^u|Ml6]]] lٲY7_5TU{'O211n;Ʈ] ~v~;wq**9ru0::ʞ={8}4?qNSrEM&\9hJ< InUUoe~֙+JwKUUE (4VVQ@]I0|Ģ2~BpTIzHEEᛑU|l^_ orʯj*ʔ}uqSVQ\/&;f2n793|St% "1_7kfATG%􀏓0w74?!0)txEUPqdIBS%_pI_p8LCCÌ]V%vlXOY ^jjIR89MOۏ .3/8ޢS(k"p8V?^e2WHҐ@G,TN4l)1B2ݻn!Օ~nbEqz>HE'\еkp:oT>ipmdl w]PEJ r/p٪^^iF8Ara1h3{tWrlXQ;2MukpI&r{iTQZZQ#:!cfy=rH{ض'J`B)O̍"2I^9ICtﳛld,4A]aʞM&8f &kOl6[o!w?H5ķos1u%B݈ÆV6d)[Lc(\.#]GQz-UUSZ[MBHLpJ1F/ r)d ca [Ϳ8G h;7Yjg:ӵ\8 b^fD^2k"?NOyN9oщ< ! bI_'xL}!FBw)ct"e2qlrbmt v7ǎT3ٝ軂Ug{8|ٵn𰸦$XNYW+ɘ^`bk89XoFhWpGMm;c'q}\^z}nvںa$58yΫ,=σ?a:YS#CH# OTh0J>[F(ڂ9:Λ ?[7kKsӚ<{qq{s)U2ڳ֙ovt.Ižt4@ XFb1GU֕ƅ Gu?~Vc A3t;Z{| ~%'8[91V#IO:o@nL10t)/%ć$vWqWI  >[Pq<~-/4@-Fl;N`zFþ'^ {I*c}}="ssruuY\QUPMFְvt9p4}H y%Y"ClX^`zJ߫,ȷPU4%@ v9 a;_6]1r#T3(v7ႽCS#0su.fW"@ݛ89o/aJ*W{Q`,J-vccf UQ<7WVո:?~^Z2_ۼH2,IA+ _>ɛ;B'^"#.P~.N ΩTA 0s^2zJ!n3{Al@ Px7|ejX*j{~vn,=#?E[:}gdٜ.N~qʕOS5Y$"x:sxYܪlscđn|qfTM3ݿwdVcv5e=Ąh#uRm M:4gHSFRn7_0=˦O?qm7q2>M՘U8ۤӹՑޥw ]t[ ;Zg k•@"{*)`_O~ֶXG5k9K)<߂J*@W ~ݚ>:c 044@wwiWԎk} 2ŠVxΙZՂ ,X` ,X` ,X` ,X` _Y` LLLo~ }Yqg}~rr??w!b8uOdo۸gǎ|s{ ȁ75m_eN8>&''9t|3gΘ6nΞ=G?Qbb|̙3I6wCnx\E<ee3Y%srҿ!"G&duinKÈH׮.DAD%B({!8*}ύj52xoFo=tPU`29s4K@ \y9_~I'_#-*!2@%$;|F**N22XUF\eYÄ  D(AruZ(W:'/^_"i*muAD~_/FLdP39naS 䬡ic1TM7ۦWX h 9=$GH]e-#{6TM;{WCCugD"n7~UUy򹳸fu|kfrKw2:sit"q%oFvI Z7bj4$QҞn./ !ˣW4R7ryXfbzo$ rϑfd2bEK3TBGlHT/1AUUNmoq ST WWkZYd,F*46j>tgӈWMa?,DٰoU𷴰bәz \=vvNnL(y?8r݆ %ϹGR {EnR׭`\[Y7c_"܉`2G})Ν{`ƻ?i=Kէ[{Psf7$DU2'j^:qd 7(f>u e#>PUdY&!Wb+ĽsT7VjGYYI OX[]dNi9'Z'UT䬙Wacf|W4(=ÿ0;fw<š0DRpT8{~NanVWΚ˪~(dwQby;䖛z>x;Swqa[3(A;3<|UrN궉?0t~xcAZM[%6}Y<,.I!6ݖ3Y=&Nԧ Q8uˠȵ=%c^ "lv'$L3,s݋C| ]sCD&#IkW톡񴳰nI&#TWxiZۀ0[S5Jh$ ՟;ஆ67Ü֚aSd2MZd`f'dlʗv OjQWQn2.NS5s*]200g >o;-cEj+z* /L?í-u-uܵ.*۪MOqmq1}u`}{;WW}Zf!ckV* _8FCo~ě͐t˸/gl.'"|fCp}B"?NOO#cR衼,FQއs$N KTԙv; pv:wyPjk |aqx*xP\|]T%B=&8$ă 96svmt[y!P,S$EKvl2HaDNJM&KM:SߖiՉs;k]뮬ܩ3-&t&3M$@>iHlӂ,SE %۔@)<8&b~7ǗS Vu)./J+rD"ʘy3as_[mqN3&Y[ecbzn>CW;i}h@rzLt\x/zxdYl^vdj;^ASE7o9sd#~'??HXi2_ɸvȲov_dԅ.͝d8 @3?CϠDY3O'wR!ئL c9abj}>plg׌TкobW1Osش`_3ӆn"x\^q0&bǑcTV"ˁy]Psl!yO1[gBx2 z<9s+ΦK>BUei`C|7bsx~ĐXT$/ 8\3g}ghjjaff'xڴfQ~E_[59dA'!P&pCs4ӫ\M9q$En>dRĖuGR#\]x)bXκF0Dߊ D" IddgC;5k{-;l[Abp%)@؛zl'*:7:Eq]쬗4W6]=oLbZ.ch`%١"qFXI/y_m v{DgCȍkf^Ps8wbCȫ/LR_ܩ&?xI>~o.$Uļ=b7X䝅Qg:'m<ؿu;߉_l :Fu^.Sl1c<@QN'J(Oǝ|vg'Obiܮ=Zq=ѶMÿ~8^c?F b 96kWl<i\k{0EQ(,G3pߨqX`:ZXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXұ,,,,,,PYY0k/7oz/T34HC^ef{NE{i >* ' CQ$i'yt1O5u>=*qsz{~͊L. 3ݞ):..DªA~OiŒ 9 r2E"n^fALC1%)ژw硢xk>@c4Y`j 挭*.)g4 :t~ei_gd~7zbegom+M}L_y@܂d7i[jayk3a{ھ_L9-ؔ*fyV"Y=#-F-Mۂ,#NSmSNAO䈏,(Q;"m3㴴T3=Ow u&J-4 ?'q!ܺ.{|Ɏ I%3g\Ry2Dl_+ƁۊysX7!7Wb* 'i4;tmjcn˙3 t凐 T0wZ,^Ǽiy'; řݮvXSt,z~w}- Q?6@jB^Iɥp(E?^S?'{ 4 _'Znڢc__`J B$4ALJI Gߧęg Mlq>O_歅5Pl~ɓط2 +HmV &X*|2mm]Az=m*a/`V\K .},. Y~zq5%rk/3ᮩ!06V 0gq**L0kxI? ȼ9OS_oURJm@l%f~3BG^oU~:~ߨCVٵƆߵ8sI .YERWNQAʒ>"`9UÇ-7e92FjR&C1 V0c?X.U>SM~h*Īmۿ Y"?\=  oh {CA:[;q dDOrЉw =|YCiocCns!WUDhE|[]+W }M:2er+t Μ1;\S8~**pqBeX+Fg>+O#rܜ㯼mch"|?DMnG0}m))J0(gD`Qz^s;CτIk/,dr@ƻLR7坡 @#y,>姕|?,rK+Qww K;>k毕ݺ5 ةalFFJ:rOo/U亄qF֊0Y^n'u]"!jZf#_$hXw)oϮU6#Dgy{ΪT_׾?`nnk_x/˿.2KR+ӧq`b2FRN9B6dRu͉?%ܵꈍku!#lL[,m-*NQ\M5U;}Ȳ dy{#2bDQ\epFG/͓^ 9rڨhfiiI%oµxg* `-347ǥX զ}n; ǯQ]>;wb7>mr Gh({T]ũ zm431161jk;y&PwUfWv&N󱶴ㆍei3l{S%l4Am}7# FKt (۪N|`ɮnΞ 30exu݈{tl 4ʶJq3v.m+yyLI:~G 'CHYor#{Դ[++XXӔWd'ʷw`Mȉinn6]fQ45CBR ]2β o{~";޼E{t?Kg)/wQ.OSVf/kf&<18ᕩ K8{G릨lcy6>6DXОcIڲngs@K05--svNyy;-^e 9;uO%ڪ9wN7s~nVv=Ch{?fߡ9}4\TTlYnp;~L mkZ';8wކ,d`S5}af/.2rzTl)DM{Mnyk! )ټ9!G޲Ǚ4O\}}r}9Esw|5-r !fk0'C'.ҜEJ=w'Ѩ tiTU%F2y_~l6lvxrs3~U {%ƕy l~wfLZ8zV1sWFhJ~KɅiM,-JEy\e,M,e[6n9skR a^vZFhoOFU&ғ?kf&&&h3ksV Ah߾|yp=<8W(@(۷#Fy2}}Ğf8nBU6Fiޏ>ݞbXt 6;뚉<Ļ1.7H@rlmk+ahLᘦbs:1?ysqoE}8u4Nl|>TmK *Р{?fy3v;mUUgk} 9.N"02C?)oGsrjTߎ++cGlXĉ邳gUE3jѸs7cJn({۱f~?. 6NLWlcbZZ[*bۃNUKDo[β8+o;M}SQq 5T=y1ؙ؝I})q9MܒEKG"U4q!m=kZپy+}Vghl{䀕q/(<䟡m}Ο?C9m; k_)9(s53 a HRoڵhl|rCZfk$;'4oHad^UQ;>JutS[&wU˵kQ[ہlxHZۃ oXtvWmis.Ry 3]L'hsd~/rw7soRq*]xN7mG 477s9<ío!vю$Mbe%JuuD~*]4^z{fVܩʘXZq땜uX5nC~uQ?6N\3}w4R^_\;mL,%lA`ΝL,.y:qոƹ6/3hnn&z" {>FG.$v=A`8?wEf^Smۉv⯨~bo\X+s,B3-'˰޾[?̅GXgU _*D%&_̧?iXYY ccc|Sȑ#|__Z#TJē$Ife%C78b[+.W۞Ө*?$QT(d8 =祩G"45 ߟfv] Iyc)<-Re@@d`@ F]O|=OD"5>G(n SRŌE^\Tih(g&紝5'oqc׋O49TUQ}à& GRUB2>e24UCQzՂo^ˆf`F~Qp9Y:%u@@+_iJ?~mgC 饐kMrM!ExVWow8_UVF`q~*w8MSj쨪fQYQUX,ȏ@B)6ϞKIuke:s&DGAPUxgK(D~fMDvvC7x^7 O r睇i\)Fp".uwxAof&"O?EeeFQfg Hu}i;HQЉDad1wgfƖg\JŜgd{UUq\9k9Z,WȊe9&@MN޲o$Dվ>VϜ* v**`"xmdmI,3qN ^JNɖ毠(6_.5\TbAUPB!  p^TB3Di*8u:;N1!K&O͗O?e\.q:=n08^|9_b 'YRrZ'eB|E"> 9a=rB}L ^Re%rl9)qG$bgPV)ěUܜ/~6njQ=hZ;IjH}s"!zu W  $SX,64xJZo燑jZ8>9EzizOHG|(E^n:wCg s ` `6*ց:LM1i*+ct~puNNnʯ f+cwgp_áXj(S1,zֽ~::3B"qȃ4NSTO&ALMS9[yϓXrɻ@;bz~Ͷ5d<61O%ǩoJm_z3t@D #TY~jTYM9Uݒc4QyìƋTSl;yUT^,g1pu1/1D;OKTF4ec3,/rf@z{<pBÄZ|"scE$06~ ȇjJ;KVk݃#5vS@$87W/ұs{=&ַ^dk9;QMdtp~Zee".6_][Kk>ZvOA<'gLM Bgo:Ƙߩ*jtbuhU1qE}lQZ>zrgb˱/3oYREcgR bzǙWs+⃭a]Okw,[Oo->."VQdIO]_pWgZp;5N/*x:^ܗUNɾRK9nƽ^tnG(07?z5ie)5sh1 IDAT-`ԍjJ!'>ԅy48eu np$&u]zsq2(6 MXr(Eɉw9x-K8MM@6r~0[&bÃg/ߺ;j8kUb|HȖ;'ŝ-20rs/񂱊"󟱳Ź|0쐲Yye9~`\R0Vg5QP@TǂϙȒT,443^9XCƧz7ccH>G_UUN'CGEfgY8uCeAf_`1gجhsnFaLͺ[FQ0 IJv<sYt0N׿HEσDl&1 "vNz@*`^ݬ,C&i>lR" ne ZHBF-r;kidZI 8v8[0@V CC9fM -o -e_)(XѴpőAǯ0 ޿sQ`Q6y^f1-/V =%)|=R|>&''| [3tA$xdɨiDF-8]G*+Oqܹ[V$KĐSYVhmE#@6/ ȴ{kwu!*œ3ffDyx$"y;*/33;C`*r'G?$́nx_{Ah|:|4Wp*OmJ!{.OD%}IQ5 jd(B*VwDzD[RH<>*!\o^B}~TDd40k̟%pgl!:/<```33:L`|hbi(?G $t z{K9{#ᮯ/qjL5i`033 75s|T0_9LgkB10:ʀbXbIM=)?>'2d dT,.gO=zc/ԣaZhax6oG;h}V:H_P,V0U™H0ht1!bzh Nb0N 1}-;NHI:xy a*'" 8u("JeYc^? 3/@ly n7,&TB@5^N'8>V`Mҿ'ρ&(յv#>?wNM7/HܷY-uj$Dl=S+x|L Æn諪c_NG?v{e_܊6s[s;'˜Q=&Mp!wOd*JC' =KG`tgNT >.nvnvٯ{FƐ;ӂj_ײ򟕽?5}iJo|vOΘ;b~>@ւ%G~2Wyƣ lb,:ln)im~*&^)s^fǙ?容,| |xQ6>`]-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~XN ǎ _;v`qq+WȎ;oo.O]E7^M`Y{p8>d_X1uvCg-TB(tPhgE'd5-1 \\BQVWG صI+YU21΅&v U =,.L%U`` A,(A B1੢J].7%ؙd#dԞБNUM#ۋc-Q}}L*A\ܝ_;W\$=[ݳ}&K@3v[/gCv/Swoa(2Cgp8tӂ좝b6e1gUOM `ݸZ&=S Z[MG,܆,toʍy@+k Ejv~n} 8ۥCRE,ډԪ!z J!O˱?(CoQDV > ]m< -24ۛYY^z UӐNV2-<26_!v},Q@ZV&"ip'DU쯵6L^_GWM|LYAyɇ(ȲĖs{J5P #C3n>Fy;QLfDͩ d皶.n==O#z)+O?G}{.!IrNQZ)1{-D=Д%L 00H" ű\;*vWD@?m"WB!sbatٶMȞD[yoҳuYlTM#t$ʒd/{ ]dgZ'XYROv_ )k*R<8x0Wp*gZH_^fЗ׷IH@v}*'}D" 5J+-j99wG?ve,Hox"<;!0:8Z5ga˗M}5&Vzy\3飴%p龮rfd(JCmacN*>?׷5S "kXrQ[q?9vQhZLo7c$\QΪ0\Ȑ8tT,u < 39 ZDSNcc;B&v^-0/vM=a9vE/})!J(9kdlu 1Թ9vG ٢F:;lk.:e.2Ge^xc96-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,;aaaaaaaaρ_={ʩSy^PP߻]-|~1HWQ65ݟ>DA< *OOB(#T75ɗhZ8Y f(;W@q\ù+&Ӆ';d9/E#Q5ǓynT*J*"~>jix]U8([+t457ެ&+1@Q:CvYȱ‚BYp0r= CGT$)!$KOua[UK|aj$XYGֳ ++uT:*X 'lRBr8I%\'-IfgiznispWg۰Tt FW,gگqK5D"pǾDC+fƀ+CW8SBfOoʙè/*8`+ J_k[QY#K{<z#6_x&ffzzv'::TU54D(s'_ff Т.ITT1;E V]tvqzx^&f d!B&N"08/)WKi1, JD`s񺲂|aj|ONk޽Cib}Ro|Nx\Xf|X2oഄOșGK^B?c5=GY ꕟ,o4H p#>0 _]{q?$ MY6s'߰/gf'/%nXVx v'bZ*n~V]5z B4mv?.o-6yεM?Vfgo|x `vd-އhJMؘG)1;;s6Dkg+i E@#3=ْr!0@Bb}"pnEeIA$dMC,c¾nL @yKvGkEarN{CW)I>ՀN &-xn:/p?:<Ff:>UN<6yw:gng#{uec VR9: ؉FCI,fǢ}Z{[|N!e"~2}џ5Fw;5Ϡ%%]LՒJBme5TkqĽ烰[atx=]g_u5yEP]X״ %Bf{f9iLSfJ%}^$`9#:F|*NScw^R)n<OY%յ?_,0;gͲ':ϗ$x6t6\ˮv(*',#QӾ̑dU-s{SuZŽU&̿3!<h~^xcf'i'ypY◊%daaaaaaaQ"9w#j;5"8o,++}Pe==8 zzd2 FR$Dt wwzD۝oLL䆒|3̙r\t`ȯqJ^K(nIݨ@Q) 8 f}}S"\qR)&ߜA ,ܶݎؼˆ,I~!1. yV#3=U9Ϗ qqc]mFFOEeH;r.Ne$}8)`R޵u.,(^ rK&Ȃd / r=C}WBh]E?Lhncay cX+3:ZO۫w6L(ɀU ]G:9oO4,YD:7a`_ b@|yYųMO!ݬQsuc]L*K׼u|Q@vZd% D8|vʤ(LJh]]kέrIU@supQǎoR3py,1w'Q/16p; Lcԋ瀝hf&9K'T/(.V)b G}h8n[pvQZ$iΓ~]RQjk뿾ȳ~ʔ~a3 4L@C?~{&F}:2*vOFP(_ƌ/]$'>?cgz_U^U:P"BfnQ 7`gй?ً>#E6R\Za <Xcp- * IDATW]EA7̆hY$N#x06GŴSuT= Cg05sc_/YʇX`aaaaaaG?gOOygя~kbUSMjo|&;B#ޗ@Q ~8T{63FM" BıeX&Yc%(dj}[TM.8J[[Wu[{SuWWu0 JMev^3LGHԔ!YE M)1I"H4@4^2~~y ϷE@ @cH +QUijg9Ҵskf\\:*N$ BI~',]/[ QǡgDMF("4bKMB"skHmv{4O" %77|AP+" 66$DY3AS5z3ܼg]DVTMC`aA݁چw5H\BnTM5挹z`DXݻx mܥrfS2jγ~ IY;:< 2[>We4MEt9qܴ{;s$ͭ>Cv.˨n/2 VKZkTD=;Bp*oB!TrRiҦe4!!2KK46ArB ګٌKrhʍO}$>:ۭ2hL: Xsq!psEȫtVE}c=]>.inY^?>;N^g45Y 9X\\7G=A͜ZEus%FN/\i+R"^TMCTB!}MѨE|$2 3zb 4M5"uNOީ%O3uϒh2bȬZLh<4)>$OQm > g4Uc9Dˆo^׃H{}Yy u:=I26lO'c[5H TM#i^JtuY̊hidŅEz3z,LIpeK&# W\KqKUܴ KU pSNb-Cw>Plmלfxݪn Y |idT4S2ܼ&*-?+ZclƐO?])gLڰr.IhUWft o/~H]%R9"*ep{4F^}'$IDQHRiw ɋ<DU Z]W!+JLb+[4Pa+_TǾg[85u߀V_~qL:x4g׍eU̅ǯPcO0~:41A"BrJ>U%[*-~oiHC^]& 3< l+*'CLJ~YKCVH>WjECE!ɮIRhr⏗%kH$ ڠ\&MlXCq.[ (+̧9e1k!QzH~@*X#,,jdz}b#G\2"SWe|\XgאlهY@s2qu~[NIGDDA-q:̹NAt_ksZ0q|=,f 8W!vffŒfkRYڛL9?ׄoau{|rw[Mru 7'K("~?:Xil#I3ѻMjq.ZXm~q5-+uJ$dNgh?\؏eo,B70fKMl{ @$NqI9a;Y])˳ˊ jZ֫&>T5ZhOtA~P< 6A9AC 7󹼸$$vIׅF`bc#;YWtCWөDA t ꯀDQrXLYkߤ,{Q1z4?4Qx04DhY$f.L^y#U.r022B8>4MRS699תQ1+ǽr .Yΰ]a.V+ e(G^Z+|7o K"ƀq+y x9z U uי|`pL&jCGQﰛߩ~1(e|>3|9 v&`N2E6!!C~C45 Ր[(@n)*H*PNĹ MȊb7r]4o,hsDON,kbxKA!2^&2.pCّ"+yPd=*Lʄ[\xc1#gF<E4F9AKC|xC7 [jA#FN 9bT&0dN;/ @V@b{L?+r]/", 2Q,g8xanNmsJZaO&<9I{N4xtҎ/jAٙp٥ظ3Kllȅ I&u2˒3+Bp[Ϫ RltaSr8wz9l ^v 0tB3wDx64}$r.d5^ȫl raU CE8y}'iJ5@-10Gwk?z'E{{''N|O!:{؋pRepk+qWo*›z ~@,Xԯz_1l/Gro}/ %T+D&4rRB!2œ wD"3^b=hr>sTUy!IC AqmPIW%?<iQE辏+`[T,C-HyHX.M2mrIR 2p&:DJKM5cHD'VŏV`Bޯںo%|Lepat5 f&_[?g|oI >gؾzVt4', '69})}^8R[D|PȲgǒ:k3dlrH:)ʒӘ9&PO?¹|ɝnmܾ#P* nGV#ˑi{|>nߞcޑj*" [ur"{Py۹$6~}Q/ǫ^kR@ ${v MF&mEBEnCWMf<<=7d5[Fw1Flo!N5g%:_/e|U|y6~67~4Lky۸X>:8vlt:zyVLUXG=(pZ R^cg*ȸ[M, ѨCۜ:|eɁr(7 G?Қo^pM,GX{zW֫ZlEH{\FCOw[sJe4Hm.2*ǃkc3(},sl.RBW9&''esxK8toaΆf7yN˽+|s62KrB^D"A 0~Z[G:pWj(˷!zVKJHプ/~ \!;$65TE和aKc Cqv|a<QԇBu +үJDȾތ2un%:Y $@r]ݺ4yNFj&i8)k$P/$ɓ;X_{~2Qm\ ܺ ON'p+S:f|lAr*e \o;5?X r(X(dv8FMޭH%X2 vW94OJ f/z>V d.{+.%[#Z; ϮVb}$;GU96z!Ӆ2xF=@4ʟ* SmEz{a~Άnd"E&ߧV57]cN^?)!GIJK+'L:}2 =~I{ R,bYU+sNn_k3qvvM|0ÏOrQlcy9=Gи3+rSA* 3꼤S c΂{[jAq.+r^mlOtq[*K6TA33t) =w̬yZYM>v/Vܥ(QY*(uHo/ E'D*E +]&>()VR:jX`fU5PC˺ic5Uc)D HO3y'N\F3;7ƣeuc^ɉv 𶵡 p9EK\0yhgGn1K1fmܵO g⡍_IjI-~'}j1@tuNѳa 'f]$%2:dΜ><6w⸕3K\kyU%bČYB5֢i*RW )s";GJ&,##18bTwC(6S1.Hٚ'+VAZ4X?m⵪<:w"&g9=^=鴬KY/f42,rZViVi*L kN\zOj\VX5~5ҋXޙ*PbߪJ΁#(Vl'nB*/6 Ϙ_j>c2VCrVa5.pS'R`a}S_zA] ᢽ)KTF9frX-sE[jEs.qkqxA{%kxWc{Y_;S9ĝY r7GFl%ײ:8HDY?оǐ1w+qv\M9{{qصkI}9~"P/Ư|o :w?6oGEod߅ >l,U-/ӂᄒ7>u8`$kW϶~T)7YYu;M[R`29tȈ]f=刳1-Ą忥Ԣ'(olquU.ZzY5׮vQHn |D+뭂)ܵXO9)s6_2-$t n7Noz̭zV3BtjXmvjÆ m IDAT6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ~o۰aÆ 6~#8{,>lE2'}կ~ߐd:j*3W﹛ZOTD;aY1}ʛ(~6_`lPh3Q0*H]NV]woo.Cq@Ld&H-p ߴC҇u@U#2]}yfY)cqFq)d,,r:!陘篻C29 gf}F"r)6+_s=Qje\?7&2u|LĭX¤w"N#b6=KTBȨC: PZ~TTDZ[X=M0b  ŐepbЕi5A#5wwR3Dgdȧ \6Ry4G[)@okRG"k#Eh{k~Z{ OvH.]  /kU{ZQPg\l.l.{խLCy4c[WGt])H،oޙdi 57ަ3:pkpk"j)99f"8QSUU{oa1+-5Eh6Dä/q5jX3]DF+y<ƿOk]= z)ʿw#s/yUq ׮o?f. 57ng|Ue<ȐJ-q\E@!yߋ\cMNK_Mo ޙũ yS*P`"a_nӳJ:1*vdGzMM:k09Us$[HRɪ.TʆQMbY^5_ζw|^~VmrVC`7Z?ʊ`JDjG03 RK*-t}_+W_'鸓z;k>ڛ}B!ҧNLף# "~dfAf-k1R}Hiq:R@ToѲg98Ӂ}9K꾒xXL"yHm6~W6(I/~׌fiI&$R.#938STP{W,ǣE[Y@ؗ(2L_rSڛ܏y*ۧ/ "ɜoZk901!maV}rW{\\Wb1vm[G]&Qv* Q t47U߫OXؗ\l"rA7uy=*j0H(XgJfbTJg[z.,jmU*aACD8>0w3>-G]&Y٪qWP)}[M \.B$4qC'Vg(j!:-$V|՚^Ն 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ ؄N6lذaƿlmmXӵlelzp%n gW6![$y_7շ#8c`@Qd/\徼 ..Gs4~SU1s3AA82v/1g ǟ"52ֱeX`8ܐH3ƾX[B2(>.!$oLCh|o븛=ƐI<'ř݌3:*[δxx's&)2mnfcvޖ@c"DC"adYv3>MvKzw֕d) `:B4Dc^DS5.Gێ5{ ^@'-0u4tKL ~ ]׷V-S'ld#,DAJj9[j{Å`fŰQ,_":0@hytE 9DHQ+J(@D:d:)G&1,ɸ݌px{z/%]>M{̓:6iЇDG)aXH(]+ HC@S5Yep`ӈ)zY'﹇sdIU͢RpV_kwT] fgɲ{mAg]8᱑{9~i5,^ihV)U0 eyG"ZJi*[u[gmn#*~ΊȪ ]zRxUM%2'$n`KDwyraB##%.G`y5PfkdmS4֋F\j>L NvqFhu5(?T!+5#?1E4sI..eU'ʉlbpFDrRT1%0<WAbwgyu=|nWUS".>gv֫K y AyRz8 .ߚ$Fn5ɞ]ƮF ,JL?I|A'vdQ8g0jV{ms0a HBN{LlQr3\q1;xQ.䵚CIqn~|5qyU+8S1O1Af?.X]1K+w\3꽶b%͝ƛ? 촂UUL`MN1b X#7VUQ,)qZM2\k2ytxK X,OQCS5z3,.h[XHz,{e"a.Ј(2#J,s[ʐUה'ֈ4_ηlog9j ͢!"t]3*y_4^{ѣDQ3aSĺ&ZssV~pTXWx:((^Cд=. !:ήvq.'$AO$krT!DM3إ0u\0ѻMjqu_IΨr&7{?ob}RG*?ce9)C{;A٢aLȇ߭"d2 &hLtO;Ps>B"/I"D.:u*4N%vkNi%bU N]J$p-|5bM%oonupn]r4sx8Pf_}I$B!$4Q:ː'KXy̤=35}Puv_hyb堎Ċ,g_-P܀"rT ~o1T0UB?u: ߬F|s_|rp_Q%DU 0zv l^UUB*IrIR &&$:< NRbR~އ^ )z#Nc&ªDcnw$frr:DœOMX5/J AYƦ| HA1{U+!Y##dʊ3Ofap&cM^_Ȧܼ]a.V+3lp~Kc:ifE#ܑ!HH 9(J0&̠jux[I>_"y2?V0 ۣ dk Id3`9؜YH&$ ]/]9ެ9:mxmHBhn[Nyʡ>PUP}]]z,x,*!A~xo.Wa8DiZN]&QpY-š>@]i/TO/v{Nvdm._;4=\eJV8O:(}E xJd4@|{%^O9Gj',"{~AlaG:PW ݖ#6Fd* 2 BQ=ZYeCkӇ_ n\*KcFW /)LO^L>F ؖճ*%Aس Yb875&^x5bl|}v-QQ$%>'uXث~KzU1%(Ȝsy?2 h{EM,Ih p3gsz_ y'Gx{fv TQ&MSY] 147}C eسQ`a2ԭT _̵yOO^Nf28 ꗜ]NhL gJ2/|>Q򽞬(-,ݾM8ڄZd GLT2k{~!UV[DzߴbܳGGeLfIGDA3}kKe+=;舄˓n!Pd:9”h7yPHme9ˡ!| RKKnr R;2$cAxI)+9:wYY% ll Z* !ÏA06?&ͺ]d@337np`TU- )_ +LLױ._ghe(P"ynC&>HUE(Ď>333h!,44"?,LxZ^}74zͲ.՝o,=z{|ksJβB!xylL?Ŧ.vŷx쫚Jp[71ro?G/43$3 f 4hYZ?_r-S'Qe{ۺW5!ًh.$ B!QNLVh@^S~[&'o/W$XXrc.d݋DH">CMB R4Pc_X[h* $: ?|ӟ9c#\³_J@o%nڦO:/L^]tle͟sW$aʄÜ~?G#3_.Lwij)Sgd/{8?aU`?D nrI4VhHUz)SiB/P_6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lU† 6lذQv6wtt: \|K_|co[p _!4u7%w:x)~N1mk6L=T" "LMM! IYC+W|JIAn'8,7޸&$!x/S)euEӾF66pI.MSI&#;]^z >I>f)s`߃lLmrg? r'-֕[CӃHD/!ss]w4sI:,jyVl~Fo~N\{"u*MMϛ0xg<9}k^3gFd}}LMM͞={r *9ΫWFzU1w39"t:llbjc!n>K*5e?L~LmlTjVWI-U6cs)ŋ8B`f&`*3Β&vُ lSpdFLYZ=%})o]⛧Ck[L]KGRWn6Dx\X!p|7^nǨJ$݉}]]244dGevzK_{z*$!> ~ߒ52\cTTNOG`ݰ"'9cwnWgI)d2B])xcΰ's4lTR IDATL (F/2Xz^^Ҥ:F-Pvls8(|hW: 9;xo}g=qu\k~ƈn+w:} LF8c\?=4k]]21=yWcmmjl;C{nRj]1PD"LYroPWॗ%^ ۲z_-NɃnqc8{O6Ui5}nR?K\S]#\̿73FNý%Is|/ltpgfin5֯t[s_z%z{{C3u`*1ŁLNҚleG E9/i̬"IsNF{7HݴSA0Ǎheaen?# =\Dw*``@ԒVj\Ǝvpm{}9ڵ}٣MMz2Nv1#F Ih0;sq\xocm>pd|l$Ir}$w{qjA<3g>.ZbQyV7p始<ֿ3ޣ*\t͜Aأ߼S)|>9W}|3Y"sgƏ|+s4c ZWqJ$rfg/Ljnnqy2٘+ieSLk/?Om> {>)'ǺgTɰUtNߧ=;C MjKeO7xϖw-pW{0j+m+osc]NMi>0@NĆJ".Βd*aGyF~PFd^^m"f[]^/SWxc]RjwOBIyC]hSKW ?lhg^W^9ѣw1UpWz0hkW!:#ijj_ 9ML#Hreպ=z\Wi=nSq}uT;:S/^p6qkT# !nABB%) bMtOI綧gvN=ٝuyY{LΙδ]s I۝&B<8`||\$BG煐У ට]3Iy7v9'h$6eŲeCD *9wlB@05!"Njl95 @s3%=Hjwf>ovoߊۚBw;vsB';}e;5Ѵ6@gmt0ݻ MLpnbF1b3v!2}L{#q͟bqa0twȭ[#9UQ H_(A=ܸ7xⲐP9w>rf$}^pfU{Ǵ}G.ḓ1~oo@Ӂ56@B{ow0ya(Eڹ3}*P[VF>JKtqÏ?ܛ[%F^M{Obd~oaV I2 ^ ) f3^5Y66&Y)/oLD"0fml"̻'Rw-, 'W cGGbX=ì R% %9HKWn%$aqY_m~N?Uutttttttttttttttttttttttttttttttttt~::::::w׿u0CD"Ayy9s%8@Oyzs|s@gg82Ƨibl bcTSS dl8fVVˑe9-q"S~?m'NɄ(71UcT^'SӪa$-S 1;lv8}+)IJH hSh^bKIO.W&PehiQii1ZcfejMy(ȝE³|O䬿_@孓 Է`JLkጵ_gɗWOk%Ӷ6=,4owuRSag*:ܬ}xvi/i2-hJJ_9Jz3FU޺+S_xV~<.ӲUgo_#ئ$AgB0(S_/qX~_ȴ|l,a<]&٘ s)^`OMisJ^~cf.rJuS}\3NJ::9̉6:މƮ'UUf`@"0b+tvʜ8]-Np(oƺuɎ!vEQ8}4l;Oo|WV(ΰ0pݼZ6녊pLkDgPF: 7߻o{(4SykO~~QE'-eWN<^&d|%SY+\1^Y|5s.lӐ1_qT"T.fu=TVAXG?+ >ƤΩ}/!4@Ƿ+ giof6B{l꬚|>㺶u?58Ek sO_]PKs3C4?82^sUT`)g=GL~oKk}Z ts.OLkC2l'!yZ1 옼љ2EfMo4я[q~*{ (Z6=r=9Tibsm' v0z]EaͧG-SmIAz8v,gv]Ge>#P~#ynu$L6 R5F)V0n.]W;0=j87OxY$Wۺewx"w8fu5;39| = cn Ý6+BX'G܇ ģ|zHM{h+_LH=7wSsvKzb4avVN'Щ0V(Jٞ\=Q❝tupb Z9oI9䛋6sB`qX9ǎ UQ PvFD'4~hn.l ңGm#NaPVўO*6p_)6ZqF׹D2{on-RH0VOh7#ޏM,>w?8F~WV2QT37eߒڟNѿ}*ȷeƛLTq[=%퓱{~oYd7H 9{swx8ߢ`QZZ8{On9w{sK;}:D/x/ۈ(}U= "m(tw>,a6o3mWC1:OTS꘥Xr-M,RT0 hi:e´Vr+?|'rnDˍ˚^tq\괯ho޺iM 6R8 6צl&`B^ӸNzXEy6Iudl5Pi3OtP\$dgF B=ja!ғW$PqUD$"rh鄨GBHڰds!eũ)i|v(ϻFnG小@Sg .F#<%8kcOBlt8Xه=zik]n.ꑚ4{ >9IѨiٌMg ~2B,)N/̧}FGj \wf:fp^af9[> +ƃ| W%l::81bӦE <$sOj\9}2Ƚa06tf8JK%Νhϰ|WYh^9bw 'h11C,c~?TVBg'*gmj.r媟ٜhPǙA9vX^_L<=BLN("G]˒W6kŪD:G;aZkyJ`FzZQ ik s=Z/z>3A$$,TEɻ"JHɱRb|o(YϜo h06.apXX|`3[fᕷ)=BqQR8v7Kr$ NT6>U>K<69Hď`gkmmK#h95s$j(3\\Ѣz1]H>E?J1#fGrQILW `180q@2cYY?9"QΩQb`K rOx&LbVWa7?s9:"~ӧOrH+. )L$F!Que~|9=J~|4ƟwWbSa_"if]9䭣VݡCl=cZ49qԥX8BQ*G;iqzSd:PH~ods9+y=Ru&RO˴mll"45iL.%x3E؍tv%8i0huYf\1E{-y{Nt"w{㯂/Sw Jbٵ37)wJ#et${Y̌WzF <}fA,&k38^3S lvtNLMmxZC8r+'6h ;_\[_fܯ(tؼ.E#UQ _7&@ԅ 38͇d9$lX/2U'eO< Ƶԃw_;W|S;F&ŸB}j!ElY^+ b>ںq۶Y3<3VTpk&Ť:Cbb{c~ԓbF3Sk >NSlsGlz9/Ph(BN^_sk_g; o*Sb2"wÑ@~1ҌXɏ?kD-ًQ2kƔ:3gU_'ίJoۼ曜9s7|o|_Ŝ~Mxgr+!& v#a9<.(q9*CK TM,9oqߏ&dJ|Mbee Mp< oBQ{ ISSSL8)))$|Է(o"tds@$$pXlv9?**?3,)j u=L@(b*MM#$[{Ʀ(%qݦP ÇimJ<&HVxD)&3* g?;wEI6rߗfZgQ39|B'&(L*2#p8ed{ 9k3_u4TݷXA(jr܂`B4M2CqY&ڎ TژGZ ]0MO&It 8ƁR2HPX355sǟ#6Bܦyn8,cpʸQl:wvwORe#bE'EHLILM!CNRX'1 ٔ蟝ef6N0t)-D GUfgLbHek% P,đmhz?S3sxǷmct!}$(ȳ%s&,1F[ Hjx=Zoޚ}*UPS~<&<qc+OqS0ֶ&8~ʚ3Z Ys2N{oNx[Ni]2VӯlRٚUIBi1}x0_F2J_Jhr )MsnK>_PM~Q8#D8cϚ0 $b=pX_bm:ޘoSc5c<9A63#`f&&_SAEH g ?kL'T ˲^rLX*St1h@[ᅞRfoDlOA`z91 OR>LNlC )yFqpeI_ǍW8vT+g+l,M4IqmvYpY= T1z(.2qߜ`aVJ<۩`o͝ߣ7 *Y:e2 %1Ͷ%? Aͭ^lFvC"tt,HLQsRvӛWRlQI\mHUSd=%~3ШI4~G9(clJsU5Fkk+SSSFS˛̴^[PffRUf#2oUe5XFgop[ $P+1l~Lϟ qD={P439ޟ6ʞG泻u8^wz#`I#w$P+'p'* ֜s3HJ){7?' pfR̳Z*ߝsTbg.3xee] ͜f[#-X<{ߧJϹIXs.ѩ?3@g⣤E +\;&c8L ՆH.ORNskDkG}wH8*;;96΂h{IckW~>_A yɚsb,AK4Zd6sLÙ(kS{Vذ h'r4}-A  ._]1>:0{d38[T IDAT_?;KlYV oK Dܸd%%/Ilc*o|#B|e>r(Rn˲)AfT% b+kȰo6 5V`OGlq_W z.$0eN>Gxi'&$?ihsH镗Bvqv9a\Y2dcϚ8=m f:g\dB)A0aM^&s*r fX,g60lp &k$2U,j~;9묬L`r iB]s96J]/hgA,}g&g cGNvOv,l6&c+m9]#3h5y)D0 T?[GӧObtvG4̾}93[,^~wOZZL(R~٤-oezj#kwY c;|< Sg~#+'O(/s{YW8sġ!'gMcF(7k)#iKȴB~}ߐUθD|r y XxK ks:0/?|;x+avsR'145r["8&07-Eٶȩa3'CCCx-*h㭫x]#?DvZ؏)Rck%~߭[coGZXD0p#r2usth--\܃D"1P̘jD&g.Ξכ,P3JjoBx=v00fr#b-Jڹ/eŻ &G=Bxڟ 8y _\bȢ&Z ǕZzGٱEYMlOIaK\MƜ:%d=w'f$ Uuy!LJ)#B(`G$ U`tYZ'-)`dtPE;17Zc9!ד~Xf8QJ6#Âv=kS̈́,#T% q8\: DYq<Bf,o[p##A#n!m/VPdk.؀-JFrv{+Q|>n^|q3;_nDn]Zcj UIZ[se0jbuY4`{׆d쵭2w|_vùjHSJ ,gY Pb:#1aaC|˕{~ãS]l/s}Q[95{#זrS^.h*)-]!]%vܺtgL\dzkm ?xHR#Ufq:5@MM /<{nnE"Hu@6w%Dӝ/doWܜ܅kNB5I ;D 1>NXPh*˧les~gZYmhX{`mo+f8U=Q 0>N]ClteAW?MA-x4]6#Sü]´z6Ennf/Ԍ) Dj_ĝX29}RH$}.ދȵyeMmFJ\D_ёe-֨qx}p\BT'fN]dj/}{Yu ſ^>l;?_qE)_,O=~mMdWfQ 0~ygu{ٍ<5n3,$ XNoorVkZ"& 4Elx%,. ťuĝ7% 8 b_+.BDE'\.]FS>5DkbXKj=tc/g48FshsRc d姬/˰{w3%lwF܉jτA#zYcD+B=|-\w˸Wq}}DޥD(-)A05yyspF\[CY&Vw3=^(޷e3Y$)fdxR^_BjɸA`a-W)ںhl${^˪5wq LB@bu!=S޲+Sak3``ÐsW$υhnag9䥍'P= tb6)\.b3qg_~aD,P](ٲ0V'Ml-bO2+[;OфӒK]jkdP(Vlho6K ;wq~˹W>N[ `b!4:JĹne~n[nV¡CI, j#t6%)7p2@!j[657]a{tKfM)0 wsur ,ufϪ:::::::::::::::::::::::::::::::::::NtA'Aꫯ77|;{?9wSSSS\\ZR}hbZ q0t3ب@(kehَDIf9?Kt۠Mú2[kYaq x(s()@ؐ( z/'ѥ~>K\!S*<ƟUpXTT?(3..ZWL *3Az0ee-tuɸ\.dYါT͋ HuX,].j@j5m6UT=xJ_γ3M5U\ S#40:^|kg9ph fCVSj/kkkZZ $ I+TWN:GogiI%|u_$w*h.O傑;dt 1܄Nci|v9ԼFqCF{)߹f9]YkiqYU0Q9PQsw yKj@7MxEO)`HkaJU._5k,ܥ**鼱"{\.1 dh29ф|'Ë1U% KKkYy|+򄋑aI(-Վ95wY*yfoU܆GHiIڻ_bq"B0FV !3b$(6$A(CRvRQb>x.;%񵐯F m`E?1ȶZI!:7CW"%n6xf=X/#pmOts)29W55CZS2QUr'3m9FFpS^hGn|[63{&}}y"@](BzFgr?*:#U޽?zC|GPI(MlvoH\:Hk}=ԛ+hjcZW5e6ֹ kXՊ"|kXWڵe|`CvxM=\xg,jR0:ΝF b3x^#s]IŽĆg0XKQ/u&CjE^.MیHlڵ8MMG8xД/LjƵ|jx/J \W ~wSmv|҆R`- 1PE >ɐC:Yt w.!V%z"~3ggXYϾrBh@0O?C0fѤ& #Թ'/)*]Խ.³Ϧ.2}dJG*,$WZ8(]Bd=phtbwFY$&ZYXX Z!`2f ֚b{qB J8Pk*&nUhc"e͆Bbm&'oI؊Օ59Ls_ 8;3x*x*MM]XO_϶IS\Gmnǹaہljrۿ~ݤΩf9ך5gUH{w<Up \tv# 8| Ry:rS%lXz-jh 6<ˋ;mنc`,lCԾ)_nʬKK<8D1,37ڇ}#dz$@QU9rćȜ^BunCC5/z5N"хJר\&u˹PT0>O`l9 ;\sZVyN{~1Ώ[q8s7 ynTdXyp\wJD<|@] &Lܕ ˱ мU!L ;+υln>QExTaއ"Wɽ{sp{ F"H>_8L>d]A9jFc1hIڊ-WqhA0p^\aYHHy'U5QFUK;v!MM?/g&+,^M 0mrfw)>ǥyI/7Fy] .`Exl  v/4G;X$ d&hj`.7E:絾^>s )r=Dfve\ecc 81"Bs-EGE3Vsl02l|}A+bq&=յ&>>(z.D](JRwaZ[hġCQUN]Aa0ymb.,R&H5 dܿil|{xhQ箼5^;J? =^ O8>L( }n[~n|=_ݑsZ-z~D{m P(gZ%<ˆy v>DY[˜7-6x5( ],,q»QsK<\Nm(E]IGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG7.褣/y>S AeDi޹3ݼ>@kϱgedyfH| BIQvL O14P-aoW!4h~F<[&>"{X8~ΐA`"*OXY-ų2ޑ{mi-X-~q#FbMyc!GٳDC`kBECD&<44nr`hLyH׊ǛRQTRfRx#muYcR OdzG;U%tJKxΜn|dsYj`Oj~_Qb7- VOrNvQ҂s%=)ZԷ&U6l!/NL&z{&c\S18oNdo^FJT8N,/9_ /7Sݲk'd%SnX>**L$}=R6ۣw[RQ21]XC bR%SwOi4jldҝRrWBuٹ3Y;H޲R/v1^6"xD1)ޗ4ç($x&3r&jT'i8Ӝ>}Zo5&负iI1JI $ρښ;NG#х(XC-m~[J34 Pj%%^)*!ZZZXܠ(/B&|ر@L!(-rԸ8$E;wa[LGu:JI! ݽӶn=Ykt20Opn ф,eek)i|\an$H-d·A6ӻXڎ0ƞbNgl,H(d%Y3epx_sS{F BM& G0 ٻyc(A5j#beNά_S0Q.Zj[\;ShѸI[UQjk Z1K»?gƅ i,+Kk\akyvK#K)7vݻ{jBkVyh/r0 QqVwbQFmL`-M@F-0&ҍQ0azJ믳`|1 5F޹9OK^RweAVőE;݋wbng?jB'F;|2?l޽Ԝ?lI_ܿkUt i`q:PH$sVKٕ\QXd}!i ޣ44΋/ち!v~pD+iq3) BKGwW)޼"]uPlZBY XQٕH: b|ݾ}/~\.q\ccc]`4&x,=2uM!̒o쭫螚mGs|HD(Fwni?fTat D[ 53+5fw(8L2|(r~`U&[ԭTL8~.e}p䷇\pwO76=yK c]=̗.{>7<ѨYe;S7Svt9>dJ ȇe"c`I̻QМe!Q>8Ṿ4z w}J?" l:(>u@{EG+%6{@JJK<}`(s_o-] }Vn4q .[6êUOutӞ̛b%v1qA6*"(i_.Cl ,WmakDWaCX&Fcٟ(Jqqx=%V;Ě2Aw|YJ4?{^(ڗV/q%w?Q׷Lyj8D|fe˖?Xbp蓇rtu'8' ,Ň<!QVc1GXJk*kryS\UsopT`Lۄ2<`+ʈXXSac,PCf82g.N::1?cE{|sd]W5},)kc|em }Ɏ'UOn/ ;J٫K6<}{s#aOcNgNi9b)2Uf9%ŷg:L]L]ݢ Dj>*ŋFVl>D(&E{ȸ9F v'K;:HQp]ևسȏbaVlljwt1:>J<]N{%u.SN裏b1L L&v[/;pI^|_) ~{H$xزe Ӝt\s&̒ӽw1;Q=7]F u"B8,cȟAmW͔>2fͦut#lΜ`c#brzy#lڴP(if)^G ";Iꂐ(roS EEYt.zk>Amxͅ 02lIDdB;\,S7=¥%OSYh!43Ck?ÁpZCTv yǭ&,3]0hJ `&T~xWTlByu0J-X\+OEϠC0l.g%h"_ɜ &OOh} {cSX1.9* 8?9ș3H=a<a.`d54eA_SB Y% ݰ!{B33hz>D?BK؏d(v"*JYq].UwO-ua2륺YSSɓ'y5>$}'dֹ,ߘۑCg )(Rts9tHf3δ$ hD2PFX:Fq1Yau)YkA3etՅj%x55yu/0jI&\ 3t0E`?$ W*Lݸ`ȲŔ9?gfp+j%E>8y eyY[I98a?N^i8IN!k֔`^MQ}189MMp@kX,Ƒ#ߠ~bERu5vQi0TUk HDpi"HNw{uvu$p RTu;x֊ hr)8=3Hf3cz&&>& 'Nsj)cYZd>6@S]A|!$SF,Gj-RWTβ2B33H*dp}ZsQ[>q. ;95L  )߾wqq.$[f  +OQx\`0.*ms!JWtƌX yYbz<ə6$BqoUPXbK9o0t]e}Y ;wyHuuuשu޽{m Vy/]n~aݕm-HBc!<5:;KKq&2O d6N0dkJrbl38% eBiښ-B3tAOC@XPq jzٺ3"*څ )?\ 2*-䵷_t4cY9˃l7k#:"~*7a0T";sf DE+Y\N"y}R<‰txZvs]6G1KfXe+ $ҹ@0^ـ؂Q0ĩ{N 1?az/rԝ;H۶Ki2p&߿m5&k.! Iʓ7-,֕"dё7=E$lgf1; NӸhz7[1f"@q; @yowwڶ违l_#%=tE'*qvyq#lbv ]ԛ.-e*ƻgN$"}\&n0bSY\Ż#jq3ʊ) 9k+tnS]sx6/006LȝgXԷZ>Ob8ޓI!_my8 v{ӧTwtWt(4Vɼu|l۾&a?owaA Kd{4sq2Ar5U\dfA]DGR\<{t;T֍ɶˈ?at4}:=߈A4f(r e8٦DpPkc/,sY[HT8SU%\|1ZKlh"4.\l26[+]I;pa-Fȗ{2>bV_MPcz1ȑ~*s23d C-ޱñc!)4l0#*#,E0h5P /[0`qYfk+ںaQ8( 9y76bB7LVVkq:D(umkV(v:q_t''RV] J=N{g?69PxKf~t/U0pL8υ ZH-(]Jnb3pF5_y5c`_Hgۑ3xW(#]x썜TLbBssȍ|;Ǹ>׈K<+";틟$5F#.+3s{1Nm璃ry~FFG,Ws6NQ~?P!zVɹ'Yeҍ57W6ssAEVYv/$I75E5D".-@U ԦMymONۼ`رnё(iq(齥WSwPV\ٳ8kkQ]>;MͦreR0 .zbm"G3kAh{!4R<23#Y`0+43cH+o^VS㤽=cfLFo>3EU]8mN9O"3KIfs֒gh0œ 2%AKn [09s320oʯVաC:tСC:tСC:tСC:tСCC:tua``~Yh4 inn޼k=iooh4///Y;w??AE|M:;;iooիݻA0}n _ ˜9s}{\t}sܾ}ɹoffO}S|@ w]_WS̀ΡW'cOlyՇ%xlU]Ⱥ%s(8Vz"qX:JzV+fqĎ}C}vz1 Uk3:Q{("(IYkZ2QYΘ]h4؝VJ|\ꓱgϲW"{M^z+&f/3Q͛?;>F|䲬M0lYc8}GXT耹X6?egٙ^sT3uJ@Dd2hB**;:WF+UU`@j[2j\Wߠl8@yGSjqx%D-P_GjtދrD罕-;xhynΪ*VToqxIH39D'/(>d4"&yy2@@f9Fp-%eM^(vw q5NPG_EִM?6#fsVNF&F>Nǚ&|v=z']3ֶ6|/2:?4Ę%1 Ȍ8(~4?mC,b|;T5 Np8xo6O>ǃn-5xZʤ?7gҐz-`DpTeiv/.IoE=+Nv |D*[ݨɟgzq} \NBYv(S;3u!/M/i~,dz6Ҳ:ÖZ.bgH 祗3,( %|9xs>6QiFA̖eR3.U̔Vza3虘XVXfkFzN䭟SZev<1LUm-9yq 'AC'G0lWjͬό8k|t{Uu";=q91j!}}t޻,GٿQ`ؒkbFggųU>L0scwqL00ՊEn%l>m m9q=d:%B dQ9+e·ǀ9jsǾW֙q_Y.z/Ť6U>s)~^3~9[Nf(S^6/'?WYerN U$oQX'dFq#ųjFXjkFĊU܉ͲC:tСC:tСC:tСC:tСC*կ~ߛ:tСC׿Σ>ݻxoٳg?_) .O~HݦM~:>|sK/`0ȋ/ÇE˷-.^ /=__SO=7 O=w۝;ǎcG?ʢ$}ă[n/L&F~&ś=L/T))L86=>sI0޽bAxgalƢ23.z)#On`04HK\/êga)t0=d tp˒5[r&wнr"w̉ʂ3X [1L{}BT`k / NI,1 6NOa[I =*OFf(C8/ovP(C&ɗaJPAih6.3@ciA6'63<1ol Jוj|I\* B$lg~c.@8짲ґ59{VT5{urmG"T 4}0E, ]G"< lckDlp{A .TT c7"sGyD*W-OwfiJ0O@OٜdԦ ŅaD(&Ql&$mS5@FmSȲ5~d̵lqk# W YBD>kf>זYό1mdӘ*?W|BW**BX›/$Ii&%MR]R?)_Aܬ]ai6Q@lpMpVV<.%L?mDp,ҹ-34׬]K`ʑ6 ''|cA0V_g=;-|O~m;4ሙ)7,Қ:~zf%I`rܒp/ώǎ 8 PVVRL٦H@e%GF><2Zi_+ s`#[j(=㧷<*02e"i3m۶mĉ>>vo1%idWqq<%kح\ӟ!o sh˓{k6Kk[L{GDĭalg86c cC3ӱb 7^dm=Ѩ"0xmyup9=H#֗#8' I )TV:p5"mo QPu5g4a0r4ݡ4c AV.`{o/ L#;e v?)1쵬̏hܫ>bb1ȃQ~+-ݱ7,\O}%Uij! rP3bId:8MQ}q:^46/318OUsdԂl$a7bO}Gh\O)#Kr'ëO<#O,,_XxM5]ǺYS`lgAId0%9y`0*.VWq1HWڟNy5DN|>ML(6g/󛿱Ҵq#].0' gb0TАT9]6C`Z 8cőq$IDG(__77D#{y}Ɖt^Mi$OF f' {YX|kMM ?=H]<5Sw˕7K ++J. tOA(c^$Ο+@r:Q&&R** BZX Rև|>:Npl e$MMq7%Xsݥ=Kz]44H  9*АHd-[K`3 i{뼸W{O1Wakj%L]):;r0<']YsBr$p1*N22͵Z a]߃b]Jn^/YA({_LF.ɜ*sidĕ͵hs=2~E2mk4uEX$3߇44 _|vxjnDyvfrv,JC!rA50qgffGX/!)/tjY>]\D ZrVVLL{  .tq{S ձ~Iaj(6f:‘~6TXTTVdՒl;+|Z 5DfNǡx(0<!ܸʍy/|xkˆJʯYq:82ES^]1˶St)8;7r%QL-59ý츑G-?fj\^NDTl-בv;X$"Ӊ q %B~}~kh6<^JZ=H˹0ޝy]*dkwNb]m_9aW ( o:]Əf"F[N<*{ 9yWbPe]>}tmތhiJ,xZk$:93mj&:ovr96ٳ8Ο)/Z ho᜞FXU5΍׻(Y&,_{C:tСC:tСC:tСC:tСCzC':tС8y$?ٷoߊظCk.^x_#p |ML&_җ_${V+'NڵkҲ8uСClݺ5뾂 @"}wgfg0{.]]]~#1uPk!5nA.ϰm$7ozسKEPkjP&#Wm@GëxaStP0?TbbsUU1ʼ>d:K4V٪ `>1`iI]kWmɍ:n1cD"87ǎqDA{D,OB%UU1֯S*rזT"aI2u/(hjJ">̴je$I V5e<}a?p۞샀~Jcgع~F/qUĽ1uC<]K*O.'g4XNId(Bv*?-yMYR FR$ޝm)0oJb!rHTuZe0e۾4)qOvulEUK"Pae?c$HK'я6ݨ(묄uSFl6ow3!c>PhGmZee LrߏPQIF157f5۱Hf6ލs;s+(좤!KVqTMR,,:+PMAرi!f+mgN>Tã2?Dobߘ ޺=+.]>&DiO̫Pew3tA HyB&[l0s>ygQ4=f̝ةgp0gcL6J5ϨhoO\ o gY=JHM1}۩v5s" " =?4Xd&fbeJ1-- }uUt|xƒ[aÆfBQY̦ttb} teB~hk8ҍ>%v,0FԄHtr1h͋ȼ@'tt{˕el0% IDAT&p'pN$\Ⱥ}E&O(48 Ihl׺uu-/KGܥMRe"_Bi{3Sg)Bf9qJ#ggKB.ZX h̑Oka6;zqmK2 Ÿ-4e]ʫTn5\ujz.g]ڇ\".^!$UȈ ӒR"G05a:]&${dnd{m)]T#늋WަXp9ݹH눀Pį(lܸwN)Ǵyӡ1MWyh;Thv8 DTpHAzO.7uAE?o$~?ǛCYė͓s9`d6A|powq'p0i?= +s1nw\\Z2kYe]6;]w\gmmXe@hPu "7~;KD)a{ג)Sٶ>jU:tСC:tСC:tСC:tСC:tD:tСCǯ ?8//x饗'^z%/}3;;׾_;/_З.]bzz{,}=D߿ϕ+W>}@W/_}</RURrÇDe>3"(6;g##J4PϏǡC|lb9>~BAbg Uq8ZD[ Ah?ߤ(UUv ǵϥEVDQ$ `pÁأ 螚񔝁C=G>_>QVD>ljj#6A VTz͈(Xe+?T(|Z8ʫGpPFey,aNzUL]2i+؞KY:d"}(B<*v%(ã};3vCO1Eb(lZ JDۈ'+\vjc5KPŏjaNu滴_}Mɤ5IDlV!'HQXgl[\R4 rl;G#=,L=J"+<''xZr*)<eBe-G-cpzE TuWTUZ(4Y[Q'rPs7RP|"/)g~t5v(Pfc_P>?t!)+޽LOmN>\,#F">`A;B];TAP7kbzR- NQ3?:O| -UR_cn>SPPxAr=ZytAھ])3:=J'(? FrFZٙbU9?p.nZ$>*b9IϽ̵m iz%cS4 2== 5Zf.,hό-(޻Gq)@@c=HNGhIlA JqV+M6>3s{zﺹk垮&NB+LoI{6qcDz@ ۴,Ƞ-ٱ(z&R b0 1ARiON{2ߵ(M~c>R5 jkܨu۠peo\S"oι{>(gxQT)gDEc[<Ç=<ټo?n'G|S,ô HˑES$1LrۯdY"J"isم$YNU]4izY@?-ȐJb-UQqzהȹ] 1E@`BRɏix-5iN}3hh_Ƀ>s%zɇ@`4.vVk}ذkdY4}ȑP(WU>|_*P;wr1:::~ooWWzjVZUׯ Lްaòw&&&˗/sڵEU [n5/$R2k>rۻrNkhXГ)Cl0ƶ"wC8L>$ dnMm*;KVp8zio>F46tM>]Bݪ"5;oZ*dA`) uOkE( hRģ k:`lؘrj4+ZeR֖ox1aa us͗ČKDYNuB :Ef&/Ov,ױ!\y7ӆjp _p]!Hbd]ә}A"h=+G(Bnų "+lBlK/4 ᑠ*9qDcKqC J_)>N'N"\qB#E-B dM]P#4tG$=8B4Vl=AvҕȎ,Cf-o"`:i"{ba`H$pqwZb,I&&t&+r꜈h9iY2$zLb*XE̹@H  Djt3tJQ/-s%j{7]Ū,̧h5 ~INs|*( pq!y/~ %&d7u8Ȳ t|%#:NpB EnzꙸW]ӹ̉3g>tn@@.t? h}*gZU&JBZ[[gkY[/wƗnfGDKQ;fSH8;̸}{תVoܐ $ ڲ0D:CH:h"rFT9FQPZcd,Hz_JBphm50U1?'e`[?nG qK[-lؤh4 *2btXyL.ƢgUŋ8ntxQv! ޒ{-b:ebvI.cY%VH߽}Lf'k "rGnF{#e+1H۝m$\O 65V= ϝ,ap/)' \P_lHZo-3Em={׎Ϙ?JhCꩢO.okWAu $y!lL(Cx(^6O&V}f1_F>}Oi?z,iZy?qX،Vr;oad0ý+H\TdY(=Z.H%b!d%sde4M#$9 k5ĕk5e+|5 嚲OhWpy_ VD2AӅf9&f7ژ-+tGQn7䲹EnƏ/s\ʏ0$ʨRF`sM%`EIOD5;_B}5JLt ! LHLu/^pN+V($šiK!eyx~9 >1ÕSgx| 3:Յo|K|F923Q.|}L8`rScɼ)]/%O8X[o2~T0dzCw8׏8{mgQ5:%Ye,vMG|Z]Dc |UE4 !AӬ=bF͵Id\,Шܮ.yeyS6)fucy_5B=ĉ'H>`Hب(?և&?}A-X̎>Bv34ի| l|~]Xb(45_9{X|x^=JzR'"F !MD~|jsӠ{8z$LBs N/ۄ B˿;ˊϕSd^ wU%rgǢ}McxDr uGuY{\p18mzU##L~t+(<2Y_!ҺV2Qgx.Ju&XE&ĕ`[).RWf˹4^wvYbu.CBϛ@K 8h!%Y b,H$miSswqj]CvKS$Crnןq^j:R9VT GӦY^?qgXU} Jwk:` =XԓVkR,3 i]ӹ3a5ݻmuȅ=:7,*WLk~?sS΅3( -kZpw,~e.Ku8^塸|6_C N}951z&i t ~|JFƆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6` 6lذ |_"E>O?e>=P5ktqNK 5)gtvFU I(׮f\Qܣ@~.P?Fm u #Zuv{$ON"\.#&pe˖1BQU"Wuq fQTeTaC"ɫIPsX]ӹp$,I~?7 u+} sh5bF IDAT%QwLlټaN|h%鈕ZXWO #^s* -3t.A-bc!2eT咪v7MEYA]٬ghgc' ϒ=d}d\r Ǭrw[L8Lڲ>8?_륽FEn+}Hac V^^&a'#A$1k9eQ!B}RRfͯEdQaRa""mh'I>‰?c00tزvKBU{[,XQؘ!URk,Kl/vIg|UQz0HGͭdU$ d$|5t77 4ȟNr`tp2v1yw^w2tkBk_aNqIΜ9,<ڧ& V^M"0{o}e ?Q^}8 rηI$,YWQnYL2]{rslsO > RQJ.vGLuȣ:o\4]Aٳ~Y n΅F3fOcܻ,i&c XR( (XQ65ta{͸Rۚ ~)Xq K TEaj9ENF }}LNw3{{/, xnOakxAp?+zzn#TW*E3S*tҌfgvC0pIb(2Pqi+ZWpiREc|&#K3Q@A,Xփ{qI׉tv_ ظ^҃#4uz;v8[seUdr(,ń|]DQDA6"H~7︗q]&52qcS|f_ʨHcSc$ŚhMj*TEt2b&F]CB}D*.L "Dc~z>֯g{2#D^bBQ-VDeY>TM"i-z,ժ B xyNM~^?gxfEd):|ZD‰0 ;(0$!LYy.KcF\O>ɨӵ 磂Qih GlF%،0CC9bQ/QIH.!Z(%5(Y<CD%"K b_KQEeTt~a=tq HY=+WmFdqCh@7zџ?,_Uhe$8%~|R79stMϿk݂p^alN'Ƚ7F#O$8(VλNQ" #nmh;[9p(xUM8vMWb5r)oݹ^@m¢~Q{RL&]*Brh>Ύ{}&'[Uo=12Mq෕btq.Y**5Z"*Y ڽ;rU]ӹAq6X^|)FA )lT8l*^åK޽zz]l6 =AQ|lh|_WaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذa/lA'6lذa?~?Gp8̥ iooϸr MMMe[FO~m۶s6~+|O>Not;o}}v,$eXSrs^b`ϟq1s{M)޺7ήQUJعz5?GrwKk g!G¨aȍz9^A:I :DA@P؃Aeۑe2E8d\lZKVlI"|NaxK\eUO׻1|Ϟ60U[@rPwԱ9XXPحf9>n!sֵsPdJgdԤj,g{>fc|K&q4M3ȤV2, "}2T4Mcӫyu pFSl"&bM5RsaMg*:ny7PIDu}& 2h:rջq!{ر@,m$ Mesf^.oSs,ZY v@XfyM{(} zN=裈uuYӻ|LOX4u4[q>@x$°$<|~?F?;;AUdWx-g>ۨo3QcB.J()Kщ Eߠ<|JBFM8.ZjD\.&;Vk)6QnHb;+P\.?{5^? CPCPTwpoD( ---} ev J HRV̝i6^ij0k)Rx>^OA\l'w ;58"[j"dhM4yaI~N*۔Q_A%J(٩bԲjSkd7%?oG~v/'?8?}Wvr~}}q'?)\ݽq,iN[eױjf z4삨%VTX1|f(Jt~if(vO/y:WWǜNo9.%ؿRc7M<z{Xg)  >咨Geq/0e9cx\|=y52"}}&x ƛR ܅wHt5%)340\RoΩˏ=¡Cq<p$of^Nڝ|qĔ ldTy>ʮO{|xL#<~L"\Km} JB,{I-Kmz[W} qثX ao:Y v'CIvIGвيr |6μuuzhtq;ě%mmok3sRt7\6jSMC[̸iϟ=K&ZoYrr~C;w5vZ\8Fgg5V~+K~XEgdB4u5p2d|?Ԃ݁(ˆS:Uda~^w 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6~` :ٰaÆ Dq;?|>('l޼5mmmLNNV%^pZVϟ_hnn^^/_'xx8xp/|y|w>0 w,qL¤VI'*A-%ti~h޷KO&oy?8r=\'In"q9xToQL\#>JG"<ࢺ9xkk̘dZSf%yVE Ej2N_^FElMno?{82lW 57PtT*(ۯV6Tڼ\.C#t)#k-B :'/SwQUb(QR)^ vN&!sS܏j_bSoϻcXhrI8*jv6}|k_^W_e. yu໼䓼1~ES_}UWx?/WϾ+Jb q^/S~^!XFs 9ϰ2'{, +JDEU;Ilʅ?* ;屧#;5'J\Ƚޅ].""En f_*ۂ[ֈXZ4M#ϓ͎U#ddzܴ1]v%߹ +䁁" ڲ$-⏗b[#JVaZZ[8d&-,2oGKHQjPHFCQ^jyuYO^IȦm{ewݲhb2rJٗx`"@Ncȅcxz^VKYV,.$mۆ~Fag|T#L$7P~/5Ւ y(N?8; 8ķ;v$>Nv<[U RPe4wwi]K]>rZAe Aw6!L%,MFiGjM;[FAyص !hU,̋uNQޏl}uNst*ZC5ʱyE*u˥xU1?3<w0EgY霊Op.y&ěEG (EAApC :C.nH]>ͦ}*V!TAwn'>}tlVߌ*L{> {^fPu,v.=4{.|h4ӧ{ wnCA|]>3&}{H2*V [.E\ǑXbtv>lw]b/xƟ| 9bFzU6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6ltaÆ 64Ns+/o~n~򓟰e˖Yr%'N'NPSS֭[566rm-vA IDAT>~|+7oerr+7,rw {IOu ݎ2j Hx0,J-E1YKqh!z0].E'AI"#a ,トQ𯗖M:((%LZ>]~&Qsκ3=w+0ʤE|y,Ċb`5DAѼf2{kʝKo( RΠ+guW#> @V?Q0RDYKDu"Ѿ⡦&tPDR,>ݷt0\L0BUTjԜJ׎#[;3R'W%@IfsO&C-s"[Ə{.y }]ݣx<ơ7is uS: E0(eL_b F96 Stv{'t:oqr)*eULT@u*1*PF!ܵk._"6\;vͶ!Iu:>L։R.3/0R#MŶtd4^\H 3ף ]:`9^d"ijḎJxMK ӟN(B-cI&#׹΁C -ˌ3p,n?xm;i":؏H˚;ؒcZd\.B| %xV|XXgAZZ[ [ᮚXXw+؈昋wp=;ZK֠iJIQ0u&fKa^֧Ɋ 9_ W!p - B~Y- LJAo=%ěL.z.<8N~)sZ>fxh0X@ ,*t.|@.<<1SCӟ}5v<x._EvϜ1Fq5" )r"}{7љ) $g n}Z!b |L}Ic ʨBKS odi_h+DA4(aIzXC=>tݟY?E_>#J٫nIKKW5߉wf߱mZ<D"}FWwO4,u㗟eH0c]ɴQd!kd8ʼndҀǖtMg"8A:^0_W,Vg'L XHOmÙ;tDoK|{J/{o&u59gpVXͭ3>GbJ-6q$ygF~Y6yI'%Xі@ڵ^Nڵ[%&g'>C oTegݣۺC98 @ iA-Q&eJھɭrI4M⪭ri'Κ5kizD}6˾JJee")K|HeJ|$ēG=@o-1,^C{{r gMإb<|^y=`!7zuRr:-`uxB6wjBII .Ls+w(v@h8U7ePG:z|pv{|7v`tڞ$9:Yy횥2%2¶vF>ϗ?sѣBO&{ƚ/c߽ qe|f>?~;s2Je^7$/Έ90bJ3Sdw:ؕ:jy3eZdJN=ss9^߯2vsȮi{iKǾO}|ٳ={{U91|_[}6CCCLNN477c0ҥK|ߠW_}D|+|_fݽ !]FQQJƟONN;;qocOiݻ[D">Of./|exRooof.ꫯ2;;~eݻwUl{W+eT .t%# "сfb1\T~Vg0662:s[mNLvD-lV ,j"I`:sc*oLP{'ATA?C-}} k7RflGȒtr3x{ymgkc$i*cF T?iBliaeͶfDAQq:sd" [q^?H#q1αwi*6s]n4sp-,_em#OHekl~@wmhw r׶"6z^1L&(a49 MYc IGUBM(]\?dDh~n-m[;?U8M&xIKD)[<%"\ ~40kB vCDʜM,DV] XlNrǢqՋ(AF#(͸,ԺbBAdtꛧN"4ǖ6=2[!z79MDGn 21&uy>|r: _eaS &Drbj,{R%͟<@htQ[28PPdaaWesqbi2quqxpCrQ҆hZuRJyiܲ5߻1f&xlX-ؙK*gA@,sjx\Bk22M($buӾ&"#*7QWFq;Dy ::zhթd!̑Z\KXj2|{}cHf(^G*ctUo+q\x<NN\ ~gtxcsc,Kl{ve/i" 'qƆq4]W w9ښ#g\:== "sx7GX]dHUEIl3g'tTw/ncJ`Ą/5 DbaVbR5seQC:X|4o.K}w\Ɣd;_M~*!bO8i('RZu-1ϓS?W!NLnmE|DD.; [|tFy})ïF4ޜf*T3M5}Nޒ?yjkk?}wq(+1*ކ½p޺Xadrr*ڹpv&g 8S Zq,jLQJ\y |߈ƻ(3 )Ϙje6%wF5aA~A׻굫ԗ373,ti_=˘jɢ<| ?bGbrOkƴ3eg2| /%рUF 4bk>sAf/vA&;lJo[>9=T]he7)cغo\Y??le1$FKW1 ̈́N_epʆL+ZLp$u:t ~?|+&l!Ft6?Ҿmd}~ŋW^˒Abe~ PH*Gv|^WSuI? Mlsn!F.%4zA>RⲤL!ws͌C(fI_Hɸp5I u}.;Jip{.|A#Yii499d:44f*.kJQgʘ g?=A{5$QBE:TuVRTT3!%{ic~6!5/Ho b]|2૴sm܎Ӄtvvb~&}ɲ4Gۙ^]e簐CySj,( xPNY.W:U EF /'5I/jj% CԘ(Jl;{.DZ-!oR>M$CtTZkH2t\ uMRϗ:|t _-Dj-) 55 idt9%qnR D[?]UU}Ǫi*.fHx \ЯPш d7%Oja2e=jJ|7]e?.,p/Ä S^l2J[@[!}F.FFxaC^jLPB[9Tp\@a"5`%I"uiS9'O^IKKS4"$2EEEtyB&#FCQḩ[[ADOB0GZCE 8L6(]>KX޼9l4=c;bb°x]BJ]B2$a0tRS#\92+}&% rV*.J*]B2oY[+ =Άb]{SϕD.]TT3YNɚͫ1"O( eM90]0爘g7d< TyiknoGe:C!;}|?LqqqzʲC>&\'_?Dp;w~J9xX -Ճt4*[ >-B&B! A "6['#ľ 矦PHJ̌uuNG"؆ LѦBF$7GdNض&绪 J>5>$!uu14*VLDS쇀M 3r!:OyKT:umM[rVLZK$^9 {/am+G%_eYfgN_{EXL5.bߓE\qO,ZȨcz{abLNRцk)[I;*S Ȩ:Ari}j' -ٿ_ezvv^rh 8{܁f^5cz(!o~E:6BJl_H00l}7(tUMV7ݷQ55Uǖh55*55tŗΉjb eQb1NOwJ)_V;'IƢd& 09L Gkm\o7P-*˴,/X2xhzJaj*'qPNJS<;r,">N,Òmem+e<ֶa{z?LU顭Me'LOReM+;2lً/;ÐVV:ZxͫCO7\{ZKN26Mww7mNb?hg;19M*TUn<;&K3Ct>-J'ƊÑl{}vdYFQGH}ٟɶY~sw CfL4hsI _Eh+XF| s`§u ss=UZe0fQA(pr}PRs|#,GCTbDRq\Tė?aοwαojYV qoBmUJ-lٜ8l(?8سS+&^$u8~hߚz,CHATZsHȼVS4#F-}J#{("iLSJiŒdoPg$S*cmz)џiBGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG !Bl/_&|}s<R͛ė?aA'9_3>;v_җ蠺~}mF?9~W~UUyihh… Q[[ӧq9'cx ^իW)++ԩS^KHxAlRkUjdk;| Q3 ԦL*<3kBJW%E/VghLpO}>$~0;\~?7͆ټk2kbsa&o%4\%Q+\O"EG g ?LE;%uF'wLiiϋ|pzE h.b6U4pew^;mn:Zq2m0Й6tfQuܽG8] '=i$4ÆGc,ĬRΓWSS5aGϤr[s'v^CNILo!x}C[xbGnKp,sEQh9zDS`R&ܩ ՙl{zl:{qO5L? rJ7rjƺ4z7ύ'=Zl`}?*IJwۗzEAmQbftE G^1e g8e~-OyJ#x*ڪPY)ϓIәy0篯Hel63SC]Yɓl{gis1pg=/Vse #1 4t ?Ν v{;~-x<ى뵥gL?/4gUSQ&ziBR5޹9|mel/ae#%Z0|bq~k#K1Fqd{&o< hhs g 8]O|Wx#ާ̓O%z29Uk+}ԃ) ZZTY2j^?oNˋ/"e.d>0Y<q?clUS=*B?ݏ٘_OUFW*0x+kȆ^Q佉* fߏl'2j5Zg%}mj?}Qage ueoNHoRNVvy܇f*?'i*s~Td!0i9X]+Հ9יkq*gqp;D"7ر7>ACs][Rj$ݷV۫" 2F#R˞k'M#<Ə|Nff6ʢzzPvLL+ wgILP5JX/LPrn+XW<sZOlvSY9A(@g>I3}MD78tE& 4D:syp?E`Q#AJumHPP )= ʱok g ?~Cȉ+Q'sμZfzgXvX6^?C R{qZ|"5~GoJ?ۊړؼ{;**RoHQngg-˚zb155LOOӼ==0f^H$YSJL_WBlc=Ʈ]صk:/R]vsNoNyy gϞG?CCCywG3F(\xQ9tرcϤ{|g}L@զ: 7Ft6#8 BEx{_㻋]BQEtbY,DG~!(mn!@ e`6 'igDD,nKb*66׹4g,ne63A9 49_2N0\Xn,`p{<$w ōBpkbQѴ(VJ>/n_WP{X k/ 4?LEױ:_wiMgh# Fm5m^" ݉TA?&sC]76Ʀ`ݖ$Jes! b t: ȲX >Hz4U#br2|"{D!GJN@$lb7 D,Kf|m?ŽEvOΛsb,!jخzđQ:h"Fee}4= ?ߑ>*}{ßuc D!S$ ..Ft}@`  1sO'b ZVF7A).`x 3~L8ʍ4?+T+"u|zu: 8cQ:;d4wqwWh,);$}U/) BW9{pɳϭaQdv3}g$(ddtdcN:ÑGҩ죡eYTğ,]J|S T.\°s'<%M(p.rynTlqքX,)'i"0T?A`:ۖCQqWz=E۲E޽qNSs08[bm#rI F"%WN'7Á82?a4Dp[,yEmŷ_ǩSQWkW&qZk#=~jk G|NJr fOde҂1811bZ ﺿkDg8RUdJ-['[MD",@ЧlZزU|8NӁ1RK1fnG i0yٍݡq0ۍ: y˶@Tą3Vy~  ͍Sq\D\Nk6Ĕ~Ɨ dxݝf $oW)xޛXB>uO}_Ȱ3RV^UE8Xmayro݃ޒSK{z;lzIZ韟thzsZA*¸{8B+ϲt#ܗUMMn?-{L22*r\m]6r@E~D~c;ޜ7CVZuSrSMrtsX FjRU8wElvo~5ɇ(Ȼq.=A38x:1Oz"Vyz|y>9Xrd~'@,oRZfw[aq*yF'Ю]%h%vxq=oKey=h-N%M'Ο>[JJHW#2ï:"-rKJ>hNN~әS}V4zpZ(JN/AI ~EdsnzD޸f 6mNCe!ɉy-WjFIVČ^C3'ňTi(-ق] V .&9v uuX`4G\lN;wxJJJ21Y w'xi56zJ<$ YXq95 rfD@<6^xǍ='ag2[ Z^/YˡCrpOz5R~;8 ]#Us~Am0̧Y~7JPIVV榨a|XZr@O퓴mI8ǟ4T7?^:Ez(PߴpPp&d2nWj/,ÿ DȲ̄u>ßΞ%hRq.Ԁ1b hU/fȑ70sf-I;qlNo_$=ӿg#[/ܹs7z0,^d/6w2o7ԟ𜴕6fowp+?Vv9΢d9`4:X^/!z7՚yJC,tysAdwT=FEE'6[皬*<'K \i_\U(;w\E%T&+r(JS]Z7W_ॣeHRB`݈*o{1<2>QF|J@Uׯ>{7>_il΂߃^mr*(**Jͫ^^Tp?4{/s4PHj% U*+Gqt^!NF.SSRf9m2ʀDkkfsBeV9qzZHuty+4K(~: 0q|b7k]Ti Iߵ8@Iy;,w1u>ߏ:1hT+9҂,\W{[d/?BPɜ N}iZEL /0s~9Dvi+N?ɋG^\OKRԂv^ RSU^vgdMB(r>ޜS5峲 YrI/ov&b+Զpy Wj xrczg1N iWFC>҂d^5kV+j3ӫXek{>iM~/;w=m=U?:DD<> qy i:K #ri"ɖ=g z<4xi ;Je6=&1u_ð7sN6W4MenAnZ^|"Kd{w]B^yuXh *,71?lXYq]^lXB++?yr}n`c 櫌$;w*툢a-6.bZs[m?f 8DS&|z-^Swy)t4U#;aKPfg wMe_Ăǎ,'dz(gHIJ'K陛Xig9,`m,åKy駹x"{WK'?>ĉ59c4/3FS6ꨪ|a0%.{w`'B*f+c^%ivD'y?#W?J?Vzl9l1}C; Ԛ+^w"^VBT2c#"-W=[zD,y̬͟6:qZzq ~G37>cTU6535/eۭR?0l/e5~̚GUY9ὼ {9f &Cz \%N}5);;QhaIu[ʒ{{A+5=v Qs6i  EU:*t;i99xX^x*Ci-wlQ|W0h}ATz'(iaJ-IK8}g far}"½?h$;cWܜD} O5P-g4_IBPO&>s)˴9xЇ$I~FqRV"Υ=ێnC>  uK'J뾭• -GTYI]L1] IDAT8s=gIg0=I{݋ EYQycttǃvp2-3(5JzoԃiT KmiZZ^2Weff{q"30?R\: 9|Eo}g+EJB]\9TTOuk&$X.v{W>z|1<|/X@+Öy# 9s[ػU&RR:~{: tm}V$_oAjl\}vGuw=GuY 1Mwv PS5KEg+1 g'~}G^>Ȝ3vJMu{;viS`H䥙~d^CnJāه\AmmH9pJr$`[,]ܦʷf'1d)̘Dg{UO _WBGGGGGG烳͛7arr2w$o}u^"M=)J޽zN<^vy+;+ibzQq݈i/KAlEġ2f>6bz༒9dr8fQx+lHb6D>ߏĉ--nl'쥺pjQq"4 vܶ8\ܘOo7P3cArq _{C Wth4s|bHqm|-?HۺR?22Jss'a_ޛs#D^T$V$eRXwn~uy1DmH$N0Ѫ'2w]V$hd\Po! qU}сѴh&ZߥAcqc +C,W4`Y#`ɔ.mdaH$@y3i[c(¾'ԫgd"86Bu5wLsʖV)\%hdrR_/@R7ƹj؇  PRcdm236z!338..X_)O"MMF 'D8Պdܽwi61ԻBaSQ(A \8Smb b!Ƕ04$|R2mwZOk0'jKD!I93}DxJ,p"`=AZET>͛CI4M%X6i,˜N'$vݥj9DZ|UAy31مA2y7V 8IXn299sv{ 4ϛyHuEEJJdF-kYČfGN?DT*Fk ,ft˱͔ɾ=?D$ZSR{Ț0Kߴϛ, G ##T˘ڶ} /{yy~pqA$Ɩ)RvHZ0yE@k%ѤNNIɦlN9{Ξ99gO7i.IN_65J]ʐ-ۂڱ]% HQȋH)ɤ~ѱL|oCd/1x~/ӯ Dėv.EƇx,܆$w]=ExͽTesP93@>!Qd"XmqGvOQsV4'O}~5ϑ+nmm+ȝ;*-t}J%F֡Rg}y-ǹгk;z۷WxѴU{B-~7FvB};>B9~g?OT >X[ճ W._~˶g>o_k][hN;ߡ:/zxo?~pOL؞8P5_VQWs! x }-)52܅>s0x#s_lg }ܗǨɍs+ǝ՝i#m7 ,yA%4?x9? \+g^O]7sw>=?1wsɷ:U\7j?QN^Dڱ~ip{򲵎߾+,K]\#CG >@wio/|bqQ|>f[[ޣm{JKxY}F߶m']-~gvq7=9,\Nʫo#yɾ:7-bNmk%O_Jy 7 q.>sΎj6|^'ؿwoOattWOYky^% ۶o:w\Nz=\0=e| Nݺufc$Gn/qÜ}9]x=.}z/y9-xw-MCRU>rȯWs֮`#v,f<&x_ޕSJxr}N]+lL5R3fxp\]WW݊;}}P\^(5?շoqr^;w|,Jgh("toq^>Ͼ>r.}>K^~~xx}#:1#夺$5N:0xJe\[Ԉ}~$*}ݶ>a#;'ٶ-P@V0gܶFVC3߇ƵOy"l;`~ۻ{Bb'GrI5|rI ˕e>ĥso3]{i8 j\R|GY-vN;]gW>6\M>݆Sq:}z\M)}>2'v"=R| wY>S e yXD^nosqH7ΏRĞ}Rj&Ϊ@ @ @ @ 'X,҃@<|}k15X,r( ?߿K/^̃||PT?+y]|Y5q\mZa|'-zWtrq!?xOXmRhBeeI2=zǏhm};{QQ.| TZ48w.Pt/;b|"]\DI>iij,HcX]&E % ˲헻KP/&JNc1o}udL_`2qۯWnf/⪳cVWgۭUy  #\Ic+ȭa;\s{iԭed5vq4]q殡T꘾nU׉e!B|6!l6Jkk^]>3ǻq"|0krmLwN;4|?N]x*C$;3T/6:=c[q,C@[GoMV3F j ml -$s;ffN!Z^+mö:sHBMae?Fɵ\% IxȬtvSU-7~⿯rO|"믿ΓO>ूEȓ I~ O39?bk#kL lM(EVVb<ДeW*u--/IwYNH/ǕYe|Vg+KO"O}Ҩmv?Ɲsxko7֨᳉2t.rc { rKu]G9ud1V?@ջÇhif\.g2eۆ-]]$cݜl6ci)jǺY  H;{)پO3/{ּL22 pvogQ\M: %ގ^nܚg_\k "9fYnl,Kǯ|[Y7r 1@EM'C,y9^~<n [^n4n{>ƫk:؋uq>vkZlZFx3JC@FfW^ 01|cYn.n7o>2cx^zi)iYaP^LDB`v#P4nRvfͯildW?/msTWPV|giM+33*5 EN{R"&e`nA+VzJp4HzB0Zo#ԄumcxS )u4-U!wO i41lGJB Q F5^(qxqҨz*R{/T+> QMu3p7#YΤ[?g&b?pVdz͈f؊5,MDN5S+:%o,sPU*|3rxݷXT%/b6N5٪#M[s ^XЎ:\Yi+i:HJEϯ9TFҰ)j]6 FR"Fn=|F4yw3~Qͳ\/D3Vsl>V.E̵vS}6dcFqIDiXx }I4S_y'o"klv,=^;ycPyBK1Uao_P\@J\5;Xb Q׭%LȮ(0k'RS),$q_XMZjb@KKEx m5Nj7ȍv]D 7W{Ջ)cxzzBnԓ[ɻ.2Je?)ܸ}l3w؝ut^ܔR;Rm#e ;77jKIv=sZ8+V:HχESp4ӸC!2KK(JU]cZ M:VBٷ_ǹ=b̯ǎӚn%?!4 bqFr^Vr5d[gccAVVf)ԩ$3?T~Фh'wl׃̸ǖʓg^oMLoJ 0p(0bPWm %g;;ӝ5t?9@Zj lDt\IWimT izǹƑ)սGq)|楒pGf#Mk& &rtQPq^3;I} Ur{u'te>|IT YZ{gmFϴca{OFpMzZO-nY|ؖ{h^?7#"sl1W'V+)=64n l:ߋ=N~qu|Oϐ؏\+7m6MCP%Tw؂;,?ʣBskΥ( \=e}xY#!I1栯{5VBB^2Aۃ;6ÓT 694]gva9?raҠ`3cM Ԅ_ՈTđ6%V^lڊPJ#h]׸?m+i +$7U^.r| 2Mӈ* ihjt5,h[G6=c$c|F~i r ޹iZX~BM[8M84B K`}6H(c3uqlg?|]|95UK{9V_0H]fc]$d ǽЄU@ @ @ @ ٝ]@ jXZZIz!?8xAh~b~V=l^Kۿgg&,?KQ4]/csjO|IyDC$$IwBc:9{}|6$K¾{&Paǝүf4M#AQl4閟*^3eABPjZ Gڸz͜u6اjlJMemm6wJjF`o=dd'J1۰gA/_\m`Ge̥1(ܘ?cG+{!UiyT^ˌ!u ʼ<k[M?nkZOSf4 F<%}SqxO&s;oz$)qymJ`{+ :;uaL Cc_KZu%𞰥Xˑxrj3)Yi?.ӯylxki{ȥ㥰$z"am&hqƐdd}=׫g>1'0]*5}j^L 0fjǚ: zJ)m 5 d \a^6v$#h Jȩ9\>u34r6gy^RKk=cc\iL4Z֋e,33T֣Tՙc}5PJ}%V W7i95"WqR޸x׷o'3$'_]yY92uRZQt 208wKvh;&үrdIǦ(jrүlܟæϴs"|q\/w dvy:F:7%̥м6[UjֆEQ:f>.\%"h Β yq^ni75n@ @ @ @ @ q!@ )G>IC|l\ðI}.AK-!|"k+(zm~NM Bg)/RW̜x6m4C`;^7 ^X;͢X4V36hUU+Y._m3!i0=ضaXxƌZMٿLh0CUsTt㞦OqDF,=eB )w@o{I5v,skyedC5c^r4XqyZ&6*4g[ۗC|}v ?M.Xe3'Db1onE4چ^iHfVeǖ93W>[2zZؖm21yE-frju"FV4(\T_)55Mӈn+,ƳM͹ٵw eh,e4!۸~%H, F̸ngE+SUH{sSֆ4 ߯$ਪSkGwwA7D'H|1KKl7 ZF)>9Tlw<\]nߨ&o4t =֒% |ojHB$RyRηDo>U{FSWX;y|P+ũ?}cߌ8U˴ zUP/ TI_sv=7F5^Y^.:'Q{06tM}ym796slKq[ZΎ3}(>y98+S7-iF\\N,7EZUU^/W}5uu5fIWSs[eq++M{ͶSNuuA8J[P.K^~,ɄAv ( r@k33:n&JkK&M x/3;;SSW΁B%),-,U},qu2YbOR6|emmTKX]4q\>}+k>mt&lhi_hc!E;,JMPB+t UM.4O8M5s5bls֧wWtbqBݻnsM-:j.ǁ7@n #GX7uR9fyϩ##Xr}=@ @ @ @ tৌ}k,Ssޥh`Xhַ6)I2aQ7oloSJnGR.rү[I@AI~ً$I*|0=WG)J5Eٹ4r=*B|(%4Ӡ55CYI N5@]']̥ y166If~j48XD}LXy섟 Xc/ci4]3iC~F6Yu|Ss.[γ5Ug!zSqZ;J *~Ǯ;C.4lg!r .qu^)HGWa$GoK nIB$SFF A#XڂP*a$ ^4ͺ~Aٹ㾉 @ @ @ @ @: @S:{_ٟ͟5~>@_tH\W +gp=a.7Lɡ=͈b&  UEI=gb1.\ӳ|{imo|5ʥ.1kir*O\NP!3\K&C5js1D"2>7_|kz ~gGQ)SK ܘkIܴd_M/5ENhg"1;[iMAEI,vJy.˾}Xj=ih"M4Ƹ8tt:]nnW0g6ɲ̗pwJCsȦ;MPz_s33dY/kV 51l,IpiNI|ue_7: mz7* nk:ccimMܗ/֛AV $5DnpB6 /R5?gWIWƜ ?Ą:M0ݥ_׮Mv'߹ yRSa9jAsѠsj4Sʾ{/&De )R \h!~kI _a`xKJ7+m5"ǏyM+KHlVcmeY&<526)1?اq:5-ۘihQ>g*ʙ3mϫxȓK6l4#HxW +٬'5 Db͍ZcY= lX`^46O:կͺ7zLI@f ,Fo"!hܠBʝ"[ڧRPZ8۶Z{TfNNgKUl$.e7d+Gv@60\3 |}O3:>Hb2Lig)/~1 fFy䑒x).N%Zh$~5h$N(} SR.DYUzPXS8GͭzIhlM{,Ջ{]s0c../'xJ{?Qw/d~P ?·v\0Kڢi;D SeXdȑk8?]i.]իWqmV^"z"i Ş+lY}#20N562üvi^vvt,Tge6_ƾE-qVi(QLe&Mf^y_=az7Y:G 6dYf|\aeEet4H&a488eX1aUbMtwB#[rt؛,*KG(.h?21E@ @ @ @ @ F 2/H$q[~/| ~W'=,Y(!X"OU%" ôʕkz9Ssgj/X(q"K,i*I~D[ˣG#d3&S| V׫hz1eo`O'Gf7%ahBjq'Z[6~j4iB<ᩡFF Pɦ^~L,^$KN$5nRՒA4Z1qFh.kfn07)z_&ƚ5V\# ˫rj^ܹfFhFi\6,RQd/2B8C~#ș3::6DY*_ b6FWb=UiWx*k*G"w10`C1LġCM549b$)f6N0iC)$DnBkuݻcE:݅ T-b9|e>eu595˷ D2D#UubԼ,4J ["D(Az_kVx;7c@ @ @ @ GM@ FG+]AI+Z\ <5e+bc߳ëximɂsÊ,=p,׈B!nmšr4T%&$N8qJl3ZF|}`Ylk#b,xv鿚{gn7FOڰh'Z˽WLݘ$H&7Y6h`pFjfn0 Ȳԑ#,unrݭ|a$IkF~ST qZ K/HO_ D6*}Dl/ /Ta4;&MVtCGQʀ $o\`1ۘǸg;;ٷύJU%:6*6bl &M?093w]k<7 foUgN?) (87jt]c%?|gۨm'*whyͶK5}I7ebm Gd#X*Une(?{@Gڙ?) ms!F?^|~rV5HO6).0zNZN"loƸslr-qhK0{1ƶKٗuPԾt(jM=-1}(*BA>L繴t j$nZz/+e_%7uŪq=,R(m&&&H\OW`?< wb*/_PMlƖ¹vϯLTDg Y-'cWEP / *^/ ;+yQ=oTw Ok%5K1**%*dfڠVĝa2QE5b9`رh?&v8KQ:F;lÇ'Ó&V98q'ct3э9U-ZfL Ǹ@,+v]]k [)rWzfWJY׉_;JXKU.gWKJ<ȯi|[Qk붛]Q1WI:Ӡa<4:"5zQ:$%r#OûJllJ#"owY /?ajGDe݊\n_ e&ٸ#)N56ym5C~/ jwJf_'C{Mj4 XUUzw\$q=s#}RȾإx2'a0=l(XQE sL*"э8eYR޴UU+TC]{vƼSq|שG$*@ @ @ @ tৌ4Gd[=1 UTpѼp"r5,[2~Gk,o0XgWFe>7$m1y3"IlrӞo|U^FVr}xzXg7ܐUsCDRQ /\q.;uliJgbioj垕e¹o)(6D .锇.ѳf[WDjl&6"B֯7ېl 1^үpg Sgk|'<r}j7$9A :_Q(PhcIq$r(} 2k_>eMȸ'¤Vx=OJc9kzUEWjDߪֱ%xv@AfW+h&߾n8U_%p(s lį W׈_Vn)YIZo{ʗgz!4Ͷ2p<ħsbufζH L!\-T◍PPs&E/t# UڜE {kEkbo8"1ZH-r l7W{,>P̫H^|)7R9ߘWߔl2T~lI#^Oi\v4$N]T#0BY^c%@,rBٓ 6m${[*{Yz'o2cn/{bG1F2I -b9f4YR;̝>FXyռiF%})Z6Tz{hp1dIb_!_mXZQCU"j,/#hyffE7fgeVfQ[ݛ,sK<{Iy@n?>:w\ }n/M)U6/Ug8;4~(A@LC^kZȻ^'uرy6v.TVJrsMcA {IiHEH `f`zG{{zeMA9><ۢ{y:ŸW51ɇPgV*JcSO%xTA۾ SG6: TQ@;).^Iw\xŠyw3yVuXq~{ΩΜU Ql d2~B3: iNg)LJ6[}64}>}D1#:2BvL0axz`v;I6d9$p?#Hse5uҦ( Ju:Jk.N6u&q2{[_äPC\{fOp; c+ȴ3BmpƠ&M8]d#ju{VϭhBYPd8 0``;\gft_n&3Y1l\bʨ+~]ʺo9ݬ~eh>jon6o{,SzHn}3w6{'VAz˿nA)(u}> EJym.*&=O^-=O~׭7qf80հLzl5\,B,W}g6;mn8{zG_zxcMd`hEɒ5{kH B#--!f嶚%W8='m{\Qu.`Q)pg]ԌO}=##r6]rkJ>O00H$m>R&KsbG~ȏ{?@,5N&]7NQ9p`ኚL*D|ƉD_UH$B,-.˜}M$L& ?$iiʛz祉鉸u췝 ; H7Ws~6rqOW#^*1 <PXvW¾Oux.,~\"#Cw! }:?ke.?9/|\+#6Rg8S3]?o?F )#&*Zgr&+N\ 9q(QF,gLbj1W،z9fM?ǏNoob ªЗ>=.zP: +#NJ%P ~]) B'Ya%I4J++L--wM(;:8a(;ÿt=dV뜌?RE\mL'[Ü1FskxvAL%v1= 5uXW5klE(ei'Fu%1 j .KUւ?[em]\T}rLOr5&?`5Qm+Vkq5*4O+s1-ar3<&# &kO[._b?$yzp|D]zVK=~(cLs3m1WESeܙd!nSTM v!G]ZfԸÕέEhjꡭ-5sEAsNr̶'mUkGMkt ɷBۮ/##"끱WgmV5MuH" ?N~qs4)G]#Y)֗)$Vdl5{juﶌ==ݴy X(BcɔW( wlRY4#ne +ƼR)E!  h}\k'ȿS_[`8*QS".џUsKIi{+ P1y|wnmfY­<6[_&Nь{.|*m7Oired$Z)ӽhN՛6ўvի[ɚnd)}\i\#\!={Dl Κ^ F\t1wQ ,Į+=K&Zyd*Y1OLޝ'⾫wm5XS K&<_i7\,8|) Q!b `T*šCVXoQ]0J~aa{Wܒd 6v_v淎q1::8r]\igX~ `ytNJ'29?'|w!kr[H?M"QmF3m}s0١G0Țs<&ֻvE۪a;Y"=(D\V},7 $-$ٴ^kߋ =|=zAc  06qhG^#"-};'GSwM \Y\3% q%|>897OohC M2tol" y|ΚFy ^;]M#\.gs|vPygCݝ+)9:tmH%qNtI'X]"*.]n}}r 6`_h45umM([Wu#$c.w+ZWU6ј|>vvϝ-А`K"  q4\.ACC'{AJ33<88oU3@w-m|VKl{|{LN?tww3;8b zj;-t^? +lTUD":ijm]+1V\VwBI)~:/nʆ)X`ә"͡,^.L [aCy >:6qyH@F&'L{;|S IDATKφ_$KhyUƳnpf8qDy \rpB6#.]Dw ߽o\7h'ŽP`Ez,9[OVֳG9-\t%K4u717}ۓLyG8JoLG.ҵ.St>XFm'+/`d•uu$ :;;m~.:"?!~!NGx"阒**| 5q@7\VV61_@yVY U\g=4c2~A{ 6>M'.XMgg'.\"Wwb5'{k["WgS[ߙ[>@SS7'st>MM~q5OKx#@֦:;!{ lܼy}wmэr l~2jQgg' fMDP?.mZlew Xa~?o?7X\$;\fE[`ӦAo uˮgj TUI6jv>!F/`=ݭo&:T_I$]tv \&k৻ M]d:҅41D~]j* Z-4nm6Ag{m]grF^^#?bU܉|~Kr[^i*0~4m{ tuu27ɍMslnc!x߼ǥKq&4=Xl<M iNF7C\.<g.NsWJC\S|; z>Ko^$0`Ǔ;<{yk.r2ٹs}F5yl=/";P( ެr %>@o O̍7Ҷgz`%~Vw3 E* l/OS7rOJ^#W8FK_W1/zgry_rzOWqοam.g-v>vl=OUO\BC-n+wx&;] l>NWo^L6'b4ƹϱ}NX{ٞ އr^lAD_=N]KJ (<0B]]OD`qL47ٷ nXGޕ˗e觩6=]`( ѵuk\k=oS}ΞK l =[͗0ssz~A_g{N>D]] \j*gJg$0xЛ\s9&9$ FZV]֗u@_IWzԉ$3N$e6u] g[)1ضYֱmKNO4@@cX@={zMzܹstt**r)έ7R 'Z*4ښcVC1wJz4V:q+wܹ7Eok MS]a bdk:kSڶ}n]OybL*HrGrĞξB-=O}u5Ke ݞ174N`xR)X:u0|S|dL*ZCdz2< AzQ>1c䚓1l($\+&Wz),1u LLx*0䕳Yؠlzbz0I1_j!~׾:FCWܮ9Z0J?{܉j=[0b#e1nxcRys 2!ne+e(Ȳ(5kif&CL)i|Gh؏jc3wyuHa]P Οy<0wN{(,ȇΐg網0`;Tl,rTd;Y/4Me(Z0@)J$)alEUJiSR yb4o"}hO$Ç?9IkJ&vf/eyW#ƴĻUآAtre8ms% 7|ty69w,Uf"45:w){IL Dz3==З F*E:I%av> ӈ\^U "SĦ"aw # FUi-FwM!ZZ]&vO/||/hYeެ,iJ2edmsk՚g@T@bb:cOe5_7:#b1 ά ɒӏ~dc|_Ŧ%3C~lĞƜ\Z  0dOx2}ɵ?) d5% iT;2 j DzT1Q)EIi iG5^,6VP=WZf`u~\WIW-C@Q"~~q;y0b*e[s;#Lfk:wC';}B0ֳ9\Yk_J$eh^a`?o cIfgr&ô$!g26ffrmm3eb7~@&uK  Fd%(JY\ob.V? ?6DF3zLo]{WUK/ O 6bh|<=?޽,6*RpyscH|ճjyݫp/n5?:BÏ Di=45qHu.4*7tKYjnGC>| zhrYƎkUrL~g;M;uy!3ǭ}|{=r|^L:T}~}++S%AQ~l KPk$IfO]3w2ty^uOuw{>_Ch5԰\h*OWFd -+77sGXXH?E!/MPIC3rHՌ2߼@;}FH++%\d=vn{_gh\; iksf HC*p3űU ?jH<@oI1dmA?yoI]O+ "#Obvē64[u|1!}:bӅ[٥cZX+,6)6s_]o> VܝZkCL9eJȥ^oq.r_;N[Q_LR՗9}&( |杯ε29#TDF؃{S<(K;k(׽c`lq[,Vm-qPI)ytos=_fir-Ԝml3ֺ]ݻ#~5w r݅Jt!iΗ%Fij"Ҧ% W{vx<P#!G4g N>3=NP`$D$hœIrrSQW).(%"n$Qo"bciOμX1W FX>N *d-Mݱ1n) t|i PslY=;}sU?SmkYyc:7 -aߡz/و-,iY&YVb&ACL~t'D"!4D>1+A:%]]LF5֙1Y->+NiGc퀒L H\ϲzwM✓H*(>.$U7+O"Єa kZ]&0B[=;kF/C>_ eډ&NGIdzJ.dg[h'Uk b~.b*1xjJ 2;垸DT<#A;)VC@hÿ'|lD.sSÃ|憎`3 񒬓"1oč7@gjKdŗr,"֞-uBR(N_ Q+̩bxN64-0W_#Z=;Nk`M¡֜7Pt%c=d^W!:gg<%2IŬX$OVJVEY&#æ!5t}Ӣ 29_!Ī Q@׬f/9r%*4'HaOz<08P]mۓk-VUFG!@'=yoLrh50멳Aޙz >J v`UJ%JMD"zzzD"سĮLr(4+8$g O6;1GkgVv[jF2)⥒MpR%ԜC#OÊ[^u4aE^߽0ߞ䃿(   ~cYo=:B*I`8JV-kj> gWQ=@ P *"P\ə,b22ڝ)Zj'JÆ[,E(21EP(bzfYAܜk|=.Tl|NALO}j8Dd\|XX\Jmz jfȑӱXׂ(zjZeӍff'ZHgԾ"'!;FD8! fptuL 5ky+P=n-6U6hsU;۾G[>R~1߻xlc[σ,3=5\9EO vzs% KSe~jU[U |g>5ѭ|#^`ǎ!N_>BfkUfMD:*BǴYӠTK/GIVn}VA$huwР%rGoWb#Guuž2^w3bP,VuYw@ =]' 2?XZs!%8<0qO$Aϝ?}Vw%_'aCx lX q=U|2i$Wu!9x ? '8;q 3 !8QM JL!Dogfڿ(ȴ3QTѺbћ^NaAúp[[1\`Sh?sɅE];/$[s̫#ɪjz3u6Y{ú`>d$Y4[`3pf| eͬ$lfh6)lA`g$4f  ,E^ ES#kw U0ųcHaW&dתqbؑ_H?L~!n9V_U)~U"u>F^bk="ڐL)jͩCI{h)cyUTgk9߾}.n>n.V/YC-?!Xηa+ 'eijYwt>d-+P:{!ywOdUx2N0U`-в< ?GdSN+ D"2J뿶jOs~Q!y1wg +ˍ0A#WІUa*렪G %&b _EvQQߜ:/nE2oҶ'>DE,ƳZ1ҭqf" DuU_ʛW& $ vtOMq1/gwdW lF~6nums= ] *le%,˅rlϖVڂ,<}K]u԰l V{/R' ikϳ+#z-p#k OOkTWݵ .b1ș<=xеkWLRSr&jtZVY->'[ 8Hmif{cK81y,ul>Evtt͈p+اu8EQ_gY*N]{z6rY4Mef8F'kų@0rhZg׹!ʇ3=9G?b5 V_WvǪ 7?= ]B}͚W B[O0$& Y?yRC\h r47fii{ ݙWik߿*?Jf!ne5Oߤ6L532aHX pn"WSH'=>N#!]Js[S0JXjG}_c|pj$oL܍!nqx"t1E`_։N2{u$Fie|OsRe!;izTY[[9;;NskD&+CUM} b)"ԗKx&GXRn1/ZpwsNjNhlPhlqG#ܯeq2O|oR(%[a1Fl3E*dP*Iʤ&gKk\ +(p "rOɁHL{,k vmtj8ln 4]::tQ&Q4IH3m9 wlkƙ~㳴hx̪XաdV E%rHcZ$X'R5X1,2Xs'vld%U8I9>]4r WA&f'Z\P*|a)QW.+,/(LM̼4[FodX; RH( Z͚QUmlIaj--DܙC^x.caF;vD'4&ܝ1lfP`k'I@C Wܺ5rpjy n*6g|Hs"w""s 2wfA3mnH4E@ ̣Cb'gl8UD4ue__`2˥"/Q7T䐱Ϩy;hͷe9HKk u_+ < _7k{M>Qu سZ""=Q"2b5kmc!reLc4&}Jowx%J.̀:as{{\{,gU2AX:yUν)z[KV\THBOՅXKZ <}> TUuz[_o[Xűv+*j% G[LUa,G])Fj*dYjA@sA)]#rY6'g_k}. )~^`P) /_Nr2k/ad)twAYZs$عspBG {O>YjD*5;^zNU5<33?ʕyg@ Չks|>a)~\FΥ9L:?Ǐoeƪ~5kpr$t,,m {*/&9@Ɵ'?f_i\$K̨|r3]m/eDFFʄz#0d ):((u:f㳶8c,M0>&$aAZH +9lg^E[.0і UP*cL\cV쫫(mu/7`?͹9(J YVnwn%vi*wk + eƦȷ 6*` M\@VP4oWĪ :qG%R9/fgM\X"c~O4 CA%|xg :Qy~Cs\w?9MR.k ۬k&D6B`b=&6ky Lw4w382H(*un--C-o(]e-'_ -a.w"J6K Gz`~^ЌqEU\Ufů$-6fdju1QE/(egsqm7Pr{ĵ4U1~ʗH Ig:u^}_:Mz7 4i&HgfJW ay}Va!wL[3EY{u\r|A ٱ"a}ou\n 9z'7*R)MqJ^1/ b=s}#eY#\;6L,}ܶ)ܺ5/R+UUTs96xl|Oll$sw7VӚ233n^&<|E͒`Dձ%ҥ4ai|^AU)^cTV{o~5w" ߱xa&sgJgc}aی|%|6,I̬ȇrCyAFzU.yzH ? E{.0|ONT*)%ԂPc'X\D}kLryQ%MpCe[u"=7l[xO=B; <˪ҲvgoӶ1Br=D&EQ(c*MyiĎ6MyZAE6[ 0G#r}vЗv0uu:~1{M2R巠 D68@";KbDvN12.䪓n]d2Q Y5*tB:*78S5pbT/&vU8Jz )VA"'{9]WFHca"o0Bv6A6Mm0 g6͟[Ix* ZvP:,+r8]D:<٬ FW8i$!jb>\ s,͋h rAxh!Ԝ.F*7Du\I0Qjp[ˤ.(RGG*ǯ1y$I,%#fP|>Bb!B1B6"'X{=mj"O$hQ 2fzjޕW_=F:mgI$ږ(ȎIF"h1Ό'6;Kd활sN&S5[Ii&#'JN\;rnlelB)Sd[[Ȓe4)>4Ign;[T'z*s5Xgnd2^7 It^lB6.jQg+#0=1ΥtW G8rP_T_!w\ uDqJݜI*"GMI~X JwCct7ԭ9josh 4L3{`?z$#[^_!kRRRB"+Viy[u8(9ǚP%. ~(Q9xxXS5)$@}PUr'Du(2.CཧAx~4(2^K6Dܹ8_WM3n-0o޵4{a#a{9#hӇc";7~$y1չ:Nݒew+>*ڒ*Od$ >cxZ|ZQ_ Q~jlv̊}5 7eE'p$~xܔ$47Ur/WF󤗖aejQCDrvIl-a^FGO5 V`֒}bغI7Ad?:L6J; K9^z=*W/=^ =Wq.-a Gx<>`AQdjώ3LVc$rSDZr @__յF>4#fD"ž=#Gd+y d݆ngh6`QY<$4nu zIVRǏ Av:+zx=+|(bu-~G?O^nF_#5cѼKRRb!,7~**ٳ֎TcDXn:3H"gkDz{O0,H?>J=A HQ hȚV(x.tJ,L6KGAq߯[充e~HN'7DS5.1&Wg3#OvrVdϗkW!{ '"7"8&5 $ض}g $ ݄a> 2]>L1[ koOTGw>J|>DrDrdTzN'3)FT42K1pռJ$LpLttAb|7ԱlOAq!٤}Mn)k:ϼ 9-UD5o~\ܻBғM>~-b(m&Zp4sfb`3ejH=P(߿x5Ë!"}Dvͤ;0>ai\2䉦ySb}VkK1_b(׹M6Τ@Sb8_!Te/7/.oE Õǖ?۔OLB#/FirĔr?K]LftzE _16w_kahP(l6)k188GdA, j[w61 Z[M ʺlذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذa_/_† 6lذ~Ŷm!8{O#ؽngA9cba2bo9'6՛YX̆;ppmqpy2RRfݻ9?T}-+\N!I RSz=n7j6Wpy Lk8F*bc+r m.,iDIOSS 6Ʊ_WCU89kuVlG/o i$ lQ_W5 limh*ag6Wԧ0[u64U#MRRS.@z33?s;{' z_?΁PδNpF636fGBd^_d$O#۶q3 j7328E IDATX\od/򳖖^ktꝼҘLN ͸\<jWb'վ3FQ^.;a W|vz6j[?MvZC!^ZZZH&/( RS#1NA\Iڌd2JUt2L>M&:;=Ů86o*A;Y+C朷L._\k\-ytRb˽$ ^J[[Ѩ1wEvc~W\xʕ@ 7qo^z6ZmkH==xlز˗/Ӷ#'juF 6nd2͆tKM ۶*j,;V---DemZٸSmb60Z]Łuǟ| s:x'ܿ\D'c4)Uw̕NAUk'o݂ӧ/S]]V6o޵ %ែ@ծ},r}O$aw6.\{r_>h+7uxn͎gG>S2t!\z`h?;gg?b7xye-,J߯DeVvls12ׄ}@~3gp{Ƹq#w7U[}]SwOcQje9<,^IŁR;(2{o:E#l8Ι4-;\6jjZ 񪶶( {"YN?Æc{LNsTma!݌}i_D͌qϭ$ ַ&Yl8Mu&.Ss\z ˺js:ia1}͝f3})5p8܈ǑpX؜fӦ'X\dm.6n0!UUFnApRY乱 q5~6O9MLr}\UG{;؇"rZ(}vk54: =Ј7cyfϰ^' 5ӂ6=WQ-z{q:E\6N.l]1az8m\>փ|.SNR]]OaC9"w=~:ۼgOӳ8G_?w[E6uUe[LSqM\Τh7}_yXLn3+FX7Qg\45%l7r}o@8yp>Q}U$ܪCyMKi؇?熖 :쫯GhwyWtk5~{'yˤ mm$7d |Y ۫s\6ͧsW䨡e9R"Dp}-D"'{ioo/*$ p|򓫲h;k~/Wg]ݲgΝGEt:IGѴ$;wr\;u0y ƍe=rr:Ns 0: )ʫd۽$/.jU W8ֶ DU_M[MScoDx<þPw̜9oK2x<ľ$[S/R{Rtx%a[_iv]_._"w,LJ'xߥ 5+pWUKA_ș |7syr>ZZ-8 ̄YU^^ <A~ S4u5s6}5W& Ŏv䍓6~#?ꕫJ7r|&o/;v~RjgU ղVSL xXHLIkacXXi~=}͛6qUr]px8}ZZUW%Zcl>Ń;u{9s7Zn.ō\^ )ʮ3_η4'$޸'ظ-Ÿ߸z\u'Լ4m8}rB^OI7Wu6o 4f,WCD&ѝ0>]իI]/5VRq{^Ɉ1f+z(Bj_ SNSӰ۪94V$PS3[mrpt>R,Dn&h!)4и0F︎,ij}/fe g|;K?sO >{up[bF2 {0ɮjjqJԪ#Kj&ce y_V5熞C|MdE~[/8l=;SܗVϤlqN6gE" 6ol{RU~7nl7p㮕+ZziUMRUՈվq Gi5z~ڹIEwX{zl<]ZVbm$sm7鿦ūlnvԣ̝,Le|>ZR).<| i{/S\()up.6hlacc/NAƝAvʿ5UjI,{N$8{)8=Yx_#{Wȉgc\LА} *ؽ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6E۰aÆ 6O<;pNSG>o>̠u]P<OtJOx2fz(ҳz₢Mddcls>NP~uJ8[Ke.-tUlVz(_FzAG.fu58 ٢T3KvԧgO8O. YW*:ϯ.>l/ŊC /<>B RF g2gvpJ?__\닋R6;_L$P|%]P(/-U}15O3a5>_|-?R_~lpBxjN>>e,ca%RXe>W</^B=f2\Ph`U SS>;>`i1ʡ>ÇCp٬ kl.LO|M_ZZ%_z|j@?̴>돥FC&fu=Tھ!}` /,,ǎ3Ź6`POMN͕ʰmr)귏/o?wN/%t- O=>00PYz!)\J3y%xL:1e Ͳgе>s\Z=)P\ _}ֻrY}vv@ЗÇWx谞[ZԳ٬~zh4T'Y]dK+4맂kS2&_^L̿ Z],&yW V$ b\顐/-kΆ=*ԳSӺ뭩WJ0cdžTF?|~wSw1]]ʘ,jmdV811PVW̌ԭ[cYg;:+O`[_~Qϯ\vT̻ĹJ}ճbvqC!]x^UOJ@0WlME\.[9W^/zWjR~zpPW_zI +UBz°>#?͛^=v`uVFC_>\QX503SQsBX9.ې Z]1m8WY>.a󦺮pg)|OQg}?7w gQz&V\6O 4zcX?vaVZTRVXL&;6lì֭df{3f\VЃo i;O=1R8lV׃^ԥQ]8_U{I*_/Ԍ翢z~1}HKy=V\P|6 i}"tL. 뗚`.oO>/t} J1+} LO>03U+ozF.J02Kq8[@P0[z.4cϊY4vVS:wktϲJRB駌躮/frcsb}n1>3eGh}MiB.է&_Џ?L:~I-=~ryV}BS^CSrШަ`p@,f֌aVgo+-..f}?%qY;!=;޳|׭.e _gVBz>0UW즟ǫ{}7"YRwwrS_H~v=bNK*GB9}z {ݫڰaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذa6lذaÆep]wx<ٳg9s SSS;v^vo~|3*A+>Ptrڵ} Ŏ;^~OӴP[[KKK g_ 5Klg-0$U"Y!A׵j(y,|X?T^tp >BTS/y֐!~3 DOA\gyX*H\:*χԻ%2p@?կB3xEĺD˜ٕ!N<*4ؓjjb{LdJiWpzH?j?QCKOUqFAVVS-/Q-֖." <{c9Up ¨Z,!VU!'Dq|UUENhn^nfY/bހoky"lw@\~^S5nF_23EVfpS {H$Œ_͍Nxp%vlP2ٝ(H[2jDUpKn3YܒX[ >w駆CuvK۷tz;$d5xgQ[}g\$HȊR8l^ksiG/ںO^p|H3Hncy{hA8F-%]ʲC^R4=c.5h yX`d[30*j*7dKiW_Qp[iB%x(>`D"LwCl±N:..Lu$A$SN? fdYFUUyhF"@SZ{:qQY=Uܨi`{YMe>(HÆܦG!h+^j+r**f8XgEr5u v#Fx8K^3ÿE" -E{;^ͱ(t,rt-ȗ-N݋*A< 6^`j׫9s~hqf.5˕:][FGD$dNN\ :=k-G= (#QU?'空>|)ܒ.Tn:sy(2gq!=jĄ8\Z'_?, JUBܒEVL{e6'po? by1T淢 je@<J]Ν<[=W86j$؃FyݘPW_$I*8Ubfa&$|>4λQ說PG'O߻7X뭩EWϭd-}"?LS3inh=` xz9j ErIģv,2͛qp"h(bCU5{*SӌvK Υ _[]c3ިB`+Ԭx f]?T!Cx-X| Ot:GF'A4Dzؤ(>߇߇&hĥ_qz*ssFmh҇R'@ ΎYo(riH$'q!fت?SYg RwW U"FJ^A嘙DUkы#5zegl*2.W3{:I *Hnm9S]r_?Zl(KN)J_DS 3~HP%UUȱ#x= Ԉɓs^E}R4sj v5#)]P,?6UxgϮ^ ܯj( ܭU >΋5\ZYBl;wQaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆwɆ 6lWzz!GSS?Z=^{54MÑ[{?m0@~g}۷я~7M:;;|_x<~w~O?Mgg'gϞ|ݠa_(pk8ͼ~8>{ppt@qMT)af2$ 7'ko21I&0MHSw&hȡi182kOxDfdl {ՁY:qQʆE눏{p+@qwS/%3p,57oq:$=y=)0;L)d\C[$TZ4bU{q^~X d~߭ 1Jάt`ґV?toUIVEVx/F)ܒt ʾZ PAAsrtpV:3*#-#R]yt˃/,p߿=O%Î8` %7 Ǯ~+7ؤAce%KB;X`g37>4ca@! 4*9` 2+!.-T~SGS"u*ڋ-6kW=7:o-k_J IDAT6*\PWR@@i h@^f *֯nTU 󫿿ŃWcD_?37>CwA^Ȳ"(+ֱ[#86}_&75I%WFRf $H&EZţCϥ&8_uD9^7JCl\Š&Oȅm8$yH R8%; ֎pk;H~V#Ѣ }m}/Pҫd&CNwk&%ɕdkEhoo't}sߧ5QS5q4UC@ϒ Ek]O/+Kyaqbk%} ǩ٬1ԝ͖V4[lk$1#FL:W ?QR!?HD2o a[%nS2MS*gHg|CY:L]S/MYXkOemECyn rj!bTE"_4_xlKyirmg1ZzŤwEX38O~\(xnPJU%#5r-1<<4ɩp0Yg=&Ul7ɢ?pőj.;L.e?!9+qPIL䊫g땚4>Ӷd(s1z^&~z+LIW t|3KIzstfU +qrMH#?HRQr~ @a*{qmrUρzگ\!P_yLso4tGc{D}kBI24jG&n"O'O Q6V WJ }?ord>,9g[IʻRVn;i6C% F4S63KI櫐rqz VjUKւ,k'w $x pnWųߋ$_Iܟd%UtYzz]UTn?Mz#甠fxW /sB\~w wZ4RniV=lTs:i4%tKlO׳m#:juMŻڒXb%XHثH##̤ӕ {WIC6.snBSU#GJ_ 2SixwO#aoI"\u$ 91 XG\e #Z`RUKw1=t׺-NdZjGOӑf\LB^,շ$M,u;o6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lǀMdÆ 6l+o7n(FGGW~re~~ooʾ߿}6rwHȃUϹ/ )jqv?lli;"R6/7XL)72X4RiVK_C^.i7xٗ-ήxHP-LG h':/*,'I<|~s3HO/S87:ݜ?1xuK ^=j=J8vH4{r3[* ҃@g?Fp Xn$tj9FCw%2V:J)?`(doC$EWUH.Gnr_U/]gdHL%d-.շI :N N&J.&vB@HjyV%]]2z]=(Zi8 L_qZ *]P.(iOV>:u #Ih&\O0d߾}ư}F\Dz[=@sxF2}t? !?BaeIz{+b?A|n7S]m M y"IR'ܞ! b(RqCmyX)UUEH-RY7[&,t_B`89}z2jnΆK]]g%"e񷔸XFdk0VW;YQu2sM稁)֮QIBD{!jOi3zD R1qH,{/V~4|~g^z[zq8e5\i<ȯ974gw[[Pla.45ǿ]X+ ͈mWd'E~/"^6!VAA`ψse #ODMariC@Wh_ YQ 5Wj$N xPrM!uMNz<9B<5M!G<5šݒeBij,>zwcį~Ascl,sה ~ԷT ?^I1$۹};~D8 h2 I`vVD B̅dI: Fv^kΒ:oY`Xn, gOޯu uT@vmZ!o;NQ=#868{#4]x{OZ Ciwvs5c^WO]B>47W$r9(by'u uhƋћ4UprA(2j{a[ˤ[:>[QtfD_Y"o^<`~//C6F@(%6{sD)O s[=yvntn7xҎ ,|n h@7!lxT*]a {?,kt2 9|db1M rlf[&. 9O55;gäV9 /9XȗeqS3}7ֽYL{]FS~,Diɑ\ ޻"SDAE^YFEfe`=5 $]ۋb)YF HFe!h@aJ[bkƙ Ζj[Mn0ց:ɤfs"׋J5Hs,5YqKqmp~73skLG <{$ZE,>Y oA`gj8ZmذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذa6 6lذ saZZZg}c㳟,_W'?ɮ]:׿|s+po} ͹srmvşٟ>?3v[o '8z ԑ^ {Pia?Wplq_28GxDُh 0}% mjDGD"B&VK}]&f,Ni/5i!JrfxC0avTUmJ.^0 bkZ74ojF@tT 3=L 1.(ȏT+(?xTi qӈxj8SCoH\Phi,jyVG GY!6^{\Qw+'6w+=r(+V%fx)jGF~&Ă Faڊ܅-o]Q{jvZ~ }]_ph^{8? Dj*_~ޞ2e'Q;tqy k-+׫9?os3a9sX `#p5^6 tpߛ4<̞=a :c+|`Kz<%~j_=+no݈@;hRүұ3n7R,,Á=ܹ3A M8p/upŨVz|.dŖIru o}u顔L<|S!K? Gz%_8.Ⱥ+zJ $qŒTq+X;Azh+jR|ݸ2dmv8B pg @,EUަ J bVT5'7K y_DL1#w@xs,KY͍J_BƢU54't0RM ]&iY1L)A=$Ub619?YT _^}lMH==( ROހ{o| O|$#Ѹk?>d$-䶥>Ov3>䃍^e}bهYS)Y)]@U!R &-AX>/V4LʖSThn­VATU%,?{yy0RxMeI\\dt C,nJv4޲i\&ao46LM7i42l+2aC%Bo E  f $R0TVe(DwDyō*V3w8k}Eml*SP ȷі_LkaZ;|Ç>|Ç>|Ç>|Ç>|C/Ç>|}c~׏ ?|@ ye/8wQA2 RѣhcW?dd3' m;ͅ6ENKa C䮴rϹym$PJ]c|8g~!^}/8v(Rek/l,V8h+ѩq`| D)eE2H&u;>ŹC,vy7{b(mb(3۷_5s^l} ibIJM\ёHpaeu)[fH^b[&@[3AM<C)'v=)*U&}:k66H$D+dQaZXZJriJE࿐A84Nh8--m's| ?vl[^fW. sRF(JX&)r{iAYZUKvZcH.#.HX3 ټg6a6RIZӤ\BZhTc9y "s,˜98s&_(a3F *W|UoqV_MBDJ_Y]]*a!72Op12OT敏rH o#MX5Dv-scbLꬊنy,) 6 굢mi&tDn.SUVij >']u_xˮx)FĚƹ9952kzD:$ːO1xvՏ,q>:%fȄG]ؠ{/Mb5>q iI9򂀢@kPm7ȤCx:O^XGe J%^g["u `3 IDAT ~de:g9}WYj kK'ȒĂf#CJ8@+jLsAB2I,=aB~KĵE6U]HmN",=|ӲR_p//#EkK4rX_ޠ}H͞IU!P--ըnD&Y Do9Xj׉| 1戲mt+NQWmt]PPy~CЭiIR͒Ԯ*{ su=t{in6;5u.T6~{+tuB.X+w&P8{i{ AETbޘQ|нFBG{h/hz) O ,x?/fQK|~ؐJ6K4 R)N#ft]3δƪ𡶾6mAxgy jγwsמ%Yb5gv*[x.VA qc?`i32r92)W$K5{ƪT^=DGÇ>|Ç>|Ç>|Ç>|Ç?DN>|  /'<~\s=?q~}c|_˞|2 | :~o{·) q[*LNZd.7Kh=m'Nc󺦳~r# od2nk}P7| 5T f%NdE ׮XQϪH{S#u!zb-АJ2,& ӞҴʓׂ݄C"aXxG"YCXط>Cj+G@8qhkګ+e  WH-QwO2}`<-|՚  JbaIq^FP "$Y7UdBEDxظ@[#MuCIL]YKW()R$(^=yerQ CȊsl(XE:yNӽ-±`]B) 8HgOAL%oP:K5H{! @P= 7": :\z[Ţx7rzI;qBA%q;DMx8.Urc.Lj"74I"sxY$֯;b~+SC%jmq<?X\Tf#,.Us_U1E?Qn80 Xyx̘qx"dOSXLVjq3PUZ^Ah.: u`'fTbALe].t޳U΁Aw" A$ɨ`Sk(ɬ^U9[|ߕ m֬ @cӄ:>z問GuUdۂdxQ_ 1W_=L_=64+ 2ˋL}*RkmUs(,Αvk~_.l2I#]D~Cۘyuᮺds]iZFE=n s+G`MK[ XWfi)Y'xɻ'Y.zr m5uݫ.!{VQ_L?{iy_ h7E XOݫ6 /с m8g{N:$!&4AȤ-ҿ@,:w>;/fEBQr_+NQ@ؗuku>'YꄝǘXB5;EU/+Bڡ5>KPY)d%XU3u]ҥ8O>G4O aæ%*{zk6ʒĹ9P,,׼6m+r7+מ\NH0؍F-eC@eoL2zYOqZ%m\pM;6#Z,V h gϿȈb,g.KUgn9Bn9\ӯX(!^}~dw_dxx h=!֭zV_Y}qʮ\o\;aU( }(ĕ;=zb/ *[G% c~h2)7˹kwe|>h%^j @x #GwtTo'-qMöL6F34:W~bKQl mshh"nv $Ɔ:j;o(4]#)pSM]+e$TWWԲC[?UtmxXcS!ok1i}xvnTk/tYhq ȝ!Ԕ`kllvm8VNsMטRPXuD*}\g|Ç>|Ç>|Ç>| :Ç3\|#G4_?SoGy=ܹ?G]w%^zC1(of̿h?ZdPP-^TP DOY.C4\]>q+z=e?gμUhk<^#,o8Z2ϺטA?GFX?PB8` 93UYs:KM Q#G GB姇o>D{wqLmoJub̊%pM4dDNjy-bzV%wO3`Pp\G4YD."c:j[ PHay9ᘷ`Y:4Dך0)eޚExKD1jB5bij67&ANp͓S⽡xmBӧD"dg}qqySL͒$Iff&A8y#o9q"!7QswY.z^H?V [ݭX60 _펮I٬7O6[s*0~mdTP%>}} ?{Չ HfgwrDUif/Ηcrxg~p* [{ٷ/XS+DZ%m{ Պ:$(5fcԳ*]aǬgm5]YF*9yL-{ w߫Y1B , t58A<7μv w-PVmUPHcj*k 嵶"A6{{=szVE xe]P]Ec^QH6)`.Go~iCw\ZJZB7rhb:f$1]!RI˷ܷÇ>|Ç>|Ç>|Ç>|ÇKN>| \pG=l6"?~#G¯/~UvvttrCsv0Hii/[:YtMgI]EWjHDqHIdӧd4`8 [ HϼBݫZ"㷍Sֽ$Y&V٠'HcR,- }A%PfɒEq9S+Apj%X] mk߶uu/\5,loT|܀!| ۏ!~ |nc٫VWczM8Q5egZ$µj)[k+@\8xL>DK|oOx0㷣øg.0Tɋ>v~FE:5=Kh[s|NI<܌zV%˱`4ea>d<QIXC=!;/?e)37SA$)#ԺWQ[GjPqM(e(az?aORk+ND#F1]qg=Jgpi3̵ה3H&"Yڨ_Yرi=myoqElUSK֘áQ/..n/v\M"u<o:W?Oʵ-ʚ1$^L);69DD7<4)Y-\c^#Jvj -U;OBqmBr_ G9:E˽n[j펚>{.0d)7=[GZXѲVYs:l㞾p[0}႑ׯ/3I6C(ʇYXX&:[f<3xz2VN1Kq׹b6ӯYcq\ P^͜lĈOpQ:Sbnu9s@>|Ç>|Ç>|Ç>|Ç>|0 :Ç3!g9{z(  "I$q=կ~~lll|)Q'$Τ9s`|*IV`n0ɽܐ')GF"bwA = 7w5$=bS.5r;4 R@9 ˡ)fnR/ED&/;|l(/$rO8zt ]oLnOMOIM6L&8LN]#S/y!f|9LYE)Ɉ^r VGN} P+vdIbZRYE~;Ts&&4;˫<6b&Cl|>&wM[UF*5N?i  *m'u,^*k:%ThX'v`N)+UμmOdd5Bt1g$Yf42gqFbA۰||WNyIϷUjm\L9($ӬǿX//\/|UyEl?#y5T_;:R! j!ۄnP)uhtzN[yd3a5MTŻWV2SaQplp{]W_$ffvtRStʝ5{F̴a*E'9*M@zo)<ۉ^OUVMח4;!I27kϵOGYeߖ-yֵDbfJUU% 155h~fKgؖ?ϱW/7m5~/4?x͋oIcN>}?Ouk.xF{UKX"Q]+lLQ8݊LY+xO~ j@(`*XԈ]#-wZ/o3č07v-1BPG?F UTl5WU1f2!uDY?GVGD"8'TKhp,=NGE=X%1P_AU91;˕OL% bCEѻ"DrvnQUH`./:|Ln+i=ΜXr{gʒDkG~myc=%z@ <ۑ=+k gN#4P*$RmA*@n(`yAཷћr5}8i;*Gfuzϋ+n/uSQ#LyxmQ|$חvluElVFmB{7U6D) IDAT{©D:q}-ReE!r-Ri%pц≦ӕj.>1?oA ^|3:i9[3uzr&EUU%Y\P%A(T[[gxΝlر׮1cg""cyI"g˶C@ ٱڌWD$ 5zRjYRB 7;$"IMC^r\q 099M&XϿEADCMMkՊf{ { K&U|Ç>|Ç>|Ç>|'>|:<'?fC`K.Z~^[[(/_>f_0;uO=x~?s^=7Yr  D "I4 WDF+OF4 S)ZB2hՈFS{$vaORrKDA`R V?Y}} Reye7Lӧ<5$ ::Y. n!=]k] 㷍9^2 ̅O|ގPQUpMN4R)Nt:S8ۚMo`cA9W2+B^/Mq-IKSVέeg(9Q-tx~ͱ^^x|zO{vv%(:b*EXT8p,3ӔRbbHaїA0}rƇ׫ (R'p`u+cr)Z"Ͼ/,/,0)diOZ\%)Fs3??.ܤ"+83ji&A,OsgPXN"xuQhFAQEA`<fdzB_GY?l&_׀,$ EAUU/}o>;k}A~ W_U>{}!?yhC~wo)LWH4fy^TByKnx4M#5"cHM% J8JKJpHDԿjLUO }nuƜ9oSJof(3"i먫ccV=Feyn 7X+↻6*}5\lizeH} AeZږ~a] ydJwpG'0bT@gYgqMEmGcڐXOjMEH$uHo:,l'suabgsh$gNcb晓$ !M)a-+X(hj!l33`bg[}oU`o?pa_Q ȻJ.ATǼq1X3^_QjƑ2L201tr/^o1,Zv|hQl|'ϐLz '*HaCw uůC$xxwu6lMiG ?=Nx}1ݷ:Ety%uՑ?j ʲL>`tTPP_z-겇cvLq-]h Vz(GP>;X W(X>fEzgd\瞦":n5$ qZ gn5w.d= Dc[WSEmL&P\5Ag>OwRx*T%،Us,eU?/jřWY]`5eޢqAU[ Z"ױPA[j=i$K%9|CL?45ʼ˒:gOpwsp!>0qwݵ_?){U>|Ç>|Ç>|Ç>|Ç>|Q‡>|099ɁH$_{wl߾Mwff}˿q8@{?r?qBsI,nQ0Ԭכ](T$x߆@j]Ct j)ڎ9X #"v,[Qk LCI]NH$HsKo=]"s;yr}䂑קcG\ AI&5@_<Nnr-?yMvw+"7y/ՕTfشƫ蓨A_^vg'Awwd䞪?w!s;/ם۾k]#\H?N(#w/KIF\ skzUKI-s6)+ԃM=F{aiZ'@e$ry !o\ϳu'ZT,qa;&vlk ̉?S63}V- Ha-nt]c)b KR'y~kS+KA֭9UYT&ikm`Y\ d5٪3.нğ4}}1j|Zc4#ƫ4Rma5L+ikXJ, YN,sVS!f'(}oޛ{ԦP+mu$tw7y(w?O;AxsVUe4HKoƞc暔q<,<ݠܪX[U"R/UF&jΎ+q/{N/V47}r@[-;~]Oq>+~vK\b/"x1'+5!3H5rBH9xsYB!&|g/[k r2,VF˶ΖDz#]#y@pUhnAeء.~CnO~$.1>T]qT])JNB.7qdH<]_7& Nof:vy.b_Tv~{iI B6['l|poXXnȗ#>(۔\9ĞcUhtX⸞bqqy¦l-1p䝀8:r}+u5g=#vg%Wc=[.%tp,1)lv}4]#9k*T1)up|Ç>|Ç>|Ç>|,۰}Çf?ȫ#; PȬ]CCP(ߑe^Tly~ҙAB5q0~O^7 (pGOPasOy*NtGY g!((L@ӜO=ڽ6{*boXc dYfnnRD29fm+oSKAE@a~Ľן$;n$7vn~B99$$rl"/?vX> B!2hPav`륿cdO7o:DŽ,$33¬fwʨ)5wQuv"Rz:0mYs>ç癜 y]'WŶׂ-,A[@%:O=\WC3&ԋ*ȲA,,w?~i1l+G~P8ѣvNxK4D"tfs|EP "--ZuCFǑPU R\BAH^fr˴g LyZ#MXL.=e1(nmJ 0X c^M}UM}ZW [Tb9#u{*[5s QnTrQ+099e9p[QfgO"LH$X=iݬ=>]^#۷o??;>I~o/?~r׽]d c޽U{S}oOױOA'1^՝qC"#,/Seay;exmRjv@ ha>1*TH$ӏߖݎv(DYeG}ed~rMCTdͱ?-FSlֱWRF\9^s?*`DoF&-0 EFGD=NM MEA  5%f'{U%,Y'`57$Yb9LH b vBtK^mA/WrLʌEk^>><7! K5lϣG9zt}9ҬYvÞ5Iku66{ڡB056&xUu  5.$K/^#;묭w_jDQT 91'm<g/ YNj 1Y"rBGF*ynLʭݳr.$ĈDG2GY1懽*Ζot&^{m;gU BS/%оGjÇ?|Ç>|Ç>|Ç>| >|:C:(GسgwqWU\B `~~ /T}nmm{@ {~>% ̅562 nI^NW "$j}&"GLlK-awYTb"L9ux8l(B,x쫳?Ėbc)_g_DhdEo|4]#8'+QnRi"w~mI,un9zto;#WTKjE&'c\^pIgE$xBLs@zBC栮kh?DHGT_ZE'4'R"84[FG*x͈Fw[1 HT A<(^7g`gZ&FE;$L!>8KNbB2R>$IK*uLUxc[DR'Ѯ_Rŭ+H#$&vxQcwD+mRUuOiJdIkńOG0'gvڸlhnls6c]rk"#uC("/Y@39[fݓIvu):e~n&Vol`w.8u{i'O,sd표MqV<,^{JD-b GMaD/JG*l\UEV'N!&+ܻc?+C;g=ohU!kb'S,%t|;d1mFMzOviCCE&a,F֌B> ]Z%\'\-[oEq7#8{l[<6Zt,ӶYT^ubrVaW*ȕ{lm捎mN$B_9,թ IDAT"3sgi?<1#vh>~fmA>9COU A^{ tQ8ii1QfQm^/tD;8?A޻Gu?Ad8AHD%Y$6qjf&vz3*]+NfYwfm\]c֝&eN85%e4) zشI| ^$Lo-/$=6}W]ٹmXYY6EHɆ>uaH`EQ4SO177'on-wOTd]أ)j҉4/zQE??s~/o>rRoo L&yI&6V! +Q2\lJNNlo'N ;?g.i>ԙ3TR!1/pJv௫ˋUTntUIwV 6o p&P&烁 (/7z|*iV߻hj_2ӧ%m߿zZ[rY?Nct`˗b=(& fgDQda8WiikAvZi~ sgVKquz}NQ`=n?^Μ|{;8y3x}ߢ \Wv=!btvhh7oOGI/xś099tÄmeb9S_װeYFGS(ɤ>Y@s]HvI)@q);R \e!/҉4>;I~ym^7^o,5Lg!$ʞ#w&)64_QFQud.{E %k>3qUbLjN+T0,.WSO}Q,z_PE,#$8bXCt{t~^c̎KoK@Iyjv'vVy0Ȏ>kH?T,."񕼴^.r:?IjY~3g̘{'ЭsՈ4P[]EQ8w8z#0SY\k%c^N75B6 CgmR̘/_f6k^UOI>8q0POvy=550000000000000000000000000000000000080:wFEE$}ؔ[n_ww1Lo6y?7m8s }_UUH$줷7}_Wرc׮][VϾoq5$I_?hnv^t>dNkį/v::ALx:RFo8P CHJŒJAUӿ#-)Z P[Aq#q!یC_SV27+-lؠ*vlӥ qJFH̗D\M8 DAsgX\uFH=@ %S|QwP6o#; DSfMq sh/Ekw-5(B ."LĻ| PENj ,.s _Ln^ ]FQu᰹*"3KS#Gq{ANr{>Je/ehXRRlRAUknɇɢ6'b.nGhڑʱY*=C1+m)k8BE@/W/ " "aO^ZI:ut?pflCō-H7KQ݋xnxwx@o/0 g$ýGDySذg@FTCK27fP`xX;T&9+G|gA\QX {UUC9ʬDZE _Ua d`1ӓ "K2pfh.`6#65 E6\,339`jjjE/w/$(ri!)(^,C7B}YLs(G~}h0ZUނyƌHk:nD#̸vQ/]]7(ͱSZco>'jجqF.K.{9]J#&g8"e07$|.\rlP#C޻*˫exyUކ,s9D4_w: TTVê0NiFׇj'b&v5! CQ`SI͖7R]jJXھo|ڵӧOo"ĦL x]~/ qMBPQQ^ >Eitʏ\aa˱B$b?+S#0:Kj``KM{v#UNcy,r/]?w{]Odr69֓[U.U2ULDB{QI{ ˧O58ac^~ڰ˭*MVCyҚRU%r Bk[ 2-ʨP ϵRA KsiܼzΗ}n!0?/"utd Ĭ]媡zٯ%ش.Ꙗ,i?KK7"vfIR⡢޽.݋ir7jѾO22>*Eg dJd, a~Gu-^dQ3A",/֪YF{X\.-7xH.~\Ijb쿮 b?D5,HUIVnEw#V?g2-!|>*19ݙ݇125Em,(raG@-+ G$ST^D@Y-_]D(wfj```````````````````````````````````1t:a``````u/a2~|k_d2pMصk;w^/sN߿ϻ1L^ymV_ (|S8۷oܹsx< |5|I>Or\BMM gΜIo6 )(2&"d 坾L,oZ bnm' G樱˙vE0\ su&ў_7'xMi]+Jdq1ӌĭq8>@3-t{, a4ro)hno+:aziM~佾}LQa9 ~U`>vzBޔUkT0שxڑL[hy2IA\.ڔV s: sQPUյ>fKVY(-/';"ga7djoi1=3Uؘ,JY]h?jIp6510N*HQ"Q* Ϳa~0" j("OQc՟} ~-5D9r83@o{o]QtB}dq4&w6~p8ϡ`<#`NlnȎnu_Q^`)Q2d"58Hb6MM%eZʦf\&hgaAr$rUM)\S#̵&O?GV{_27O! bu-s-#cm$"jzNn/U8y*K4E)W0[ ,,vcÇn܌͂ g q:LꨢYf3*+ŏ5yrR!^]=zB@*Aw͎r]ײJŭ]] ++ bS`6"ss&|MVLOc;u5^{Owq{|]e)E%1,F }ϋ7Q0X\lQ# E ]&)Bp£G8OWH4="g⚚b^WT<3%mZr)i3 rĻ}©6rlf ߞ˗z-}\",˜9f|<㬭Pn}rHR4; 8?l,s?IEƞlv? dM"nӟ!Luuu[e(23yCV/f?jƷ.aqQOӯ穨~7>_PPhMDQd"^|1bzzo~x,jpϩ_޼#8?/sg8NGE c5o+MM8$Mo`faNJbἄ { |FQM4NbE:Ox6mqMN25t5|+ttݎNN$n1̛KnȲ $a,kkQl6,Cd&dwN$Y!rkfd0T4W0=rws3 dlO2IgGq)eS0W ωAm,uafyFf >];J2][P^ fhRt :Krv~F,ۺn֦ɧp̂n% \INff@hXmGI,b',Y)~*4'!Qg~S)4,ݸ=Tu9  4-Ooz˞&3aF+WOO#InEi_d[}nKرRz_gEr ٱ[nz2ɯ84ZYaشxcܣ 9qV3UE J[sxNo{)j<6Lºׯsĉ grX.ŬkQ.8!CF wmvZ:9qL&Iׯ_k_ j*EzüVt\Y^f$IfsŻnehgt[Mc,D޷qnLM^]H-};z(Xȇ䞡96Ţ<?g u2mUz..$ˉ:m=H0菟өav,֖<+7۷3Y&u"&6y/\{M,v̳xlx[MJ8L|*yhw{3'Qq /9wMM %ٳ'J1}q=lHtG]Ovgm&58ȁ>op}T:HI;ClE>w)"Gp⋸n:dŒs)5rXM7?6[(DB!)Vwq\F'"T&f8`>@mWK+e‹7kkӇxnwUJd``4[# o#luP'$r4ɓBp5ڪNۻv@,MfWf! L۟g ߛ7n\#Y\Do }H>T8˜v* !؜U$G `n~CLK *jk6KuG ۙ^\䛁8!)Iӏ|ԅ" duH?ssLR8NBNʟG(z.,,hA02*EmVUɜ;'"-؂5ʭGz`&]+_?J &xz.JKJڼB+b G ]*]58!֪[$-;֛W0H]P(ũS,g9Lb/5|t&ة,WCkvʑ#DVW~OyqOy|0̤~Udy ,0>eA:}W֖9@^f?5ȍo/wy"y8ApO75[>1:A!+ ؙyor-M?ʆ(sM\` *fZ}|GWal9ǟZm.r`x#-C% SqET< L˗z-A{Uٖd:нyN)io3{.EaY.я 5{L[X?g{xs)NBX2ӧiƿ{v\6=Bq it姆6Fa.?3ǃqzy XH÷9<ś&GQ^'<`GGѹFyk0p2S/h;/ri/=%e^*.#{ALrG9k'd.Uҙa}vLanlv XXBd\Rg"_܆٥Y̷E\D)5;ˠ\2 VF+뙹y*r^7_$q~]^Hv}}{g06%+g+>_W^e*@@qRMI:?v?w#ҷB ?yjKEOO#v!vKByB 8+oSbleEgH|]*+<4-u-=G ݻe2/R(>}OI$ec4˘gd=frs,/ssxv𭏷o\o Ώcq477p8n|pnN租ehM*sf%Nu~q\.&'p"rŻĞ66n&p&\"qgn<`d{;37gon9Z%W.^357 ڽ.sQYZR7soAlEװvL)*pvZ2?'svc\.IW^%ELw]ڈbڍH$ ] qo{Ύ]RE)v1>+WVVHn u"3 kpA/c}zk&sUbe*oevd"6wK oOć٦_?@&vP0H<G)kGv;KX,.֢i?Oe 'ڜJp8 ߏ3Ϙܵk8Յh6kvYr$e!TkjW޼DKMBx"۶yu=W9vb8qLs6CQU‰ikk# q !Ӹep0̎.c ں}0;yٹs;ј's uAUTj.&;>"ӻLRsK&qxm꧛0&}U)_ 3^TFjUcQH^7E<С:@?bQND\zl .E*?i`"gY3Ds.moyEfe%E뚛ջwk`>x1k]c)#Mٹ=o-VɊI0q!nŞXcΎ[>6(qTQ$&~cljދq&b4T60rv{2" r>v*~o5?|_\#d͓]5m3y qFWrxn|\. O|Miksb_i( gq"7o⚚ $%׵{C{Wָ0m#FX]p߯ŗEk3}-Gڐ~v$n݇[|>86-P,)6/1B-+}4ոyϜFqLBpDsWUo߾ o`> _P~9f]ZfIe$#Wr&8W-Hv'Z|!-1 &*J߫"wt2暅Ưwz[#a_cB'8q^[%O(l8̻--$q<-kH9EKVEMLNBJm'T1Aa><'kWp"~h<6>羒gW^e檅;0m!<.U$LcP7&=\1FhlDIzll=+:$IbOtq95ҧ\) }l-v17Ȯ09FQ&nvXZͲcdr!Y&rN{M{.qRn6V۶2/DIvݸ%]H4%N|b?="3ڟh'v/mܸJ#??y?<*K=LGoo&v-edŒԜ`6=dLrQqNZ(.kͅ$q¡ UٳdәyL^66O_!Z, hmlh4-cĄU}d2]Ox=z /JG'㶭%. ꟨}N"e?giBoViw1?έ'g߮\Uګ4wkٿ@rr-UQoF<]Cb)NmUE??{$G85X]œL<\N@8?P9 ͆Nk(2=1[4sD}$oUZ!OesDFd*[szV&7%:%TKbϕsnOp*@ os ?9mwI4K[E~( }w]X*,u֥*Ck;a'a3LZ$nZ5M0v0<|}nvP9>wz̵8#ޥz wU;ODxj& <-bQjg-^ΠK^EgNo5m;˥Kq߈3eq[3\?›s * 4f>y71sȂGhmEE:;;\?g}Q:;=ݥ1?L]D",U_nz ֫Vn7hU >N|eKSWk'P,=G{Ft"C߫݃ dBUfIKEU9; ~;۩{bOq$I LldԛL8bappl7 ܹW{EIGd$W]4g%5i)E [?\o"[N$p۬x[M)7xVjv`eg#3 >0oґ-5N *D lj4ErKFX[lb+*Ec^ː+Sif7(jo]oe,4F^Meb銐Woc6FQys!H޹>Mt.Jgs\M#? ljX[bTVIޏ|X)Cepڽ\| ٙۂD$b1 .Yv *#+?jA4 Fmp4yg6ɭn)BYѸ>EHc~Mrz/%ɪ8fWϞn!46Eѹ(N`ok$:#omF46Wg<\{$pdUP\l jfoK>b1dV6lV $Ӹn?x}N`29SVAVuO$" `g%} hy1QY,1?ݮ rUd r1YSfpq)oNɔ)(G$~l+h6x2͜ x ˰iGK5c?_vrV=C<]uzlHi4nhϗj:S:<D) Q퓵B|qgG>-41:Dϵm6\U{(I,@v\JTG|Yp LL\]]:)Bd&h7]RK*t Kqn5Q);Hާ8= cܞsKi u#H>B,FxTQd1,*BKD$2kɤZSklLLv3wT?U??̣I첝MlQ6<nQәfwsQ3b$ Io4H9~3SۨuVuo+*k.I4BzmX11X%+XE}EDU uHW|I##4v^%W{O-okciq:1^{Sy[ոMDg  l0l6Vp ^c{̙_g21C ?rE#(f x;YWW1.qӼ ٙwzn j|f$!D"nbxSl[gCwIYTk9 &;5x O5feV^B1E_ "6# dr߻%pгaN۴mF~[$&&bC+Z^#m`'02-VV+jTM^1Pm_=<AAzPDY8xDPbbIFMN&iUo6̽wuݵά0ҺMܶFI -T\c$XDE=a3>N,%V|' ݷ(},[. o#UJrONgXZ{@0(pcw3^^f!sSUTfBb~tvU60FԌ,.Rg-)G6ݎwO#E$`B$C= "=w75xV9R]O(BQꙝ7|ʮkFUf'UKwiJzEAHs<=I*Υ+m$S>V9]"]DpI&4<>1*50@ek%19ki*Cey8t]pjyCGr9q"I`]$F…Q& b{^Ϸ.O[SNt2ͯ\>g/ I r 19Gw󩭻xoq-EUîgvk;|{:p-|]5Gr|h9UU!|%<ѱx+|Be Ygӡ=^-GקՇy\X,kk{#3ɰÁmX\dv؂n-͂Hs]+f!+A<T`An,78$s&ݧ Q:Cg+yjZڽڥte^rh'^Y"X`Vt2]6^/ܡ߫m<._`Wf6;[Z .Ի0qG] ΍.EG@j!T&& \֚t|n7|x]&LN*ʹw Fv )KcԖ%ɓ4i=Q&++oDCQ;x0:x6Y^^f^Q]|Z͌&ֹ 5em6ߊ<Գ۰{єRzB5&mcFQ= hB4$ZGuc~ͳ{퇶smg2=uv`Zd1sѕeº-,]ϣܹύʧxO,[d_ UFqn)vgv.cxH޸A*cUUcݺё[8RsIτ۱[Onr|z+YYݩ^Ovv2N7==I0qٹZ|HUTS\N^̽'}5ޓ3rJ۸12U=h>v9QS{Zt}-y|Zl%$ʆ:>ϟU`dNWb|HjI0u rF'o=yW3&;weÇc1?7/]Lt$F )|` q"c%u%˜`sK[|?K.FAM]o{b4r2>3N4WdKJ9{YjF.~-1eYENtP"7)Ԟ6#4|l=+f%[JӼˁټ۵>?o4;'bԖՒP4U7m8Ϣv3JKy GzV+ߎ<[dQ?+kvͻit5gySuBzgZzMeZ> /\L{}m6v:k~sIە+4d/. 466QV֔Y;tZUx~_ǘ9$PV9O~G[q.BxLT[,Kb5Z2 nzc_gH\&(tf>oO `ՋY0TZFm/X]F2H`~6:PQ 12+P{%IgN302{0Kڜ>\QWkƅN?_6QOS0ʏ#|7.Ry ( ڟ8Z]eO?jCQ@r8hhY!@1#9\Aq:;v#iE踢 t"IBX,3 PWRR4br,+ȟG:fPQ89*B] X>(q>ˉj7ެM^|P)_:d?xuF4w?LX.WYKbp=e*jF}t8A)||BʸLi{r^(h$I)g%Jl%ttt~e^u)W #wsW"ppfbk5Ebeyv NbW0u 7AQZsrY;ڕĈqV.$3 qOTm0t\0'|og/ l͚WESu;8n<XV7PTo~̂"peDW7tTVrg~MMJ0piܓ3 _hj_VVh͂t>K 謱)bqcȗ'8:$9X͠ -wEv롷*J Y\Ukr"=f3:J颜RQ0&ֈ|"@pz;$+LۙGUt|j6~ 9b^'Z׹09 u(} 'H8Sz;ypI 8L|jz9r fAd-o(B2zi뇑\qH^09F^<.!y_< t . pl1Wo*XP??b?Zb=M "k[|8F¹ތβ. 8XFCIp嘬X}!_+zerkNT88tiWrmf׽E#TjtO-q|{ *&Iؙ ۱J :h;R[9{ qlKCE%wbiuuuZ#"oNcz<!uuu?'yosa~O~`[oHeyBuĹtt +b29hh{$GQUQvU2quFU o]NDi&khP `f&BN,RZZJS,U-ԛttCrdHV-jj|<Ɍ䕘M~ԕ'}I$X8LesUEA9 u@:gGL]%ː?Cݒ9u3'FyҀggs\Lh?^p$=V1f絋=&BT =}PW>=;*BN&=EUC!$ qj*DZLGY+B~,ɛXV9ȯT`nef?KjWITQp:M8Φ5yI fWG浟04i66g7^<KD"EuS,uTŽ 1k?U2~ց)cg[v*Gzb#TE~}:H$qv8*0=r}@Js}%,W &9$Hww;[y&*YE^M$:ߑSZƥKZ"]n&\E)gMC:JE#c79r9Of|X^]^^\e%lf@w-vdӓCUT{y"MB7U6ϋ'+:~AcZߡej:{ U^yf Ls4J1TUajAUL]'|,0w/7G6D/4y=zI켦"5( 989v$$ ]̙lbf^3]2&Azؾ&,ȭYDPnJB\+\6[#}XfDrs݆l{a.vкUOBAp\ڶ@oϥf/cR&.7Q?P*]~Ԕ&SJ ynN7i/.!i<(OiUdZ7Gg!˅5F_Oi[3jHNG4k@0 Q9H6xV1?0\8/q7zbw @!ß ZUD16dP1f'/U-xI믻*5 I&g,<( *_\/=fRɦs(9T+Oa65т:ǎ  ~oww`.ݤ~2ӵK o *e~y,)}^|G/z k9\b..?Tt PlR{{Ρ[YN,Of皭x|%'u^wȺ{YRZI/#cq_}SCOyyd W=IKk Mܸ* F*3$ CWhu򴢪ah$! |XQBAT/ȟsC^:-<Ϗ,8tjGE{Q9X+}ho/>Gke9}w{K7O Wo&zsHQ̭[fg7;r^G/H\%f;BGuM'u|f? uǺtvdr9fy:yw$ kGEm~?g||ݻwsQvލLMM3})^yo~86ry߈TTR>ԝ8 C_(>A8}av kL&AESexcGkI;pFX&PۂknӻvqMV[Zf x?P IDAT#5X+*[{ݎujedqxyQ IhLL JOBtVrծk)SUT(;Z26,V;A{)y@ Mh|$;h&TT02#V|Wi4A/v~lF@QP/;h>٬#^ >M3cK*f>\ ~1obEa8r*vh ( ][𰖫su5:F͘B&y<_qp^^s@?&@'.Z^^-6C,7M&pd+ tZ17*ͲʏߺKɽ^楱Q(Ӥ7ک!sdL '>PQCc7G[1YUU3樷)+mgBfOWgM$2kI0aZ{rzT.EiE}e˛_ =VZڈb5eZw^zux'UX,5|^z-[lz\t?O6=]>9иOrU%q÷%pv: ÚAaDM& rH׏F5Pa׮%UZ*~۞c2 !%n_σ 2&ټSr U|JhK(r, /`*kfAYP+flbjl*=r7>g#X/&,0g$Wqh.U[+HhjZ֭{ecVAWh^Rl̂*MД'M_ J"!u Oʫ(c,RZјS_ iZލܤy_KKKuրEqnǢ4Vk~mvk&դE8b:PT90S;o/~gM}&Ӈ`m }N!^Bq:<׳=7n "64+Q z%^-͕Z\ל{W1r5 n.]ȋTzTⲶ#9 @]NAu$Xr^V楴xJ^$߂<.PSVK>{Xؐu6r;J|f--|Ok9~;oO\ye { Abem rIibg8.*ǏĈ^L&&._666i1JH"cD=B8c(قs|?037_Wq< ,iVWU1n}mUO3&$SFə(LvF]ArFFP<>Bfttcwrq?]>zWnOu;.η*c;mw&.ɽ=?>fFukq̂m8uP[֯&Tt& h=ѫLι pȲ2RqOI+|LQPgVp:; ޸>r,lk$L8% 7רj6o+a7';<3{Bpf +If3 W普5sw.\8G] DsFD$iMWEp" v "حu_dqt9"ERJD"ԛE:|o.B,&c6;i40?qQb]!DXwl<CK6ޔ,r |_,!8"<&zsc1\f?|?D{kQpji}~M}a:Il/wފB @ɊwwIR Gچ}^USqALXorsZP$ üw`/ɯdrHGoreυ=2b2 K7sbUQUi6v6T==V kȲvQNLr6Kہ*2yG$"іj(l؆-|5#^QݜlՋeK*+1<+RbQ9OU O6+#5f:?gwzT0 u%%9z*&Gz zʓ۳d)aZkfBәqXV>/~lFj%euj,&c8tRߚ)?{{sӺ g>sŒg73YUTx%^O>yzgqeZLI%&sCr@aGgӳsuK ꏖS-nI]e:;/U\FC! 2?}泥 VL=P4gY_LIs?|D2<;ޮQD<_dCfǂ=]8?sG_ot~b=T%O@ch1e״$!.Yu/k}s-;;wv"" `6)ۗMNjz̷(CԕHzVV==Ϳid9G?ׁw  zk*AP2ۭ\Fl.= -L_*oLEW:\<0C$rGL^F~u|=%a˜LXz5Ĕ䕘$9,Qwtpf vwINR᱘~@Ӂe䛯o~dV.ףX+z[Q&p #O8[Q^1{1f>@V?Ӭgt]'kXc\ ֭ Uh_={o`fĺ\?{0#›kCb#d 97w+Br`ԅٺjQrܹ}-+ ?eDQν'۩". :Ev?B?w^Fr8x;$tOHJӡ {\L(og>wpXBog!?O^_Ǹz5&  #u  wQ9|HqRԔ$rF|PII`SȆQ$ĩ)$d~@@|nV0(ttG",'Wrtɗ2n Pi)kIAMݑ”;TUavV]f?5?2nx000000_x9{lٳIoٳp8Lww7O ?/=~#K,, pHڛ=Q|= f[ȝ쥔6f`**ѐq>XDzDZ۽,c^AYπZҼ JUIcfޢ.Lf))7PjcxaS{2:2ysFXԟƬs" tg߾NL&-iU%Zcr~y4T{T*q8G?Fb$VbZ=6gxaU=~[y3,>ltxw`6hDBv l6L)uaNǃ(|[Yrŗ< cI'-w);$s3yg7azx<xߟwa`l,Dm<bV7C srʩz61;O`6ksE--.*TUѮ6|,EBA@xn]嚕JOW퇬ESJ=gV1)yp 4EV;fN ([͹s^t$TU06?^-F~o&ʂ^5kِ ccT>L ֝,>0Yat5m{YLǡpw0>k?η#3nr\Ŏ~hmZl7ģ.nn!;¬#sCbtatU"$ ( scTTZ>63-d$jjY\VQC{\z}*ĵH.hWU8wX CmMmfzk |> @0liiEHǡq18Lϭ;LkGhlA  ej?k1_rKN]}?´m ++k=ngeqnߎvǵs8e_r˴^[kv0:5c1+4W9@[QprYGZ; ]ޭ[Я؍I~-7hkbMv tWr^n+:V~?Yg٤~k5\$fsN+.%Vn.0qNX67;9qM6r)]&oRшD V(--Y5 =&1\ y~y|e\Q|lryh$1f{_b,ق=#Yo|KegZ^ & P +^Nԛc^E1bJ2x|33$c?Í[˾>)KS9FClwk_,5h^o\A,xOƳ_F BԺ$ijl̑7_w抪2؈ ^ڻ_;i˽7e,Oq t+WFٽ}4`aCBuTc1{ye9zg-2!<尻ҦL4nڢ E-.R[R»s8//aX뙝_ ޭM$Y^F,_6U~ΏRsķa]*r<܌h^ۓZ=TWGyj-[BcueM&Y&9S[S3҆yr -o;wHTzx<nߎj{?K'1}|%x`tTi(dWQ+.{wydˎ5"/G)O3d%D!Lf'ckuj?ݜ!ʛx{EfoKo ѫ1S a``````yK//2_җk_S>|k׮G~.0FIݻhpU.qH4 IDATP(OjcpMNFԛr33-"++t8eHEJ!W<#Gx_  v(%Cr8;2&_Ķ0X6%k1ժJLV[8Lۦ1JȲvG$BEAe$IB̳*bsU &pHQȑ .uY`WPYF=Jl2KČ\䤂٬?͐Y!9p[,L&7 †ֳ@o0AWpZ|Rs|+!n|VYV8wgΜfZG0(ihQ\bs>vLa~^M --P]KL&==}şf+rHkB__/8Ƅn+ ]A"ُPQAӜ.EH~+e rB¹y^8SF8<.^,ioYVVuk9n~ʲ6p!58sRtnF輋܁8P916A.A-zI@${hHK kkTQA߼siIrG#li{C/HZA$۲IFX0yHPbbfIm:ͤln3wvg:;:)2n޽['i"'Pl&-)Y%KVH@$D8<H:xd95P *Y_EgGi`v3>c:`Ѓ/')޿??7lRMګJ)uLo7qz)BdT P[znv^yOz+%ꨩ4;[駵{ʹFy}#3Vn7`_"}){zx.ō󽲲ӧi*)sgά[: [MǪ@DAB*|N6~ޮl>գAhm(*YYY+yC!Ŧ~Ɂh|~r87p* '۳b`6mo'_|mq]nދ9B}ʰ1A@Ez%P](vlgܩl/*36]9Btώ3 6e6{2A)Bm$kYyƸ^{ЧCY__NǨ/|͂_x"CP^^/1`㪪07FU+Թ|t6Pm [{EQ5!EQ(/E /2R/bJoo//p'卟9 {RIN>ݛO}"/ɤH4HlrX[zS1Nnuc_z2nnv} ImxgZ.?֑$8/ Hggƾj)d,-U.Zɤ `+cBzc}(>ϺBR A\!ϖ5a&dӟx1*`g2cTQyp5.Q9|؎YPa|\g·WF1,˸=Ob52T$x$(tH!5Ys͊׆gb [v&MI׉m5(ʲL(d'LWLNh׈FF7!c3/w?SUf93M4q9Ye'\x/an6`#].v1eobjU].&t3zY9qpGs.a?zOEql7dXH.'8AHݸq/c22k}]+ƚ`;sk\$ 럢VVMecYcݸ^BſGczͭ UӃF$&)'{l>5;rV}t P.ĝh4\t 2+꺍^\ ~1 ʦ?Mƭ ΜIs<$`o~>ؘgMі3v^ R<δ˅gR0[, @S6y`ڨ@.eAiζ}H_nvdt`e\_GDҌ\m{RNHU"l |&(݃HU693l"N>}D*f?eddh:/B i'Nx"u-+a*5/;ߕ's7˧!{VF(TÉ=xH$Lk<nj09HO0[Wqe?2* hE8vl?ƱUyge^F])Mmv&\uHR]!I3 \m#Epə,6l7F vo{5E%&ilLgr1=046ccݭ#U|9 Y>K,&lgW殁xÛ7QVӄliaۘF@[E,ȧ%޹LK>UQi\c*@_{,=tf6=)<0vwŲ} AIMiεifN-[_3y/qYF P3Y(ffl|d^/wj,޽^ΜEUAz$3ˉ[aM^7!L9 ,nuOcSUUԕn<%n YCH-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~x;zzz( ?/5o_O&elvۧPTdR刧gv֢+ߥ]kH8I<&M0xԖGf*Q*Pg_'J/GI?i,|ؾ ]0wLo,IH'"zPHa-n$mӌL@BTX߂s6OG8wy 0rINrIX8ӬAVQ}RxDD]ݖr2?-;Km\W*忝IIA߭6pd?17m͍scq외p~BІ\D+e->/c I&eB$ Yxc\tvmWƔ4eҢ5UV7l)hH&xޛ(y ץ%+gL+wN붌d:;ľޣ|Mw+ۉr}@eߩc6gp0IH$9yNR4U4Y&yÇ[5jS,AIf ,(Hikh?2O>(-- 6Q4$ǗFA1CQ@17m*CJ>ᦤ(LsN3V #I"Q[k|UˇN ppÙs}PTouMsfƺwT4ѵYSnx\&7Uq:E=OR&QNzn-ecIoU n"b/g*LϜ!<{YN?P hΉmcS+[O2= uO&\1 &&? 4:(~,q:=̤I#j!VT^C8'H[z/Bp!QH&%4eQn,N͐L^\.CmxKi 3%2wg!4آq5U}*K+a\0NI8, [ݛ"w hTq J:;taF 7\U9n _KW/}K|j>Çɓ~/efff'?<[%1hf/QYޢ /mjjN)*FfPn$S%{i H2WhA%7ET9y(EG0*ȆHK֧2"Ǐwsqo 62~nè3M1-ܦ yBf<^H-?i.?UC NjH0k&4YMÅ0`P՟L\cj6#>uM|%Sҍ5f qf͵ 5HDDmބ`٣h+^}M7lGEUa'㗵.C |37$@ o(B1?r옾g isT>VI~1">E S^yۃL(/rB/%O#UurI.ԧHWpI.PXo<ABϦ}oͥWyk.v*3j/QDɓ$ 4j3&ϴ+ge j%IӤbn'?/w>۰Dß T[%wfS{T| +j`$)vzdz|lF͚nq.7=>[*/LHM y[M~1ꑣ46-O'e$W%#ҿ0 0پU^.10@Ktkn{y,_{qQkvOvc!E"ؕ1 c|,'<FE^8!nӺ9R&Kl j{=5'v-.%NdQ[nCYY!}۽S{&sR /+xB"$Q@DP苨T(C2I$imT_be蘹Xf5dnAEA*]D(b;ǟu//&6hAtYLJ]@N&,?.4?*'$^Ϊ[Ecv9MZUj ?>0EciG3`0,"c}f9VR| ;2תԅc6z6g 5%4M2/ bJ\"q11:'Tx7ed_.s#oΆN::ֶ$~Ӊ*?VQ5<#MtjE"1szD[ eݯ>|!}n+sTRQd9_kWOlh}S9z| T,&EQFY'f5\syXeXH4BYj;o#i4D4i*丕mr]^­^+g^EAJԿMd&! t Vy$V"ԁ+vZ9S孳H]#62B:ɲy/I051RP 2/,( 4k8DP]3:::DXa*H Tt:ͤnani1ZsٟDg!@1V60Q i~hV:*G v [ΜH$ýr$7o;WQ9k$fż)q IDATgO@͞a±%=q>}!2Qc,G^HMy$ ۏ0A( | R~^yhJ9H)Ar3o=r{3pևNKLB!XЎ^k4LmgAxf]e2tq\5\:'okf. dԣٌ4ZAmH,GGFM.4mօTp8kS/TƟvSYy7[~=.E - 5ҙal -lټQ鄪f&I3ه$0-d,[xJܜҝ,/vpkyX-N( %6χ$#EDf,_W~pJ.&(0A!5,ã2U/鈜7`ך޳0'N6sswo|uTnc:ȈZʣGA$ˬ!*4Wˬ& I_&~+DE %**^zTjjܵ)YU^DMly\L]dgs?1O=MEYTET6;:nu~DdFFD"24oZF),ߟ-kME،ha`n-g0 v2wc,Z= v=WĤJseyCt᳟I&Z^njfqfA{Bv7˳YtO6I1|M_d axГ ^zaK!B;1vN`6rkh+u?ŋ/ZKH%W3–CCyLjXRBGGPihnZmx񫯿~ݸnN[|/eYZ;QZڵJ^{ ߽q p8N3{˲b` %\7Ƴ~Iy-kj@?ؗhČA(ݧNx| ?ju!E&ƆE"\t[3qje?O =o"<;-WE;O= fpnN?c >^*3KKDT9wdĨNO8Z5sݻ /B8TJEg}`rv޵Mfd{7(٨iMC$fT&;͚=cxAJźg6S 3^H|s[ dbQ,|!l3&O_X%m yk;PU!|l(eLXcF֝rY6^9%iF~۽aWQ%wf( >(=z }0At] x_bfe$u 5 VDD~ZC!{BfBz# ÄQT5%yIѴx\&E >M^-@(t qs8iwUf7.Ȕ\'w`sPDLJ|,_bHeˆ0X4xh$aAN"&L6̚r ~&G Gf6IC4NCc~Ap&q{TFB_kR=E-zI"Z3smmO.q-0!#Pd ?]J!-;wlJEQ%?e͵mgrk/8.AtrQi.hާ[xB5;eǘ\ c[eK~ gz\"/Ͷx*A\L:R3kynac6b Him-dS39t~f#=ouP[-fOn\M_{tJ \w勲~Mx=ީ, nq9DA܈4s9<=Qjմ;"ؿ2Xg-.Hx{'O}jsD]ͣfOnɘ1+wdb 9PUT~5|TY17qOU0va5_.ASqY&2Hk Q~Cau.1{($SS}੬n]w)论_6k[ y2© 5cUM3s(?%v/xy506dKgᖉ_V`mzîew v&p-~ 36 $is]NݨhS!q8b紜X/--n4t mB箒b* |O]\؈ry֙ϭ`fڹe#WyEAt$xUl-(X\By~Hu6!ew%ڥzƂkUEe%Rzz.s4Z3:5YP,G+ D#郴UZcZ,7/Z_u}HbjReaR;p纞{$xG'U@mmɦgh4([or}lKTt$*2eIn:Zibw"#g)\Lt4J!Dá?Ү"VFnn&Fx|I b $3V מ{wއ۸׋9|5p]H۶Oƹ=BYxb< o3ɲ$ԤJc:[tOs M) tO]#SIDXa~ɚz9$ @h^YQP#呣`JyŅUʃ{ol`(^#׾fL"[я  sDe[=[.pV3 $zu@=oaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaK= _޽TNW 8YC|D*щ$C4:C)S &PZ`e=_8y144D0De0<{x3N[Tj3~7їO!rgp%g;;HinQFcOK|cՌwҴ7;T]6~6@"Fz{W 5s7 yρMXk4J{Cn7 eNmwg%mno!T0XfsoS#< ;6x i+;װ4DGt(R/CC܁ U^ӟ]"*F+Mr0e e9?UcEQdAs)qܯp>KCK$$I xʵQVN(58N|CKK$ 5%L=c930M;DfjX#o*** n?-ѨksxɱCgP~!&.MCdt=(Zx+w2_9ct[[{An )n;sOnE}+߽G:>ϧ$i:hΝ{'*-"Tzu>~@4 {>̎!lc6U.9H򼵝۸8 GI-63$f?˧GxvG7W+SZ?;RTeii3>"/co1<6vvYW2<87|q)!7ח)qPZ*˙Op爻En{`nlrL]^/_HwI*'e~!B-ϰ:~<Jh*Ke~pWI)-3Ohy|OhkN7ͮJԼuq&k_! 7m-~ݸ>+W@J!?=L7VPZXL (c2΀SS~şRU:FbN]զ Ǝ7Fsi5IOc@# ]a*sn/ ~N%vQ{MMkCCL>g/x7^b5*- :GpjwS zç ׇ:} ~x}\7:'{<{bJ^1AkocOߑ|k*o߾O/{踛fu}6}cňTJfi"[W$ 5w.Vc>  \ P)~&M$)yw͇_m+!˞,$QDQ{NeVcxy6Y~)ɫ%>o0]jZ(+7˰55]d<6{= ׇtOˊsHS{" 9>hznY,rc \3k=q: *Sq%3v+KeKQb%n-+ۉ:E<HP@9Ls^=15Vtg+WRO2̌Eu+ |꼤Y<Wwd/ltn_=RpZt &< ̯,- Qg_>#y{ 7Nfw#p$12Y /oU,oO};w_ so-|t̜UwHX\BܳκvS4tu=קc:2MR \r kkk~-ǍT2G6_cNJHݚz~kCkZ$g/B"'L. y4Lȩ->m$N1J.a'Pf^c,IECFM vORT$Fz i~a(sR;=0QYC `~铔E01O *DJmw1x3AӾ}ܬZe{STRH'} ;ӹQ6O=Z;,vc djZ טxkQr77[*IK>̂U~z|hO@ʛTsk VDIM~,* >sjWw.8?Qgdt{>C>H4q>ij~o]/a )~\{g!2'<n9sZZb|1FFw*jqIE䵟a7 }+B7//ϋ]!'&8NQ۟0?ergi+rR!**FMo(Մ~>Ad)u u(7Vy4])Ys;k[.[ڤ "S؉\5WMknZL)SfE|\cռ{r&ߐU8>CW`/IX-{t[up3gJ#sڲدk^Kf%ёx1$&h"Z_/自PQ{?1EGZw{{9yN:eU-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,E,,,,,,~shii!055 /]ؽ{0͑ico>kN|{>, C ]!xy*n.hVs{%ᗇk#u}XZ 8_Zg1&/=݇@tnw Rޟ+@d5{&ػ!< @ xCį]3UP]/boܻѠ$F+ػRă):νxc\՚mu~YF12u&jS80MDWE3_ 9{,+;/cih%XJwľ1e~ʼyvxy/F.L}{9Qu2"m!;kc^V*8?n)su%M+Bm~qtfDa$6Ѯ7̅*R[}}m 3 -#! |ih%{J|DJr}[0Տ|]XN u7E"Gnpٗ9H`b԰ǓzQDδh[4p,.P\SO(۝ה'כ97؛Pnn\я8T|$ =04DߚwyϞf\>#{&7܍ϫuh7~RL$ pxct{7gI9d-HDI]r͕oj6} kK68eq]wܨ{=e:{HG>,ˌRSS7]k 8č>҅]|&έ^!_22}W+8N*Z 굫Txj_ElNV^LrjƖ< [sE#&hnattvI"CK[FZAk[6K69z$7gJ2)c=7orbMO|VFf5rTQ bcĽ8[KnoQ(uf .8{^_/@ 9+KP;gKQ*E|M|`3VZȷ\U,Ƣc^9VxO!;4ۛhiQ+"*@; 6[՜^2܇|whi䵊KmmI嫶G~FyY#+[qǙ%"s CA 4|p5Q^V~\QΏX \3n?/]flTD%>΂;SwhUDkD"m0=gǕyc~,ҜeR7 l8#%z W{ϮqU&&8J?wSK+lq{QY$ 5#oiyB.EςG`tTС:R'D\!08Q?%T[{:7qkgg4b HFSxG\TdC e?ՐNO~6A"^AUBi !e.韲ba ү=j;3nz|㔈kq%vRLjqԐr**s=Inqb޽4ϝK͈(7"SbQiRƻRӜ<܋<*أ;DyyqŴZXPxn,PpLƾ][ij~?`0/zp21.3?(zʮhW©cha3[W;t^ 7^ɓͫLFef]_߁< ֫spn%fsp"22c{=2ט =ϱ&ی.nMR\|>﷒)ɝU%rE 9C&//d۠zj,jw)%Rv)th{ >T6 `W5۷2;  / ܺ70E[ +u[4rm1H`,rwwKY;T)k{vjwȩr &L0a„ &L0a„ &L0a„ &L0a„ &~0 L0a„c~: {n?O cvv_|_{!3(UZy~i$ 5[s7`aèXVR)j퉫ת> Wnј,B@ пģva Nݣ٧zdrxkK b;)WZ;T۟%16H7Iwsop}m(/ߌ" VXw% u>{2o"H)oc[ JK) 4[Gb 6{mA}AQ$,GݑCat5SQ G\^f(kՊӃ*Zqlk_dW2 jnF %{nl׆;rH2{n $8JEE9ݻt\b14?^Z6W(d$>#oȳǧyWrg/d4>qڟ 5?cng4uJRLŊck=Yg罙 ꭼ!}g>@>um;cvlX__GUPAՅjj BՃg4OUR8"8 w>$P-^v+M8ޏ >.pÇ8٧腅RTk&li(.W6l`1jD=CPQ!ѓ5tʓO#LTMHUbOڋ#X`؟|Psŝ3V9j'(DLSTBO=)mddɽl}r+ 8Kۓ9Y d 줝G]iazRQ[#CǻgX Aa^U=8-]nNǕD.'VN;z9!mF2NU\dao,0grԾL'd3JЉk+y(¬ r,kC?ЈVuN:ȊBgm[VVPN@#j&rI@zcHV+8zd>+Z$xUQw7;}4VVWW/>?}?jg.fcsԸC7yVN1{kva\-۝T_ObB/e-Y ǎA9YQ n}k7YzY*}'bZ~y񳬇\b&]q9~ֵ͆y b;6zL%dqҜ|WS .d哌ݔKZbj[kҙg'ǜcU=iukXũ0$]Vc $.d:q.VcNќC? &ڮ1q~M%Fnߠf=lbY"?]adC\ȩLޯFk\Aj dz>v[bZ/T\{ ȩΐ˄6ot7!u/r׊Aj*{]{\/UvML]D_A]F# ^KuvR%GvAm̋R{;Uo5۹7Źu._:]X}pAFgF0D,AydiSKH( >(c[bI@~X,Fݻ|p ͭ!l6NU2q;Y{`z tbb3A=$ǨjsldgǏ&8j5CVpv,.W+ɞeV,!QR[Jj3g9$fyC.b$WONpEU81v1kLU(T<ɱc47tb9NU-Rc/)6 Q?7i~Pngd1TMڠߝRu23Mv耩>@(jUTc4yWzod #Oiڸqmp)#t❼ƽl)@`gx~}Ooxg$R Ʒ~GU,8ٳUq8Enap'%^{LRK)~4#*Pb+Au>`[b/8|nw3'|(h᠞s _roy~o/Qc:AMvkYZ_a,O3I1a&&xɹz/p@ b<8e<<sIvJW2MM\̄e"\aƞ0tyHIT[M@޸a#sU7Jt!Ha*[YIaB GLΗ8UUxb:s(S^vwJ I%66uudUXETD^#m+ƦLmbdd/P4a3{1沆>F zK>5Dn r'M$Gjr>g{zL47ğ^,@]I31ՏUoQ$fwyqu9$KFO}CNQ[QCxѪn#iUnzp‡ ثD=ldZXȜåm VZ,nqFfKvV1N_Qw&jE ;7-C}NQ"ܺ6#JxX@X=ʅYdX4~zo\quYc˝ʸZܲ,2poEp'e!'U[X ~m~.$u!Okov,P7@}7>c7!\nI,H& TUTWp>Ȧ}-NykruEe"97G&?̻L"ÚCl X,4x#ɶC6a"}e  3GEStRWC>n$뼅HedVẂre~Q wIuc11Jfrg5v &98q'i;~eCQ /! b^!9u SQ*. 9k=K RGclf6idEq@B{|R WQLtezU.8TQȷ )Յ8 T%(,5C:%S`bfZb;2XUTcݩ֯flo3N-.FvVd"}:q!dMp>=NbZE9s?Ά F#!T!( ,h0,vFj\SB!E;up\E`KMw4)5u8)2BuJ,=.]wP(],<7+߁I `a6F!6ls0J<~FWhrjnϗ(cZ^J,߿Jhƒwo1|~D,NT|Hb1 9vdxx`c;VSٷFϑ]GT/ @ 63٣Iu[\^$n~3uLvε܉vY3cvv>9y@얛$b^ 6mb_^gc>%ø.wS޸rG&FGFqOu)r>eS#ew R/#zw=o_D}<+,X8lp*UԿ6nP9{.ڰ5 [XV=>g= &L0a„ &L0a„ &L0a„ &L0a„ &L2'L0a„ *x7LCCyݻ8w{" 2%4$ wAp/dRfLʀ6DFx뼜 ⮔$raݻH<* ѡ3 ^Ya}HQ?4٥q^HnwV(5C]ۇ݁ azB!DPH72u[f.Sd+edfg#jΗV&EO.B4ᰄ,}<)'ym0t8]RB:NKwk#д+Ml"JXp!E'C#Nx˼kQ ڑxU%dYfq/PTEdyz[Br:'}۷u'BŘ~^Ydyuo5;|XL="gR9=BmdL4W6twSF5,/,BDdP(ĝ#<ɤ*{eؿ9]1H86v:p]N"7wIB傁#PP 8bLG\ "'|v,Şh螙!TU}~Տ%)Lbp{ bq-5v('xm6dR^TEe62r$I"Ep:%q:~'t' "CsVuygpq89aw #%*UdlO6;tzͱ(˴KEݶfM3X}uv]?#WdzyO>#Յ׋bWB<~^W.Ǐ1׻ۉщ"@zJ\aCz)0=Ԝ}#ik3ADͫy>=Jt5HKS#^;3(G…@v\Uo#5TddޛHG$$kz9w13-RY)\]PQe8C:6BnL^JG%$NCC{P5񋇪*y۸K*}"'9]`_U y/;:)o n(‘#H[RȊUtޙ2}lp7FsڝKW ~P(-ZMWx2q݋s^}KRWJ&z"n=7k݋XZy8 srh<5y:*d7D"B JLߛf.l^3o}@@I MkKX& "͝XW%yB[Fq(. f z5ݮg) ma:Iu䚍S>4'ߘZ7WiB̏ӒTҳ,q7x̐2yte:зS/vn{#8"I$ɓ,fb3  aیhL|F'Ȳ> ݍ<,{&:;od-ōuUEe&Sgo% .|uy͎31kdDQDN&1L 1 R:R(x:hk0VLvXW!˩YQ܅͐⶙3XJ͐p'ϏϹ1"T3TN [Zp1=L4-K9D<*1iC1]dSjb6{kEQu_MVU!Jk~Y~> duCnCV@[*^$Բ UZ{^Atv+@>* P:EQQb-aEy~^|O$IoV봾>|'$N!IڝF. ̲,2`9Ol>,?>ix|Udrt5Oxm63D1(#%0f.ᦼ;>o5 suWܳ'$Vxgec 㥦:KN{cuN?B:ej*$ik g1;$d=S!=gJbVn Zz,7Odb%` PJzB!d nQ;GֿU2h`EX[U1R^_Зd_>dYf„ &L0a„ &L0a„ &L0a„ &L0a„ tN&L0aĿ1ܹsX,rLbQVkmᗚu/5idٵ SfNkqqPEd@AbrgO4vI2΂gq(#3`^/aú7A,HHBJ&޹]!1Xөk<Ol 1 Ç;FE}@Q` $5 -w&D޵+oL,)e^A,)%935N:nh:\!DrO)?u7Xzog6K Ǐjiavރok>(=QhР纄8 hYֽ{lKڠ_"/[#.[GDppFsbC[և(AͼuYF!D'3?yXa5rp/rZan>#wEPHbvvoTatU{GuXQyR)?N`a&k4`>d ȜW[U">tX]:$AbFȧ\smUlH,l{!2n.-R ]2{{ٓ PE0 P^*dkjCi(r#$f\òSm&{.VJGX5A EU@ADHEngqN'f؍~>Uܹ&^%ѣ`2{!?EH{$I7 !YIpfftJF$2U;3:0=ĦVb7 \ɋd  ?ydYv#w܁SO@^!PWGӣ.r! R=2S7enJJ. Z^>.$akNɹ9y$|䗘 5٤w^3bNnO٫ܿVUj?NIsN;6[`ۻ~[Fpqa"p2 шG2zGF,]{U[[#eI*p(O!uɪ!_ OP #go}o_G&$ vwls=Շ ,xr'څ'ٻfDžtҰsX{ZpZ:C |1uIփO։=|4(1]t8Wmfad Yy5V޾^^z{.&\X#L/%H/ѱ3gúuӣ*tծ!cDFZ+c'(Fk0Ğ 9PWMV>NvE|s}Q"`08TSKrD#LdCOx3z~"[*45E};M(bS2dgrF: 'g% oUF!9Tdle.){֛_2K9o>N.pWmTeY΃ڃ(jkL٩\s+X/b1/^pjvvW+Reh/!MC{l؟ksAO}}ѡpIG}%v_gk>+tQ-ƾ'z[@U^}GA;:zȮc2okcEL I}}٩ !oϠa")H̓)fiezpJK ]r=: Ϝ!TQ75EӁ-zku Bd҇bѵaRUA| -UkQB!Q{m7&;'d*wFѦ3vRUlM骀#vM#)447c|$j%PZRJBTo\F'8|Ag3gt'))Y#yagw21ѣpE|sN|ߎAȏ0=MLMMNKdd04$nO03g,ĹQ>0֮]Qp}l2ӫմrk6I)cO{ 5hww+twG^azbcS'> AKpCsbXj1ܪcب"0n*mRS z$`a$F$'Y%ȭ֋ w>IbF\~0{3;;L 01(biiI+YQ>(-o7Q}Dr (+"]%!I'Rq{eO |a`DJML䘻 7oX9w96-GYFcҪYut0K[jPI Z?U',T=K~)ci=ttz:zpXD IDATF8w ,nyũh:pGSF8[k 0/W9_zH-tŒ:tv<`E!Nl8ɑ|92ך5ۭOJ,$D$!d@Q\ݭQk|RVkԡZ?iz'[?[#^>N#AOO,ٰVM>ĚNkپ!I4n-67K|\]ߪs0{}EQxn>ېk*J4ٱ6'd0xDkQ_|6s:Bk'{n7I~N َ()Tx~qaƓ_ I/:ٙp|XdCEeVXf7Շ7Mj,671ͶߒثT;?4)W1cC&[ ֡")r:cv.ѾP"qkm,u$O22E"E,( rL&hs@>Cٴ2@UUiJ-;>Oܚ* d)Cg9~&L0a„ &L0a„ &L0a„ &L0a„ &L0?̄ &L0? /sEVVV᭷ޢ .̗eoB׿NWW+o}[+@kk+uuuuVN|Xc2$zz[^TX^Z{i}[I) Yƻg++W:II҃6fg#X,^zzҼnڪ9wRl [y ˙^^;zgFfr \Fټ,/Ovw~eYȋklBQQ dE"^/tt莨;4$ABȕjb_2 ɲLڗNعrR)= OEfgle$4~ P } 8%'*BAg+IR>)Q׭0@Ǒ&=Fʝ *\zJ#f_["#(EeJ&6/F"]j_V68=Ţ59?;?h_qzB!>t,uR93}j B2ֆf y)o7/b'wpp/F~\& !/;{~-mM#R@F'9Y步V zh޿,Gq:ljfxXk볳lnۈbQ;+Z4|\ PQcQp1G"N'ɤ.5 1K^;pmB7;7MGt]ݾe܄ cI]JIh+sw2^:oAЃUMx6==yłz)&g>bɎ y+?lt!r}`62e'ËG^4W!rlߕc)V>L.YϮ ŷl;^'튮c2v*G;nȼ{z:1 ?A4dV ˠ"BREUvX+k=(Bl,lTgm3C5\=Ź}Lܽ[yJsm,v;{z{?v[C]2ݻw> 9vgΜgϞ{LzػP,tj^ S_tZ,E"pZ q-h|'# bdv mJ?489n^D*5А56YabàvLCU.ڷ3WXhF$`>+ tg_Y/{rB/bܺqt:$|ۉ% gST_#EkQ3l!.$'ϘR8yb/r{=TPUrp,'W'xw /}`STo:O4,7TsMkd*f_]@MgQ~ WgzZ!]Z[/CtmprXwaU?Pۏ*t]|pC3Kiv y_PRZbld|}%L;-,~2tiY1ȇ3guևM$9rda!׹Z'2>ѓ?e::y봔sJJ4~#dy"3?( O.p:YGqw $B$z!mڎce9 CVr|i&ܤ 3ݻ9{{JNzY7cYvA@lZdEҒAiH 9?ts;s|h=itvۃjǠ"|5{o66[>b47t75es;t&2$mimCW2#^"bn "zzZ+5B+)*$hH]/b H1Tn-#1҂gF2SoafZ9W2(ILjDKp=6~tFgwW2_Yk5K<8ƕ- =OIdYa ᧞"зooK,ks  /r2Wm:GGKL.k$)C6=Js~a.wfu+'=)9cJfjXWX|Vf}ngݫ:p8p8p8p8p&8Y\\[oOOq\?eppo6ܞ~;o_/{ɓ'=DQ駟&q˛ooo\/Rğ /#o/}l6oo\w%>q%dk2 w( |scqqe۶$w1G`[ġs'H*=OdiX~]1J2(!WM׈Gc9='MQfxHS"9[^OHHr?M4%NR";qYjr$Ife%ǁ=vX 5عQ?m]ö׼M^M7Js*鿀JסKR㷚ڈƝYP{kFa4``A=D#@3PZZ,Эz By9 `9ޫoroVz{٬(2,RKF2edO#Xdo^Ӵ˒d[PH;L($s( tv2@e o[+A01A)%$4/qt~(H2k(xg_l2ʔev^&sss \^瓇ijyء<\]'6;hJּ(v6k ?:EWƠ?{7NszD~|ӗ_+U( MdήGu5b1Nث Mjr6F_pe@[EIQL01h.ɍΠk( ~ex>3Se44`[ b4# !`(+sH?UBUl6.\W IY>C(b1fݭ}M  ~ K~º1V@_ӯEA$rmHquq-pfgK%"1fyf6R,S戗d\.o 1 [W䐛Ǟ 4&~&nsUQ  .ěh}~UAmyt]# "7yЧ?mڢz]4]utYSz]= VC>BӪ˨RIy>^Y{ggcasݭϱG5)=Wl* * ]WnGhiw,. "7HMĈ:n|\ D,3tn'-ۯ8)\Wo۴TdkIܥ {Ƚ.zz*Tp/m16cydY%Xt@QtV=,9j^4{L^_v/W50; cUuMbXŢѫ޾֧gO A_2qH4QD}̶m.-xByf>*{]$><}|z8e2lRB% _j|ب^BUT`q9駞&s-'fKc@^`[ 1no|(炑߼^&Bner($rl7eJRTlkX~8! "i(7WmYbZtĔB[7ߔ |Y꧰I7F55F ф\\s)w]nݹݶuH9<6nKKVV..ZFVfO(@k>G_o ]VAģ \>W]j5kS5WUe2ac{ mV׿/mzϮՏEAd̕V͸̽{,{C( 5~-:MvMA5\>)uXMmݧm=5Riu]…89[{lDT\3Ȳ\ 0e>1Y} ϬKst]AF=K+qpPY>TL>ڦ53:B/ 82 ^y֘eo;8h9 bkI1~(Ǭ@lt)meYXb.[ kkfMI:7kyRYu/eKW|G._Fy>)|Axxm}{ŋD"կw\?D$^z%ѨG\gG_ٳg=+`g;U{qoxurCCffJ2dF=M׈QxIZO7?8׾0ϟ0m6GfTTzCRS=Te~=3TJ[J338M#j c%>Z~?sdz]6ᬞAū}][X6RCjd22*BXu:ǵ%joy"6q*BS ^,&X^%ާDٳuJHғfqhnsG~xT @W2nDxzVs<03~R)c\@K|vKH͙~P5]#-Mq5%P׎}]kc bGjr&?ތMi:DR)c6"=t}obabxO4_w34Qy??BKMy$IĹMZA+$V|݁"3,&c7i dM kk>pT6Ikgjw3?q=yȶ^vׯ|}u K{1>ܯ}߾M,NJu K@<^j-"TfYּ(eRCi6bx-19XIK"Kk984\]nv;#8GtaY In}ㇶvȞsvqOdr^&D*TCQNUqS U,QwnGI)'Y= ~_rE/P+@њwQN@]Onԁ<lM (׈"GvҏzAmB>||ڿ) o<ſĩmy.^Çhn-]N~?<D"Ri`yosw&IW 8ꉺ=o_w-,Itw;u;\l^w5wwfPv}r;m{@~@5gl]W3}|(qCJa$p_؋iu}Lb;nyXN-,,pG_GִC2 U<5r~vՆ$c~/셨eY&;=MMM׉rDWFY&#lϹx$[뙈OϏ? IDATBu Q~Vx*9gq4J,Bg|mmD=,߱X ˴I+-Bƽ]M\ərH,B>0bl݀WnDӸ꣔/@M>M^x].yXnaߣވBʰMzIPEyjcx<(BckZsh|=7O@hE@7zרRɤiDk9~>n9y<uzzJKEӾ)""Χj]kp{d@(?P8~8D~ÚOTzi/Getљ pۍ%8p8p8p8p8##7yW~s 47tӯzx,//O/|۷ogϞ=?rսnƍ}m>\rMVVVjW_R$3+g>WƋ/NE#n7~?#ߏܺ6k&#532|N|s<͛ݷ_t2q_cS] ӴJ`e4M#y42MM[u:_woMaz r!ցVJ '.MwM\ӵϋŐ-dtIY\̲R͑hMLa{ !=s=%鬍t*ccLzJ[[.iZMN+T*e+e(O ߇Pr(ws0nwMjX92K q]anNdh |XmQs\4lrQFnbЏ TE-'ts'}!)*=Y6B iJIk:=.]]}|S :XE8wy?L.Pj8O,I6ӔQ.zYL)tu ޳qnzXIް]ЉMhi `^i6Kr"n_R q-!fa/(Z($ kV&YlT`}όՃGcB!r`X2\KF3=KL_=GG>fGNX},/hfEeS#h^/2JE)C9?66P!fz]֯Ȯ]Wqwy+sxzi-m!f.ՈՐ闊 ڒY!Yf 7-rӝ0MW8ѳfz56nga=HRL2>k)K*m3-DK}&d%rZ 0\WMypanPjղf(1˒D xj(d| i<42OUDbAq.+}=4H6:#\++ EMUUHHmF}M( "]u_'sNcV\j$o?Joo`yCM!3gv*:f,-I?Vk I ?ݿKǿE3'>FJBzW"vq;??EL;єlUUEW0,I̬h|3|˟}x<%<+O5*ҙ~yvw\r -dEE# ua Q6^HjO#DSQ@eY}|huyf]Q6̼$\r]ƕ$b n"A֌oRYT`IkkqyMuܲB!;:4Lb"L!Xna6tvrjdĸ l o߇ H]]6{%&L I$#L]4-D6JUV[ kjxSf,=mZQYԫ1!}&u){1ƾkB̭Puc=59y-ALZr08t} 0 %skKE^);4̙D4M#=?}a]Wvߺwo{}E(,L?h Ѷ{yh=W (!iPLK屸z3f̑$?ŕ=C))S\yA_x98K.+Vl0)ɬQģ ʔBh]H\LMQ| O0::8ups cٗgkr6B3Bi^_ɏo-jG l fV4=jþ ~{ J0Udwn2s u^e֯mbU*$J&عztJwd:r.p]4 Vg&/Z&Î/ngj"_'NټwHꇜ]T2@ȼw{X@͇:(o|ic'ɷFl^='Ix::"  -(5mWpW>S}8;:ZY? 45*wfYYbB  MF=^.Oxk7}͹ 7oi ],( 5718+!v~7=K/EitƔu 3QFǣv@Yu>.yb)&޻}m쳱Yx~D ]DMssEr3HK6"G}a..i}.8&Lŋ 9[ftCt"Yu@a"l|BoSl [D.K&N=5KYY)Sla~>U%e[ l "CK6 B n7tt_W'"_e.6_3{>c0,[LfCdb_p:bK&bު"wWʲlVgyM|p(E%܉ neo:e2?b/¾a7 (9BFtCh>( c=ټ9BSY5`o%(1!v~.v,Aeb@c19Vn쿇:̺ךD=L2Or E?H cF[AM6R k X\4[*sarnȬ/%cg;- Uf)xMn›7!yYP}!+!Mu_=#n 7 7bm;y<7l܆]K~0$O0P9hYvHi6muEAh̠*qv…#%\vׯ'^s6(usզ84|]Ӎux1F62w6E617]l AYɃ(C!Nb>)c߶uz2T"s<wwv~ ypE̊` Dt` h;Qjann·XAm)EfqU UU~v _O8kB@ U #_=eIw'w6]O(jHJ䴒 {}ǘ@,\7B}twOrcwK_ rڍ BׅPKBur/^LQ{P sYH $}S B E1$Ifǎ.Tr.iCEQ7m⁣G U 2 VvߌІ)htߐB)AXj%疗 eQ@rݽѨ-Zk=s ?=D s϶>cuo.Ni~..j;XD"׆m5)ظa؎JSU[Zne8JA$%Y{Z^CWEhW0}N9η"4!347h,LN5_ 'Asq'76DXX)""$MH(ʕD" qNQhس4u>e6C |ǹ3ߨ\(WC_]̼֏f| n 2{:ƚSZY!};Dva7IO[V" ~s#ێ rqj5f/AdEN,m=w{{b5@ϕB mMA4(fZQQ Cd/͘UPڬQ #@++g? _ ߥO'5}M{4{(^|Iw.+R)hf9a\;nui;]d2+~꛲$ѵc]b/eBf3v]Jy8N)ay;53=? ldԆe!r9Pe!O'Ms>l+k˾io&Ol>'6DzꂎrVAn X0'G;v{>JhkYPTCƺsǽf&w#?' 9w<1xRcv+1c>\]qƹ1tuno7/o/ԃUOiٛnb_Xv/7|jMW}lBdej0D=ۓ"ja'LU{j`MWԳUHӺl{?_|+@'H̒qdfU\\;e~f)R6_1?G!f.;8ptttJ&6̌, ];@S:@JX.fM1vׅ9\Cs۲}My =ӷoAsk0$Fz=3ʃR)D(M<$JtօGmlͥg xdvww -s)Mes5_w8p8p8p8pH8N8p?2|#2ɓO>g?YmJLLLƍ~f۶mLNN}+>`ff抮p.]iEͳmt '&7 Fdǎ!ffL2^>']L_3uF8~]'Jz[|qbOS~?y=}!b1hiOx0&hUWD>d)_f|.JbQz{\(|X&{ }. ?|qkQ\fņ ͵c/l{8tĥlp37r# =A`K晍î# Z' .<͆NhMP2YB ΦtGHMiDOW]Y JEssFD,/hk 0J*+u ~P|(d@kk'0ߐ`*>'R\Ҍ)LpWPU[l>iQ.D/urD%YD+2V?/Ͱ^O޽hh4ސ@( aY3A3Ѵ /MHdW{7JK[ %f959 \Fy0Dzx5J`(Kݨ&o5x@ȑنPV#vǷlۅ LMѧܸL>vaI$ǟ[u4jrKa3zUM5@&2,m%y^/pk 絷XvxihR4jYa@YPft淪HC yV«_ϬM4xD<];L_ȸz\{VMc4(ӌfVsgEH k<?dQ =o]|dϧ;5FK|Hw8Hg-TQeY&77GАa\MR YJnj7Nh؏zDA-E7 _u=̭ v]׸p!hdR| Sw 2]y2v$f xa/Q8:b&UIXɯo~kqZHA?0rN0`!# D~"JQ.I;er Y3%,m\6k>LX4cLFe 5UӐeN(m7Y0Co=qH/i$fzxfSjry׌[j`[x}Nk?&,z+=^x4wt?.%ujXYkؿZ=rG *vu##H~b߹Blj~X38~w=R]1J |~ ;/sRܘΝ;WgĜD_+^]S8Z`{YW˿_ъźhn-lhZgvG{b*Ł}_c%CݹK2έ"(~"UX-2pы> lJdC@!]ܭg7nl P@Uty>7 rq/$5, vl+D9nD+Hcva`FO.*V}T#iu-$I6~0< xztIY)q桚ZzֱȲL6?=.LP#x=ܶ6>Z"cPolT+[RNs- * yHio'b2V|}S]SUG+!|oER1n_p&3&=~ م)>U`%byNާm􄪢Աyq7d9d4K8SyDA4v![䞏"9߳ vvKt֟_y7g׻Z)6Ep%NK IDAT+p=-73AlNcy-nuJYV!* "tEgg[CYX&g6KI2fe!](,8EŢhP$Yb.675m=ajJE²(md5e=sW*.& "{ͬ!`. [[)5!ZWdBk<@Yl)@a4wMr>w>zEE{WliSD49L|a4JXLgCXd]VIt&D4Ć.)|FuPwtmi0EB$[jEMJ-ǢH-F< LLD k9QDeaX=;B|6c"Ga|" a-CکpdJ\*r3_Ƕpgؒ, {;*ACR?dy^x+Daյ.$kriMm)C!Bn7v(4vqo3_8p8p8p8pCtr_;4#0==SO=8xpdZZ bϼ叭׭uk?,Q ӕ8ʙ8{O( x}:Ӌp+dLgD3K]"6ؿ&^QxaX PPع717gxst0!p)JؽEQD@YX0 #q_ӁIPD +c*U=Rd>Qw1ɤKLNn!nWb-_O;(&;~)Lݔmz]r++8PJ9|w(ĩX\H+׺ו)"~`ĔJC;DNZx)JcF$3}Oc2,'Q|BghSi~\{ 廊MG[Ό9FoVT(lIPӗq;1n-Nz_aC<m(I,ر#AE|"W=TsYfK/1~dOӯ,9FZ2P_GJood}|S*}}㰸0a|<'X+ N90t.|$^ÿO|Ҝ;qˆݹj A:F %ع6hb06uc<8HKE¿~b&yN? #s:(M'LH*1X#Ojyjd <ǡEjb(LzCdtM'Ln"nF͘Lb≺q@DNg׮fz?as4SF->z$I'-WЫzҌky]+)l$"K?\O*Q}n mAyŢF"rI&G|Vq9^\JVzA g{7σA=Cl\_iR ^Y騉 W0|V1 ئ(LNnmm)1Zk_59Bt ©nY;=3zUR"HB '&ЂAӇg!6<|V/D"S}v PS>;BQ{-EW ;-> Sㄦ{V(N/6ò$+iCu^ɷF̱X!$xƑJi+3[׬d?43 ##x5O}y65]οPЋKumVF37!$_"VGvO<42oJb/Vz–`MSEn7 O0Z`-qTI.$6#5bnC$ ~a]K ځF}!:h[lt[vZfU#Zx]㼠L54AC|;u^!x`EUUd&ɺ[(uh+ڜV(AG`kU[W;QF5Nd;5|9Koh"kGdY&::]>YESd^UX"|{>%brtA8l 8jlCgAʭJ¬1|!ou[ZOr񴈞V#amk{}, {XR'tƛUH ua^5m"Zh![PbB2AXo+RP:5bwMN$ZeN2 v:^qơ̝|kĨ&ίc m\׽c-!QqX$Cf,kVҼzi]0 z{ܤkv¤޶iR76cJK2dSX#ٖ%H`HQp6}g--.9> JO#x2\bAYi y!@($AMPTTJϏ1qMV Q!c_[lذ~N^֯_χ>!,۬ yuQ$R,Cxl|\'|f9)Dȭ2Vn{Ӱgxʕ\2wf2,jF8fX=gddD g#+) ,[e~Ɂ]?SjeP-!\07ͯM>O.4madPqǽM&A75)-WmU;?!~e$ 5juǹveaC#UA(ՉGN&7 F^=FSQ >*^Nx@.C3xG#)pi#QL o (8*KL} ᫺_{+,̡aO+K.ݥ[TsqոapYL{ui2 ZnBq89IPk+^Cױ.\Tmy 0pLiyЁ$tumm#FpWR 6s_ n^ {"~4AdCff’x y`dxcqg[2\S2L_WWEEQzg#/?j<ɇPQgjY)S%v4 ItLzf[7I>3U*k 2[-1y358^*2=x_ κ:^- "FY32Ί ~GGyP]χ>$޷7"GϜ/?8D 9z4IOO?I"$3uxWN^ySs*_|3RN&Ͻ{h#\a7YpsV՗ɜeI:sB F/'_ t\##^N=44whu fqvfK~&2p;?plUwQSh̟=}`\|kba$d]$І.ڳq2ɞm,6I=̫H=ΓGjPF {@^hY4N)8w@-2D8"F!J-%ӳ| 4m_*tZMP%N: M$JG(fS*="M({`"FY&.O gt,%BKV{O%ϞĆ%B&leE~.5F{rOa;cArCDrxc1<eJZ,m;v$ lYBGOGNݱi`p(QѕH4g9~AXEe+7?-TT:=ʉƸL s+(JN1oP36CNk(pW,:>rayL.h2W+>6.qdM9ʼnB|R#SNQ#S(JAHc :}w3=zAtV1jW*'oU4ߣBl$.5q0?DԹ\ 9lBIIF[}ljVT{Ay0II^ImlիDR*.":׊" p oxB 7 dZ*.U@bϋ"L *llllllllllllllllllllllllllllllllllll5llllllt:[zk{<&'']3li^˗/ظFuE+z9y$է]UwosjyF}CQ~i.; 6-E8Y("9d*Q\>=t5Z(ޒ{YT94de־k'<'<%4l6~m5U%'24&rjHjP#";C;YZ[(e8"HT 5t2d| (bPvzQfL~U+_TILʅ鮏ɿ~F" &} /#F$$>8YX`Fdq`I4u [T%X -WATI"!$%lxw& 7|B//@3{=|W`c _ z[d! "rs%*%/`U\f,Ҁ097BIu y+4_&Lp_met[XHcl f-SPY'oXYAlt}I.K.  cSZe02N%h+^+AAƥ:d͢9)["r@ Kp}kѪXҸok BΏrJa~w(LúzkסVD4y EQx[%z@iIlt[? Fev0`ZKf IhZuk,r~f;.;bYpXSC".uIaEͼNw]O9(FE/6DٺJlk)#i}$VRe2ƽn`7<`sAI .b #4\Y*LiDGb4,OR*RO! -5yx ȦL\ KIL!=)0 / ,C}Fuhsi K/<'K1ggv6X5 '  K/y^~O Az<Mv2/>F*^ͳh p N^wFgK֋)_|$}D8 r__&e|,/˖2"/r2:=_Qr50H%H9)kscλzi_2mAEh[90=c)`_9$Kd&2ijϪ5A ى&zEZAMM8[$` uIQ<T9:av<?$JWhXi ٱ/5 y>Mu1Г3(ne7K: IDAT EgK_늪ust,Mb }1$Kwu# Oi~Q)rO+CӴ,[vAq7Q$1c q4L#'ڝݲ4[jf}pv'},kb+hF4PWC̉p΅aʞqE>[n!#i,}"wwmZ`W_=z/Hzj'O\֞6666666666666666666666666666666666668~ٝY9[no?1я~ݻ#_rį(h~?J,X QthW!!5UQd T:"!8@*ۆbd?~:W5cGo柞>B[{KU>a^/d`(AtJx R,.(׶18x`TJDR+"~wGO.۷yF?B{zpUJڳjhގf \.ͽY1ྺ19![ xc|z-+X U.[)Qri(##==\IS.~m,>J ?g1 A޷xSq摈x ^G,i(*He,(r6Udr#ׇ23CU*S YS+_!±c~ݨ\!1\20c'ɇsG9[[Z4g=MfyKqW4]cϏл>HuǰCgЃL.< -lص+]5Kyb8t2IM @ll.r^Qա_Ug:{{-c7;4)|2P [n JNh?竛7St]bV rם ZM6a>efj|w9 ,*g^ⱿҊ9r[n}mg;^9s1l:Z[QqU~'r9~KmGp}miur+Ӗ뗦A^'-XJ0^I"+:gXB|e px`RT}IˠRvT8zc_Rx =Ȃe|gųGa$\`Nt%Ex''Iщc+[^S'.-7U$mHlzڔ]7[6fQlM'.^q{;ƛ?W8Is$nm)w.^t~}"s/|> xW&9P|;K:bx^IB3O<7ʥs;AϦ򶈜=pܒ]*/b;sS)f r>4 Pk*]ә?ɱcmCg8eMMϞ%cepáW̯oc!;i铦ΖG6a]ú0LN l|i1^wE_Ljު=?3br_^a1Ǚ CO ?5U5ƪ̹cFi޽EMUVۺ_H,wNtDyq*ύJVq'NU"s z!~csI|[WsQÙHb94hj[s+։cvho]ǒIdh8>(Ogo5 YAwZηuvOѷyuV mSwO8w/mv!.sK W6Ϫ66666666666666666666666666666666666X_V0O=[lo;7pC`ttno|Y*'qa>|aꆆ6o޼u@^]/=Gȑ#?;>?<>Ho> Gx73wkX{E&3& ^Dq**>&p8^`ncGzܨJbOHbO*ԕPT5ׇh52ez%oVUˋZ|V'qkiJ4JEgs/ZGۦ\ijq7eXo#AQb#6 o.2sEA$#(A> ݻ9?@O߀ WUFG_dlt4[#QD9 Pf %PUJަn,O7'vD0Z&S 5W]*k_j_tέA=,7# AqpI&/䞷V Κf~KI7[%}>Yi znڵ4昈3D-qag@_G!KRɼlLJrx^]}kK!9EF i7$/]Y &}ef2X82BEYwJb8wā1,;{ZɼKļ SmrZ`eDչ WSu,43Y30_G3EUiŹm("KjjSx/B.;:2t52U+?G_xnΠauk(+ `5ǽ|Zp1s;du MkEeq'<:46փ AA\&(ܶW'C,Hz̟[fajI6ǠFF,ci~^p8JbX~ot= ?\8O{ 6mZSa\|1=zGhjllI"N "8*kcMC *7ʸzx_kŇZr=\oE"fȭnQ DZ[s{|1ʸ/ 赌Kr}=ʑ#|t:bӹYvc%hmm%,]"KNl*^%LI>cE,(FsCDpC:#:D"|wdG"{eogU9ȑ#'v|#a~?~hm"æ"c>TIdllGwB\%kxEXi .tBSSn#TU䞞e_J I)Fq; - $sp0<^|>-w$d;Y𵷣 +ByEW\:cnzLMq:'25ea3a_7ݫц%ӿ0 eB_ƙ[$KV5;X5YݖP(6]j))gҼŦKf w}Ny٣ķi:)䢒E^uӘC_ہ:]z.$I\8Be4mĜ$0w`eûw1sF<sp&Z.wr 7t:r2P%c$ rhzO\̿>Y YHgزRipp]?/=Ad`$- K o~L.nZrhx㯗_4DZ1BC Yٞa5&c,ڻ&-jL,ŀGΉ 2D\ ^Mr~7Jy;s hMTİصklg9̑)-ܞR_8g>5׉)~.G}cw\^n?7J5 2A0bO*ѱt&ſWȷ֏!'~%ՌX٦]O({v(K5e8<#ŗ%ٺu+?ɽe:Lsq={ .|||X,WPwwe'pw~%-,,p8x׻ n7\z~N2tMlw)u?7}r$/BM@::KsHħ o8̧u7g+ސ%uQLM _Q!~Hd۬m(ɣG ^Nx, =~FGܲ==?S]Iш\2lo/)B4hFx8Lr8e+FfP޶/%IGp+\ "Cz.{n˽09`>;27V^oIaɶ;r3KY»Bʉ+EBޥUEpK>ra5gx4spjٹ dZ*u_¢*d( y re.g oZE]QGDrF {^DחD8KL*2>k.,!܏)b4Pr&{d躖[ElVM(SW$zR(B!B, ^O=Xv癜G6{g20C^ԮjbXt:I!`y19{wsNDA8'P; !0 3,)h/F"< T+ ;;00:!ϽoI3EQ>봵x]2-h$)fΞaK*Ir"ڲ:u珖 @ggk1rux8N[>'Du,6`Jos ;t]c xj&_u`i85'D-cvg;ĵ7mc3ؗ&Vc[ ֢Q6Ѕ+JNGtwqvxnm>FWW[n-Kpllllllllllllllllllllllllllllllllllla :;~8rO?4]]]{^.]oo{p87??U~7]y-b3 Vb߾}yt:MWWPt݇>!nF^~e>ϛ~_~f>cSU]^ϳI>w݈Ea0?ֿ Y\АwߌNL >,xH6[gpm(PSUà 1jxXRM89W\T9=c͚1SAIeEoBbR\|kexqY˾}|d$bW\z0g*W %B 3 X6).u={ik 1Xћ&V,#"U{{j}-ߴog( ڵ!/֮E4A ىۋ~qvkJs71ra/}5JC_CYY*-KnXI@YqJuْ,5n9W4dhyzw''ٶN UQW^ŝLv?>~Üblti;=׮i >VTg$\ k?S_lg8AU-|'=)9NU N9['޹3oZZDsڋbr_C)3K)+Y4鑊EBhv&JQ,B!'ZV(\( @%a@L{֭16oEGGlY(LԡONLfϐJKTU!C |QskE_,ӪʜUEwS/'G^_l=J2 IDAT(r^UjaO yW'U^MZ5a;8*R,M9>3CիtJE_0sy0Z6geߔG|U>|>ܹj۶zPUxfPI1,ץ(9+eIDGAӉI8S >H$'ˤ''ƱP_t/N[b3y5pAO6irݼ/8?s{>;W2}(d<ݓM5"caҵVPEg:VCy~H: 1@Ujj\NĦ&Ę,k(JN:oEQ=yI%0j/Yj v!4Yfv,7PbJ,$a@WwЍZKCHu-4TvN ym.SfM[H4BBݵT %  " .Tɚ'͟>"n>3_dlDaj*ϟf,KJr2EAz}>;:@Us_\~yǣE~sAZcYQI4JQA'=.x<(2Ko0ˁ(='-dcccccc"8wu۷ogٳo}v&&& tvvf{ eD"nVy䑒֯___t:G?y-c=wSr]mm-Xj_ioo~hoo_2 |ߧG^L慓2ۚx`tR W <\H1?rWM89s~OShK)ym'ϢTW]X܍^9␊h+P*..1)+2/#.R/UUmb6'(=x3QevtPOO&5,;ƷJJE pk_ћS)\gy*|T$URs.^K?Z5cFyΙ KKfgQ9q6-znڅzmvU|{\56?==7dmAPUSrŴ*! "…{辿<,WXsh8--׎ScEJg#xWyzidE%y4]#:`wXȦq8ڵ1_dk ƎMoN7h H]U:ǨwLb!ۜ8]8C45ņn^ V\D$)Ոk̨JN@:˭m=ȉ Xy-霏Ap.$SF|E*e(p#{k97>&/ib[s|ղLe+uRF;˹o$|tGZv 1;C(Eq,جڹ9BY%QH! D/ڙA0JrU+VǼ4w;soZw. ͷtMg9ϱWUtBEc'Bk&o a~Wth*O2}=#Oe\(s!I 9wu瞫Ѭ,v@$9HXk9N=Hd}YM"v+Λ{Q-MA>dD%ld;\V_= IV7,v8K^91xHE{rQKưxP+3$n]NŪ&T\p$c%{*nPWW$>k$'D}c7 4 }iq)K#I XG «͕091r{)^-AWE^K,n}Nǽ U^"g G<2Kt*elߣ)^}2+3DNif?6U62w|Lϑ (1}'K#QMֻ(&4|6S{¼jNxO><ՀϿe=]Q`l!‰×X hqE{s~LwG鵻pxx FrgKvecRBkP&7~_rXu律_Hء\/2_p.\ի8fff ~'?I$u]p%8/֭cַL57￟I;ƱcǨw馛,knn}/r 9 ]oo_ ^{5:;׳cTl>NkF4.+'P3f&rõ5/Q[qٶ4 Qhnˊ9y$,#,f6kZqr'gkoĵ#TGmF}r$$tlܸ]d$ݹ6miqbaA,?e>D餣i90B[[/7.oSxxgΰMFݦA`ƍֶ>ot˕}vti9w7^| t:ΰnggff7W6%0!8hx6p*W3:,u N6#4бjkZLi9<"#_Y| ?Qg]:C7 $#Q Bu1)e&7/(6N*7g9tO&j;sǬuVg3id)=I9g5iӺjTpSn(I›(٢I$76.ĕ4]]ZY2cee+?3YooVfdmS`lvʘn|wsMN|F>A)a CG&:]w型mw UQ[h E;u^̫Ψv^,gvlʶzZn޼ɫ]r5^}EkT*D"!f2"X[w`sbqj=N0D"!J"A(ZoRYoYrў|2tȿ]]vWrl_UTl7mYh䌆 8~r3;?cǢjUgƛoɎ;-g9+_a(uϪݬ21)Q 15(]fW=:n:,F `8A|̞GG9z$ze3+#+X"[תi#&S D=I3EGGjC$ag8\ʮy̹')xXNm W~k: (J(/{YX3e`CߑN~,)VT[^Ȧu:r^A(]Ukb1eQ:wɲnL:RdD<)2OdȂ~wSr}۸Sη ў5[&ql{X /ޝx^DRsrjRtV,@xṵ؝SIt12"u^$fc167ԄeJ%EQ$u\vW_}>o M1SÇ~PU{ަ|NVc#rϺRyሏqG (ǚE} N' \ /[M;x'Y=Lmϻ1:ҁ࢖]fpMĠe#LrœSeRg-INB$mlMb2eC!3 "UA1ƕǓ8\8;v}|w|<03')D9N ^oYFfیE~㫣W9ƿntD"Ͻ  ܸq#Gp9Z鮨䣑8;OG(" !f@'$l/o?Y υ篿!!?l>GMdIrUQsӟ({΂ܝP{_#SUh*H%%0mXfDY!}鳧06e`4ؘxQQx=0x{;ȸޅ>i=)2Dm3cpPB ;K˾ZIϙ{FH`9Z@u 5ٍ+~Y{r?W*Ɏ.Ƕwiy) L0."pfU;Z#`γmJr>lbJWr) e9?8G>'0X\iGQ\ }go} pFji!H~=ODW㉯Mow"ߏb`lp8Us{7?>f\[rMi1 Q%wtܨltQ+:zyHۉ:͡t\ܻBP?_pdÔ6%hS, $pT}44_632Ʈ6›>ۊ 6G%1>s;o'}[{Bb1Æ<)ooa;eOi7mYlJC:8ej6ecul]\LWlmAmJ..PZB4x# 衳3)lߌčkr䓟GKP46r#/iiiyj||2?Z~&PY='ӧ1R?Y5?e׋Lgg[ nIu Ƀ{X^|OUAAL]*Z3Á0p0̮ӻRQ93=MN8fC&v4ߦJTfxؿs1x^WLbdvctN#;5PtS::~ɸKF^]P ^s.axq0wnl@U|OT0xH'cA VuOd\[*"WY|Yzk\7ܽr}6/ d]z+N}>>3KX|TFx,~͘;8e+UfBcC1yȶe؁<y AUI(<fsC؝CLOAx^s_̔E`22ʏ/֜mχٖK-B>F\E[GFǫ?p8<}Uao_T·/!jC(3f䥪q)[+UԪOwY[3/g+B(pz x? !p+nOTMq=~?9wYc萊40Ea t"|HGc~Ml=ԗNa2[JOP^V.>s5 IDAT3s*tUpp^}?oBoo/N&AIX^rP bQI.]e.tޝ>Μ(1R: Ǜ1֖z \Kt1]/z'?34=ۅYd=6MLlcSrt5X 9UtJvgT~344t173K^|Xf=saw?[E>&F/ByGU*p:I#12sC}Nb#QYBrZ.isvVWD|~gb Ya$z{q\"ԺM,$p݀"I0X)FU-?+G8 F'Ն6f4CrSѕj"S`6 ~.Ǒ'傟'j7P7b]ޮL;gj=Šo0Q8ztX$)$&$@WW׶Ϟx\=+okh4w &>?&\}8]Y**>]}DQbcc3)dEFq q#pgYI\ vvכgqU{>+;Qb#?^,{?oJDtwY}v/ HÁA t ~yRƭWWLF|=#|8c (JIٗ;%uA0$"%?`@_uZ2Q`{*wmV޺dYތ3Y~Woo. 9O1z6B T+I'xset=QP/) dd0׬D++2:$)9x'ߑ-(寂!}}R/4$uPw5Nm##m(<'EU'<^[CĖ.\.C(oA\?k)UnNGk:Y^NH/2ccb^)T!!PU%w96} c0 &j@Ef768,XOMPPp߃>pC- v2Qip> f~7Id׶'yCe:N7n "WAڥ^%p0yHۚ|W2=O2K~2]=LjMSR4Φ%昙#Fl6 r?t>=&=]($2egPS2zu )g$08Q:1WN&21hdB+s5&R6RTNi[ >].eN'96d,ek2SC4NcXM`2>ȟ^nPZ?9t=8:p$ąp!%]߈L Pt1޵O3޽H?/Gy'JPunnOse4һr/eW'X>Ex݇ƒQfK$>h\]k͍ƍ~/!Q&Fxs߳ J~gVR|y<k=\6U؂b*pEM&ej\Y;N?Z p |S*]Fr4%d9pDU|Bˆ0xީ;#] ϾX!ȯ/r{[dCCY97Ng~Kښ*+A-]IWU  yq6 f;%=9e1iv9}.r&c2\3b;E1G<"ಘ6,#juDCL|q;IByL)V5<)AVvS`άYB7ᏺivXQoJ9&bk3NY ͇1/ө cI*\VJޛGوgKpOΰ{:ݦeɡyf fanO[eYlW~=osl [BMzQ_F =읜wz!Wr d9Yu.i]:q9!dݗ}>{+vǎbKҶ :?Ωgrn.EA`wl] |őUQ9\.9s䙝eVEQ8 鐣QQN&&U!lvr2e[iLSkz:/={D<IX_`̷?ڬRfPU$6[zdݺYCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCmSr}@UN_7[U16.܆'4K71>F .)$QL66J6I j>U~ǻQ[Y/> $cC*rTUa|D+,&1g c=qG\haɳ$;9vL_?%Fm9uJbrrSr5B"gm1(*stx2Nt7p2P.!BI8MMA 9d)QU5"z߄ =T:-1K4߷鳧h鳧rejBV Uj lF9.#Ϯ|A^}xfgQ[¡Ov[08#1>A@w1D ;Cbu$6>-l$c4&"@p>bndpDZ bn?I{>sW^~d)Bq^>ښi;_߅IoLxVQfc#)![5Kg7JڜN&~ڜN`i _m*8o /_Ḫؘ<7 _嬱h^i:ܒ֯e堔U<$Ǔ'ID8-I6_'->^Tgfi?rAA谘u҃Mg<~+GsBUǫX[ }2_tJKUf/0(SKuo cNQr{&h}{;C{oTO/ <>x嗘&e}pMŵ"6g2ʡC9gv "mmGˢ͛6E>׋&c؇~6D{2mՔ[răAyڝ&nD2km|#&[ŒSs4/s>`4oݻQidp,'ߑ_oxY:(BAv H cI MLoo1g+$ fCX^X #Hv'35(JĎ6`5$)qM&{Ǽl+/:l33iqɉ+--?׳4~>n ?vUtN;Y ݍ ^H~pV]8 /^c\e:;%P|-ɵ'Ϲ|ΰ#1{%؉8HAh|> ꈼ<^jh^  $ ,"E]}VwT[f8Af+U!4rQr`g|cgq}";b-s15J$z( !9Ma ױ,CgDD[}忳gࣴVTn'Gؘҟy*,޲AcɯSrSQSS ScfٛG":}F>cZd>U C-SvۓYc7tfS}9VUUڟ$L= XZ:B(2 :֕o ~u% G 4ڷD!D?tϢ3ZIb>ߴ|(LνDXoL$jg"> =b}7Sl<Ԕ 6(z#c&Q/RBxQF)DuڊD!:<gfP@r,v{g!(س}>*m6.ٹ ?"*=d=_C~?WW8>؜`etC!'g2+A0[/1 Ӝjy3~$LDnpL!q3Ki>/zp`.cq>;wfMy`0;/9D2B>\&`yeU,rX˻HQT3r_Y(J$^{4.Zy^6D|>>߲Obg ì_e⏦ChkkԗP0oW5444444444444444444444444444444444449s/BCCCCCCC_|w#txb#ܚ8]= vF$"H ^%gfkp;Y`xKZ,d c2Zϥk3t:" 2MF3 Mp(l<KK@$22š ' \)gN7|%fsf:op36ZH$bee8P(_'NY!`;`Z20jH$bE"4@D/__hGU'/{bvY^ADt.G9^5cl?ŗ+l EC%~]z>$ xE F)1<*@(r1<<כk 3=="GH/b:n0зq߅ڌL?N'`x/rngIԘt^!ϰښ FłmfGlcXJII%7G!rʳH(BLL|c ky#^ 0yBKmȓ2MM΍!d8u=<<,fA^`wn(ɒy̵KC^#ϋcɱl}xExvyKez X]-\C(Q? pK쩨(*H(q|X:BN%lˬ!NK edeeeK(nwqfۻǤA̭%&rbGm>;Pp,$I ߵ{mNmNs3jXaڙqt> U8\m$,/==Ɇ۷o1:͝;9q!?eĮPc}2B]$Sf0AǃLe3?6"]&\\GLs`N^~X\C2f?c``W˅yyXMRzFmY1ݛ{Bjjey2fS r_F*zf +e HKTgE0(!`1-5t۔9xx&dl_A[2;YaflJ#xD"\y38FNMƆnqIBFonG].VGWy%p,fT..HɓeZ#N'ַ~Dn§~pt:޽tCَjP3Uݱ#)z||^z⥱ ".Q]S]4u>&ߝҴ!~œs)b\قlΩCDɵ5< j}=!Yv#x<0YAљHj<_*{ZX.-X>ilm Нgp S%Oץy;;wUb9P1G\\vX޻kW8V vĜnG 0L Phbk%)m{H GzGD̑yţ *u;/H pLb6l?g ;Dw˛oɎ;>35s Gvf=j7^u\]F1b-m;9gvɓg:#ĘoB Ec*&BǨְ8NT$Hչ:?ι1-w)#fEh,QfeZMbZMo/q`]RYrr7;E?'їx-sdf+B!{d߮Ek鞷1>M_f.eT<4\y7ԍQh/2+μ|!Q T?9טs~H=7J$a&su来l=]Gn<)-+EUQ$on;>+xp$8\mf N[񺂳H`~sl{O޲%/j¥%l_vXk_glֈ`觶}*WORoO)JƗǩ`Tv+NB0 ֿ֞91 $ D$,֮&ffQ]:z1c? \VB WRO] ެVƜj02ʞԍ Ց_ʽ} 67vvs`Rz62)_98D"v;ރ3mNՂ~o3K;\cBe5ޜ{{3Ә=f#|P)x-2'u:!soq˫ ղŠmf=ƃ&{ϳ_ˁm-̝*zC\YħI^mvKUCe+wz+qB!ܢnffptGᅨN UE?Ee(;LWp>rnkSP(Ơ7d3>_}C# I!XI^9<1ٶivQd'KNEp$ {kcg8EA.22<; GG(O )ԂDHp{Cw?BnN}KZ)l wAoKmo=fz +JDMdS{hc۸Je'lL[vVjbDOP0k6^\$18HΝ bnh&$>7p{|;1_r}ܻv/eȵ+c88*[ȅ2HV9ù,q~K4Ues`݄x_iZ, erIRCZAWQz5zq^\~)e6䊜pajʎWat5)L# ra#cxy#cCk3> a9p&"9:]ɁX[ '`u67X]E%TA.DZ_]Ž.&!7j5lT8u@$Z3pӃkd|G@|_ϸfn--. djE&ٻ^/DR>$y]FN&b% U]bee7vŝZk7\$dW}|խE;޽Gx_z |>n"Ag:Aoجqӵݩ6~ m%PNؘ"C|b']*vcdiu۲'޵ܤWЭ(ús?;^|cLp]aw֭rZؘlt=Ynip 8%<ވhss2!m ~E+N ϩoΛK 妝O_+Fc;͉كbQjxrQlnYPGɅCҾ>n^^_&h[s Y ܶ)f,fcl,‘#v9!H zn("~N uqU#"S|Ta7{w㭁{_&X,L §Z ~e#)O1LOQzz܉RPS@*qoN C!!aXu%ÂG0j[ӄB}q7v@^O^TT48P r漨 |ROJ^q_G+k>-_>舮谝Fg 7vs,-3z~U iWk[2i":KVp`$8yqktwFXhݍؔo{Š a~$j-g-C`K8+X".,צnzz>*=@Aݲ>/SV,7c sL,TS+n-[J cO_mIJn*71e@9RQ?˦pS/_Yg,a. T9dS>83S -~p: ;Ot ~b^bjFB-n'_ym_z߮lUUa>d0 RC疲+EZVlϵww"xqIo92:" :l"y=H]n•cFxe[%ߛ"tJgRw}~mJ@-_kJ ,4{ɉMLۓvrtN K iiiF5~.rI/,CTĶxk:pE>aP1Y+s\Mg}I  HY(޷?c,sS:gK)c̣G8oߦ) eeO%E[yム耹?7,W=QJLA>;t CYsGfž>m-~O"d- 5y𲕲KQ[D7p;6}j Bc_xmg>u }ReNf͍ҌYDo'lӨ|ANPu`J\h(:ߡFbtIԽ[9=%k{e,AfWؠDqvE}}wޡ "{ ,ԩYV:>T+cKVS{9'}*]w9)3e Nf#u{ 07K 1s3ǜ_+YCWxg@Q tVUwN'#aL ˞=`zITɩQke/[a>H(mJzT,HMQed$: [ic/nVUTyD:/X*&6eY {ipipC}ۑv΁O*=9gْwiXhZq5NmEmN,STX&h\M ^xEKam&H$HAm٤,ˣaIPdd3NUݫ.+Qvr{]快K;1W\.ݙL2LYԴaYֈd-$&hR$!Ѽ?ďڙ?USt}yy󅍍pn]G Z?\V.7{:.1Ԇ H ?<0KK1PNsJ*v򌌿ޏ&WνB1[F/#GP5AtUU2C,'KvmB@j}jFTbSQU𑬺A >GI)F$_ɪŜr|U*߽ʷO7R'KH|ORGKKL_Z\ZJj)L6f}}GJ(rpq(Fw~UUw74J]C|jnfr:q !'jbx+[AqC\ykĊ"-[I!'br 3YWk Z%b{]08,״nW5ČL@L9g5h9xqaTMC l+?M]̩h~ybj6۸HtjHY)EPNʮu@59 A$rw ra2& |i)$++b׬ŜEȍil9ɁxSܨsJlYd)~}i<żǃJ1v1H,CCC:e!#m׾VSwy`Xz~맗^gpW>~::fة׫ eQUhX4Wk&oLŹz-VEyHxg~@doR͌0[-w D$!E8ab1666P "xԌ_avqjbA%`Z`;H#Uq^u|#ZߙVn,xHR%ܫZQ( 3Jr47壏245E3u q hr9H|? zýLG +L:bٝJ0"ΡA"C9i*#*;o#sUojf{VUX~GOa2|M([Zy:Xuc+eylR Pk+eWTM40xlvRP"+tr_8| YI&A)nO7q)d8E ;Dgh|wrEEF>QRȉ{ȤFö /g<#ǹ"9&ҒdЋټJY41uY]i"d!<>]J$ I*-.#[6 Uj ݞfq.h>/+uq YOڵyqw%y3 ڨJ(ыQ븞g:Չrm1gb|DAD H Z`|Ir;USw) *ϭ(X!8 h,_d1a'7Upc#+W76" NA޻nu+0#P@|:H+Ȥ.ʏ1^E$IBeTZxyFC|6ŹNV?/*j4J,G_֧O~wzU$.yXH (:؆)>l_Ÿy̰Y81ܨp|#QW׉ez#+Y /3Na9e^6Jbi '(xhRd5kA:u/1Hys:LNNKE,U w7 ;hPx(NFGo 3:zb l71[#LX@3Ⱦ5ଯ YQY];CQdT:'(r:M)X,䲥⣒SaaqQf|2TU\.,$MmJo/g58zh働O7+Ċ ZɶP)(1NƴΜPO*5^զHDߚ'|@o~DDr2hB^v*dD6;xM 5ˏRf ^xݿFG^bC΋qt0w' 9I&eX\J6Ũտ!F|ڍ|]`Bf` KĞ{N5nxF??'/\E"A/NcEo ,YQ9sdh97b%$A,vMM\ B^/l7c hyF|n'=OUɛwU2x$V ǻ6+Bt,fSkqQ] \ e*߂rm\iM5V]UƝ?FO,f)P Icf$XXAZZ"lfgq5"@́!JH[P`s/"̜_=0?We{s٪c^|AȑD@ӞhU~E-u+32_e m(2N|/T ;Ey#Rgy)'a(jvfC~?ɱ1^tf'?';4&r' >W^5c$OoX6XB'=1'k w 8үH}Yiҽ[uW!wHݴ}?|drN\gHK !>OSb/gJuelٮǒc^Q}3" :DU3I$ yF&2$ણ{XH!{5yJ{&{$?N2tvbn5E|:">NɸYcxb0{b)N+n/ Bcc;x"y<9}ŋ|pav6"N)tt*t#uvF])S !?(JIx3$(\M*x 4QPP88A v}WGZrw{HwB{u}t$"w8j3d1HA3"/7|e{b}SDe"o#nqpϟ2_`쬉$ܬFwCzjQsuuw=՜Yk䮶BP.,L!\7o׆M_K\^D~ϼ3$z1I$G(L߁_ʑ}<޽1D#.^|$//'im gVQ0DW$65e޶[fFHsCxۆr 1" \=b׮1%PGR{zvD|w؎T6 ,A3%Bgx?>[@B<,\}$+;c[1yuq鏔_7|=6(2_3&yy3c :0@cMb#9b (heSY|.YQ9@TS7_ۿb$oƖr(2tf#CfAKrUaK" [;VMDcn2򌌯8sf2 NG9k7͝FEM:#8A 8Q' "G UlR$yr9GhOljNFNFY>G?zlYB"o $yɁ(s_J3a5UcI^AJTv8[huQ:usOIdJRݷDW|xye$8úf_Sqs4qBYjT=?ˊB]1b3lHHK]2 7]':7QMRYMKKw1_&"@/iN|ɴ(+7*YE8wCD,P_ Wh=gۦ} [?j"+xb~v!xsXXDӬHW&kG~ x"MHgW?t_Z4,YPYbp|cW}$䲂@@|\DfrjfA8#6(ۉ9.q$YcQsf9Du1oIK."@y~j#W4dY=t: lj*b܍Noύ 2xI :/*rN=۞5~-qC1:ƿj[>F  2T=k4>o9~#0|PĜGN1C M,jOp"+ D&더OqnS>V6.9ͽdSN۸3S\`t|B^Cm^ IH,{tEtq5RN=?wrM߃2.u!%֖.0#Ht~" RoH_.X_$N;;Kn &2u"5?+trC^nf1HsF,K'\❋b167t7nT:%XRK|,F&[j>#(AVI&rGM/oHS<dzbb?N0P %}4W+5) 1* 8Q4N@sSV n Q~p-YYA T_#AK_5˼@?啯(h8S{7ld D9јp/$ny VKTb_\Qi71zͿ+9[oyFd1Fꌘp|^H)4@ꔓe'zȭ~Kp00&F6/*t8O"e}*³|#A$'1676{Ϡh D|dCa<o\w_%7Mx]"&:eUSN^dif1|z232^F!=DA`G<)9zN07<f,&_%fMum mG\%_w;0|ִd~Yr 1plGiC-p%6,ŗ5Ft MJMr+坋{ jbUS#t8&˾Gpͱ>P$ _|v 2&gHM-gI$>ccI o DZ8rDWu1|%*@K?cN"Rʡȿ:G}/($ҝ("2wZ"DK-p o{~7 q>[CbDپX{jnf!%aߊbAn1E-gEJ~.^ DUUD?ޔŋld7Jr,CGN D'yBta<.?d8g2i_G_q^_(a|(>>'_C"#Ȳ.0_li*RUȍ{ZKM-$# \ZNܽBzwQIfݾ}wsUn<77Z| &mSTe>'(~cYN:Q[C8{vJ] 3~#ڻdPܐZ"̓1i`k(8tt TF@ZIΤyG::\0;MƳ|y{RX}ϟrx#͝o2Fx߂eHX ̮S⃆}3"pzI%(^#1FEN wgfV3߻_3d?/\a5wXL,U?p}Gll&2!Sˋr]$ eO^EZ Sz>EOX 3vOxzWy-ޥjx?3|32 ~NTHEs3=΅GHy |f ߨ*KdM%VOr}j 5Ojf=5|gt:wcƋuL_YٜX$}>VP7Dvv&hwwNdYq~|A2%yx60>YbT(D8Dg>PR:~'8|(L&K϶}H_&DS ԎbMrenS 772q>drG|m+4r6ƘcצA^\,7 |L2 x~=`CZ75wZ%4C BUEqyl֡VrZ366kJוrbEA#?k}Ē^$CCɤYG" v-(2jedd6TS듺IQ |  ψ ZvVV&h32RPB@12Ko l(?jkTMe򈙿~ C?p}#R!0ӟmA[W2Cg$?w@3>U|ry'?L=C|:^a~7l4nwtk_Zdrn\"׈{GR̻D\?툃*(K$9~ }ilD 1{K7 \W:++ ':"` ~c?t_d!7-E4;w~ڃܿ֊peׯ s nMku{x g)ĺXS\|y7kEzukqR38Sv&3ze'Q/A'Av=jXLTEz%i]gװO5cGO#n6Tz˶a Ae&Ɖo&yF&'""+ d!d'mY?GhF5 D܌vUEI_ŀ.x R_\+d?' E  od)Y)G9UK5%h^Tm5 \\0;hY_߹pU (hlED傎R?ȍC'J>]u4qoIjt7Je 8P`>93u^~ùo@'TDʁ*rӏ-"U4A㽯ZpO˜:lyneɳe[bQp9kx~όyy>_Љ$Ici#,KDO3 w\i,w(l!u",Ia~>\"` ]0oUD*Zz~kE`ǿwLj 8͋?j*u:vo?es{N׋bՃJY>|l{^j?>-7Uߏs IOE=RRp!:I}7t> kAyQ$Ρ!~#yz#[xO۳3~C^ſn έ9OHVyۈ'rR39ΉqcǷv,IfrE+ŜJf`AiJPS_}6*hx[c>=66B,< Ʀcl6~?$kXW8}P򶯫fkkW^F54b4eøGeHKQb[Z5˲LDwYJgO16O$nČWǖ5^BT onl2rykf(Am _9F /h"\\uGx1[VU϶頪0>"C9Z"x]~R)'!~.V4+hprZzܜg=I9* csK?bޭ~5Pd~LIݔ|2D٩ͳYfJgrbd;c7bpl-IIҮgf%e}ݍ(&N\c}>=DI2m lb֊`'.xR IDAT^-jo/ӨsbD"ڼRMa[rY?'8p $fCU ވ/9vC\{6Ioc3K%zI򎡠eĶ:Y]I\[&'{$!) tBq2>.e%jV%8j٥=.<\sef&nWc9beQRPbhPȬ/g=ԣѝz %OK4:S8!3?i<26fxBcҥOs5:/||/]\@j*_ʲLG[λzK}(^/$αh3EOO4~+fLHN+=ڦ ۤ:HAN!Kgw6P湩_eal_'Rf*X##,3|zeåc͋8݀(=i32~Q-6OL≲Oq^K@0C[=%҂;%|q Jo6Sd@x&'`9,%2 މ/1)TU%7i67v + `Fl3 r1b}|Zs88+3gelSG@A"pk#WWP7RVdbz_2?VWܜ/$hld~s'7 EVF /B%Q)!<7q\L8 A0Ock$z Fݦn9<}\mPfE`p/Ric*M.]3"M¥NW'=5(+_|l_fs~SPͻ Fo'I,#| \brw(%`槈{66666666-'<;'K9{dwQj= @`b~B!DHWW Pemb !^no$2k𭮑κ$`4wyr-K}O5ݺN?7~YvFp8]ۅ^ TS52 NY}sGwD:;SQpsW5D&C;e<ܛy}n\wAޚ]ޮ;=ceׇplEp,_Z6P[hDl/k< ffC@T)Tum7 Gµܺ@')(!\Q}>.mǎki*Lfdd&R)ӸpNC?EV>R[0%p4M%4E*33N*Mݫܸ 5kw]G-.PΔLO pXa^L{emŋM r8y_#g'=zآ6hmL+-7 Se NXzs=rhep:EH$JZL"_7`C«-#y I"vu}pQQx;]n7[rSd~y>y7J/ #̸ָg;{AP^ P5D:>Mq Aw- kH<}ldY>>Brݍw݈P`'j̽.mG 8^m~7ak VWa`zaبWey@-J- ٢í)e׶cL4eqOoB u11}_% 8=/::h8\|jkMFQdjkLp}p9lD:mOݿLh{~݅! N#{;LLL  iZaZlT* !wY{ħwBv]~ Ih{_fjkh[[aww!7 8 1)mx/#$sI>)u_x‡do79' 011?D@NgY[۲iT/,L<.!8pww띡*\Ӿt:fU x[Xo t]A0/4Ǧ1,[n#fjjP/]Un7m_?}9yXcQM!05ј-F97y6PKV/,,"vlt:um͸ @ ?SF XXq}8\oKɚ3:m@-o7-B!],_Z_C2<"`<ˀd r%tF6Fᆒz?Hy}_k|o\AG;Oџkƾ}6 wrC:q+Ìc <gBl|=:?/~c,(ӴiKk\CnpAP,y(շy3󜗣Ե<]9a?Z_/#WSc~Oݽڡ6.{vѠǟM߻E}3_~ 8&+]ڟEpܺ Z0@fY6(/!12 XZgI=zA0f ӧ}6$.vtaۙ/a eY#y d=-\$wv㻫 8doey3[ܚL)(! y,/kL"_t FlTn7bS"~!m~qku|E^t25uÇy.бJG=C 5N$F3patiYMLN52?^}bJ6d \. DƙSQ#nCAz P3}c:Yj}ϳK] ,_P|WhڳLfSܨ[lEQp{.WOb۷n5bk NXS(x«2u >WVv_!BP @a3kވ^ yn۷n3 c;?pAQI͓ SqfϪ66666666666666666666666666666666666؂N66666666Ub|7~#|tDֹ{ϋgY~P,c~(?'kFX |!ǶΎOHk^Nܥᘤ7lN ̽PS-],YjdFדyy׀' h-ir=e2kjX[p|H/~۷ ,E ;BPXX#uj`+IF^FW%+˴7Y!_yQOg_Exj3҃gkN BCd Yrpu W]9, z2 kxT>lϙ7 5"z%n _'snоx):5߷O{KM?ჇAyti店/(iaU{/j3up:Y[%葲55~&poi~4ifgh=PF@V[E5]w-O|t.G^fKdb4+lgC{Rx}İI ػ,}eNc.X"e0Dfy 1g@ oOt9C bOzSimCdܡ)nP9Mq|\CAːzހăo?zpl9~q_-oG<8&7ÖV{-t b =qO~ d޷;ՅB!ӱ,}ĽhkKϞs OSŜY[8| UGčUDWK7l޶J>>1śӫ]΋1%X$8~7|Gضݷ<=o7w݅tEL&֏` j%yȉLL;ͽ;p:ENF7feoQmiGWWK qb)W &UtO{&ӻg{sf<5s毝ssv73EպwwkTwόfRwRMCb6B 6 .JWϜ{yy5W_bk&?glnO{,&xl~te7%HAss1lNA8 3¨S4_RʻG|9XP|bcC ]H2FwC͕"fj5=#8f'oHͽQZ[ںxGaÙ@4a|i(<=LCIj8ELUӫ7(o|FMƦY!qƹs;F.Q{b =}kcJ9w}pT}U>9Hat^K3kyIre^f`>d>[4I'~RW^uZvgMDssPn%Sk!Hmάg|\g!\(I8.jKܯsS5Dz#$F@@w6'rkzov; ƦaMC(>2>hqV\}+vj\L|RKPjr60Nl~qϹ5zR<ؒ\:Sn61˓-Ys9u7~vNSsm\6EMrR6*&o09`2~yaAmS3W~_,++1JPoowϰ)E~n1s׶X5-s#r{gѹqv7Π/Lsfb}B)L{{rg!/L;Y ~e5r4@cO !!rSܼ9ƆDkf>r"͵q޹DLQ;4{ 6*U2px6')+lG%1Q`wI >Tm-Qp|ųE{ؤkڿzQnlxXt[CUT֮TwOOĥk+clVw }*CQ8ո\F\˥s2},/t( J4Jiܻ9fI%HݴShRy;M` ua/ ""~v; ܲWqX^F*R Z24;)ZSE6,!uzʚ*]'`u]5N;5n8/sH/B\wǽ-]OdrF|{C$<( /81j)/:|TE%u:OsD#ƝK`Ľv˞zUEGAl (6WV'cu^,ƍ=w߽= =QYeAq{:Hc*Ql?mN5:~]6`oǀśl}fQWN?.Jso\Zm3ӈt[SCCyi#O>m4(o.+Xb0 #wY`H7 9|J)B"^bݒDHkHwGvNl-fU}!c6hL^;UW)^^uW!OwN+ ?7g+m𹳃.Ē\c)삸dmx_$ֿ7݅]C IDAT;%kĹ1>/>SFPݞ'yj5^[6%N=v {^SF}{՞un8nwi(.g*(uwFHMSRWb* {6K?u9;0]Վ4},)wK~\7?$ݎbD ́4r_v7ۉbk'U}25JxHC>o5 ;ݿ{`WbgմI}G :.05uuЌ㩖RN0 f͚%YMy+&QU٫K]9"v|>Xs9˜KZZ DCX򧋸7n` l<_Rj) hx " <߄≳>ꃙ:o|>@үC h1[Cr4[D0Ү;\|wC2"FҁZl T7jϑubC[fQ%T5Uho҂[7{­[ nyS{~Rt=1:7pH'nA}&>j~WVl5a_dTwVSbCK$.j>q-r( LMqM__+p[&i1GEǖuVҟ R+cI8DG@`w=w8-As9%9ӏX=H'i YNXN;$K_ܾ}$1qV>=@W`bW/=:NYsٯt\fr΀[mVwnF!˅H?sLj9qo$E!M+u| Á߯}6טNzQ⦢Z 7u>[vn6 I|*l S/Q16x66&}ӆ \A~!5%wS\A׶9i)lalL~k\[JF 5+seM[e%++G-?mm׿`"6[5=_N,nci 6Z]hzʚw.?(ڷ7+ 2>}%f}'\(bmxظ-`ψ*ih4Jc,fl{4@xìL:3@csc=AÞ3>wt1{3]U-|U8TU +8>C3 飸πߟ'0sZE8tW >( r?Rk+b/{alA'!NL٨s2/?kQZK,*SbNS ij-E;=i9h!1`W u'yA|0|(얇~|7n0v6wyQSs:NY˵ hJr ])5.ȶ(rOoDQ*wq: V45Z}>1MsdlL0aStk7z!n" “rsb:+KXR:M%lu8&YY:Q=Hp2}]ӳi|}V7DQׅXz2΍~ei~N4@cOuX3~p쯨jl~ck3ZӧYpC| OiVMɲ.D4*CɤLOF|Uc;+|6fǷUU'2bXNQvƹs}\y[+x5tKD8<6]ML3ߞT`_֍96Q<Љp#rf9^jڰV9Q Zn\FmkFf(m8B?yTFTW\< ܉Ls0 1beRW jb7O޽~>$6#bR.7t~no} ߉r{}M9W>7[MC/oQ{,vA 5Dc8w:NvKܞ.voO&ྒྷ0s%Nߏh#cIBiEU`MgǙwq& ?W?^ jf?i3ſǟ:ɴ'ϟBd&>}!Zji)3Ρƞj5Mw{k3O\ Wi'}SeC[r7HB^{ "0]q3|: wO9go{Σtx =͎j>Exq,7ReRyOAJ|gDDf\?G/TWwRVRho{/gNBytSb@?߹oUwzCN1l@Fz !.7'6K.?VX;[ŢΚx}( o)]A,ҿ@F`]Vrio&{N3u { ˲iݴD}[FO쨪y ђRqKnJ.W9d|Kjaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7VEImjt3R2{nYn)yp45K0|W$+̮#$>9DQPATJQ<@RN9HB[]bC,NW_ˌ؋(n;eχ,[n:Ւ A2ͭY^fE!D2 Y}yo*?!>Y lG̻u"G(hHUyT`Hr$bqn쥹CO2)SKG;E|ϭ[ `U(JPUE[|Dr/O#'J\ GM[?GP/4dٶ6S[ ^ M(B$f̧*j."$t{F}k gXq%CT3m"5OogYFz;:+/ch=T\yp8 }}(YIn?8܂}C}g 8Ym'DI-b/%렟(gx&v)F\FfcwG#.c̒nfR)~8ǣ lnElEo޻y6#,$`.:Gğ&pFU(ag=,I tx,RE0)u5ھr2n(2Ӄ wef|߼_?Z#$>PU!r~7[ۜ i"I0; uަ>ܜiNt%^\"oqHFvd|-wB2)ry9z_ ɤl3kp{w7W/!z-HƙL۳nzǸ^L {p;+~|sVqN'*5[Ha>Ÿ^VFdnKNt\4ck;ƍa2uRNR{҃}8AI72RAnE`>گduCdePWq*~[8v^bl:AaDA(8. &8şCQ9?5CdzdLZ~eܖCTKާey_Qq~]˵* RW^]?L՜O6kv8zSPv?d7A- Ra!%)iՒe6E*%%D#Q%0/~Q! "].ĒR"KDFx| STW!/#ug,s0>b$j}G61;/oW{js9-Z C^|AP-\EQT7:P;\.9{K̲3}.[-gKK-"$I˟~6+o=Ǖi͊ӣݥrݩr-2BUXzUY*jZ\.ǏIv8D'ס\&OɄk+K /g4̣ϡC/f~\^z'KbqA2pԤynAt{%7}Leo=G-@ArSj\{ 1|iUIm跰<%+ G9JV-AX; NIBj?\;KϧMsL8oR.-6I}*.,unw$ h\:lÞSџLBjQr>;ؼAWbxAC̟zr gbyؾex.#D e;jZ]ٳEP齽|NyqH4j:Q Ae;Vp .-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~@Eq~ؽ*lkAϐFV O&A3}sBA s_Uѣ^S\3%mLƓlA™IHb2yycb TJDh&5, & `sYfT NK*v,i(Prֲ0`N4/-47Tptzg-֌E$ ff .>.AHr $'JP#т9꛻k!#A ٫W* 4~6aĮ.| ![ 'ȲwGYq0-˳LsM9`( jZ{/\x눂TYtawdPz~|SڲQqI $ c$)c3t$ "GZz>5M=;O2)wI%k䟲_u "Gk츜Q#OƦ}T' L6!&&& + HmM'=^XUΊlG؋y+n2TGL>.&猄x\L#NK>0FG^[K;o*hi33SKQdf!m^"99v"6?$RX"ْD g( ׾ l/~|Zi?'G91Ͽap`Y%wVM}ū_=>AFГ³jE͏>HP穄v*f VT4ՙҙ~`Y?  KjN0dh ~%A{Zt}dRfx$.G uǝA$}:Q\p^.GHpy!JDNDQGXZ0 EdN{$Hx'37rPRN /%~xnsQI]~CEC[y^ho19sMAlv&jGLgFq7μ(D'#(D'.o|{,H0}}Ek5mo?l2t7/U!rgz{=*B/TGۘԷ)vӗHlGIAJQSk@We%b4oM6̟[6WC4l>0pvuf~[^E H. MiMܕY\/=ԮV@E+f{i%wߘU7UGm KB|̙́ yTEes_8oQZT$69/g S?!H3E3$9TC۰:G~!dd:!-A>9lƯ~9E9l"C_|"Ty{'aMX@taLFRNb؈D.扂I"1$!>Lm2|G˧1+:@~ZJaL\gTFUh?NСӝ)EEeGsIӓ 6s IDATsQ^  r<-,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,~ EA}w3.psd Tl1s]kjNGQ(23y20_䫬57 J~pk8uc\74~)"T̞&"Rg'PU^wHuG^*p LoJG\.)!fg$||0=?QBb"Ev,fΊBA b] ! UE k'xiWNK(hBDn_BϟoeؙcN=DtyJ1(淤 ֙=#!ߕ3veTܽ 9.=k8%JDߧ};~;\ "q7n2P تڊo xTt֊>B7y٢kTz}G_ZRʷm^#dt(b۹{x\6Le? HDmI5kc7ɲLc<εx H$f9sPft~d-*DPn%b&S\#tfT?fLʆ=]|'rem` g I])$!F"55,, ~.0  Q\.w1QQ^Οsq9YZ۠ 7?w::ƭV Њ=Op݆c;q/QKP;ʀ|/!Geú6D{UNɓ  Z &7AYF8DF(=?[(EU܃Cd2BtJ-!!ۆ;-ֈf봓}UTe3:&sѹx*=Ld~E/08h,ljD&kFʎ&Ls&B o<t"#•/?uwՏ g[? HuGy67FKSC>W;ӃɈsfkh%mL2rRUz?wn^vzӏMdii..^>UcvLG`~t?pK5}.̙ZH(:\phK-k*yJq8\gusȏ[ +Boo/ٻEQnGg{*Пx=$$9I;PZr77t7,@ϥiYՉ٩p_ u5(tMkIGdFPXǙ7`ipImz勑)Jt1Ie\FQHyMY3Bفkc//-֭՝q_Uf ǜDV-t{# YY{9d/j{ȴܸ@o~Mټ| #7~T@Ց_`v{w(uGPTh_c 9G LNZ"JIO%?fUUX\H45*&Ծy/r7xAzm GH;6`r|1p|ZHǽ^b/bY>Dxmitf3Ƹgggϲ4ĉ,e)a3F8KU^@!witwwk+fX!drjW`O>Ys7< {;sJJ!RO2"3a3ǐՆmw=jտHUb :YXXXXXXX}%~ceT^xQ0NkH G|W~z'.T/=`̒ėxxcǚd_V sLF㹵/t6P0mD}"WH=?A85W5"sޡC\z zb3_EձU}Xo}׋E2 uNa&,} $F>CN>>aam| oHl_R8妒d<^zjI?|j/54V(IWJRͬٳ56iߢ4GcQJFJƢ IT6Xj||ԻmyB]3Qo!0Я00OwvW::jid֬_g?`W ??2"e9 @d2ʄRw*em}Օ?CUb+Oo7mW2J`^> jcE$RSb8ll81QRBDإ#XO=D T"*Ś;fP1tck˭4MI2y\3Yt<͏ާag??kH$1c01"f>HRdI g@E ?>Wewب lJn]'?WqWvtqNsSw8|t]5QĐ*{=9$A_<^P;p8LKK pƮ剌xPΈD~O-^}4ơ[(yJf(@ÛTP5e^ٙy2x=*{ i=FZ`nJJa>T<_#WQC 9dOY/9BN<'>y~)^֢Y&GR> >q# yBE`Wv?[M򇶍E}_L5:Gi}5V{Q:\aR\;Ԇ"l)_;,HkG{9J_Y>#I@[zú=M1UnhouCD21ipSz: DRx_zYcr.L]wH4apA3<~޻ /*E\Y&>2v k,$qIhjjÇ𱰔 g^`B'kk=Cx*WHPAUﮌ)RvΦ >ܙ-BZԇܬϤp jF-D!&zy(4 |zS(ټ[0tJwi#"nO#|Fz09$J<*=FmtMn>cb"L$s 4m/]Ph͖CQUFl/p PSkЗ۫ē4֝x!l;^ޖ4) 1Dhb3="98P$IbvfW`3|M  (99&!O;Ì|СAt(q(xT;&3說p*:vhX#p&S9$|9R)$)cE:*CLUTE$nDbbP(XO^¹wTQl'I. "{&$kg'͎K|t D&#m֣(k-?4bÞInthPϋ}->}xk>ڰm)vbC "}tdvz/osD&3g\,|`!f|`/}IMIBx}y^A3$P bZJ-z* vAW/@NM.0cE+Sjr]n_ŞXB(kTHeӧB:}PssT5\*w m)(xi}tu!21# ߯s qtG9ͺ=,tF׾`hc+ D ľxDӃE`OB9.WE Hq'kSN@`D"cyycMY?HdxdT^Z`t2v#l~FgGؠґH5! 3D4`yt= |@DhjEDGь(- `J"RImQtY^~!h"bO5>gwbOk:;lQbtalRFvA**=#cb!AsMse_ظmK̭S 9ĿoH yA+u@-?r% n`AcW LO3ZWTMƟi)[T~skÌڍں6敪Kn8YCYY3M "g;Y@F݂G(~S q\Am9^{p %KHI.5ܣAg z[$Vh(*4g'N/7fk6hR --B^ۿ״Uzѱ$m/_]e.Bf p,(^HL;X:x~,T&$6HP1ҷpTO<Նij@}0HDq:hGd/\;/hsWpS|.?Ύ2u#~}^MQ~f2~EQ|e>\c\Zo?\S\ۇ ($}v68G~+,Ƨ>zb>)`'SJnԍ R-U=63nv+ֺ 1fs[oi1|앹x;}NR^^m8icg*+yV,'UHisj3s7)}Bmlgfm~% d5By-%c=#A1'dD"ViXD"Jse-4:H`]m WEl3[~M$9t2Jf[b&+6_A, 8Nx#{ WK[.lLpQb$fSaE[(4737cVs777zea-ЈUuS=#l#5߸Uߎ|/Z= FOI+ڌq6!3 HAI[s#?oQUxo;<̆Y}G=m8ČTz}{L}3$EspIUǒ9uK*=)vV49t(ğ_駩n'O"؄LHdj˜]x̥j0Jw:7?ccknUO&ܖ`Gvw P&Qʽ,#_”~FB2ŗsm6b%1b8/YcvN({ථM $S$EˑXarEj$'6i2CM۴ssnv7sz&Uur6NؖASO)K-)'Dnl?6rڦ"ַ"oW oqs&leng"\Z~'(S Ù;ACՆLF~oQ0dkbh"yX~Z];JMͽ\.-6ʾHhR-v} ,z$Wg@ss=͛y={M+gO0_ urn)Elkj8<Κ{)]Ln|uA*%hS[VL wb){~_P(&p{I!gS{g6 G'Y[d&pUl6gđ2L̳ XFfyk.~'룓UTICb4Hd$4ٛSgֆ<5087ﺋ&&IZxk`M[LpţX>H[cc1I]Y)&Dу[vfsJ{Xl'$ة8)͝r\ۃ.r6V;cU׬A06mhɻ8)PS08H۝y5~} 4܉C4h9-Ciin;eǨ7l6E""עXwW)h'K3ןPff/rn9U1=v7ۭX\2>=I25 Qgi5TE%*GOmG>]@QUhTjS+& n>1Y$DAc#jmܶ12_z'bOojЛc0"B |%Cs2."Gsc my1V_8,\ gWxEkrM&+ sc?ܸ{W^de!ȑ!{zE!!7@{+C޺/O)>u嵄fC >jEPI9"$jwMƼ#W+]K=d{uS(Dx#O85Ɓ.cq}1hE1 %%tUWh֣Jggl~ɔOzb"8vzNoڡ5:SUUO1oJ3០VEPڜ{bBOW8u,,r;űLm[A{)c+h*ot!"Bh}E%:S kiEoD{\f܂U*~EU FB*rzFdlu.#cÞE];Y-}ZĄOvU3g`)lϺ*96R7Lj01+P|6z*g2+=BƐlUg,&+>B%z&Vo[67[0[M8 (-p҃ѥbI*9O/+oL˗)2[2S8 ӲOB,Y]͹7 8_{:޿ xڇYWZq.xQl7ư?mAPnp j暖,M.K+]=_)79κTL}jJ':;Q]ېܫ?iгg3}l8239Þ' "Q9P> o۷o3dz $ Qgp_+>*mNO& gg|?lޑr8Ы偊;LOR^F`rl~'N)|/8(_ >ΤO.o^ #|NEUx|7.T\;??_r,nW.+I9E(yWk N!UB3>4E]% ѿz"c|S8( _Y=;LLײ<&L}eO9ts}a9+,){P ?^Vt$Vg|q Ys$slaUW(پJ3.\"kƄ*WUx(f졢(twv9Wǐ_"ť(!xJ;cc,SVAkZOUQE9dysMoP[tw}gN\MTP`@ ])KEbDZW֥{MvGǹyukm'ߏ+mP jnՏ9/l?1笸2/:;o{񕝃kZ=QFdnno%n`6;XXBeNGśm3yGnMqNzj\rt "_|/رKN>JRzrQ/D5{W{t^/e]v?Wnc)7=ª9u~kXʱ=4o ]\R7#m4fژs;;Y?('e$ϪgqKH+H:}o}zmYȲnT&܊bsz{RgWC-G%Ôe|{9W1%Ɓطs%:w9w_4rUSN|3s%gGgԩSSO=__QVVgΜ_:@h4ڵkٳgMMˑ,/ 4oomMfg"a?΄ZY@[qK> 2EE *Uqsg[+)+~x[O^ixsTw2`_oSd9MFCBXjeIlgg2*i"VH.w$e5 3VSO5=yr + P$d̮iS8p)2c>/Ѡ(m9;k"OL #J m<3So7B%C6eLO곞VXe}ۋJ5In ͇VEE07J/T!g J? w%$Bnh+۫_ٴ`:U-͖D{fp KT &V}`F[ PvDqof?4߾.[Ia:WS\?3:-b)L 7&7cyFw#q4ԻS&nyR3Ju;'(J%?j;qM( o2?=Ty)2O5*w"E qEU:{]w쬘MM4tVbuWqSUr8]ߤbB?udnA!Է6Ӕo բƈɅ*xDb+",Au S`4.R<) 2NP>]_k ̗#gUr{z!~|SYs==7^d{U#M;⥪ ߈?4m* +Z躉ǀwoV[ z 'X*rfͫ,*YFiٻ7I^L+ŠL֟S;ivQpċbs1."v*{||(+o1/dnxr_; Ō3]Wv.|w\蝚7nL0n&,omdЛt2w#Bx|œƴNNf4EUfE+A8*2q~v4ފ2zжX'љu7Io]Hfo|EiU7DԾ TEe8ѹQXYsUL'{>kT~h8z>ߌ>Cu{pbBT=R'U= Lt&-+߶ʸov7c1 b4jHIOO/6' .qqZqDcكm8m^ht:h%zwqv}2qW>BM 46BWײL'hwnns~+<a˙E 6:]tDOi+R=c='O Bض6>Og6so|_!f3wGkk+j-ww w,4VWDزT[EF >GQWߢ~֥l(UhVԩ*r[qT5wP( hE"xa#GܺCUVB HVkX%+ TQbrǂ u0*L U?Jژk̃(*ʐrsTUNkK&xKgq;xSsDۖGzB((#%P_Bo.{# ]CzrWbۋ5 jstSpl+*Yv^Ꝍyhw.Zge9+'iBjDNt\Q^/\SH>S2H^'rԼ@UUg))/vdN ۆ*wDAߏ ѨLQhTy~]о3'mƔ?¶> 㤯-}W'g`+5KDGBvi /R*ʅ[8' "&7 &?^CɵF娦] s HXx\BqJ*/n#x&d -qn m̵M{.{;oh9%vJ%E)}/)FQ"ϡt[z>yV(A)<+Xg!6v8 Y^;gE(E~AkAٮb 3&ʚogrN|SZU30͵?O~ܶirгcˑܿ*va0|pT%JQ1m Ddn<ԱƁ餉pE umq!~^OgUUK~v b3j$ ˜5=DAdǕ;_׿eo EU y ygls 2oLપB@SqZػ4{bLx1$5w-CJlYզh;J.@3JD &73=3jBw@ɜaOr2ӻ^Æ2TKC N,$_ƛH=0?5I=Ω=}n7C88#8)suYWxMWCjDv(-y%MO5?^VKw=kye=|r?D)JJF ۫ϑӧWAe~&&K4=Nߜ4֖|ewXѨlNK9ɞ={@E^~e|>dhh{K./|!g7nOx<=?Ώ~#z->O166'?waF`4@ rgϿ]b]DҲWs6?>L5€d1= ͆`T'OnHC* ߏ"5C:1 bڴH?<<;_U0$%z +mp#?[DP3.ջ9^l\f V+~h4~de؇c`yX5Ut ,r@/oWDWWlY־L.ft*#{tYm^ ,8ô}--oĿ0 k󩽥AٖBMlYɽ".7rv|> @Qd?6I09P^A<*cs"mdm1ZtN(ȿa!˞ (I=GeT-Ku~>CTUa7NcﴧNȣ26|=  þ긶Bdeq\at7 ʔױcD$) =kcNi͹MyT|-Y h .KFd%v6}-(mm\\ѫSsuXlƳ8lJZp=]iC|+ݶg[X>)C VѼk_UQ-^*#~ O=m k۳ Ot`olo,޷xsC_IJBc. -g<:F#.1p5E议N{NgRfC ۆ4ӾjhTa'u Vg.o{{fgumwz<ҬH { Iۉɢ4]CQUϿp8ݿFؿ"5#NZhF~~n|g䗷`>* ~.,]Γnys䲃Mw&*'8|xX#ԙ׋AĿПݻ3#PW'raքX6`(|g3䪢(P+oG&3r50jKh==Oh;nS3bkUL^Nk`l1—e c(NIy]k0%]>4#G$ !}VaQ7Y'38+1&(4s+爊┈ŗbyTQ頿`֯`/ J &Vbז](Hzx c1u7x|#v٩05ݥ6JR!4sr9%駙8rҺ"ĐL]=c99Jw#8$_^Z3]qi  H{f|Qk.?' 1gb~7+>*YY,R W *M p8?WZQP!O*^8k~CBPʆIk޼( (}20#],6^z;ǗSLRܗnWg lОɣ2:f^{̈́CQE/TIti(u&&t'qv=hg~5grGEnw*J5Kt{Ŕ_I斁9v;Lck*vg{UҜOr־8gxW2^;\9 ٓQelv}>j*Ʀoutk_u_ |+RT-'{z-=ItY#_(+Vbic+8lulѥ>X SL@VB**$þ%R=YdJ5`<19edW.Z /}A`Pe? $_?cKJJxgz+}7~L&ocZ9uMДE"*> IDAT;*mkJ- =?Jo/tuAoo'#d!$,S]"۱XQ^t SencJ8j%H.h g@|Xbl*yލU/^J9+\95ZAfy%5H܌sέ,uć%(ӚBTPB-A!`+CMUGUwc.cqxwj^= !@~B=7eDJE S**Q9JYG%{@ɣZEޝ$?ĩ`j/O=V*U.\m}[` ۖ:.I cc\BK&t8KY_%87Γ?P4pW3P toY[ͯ_J#YXXd)Қ!mC:6s~$6-+gȃgȓ58wuv(l\=ୄ՛Nl.w̶!ѨԁAW'p5vDDpmtig-m yVTIXrv@nhT'Y+Gs)nk1ߗSN CEpw/tVrP|W\25@FH Rjq5Α:UO^=jMd@s`3#-w·y wz, >MMHSyߧ"^̡aXPNw.#83 v^ 1?撓Ƽ`2g^dIXۇ$8c7ǐ$)'W$QZ-(K*_vƽ,sj>Ȇ`n~J1rJߏmx_d%/B$)5WfWyuAZgGXݷG"2!=SQ9O2|}:I{n<}wf:M=A}p6|EHuHD0襣E4{M I06 ]XTD1y26=) ȲɥE|Q\LT?8\7zOmr&68`6I^u9?I{)>o> }xSsNrn![?bߞ׶z'#>붲ԍlc"5-xG)}O =n*ְukBf`bК̜mXGjoidƞω+ Bٹ$IxTvw9@EF+.\{=ï?:_<n7.1__L<ה*=Jtwx|Ҳu15g~y= buC200000000000000000000000000000000000K&9x'> 6裏?WR&vXJ+([6s5hތyԔmۼ>xq$ *N(?b@0H|$K /ː뵵 ]o_jz$B|d1atVRZ6bS-N^pV5F_܊/. !#7gN?$煠* yul.{ߺؙ&%Ilvn&阎#[9v~LxPۜw_j /!bUQSNޫ![rjN.ǰZIf yb ue>fGGi>lnǎQUZMYg'Kyrbup&Jmr:fD1FDܛ8v9\,wJ_vJ`ZژxybT/H2Nzص+ekvQv#&&ͮRstsfZҸxYd4+W$IBQ>{g&iQi_kػw?VIwxM|e~^"9Qfka(:w*il$I):\/"ZPKrJ)ImQ9؇g0G:?C+vXjWgbLr:yTƹ^"%l^ϭj7n{_qnq"|Enb2*+_ҟR\$Ũq~?CI.F4MM9c6#" GGq\>$䣚ϟ3nl6s_fݣdrR殺9gsϏҼק/.F02e/4ԛzNɔ1t=SUjk.^&JKgY;8?.Ž^NsosvB#s(UU﫷3.gtʇw|F^/o=OSoS__茶!t0M[:0645vx |"wMICm&lZON͖- ^,Mgj.ZwYےsM]kQJKjxu| .Ug\[Kƴ=4h ;flcJwVFv^}_$09MCi1s,Չ`*SϱeK B~MWff\53loA>$X%+&SK襔;o/.U(Ol,h(.&t+etvBsseb-y잧0)TL&v ]J*X_A@s**M3ql (G'g“cl%\U"?LvOQAMQ&'in^FY//f,[vm4zjk$pԚ؃֙׼*#œD.[hި o@|lglb4l%>pJǞ*"68U'|EFz@B4 蒁9ӽk(:̍5*WfTA@ih?9SI`)'%5`2a;~bً0L%b4Ԯz'lvHw| MŊ\̚5Scw!oҧZ߰t)vΟS49LLM%umN)IEM*~Z:;91ك%G&@ii&Hy|2XZ`seK?\y$v#^j8}Y/S{ofl|hњZ/g6p{UG (Ɣ!Ab{Jy~ ?9hk`2닩a:]E"Llx Iu<`"Ioa uεwz$QjvQZv?xHoSZ-8/}g5ϐ}4ٚP*>oʼnECo#uj2rUdNwѰ4?DZE(e9#S׮_SpA]IIF>$igYJ8>۱yr6܅! 07>x&v_Y P[̓OluZnY6FQ#=뙞QTČ}Xf'2_=bPE&nK-E"Q^LGj^OghrShbF&[{[.nB26=[~HDU^JBKI m -1yt2ܽn&F&(TF3cMk}ZEӱ'2bUQy{u[4h)Sc 0=}Љ7){#7Y>{ &CXcشnF|蹃)oiַ'҆b3c`5u084u>ׯ_KE1sz.&py ~43$" B-"!h`m:.q6|$2m+U.Uw{Iݺ;f&ĢḛA %ġL (R@3 pӸ?(o ,p}yy^۹\򥺆۹%AXUhip{W 5咼aj޼Q@޺M>dY5֬# h]6zS(Ckg&e0ձ1-l*\jk]NIu>x!våەz>GԊrBQhE澩P4/B]GfЛd2S|ufyT&͍/29YZZ mJ۝qNS M7ZZgl.K{@[BߚAdv4Z~(:::: [8R$*5;رrt;v=iǙˈ[B|$=Jp ׿}wF܋~Purk=؎ShvH ?Y#I'̥KFNЊ" z^1ϖxI`g-w㳶}(pp"cuDU|W? ֋_je&>ɷ_abk- 1`?eiKv3D՞Ν@mTU۞#3R- ރpD͟?ϦfλZ]ZKjK9NUKѺI}{5>`nΈ7x9k3v;bf z9+hh8cӎyXM IDAT,ԿA_{~vvr|EրZZ$ |컽Q`0Iu^/~{U;>>@SSuuukiiq| X߱cM_A _xIo|e~D6K EUI$`t+e@n \ %\?Y?v]ޔ*A_zn,rxS@EbѨh]QU>Em;L6lP ıs}v%":sb_h,Ti-fkI`^e 1fg@̛C I2 **U*TtMCJ)7Ł~]|[})8K˕menUzK:LXc1ۜ#(Q{_J%۷@[?d[_|Ӵmo^_I:J$0XvTz3lQUr;;P БB{|<Q <n exSy8L"Xo:`06aBTJAtwk4/rh(Eù lʄBs}3~Oj> G2a;LNݫ{on=cv'}Qo>}e&h%(iN{U zX^fa"w 2-Mo,n5_ӡi6ؕZ9( Ѩx<ݑovBxSeBYT[dLDس' _Y[+-h>X2ѐ*!?b~u?g& 䡎V~|wdd,_݁/.-sϙ(sUU yOX,hC( ̹Ze?%r=`bu`jtF(fG\,X{Y#w|}ey˴CF˴|5U Z|L>9EIqil67[g1Q=UQUWM{̵A%dHKAz"G&sxp_ҾH?q,^F~(k>k}nc ́ߡivO~qOhN?ǒW*ƴkdqtGgs쮰?+ܸ_nPt-W>w{|r3Y hx\SCSX)5%(d2;dΠ(3йwoYj| dzbp/ɭ2 ӆ@(g h5ӿ?*3kG3#n:6Oum?89Qn}+B=@y_xz$MRXc]\{af6[C잧l(ʜ;>IHccʙvmx,{ $K,"ԡ"f= ^Z6謯[R麗ȟ̻@~Gtykg%5F}ؒ@5G I2\?Zj *}<;gZg/&x-IŲò8󩔛Q | DmSvF`J4*퉲 SF?`WuuhG/g7_J{ܪpykM|SS%`xx#451G-=MM缥pO[ǍibKevvM)rvv]e$.~{ |W~3yjf+3M0?ʻh@%4~IgortGg3Y(8%s%9 S32 >D3/ŌtQ???ΉaRB=<|Gw Pr;;A={ Wu[O.9ke>w>Y9DADny׵߿s㧦ۻ9rlMwe&x<0??P PJ+nkݮE1A4CMM O?4 Wn ƍ/QEUQ:DۣDQ$60_o"!Keln.hc;IW2/ͥؼA?W3%]%qoҋ"tu6*hN׏O7&)'n'E1!˹(:[*J.QԌDU&lbx'w ӹ}}q!>Owsu:iHxJw1=ȑ" AЈFInPHGZtq0gGj+wU|3RY\Nd` F$RTEXf_N>1 Exes9{5)iuőOvWYٮ jU"u׌H+nR6JQ; u6r!Yk>xSHCC'x|K7.7"ZH$뒓,{F\<kYD"A|<[ѳPBi.6lZksu˧4;//׍k:+#,(#"~|)_10$CKm]:H,Sȫs*3;V.Wp4vu$ =`YEQd_`/ȩg\sk@Z6~A=ŧJ ItίX伕 AH\?v]CIl"ZzlҴJ=}3W[>?2x8jԪ45DȖ&ޟn&z*y0R/< c)%%z׹Zvf7LO=YM!rS0DFRɤmˆv{u]c~>N*iTgiuEjjD~z55"YFFGMZn2|Q'B^eg3@'mdك.qEt͡R\E_]7VkWd{##놏wD ›?~kdBl啿k}~o8ne>_JWj^+گ +tijEg]ӹ{iTCQxVɲ$yorOBn!#7]Nk=5׿] !Cݭa^O.a{n(Jȕ+F)`sc\_$f!G3HCCk=*njfg8Ԧtw[vǝ 4 $$ _IG*֌Nq >0QY@j09D dP\0#rcDzB0~GU%q$Yn+2jHw&A#W`r/L46FطB YI]?d{G;6gdT}G'p1rW?&*>1#Fw%l(][neoC~kQOV7;ۙF%{|pgE@=UH5ȞGN=TE7$T;C[zC=KtZi>SLhڤVZqYy6qk/]#S1 C5cRu2AO4rgᰵnBUTj9DuΒ]E  iWrCx >c I2IFEYBd?ׅbW;'9 O[)aN@)%ϸKdMemi҄aN$@x2O)IA$&>CZD!Xt꽓iFY A,Su4uQuؕ 5gj$O}`*{C2l;k1~vƫ( H̏|[Cܿm@e_1Ԑ@(*1_ۃVե~:N&OIJs ,EB 脲> mD.h5,UqhX884d$NhI>MFBu}}DnWn#tSc=l@_9tMģf8vlP / /|bi 6J_:"H&}2e$pd֪yIڛt"4?[HůҸ*\DYjXWΜS2, Q8$ogpoUCH.esDQ4z#.۠YXg_6dM&w1s}yLp&K%DQOæf4bUVboq&mі1ҹcgĎVMVZg: 3Gò$F)Xc:3utRv*J&dAt'ߒp!5< 333\,Z5^S'OgFKZ K_ H "5ĴBo[l6*RZ3Qps? ~9MXFNZud97qg=TuF:W(L-BӐGF=K4*?CI1Fǚb8'4,dt]ʕ#ARG@;l} ~۝f9>lu UU$qV"_>Ȓd=/*AF M>fZhʟ!ڣO_ btƨ'_wY4ewPOm#R *$+./+׵dG? X/z ܯ0AQ47J:_[+Rq#s$?SXc:zT7QؼssǑ" =Cګll%}^B }}rEǙQ&Hk:+'MP&4mtK f}oۜ>}~wk׆?6giu!ЦJeo5D܀$7$뚎@I~B!ΐYʐJ) #LxG;ʎW`'iss4DʄR`EA$NgN$TU1;h+3K5V:GX*8%vQBƾK>Sx%ە\D|6ӰKBE?BxrC'۾ez>@8t'בH($@@[~;H1PO|A9N xYZ*0&k@I Jͧ*aD"NA"7C5^LB`H ʕ{#Ӄ#ȑ1`K _"Uxn05/'yJ)DmG-h\' d Hco5rr0;OĬ%u~22 ]:Jcu+~(vB#}ȹ֫W+:}THY5O>믢LS|rޘS>c]*p$ǒH%ם96b#1xA ǰD])K,NX!qGQƜtxY` 1C]QC֚ɗ|4!kd⚔ :D@C< =-] 9&8`& R19ε9̤u3}WS'no7*>C;iQk6v "i( uG:s}ruS!|SG7%^X vgȁ>}"PALХSTto39CpDڲ, kg J4#WFCHPnZMm/lGژnk^sk|?~+ ٜ۬Q#& "~Os}3#bϢ@$HZ"uluU7I+m5fn>, ۷ !Ri[y~(djjȜ>mSI2˅I"3Zq8YxVѵHGlnrPRPUqRD"2D"10;Kxde+>BT[qhz+K3|Tɷz5,395E2˴S*X},q>яO7~_~>QxgˮpleH:啾oP|"*Ӷ*L)uy'HLlGw~͐;D{׋rd"0BWAvZaf&?XL,sGY1VVL&y6dj#*M!Ex8=w^hQw6"BAs|n=r@Z몪bĈuFk;-¼yAKT9vdPPBG$]gc={y躠3>.WUTkNKCLsDvIfiau'eYn :~o'Z |Ja`H/'Ù`IGg4|~mm\VUh,wK6Vk00MnVF:y=dN04wgeC{n)&^Rx]Ӯ̃w45 WMRHJdIbLqDr]b2+Q52%jU~/">UU>|!{}ՆB!6ЉN"9Ɏ5U{IYfxQ&&h /@6%9%xAl\|_lHl<4uFd\Ӿ?w46s?iKYYq;ޡi \GE#N.j@[e&ORԋh ތMGri Oh2DPhx{22:lbvYcGb2c2>SLbB )& ;uN>'3jxC,$?4gAV^DMpkrTFPyH]䓟Q;3 ZWv])›¤ܤismSʼn޹q6KD~5Rj*ZgU*f*{$F 9EO5V^Yav,'֯5̜ m=؅jEITlO|<.]ia.:IMXcD+q[ F~;E4]]]>hBt:GV*,ҳuLgK](u]0k2;["PoUd(/D<=A'*`l l ]NYtG} |g.7Q)WM8s%ʋu+Ǎ_>/V*\7Dڙsi& k&Ez7(J#Լ4?fݨ^]w._n 1iųqQwGᇞpCfN'$WI~( j|9"J51k]F \.4(Hѐ_J}zb<;ӣ|$dbbÇ;+Gd4-GC :ŕZBZH$%H.OBT ̚%" 2gvY||TFIϗYשSn:q݇<,%|_W_3gx& +,'9v`(Xoˇ})EKP ǎ)lcMk?'bװm W)e*w|?"QYt>9 7Xvf~pv!h+] auVriB%VfW8O"&W?ZOPD$mMw5[)6QO@Z]GGE ]7xUU [Yq][sK528|;#>OMp%!Uf>2VFN$^!,N2+R).w}4c+bp/ w>ʉTk'FY_ n!Pt4/< axO04Te5a7aDz,]i*`-OϞ[2+R$:QF$ .Q@4d>Rlo#M ' vs舸 (m<]nd@n %B,{JqсZTqeUẃ:X67_!.2SEQd` F.'" Rΰ}=a7s>q3Ǭ:4#=,#L̀,O߿S?lMl!?O4ѣ#fyz4c3S(\.7./xT\z %8}ZPSS@Dᰈ ";+Ǩv1}ך+.-&g PGV`rqUp8^8s t4dW&XL(4ur wsVnuS 2Y9F4Z7Gk!CaRXXxܪMZ£$As<*KCIk}:AE8~DyH46L,P{8}4kVpqTEQf ݺq"tG*DL}갵ߡ Ssܮqi?ir-QAd 51>Gm2M?KVar42RLszpȪ('v0ȗdxq ZtM*V (Czo+Yl߅$K[M:AHռkDȉ?f1C5qKRp:K>GUs-䬣/pԜ@gqצ8wD\;zҰ~8u2G8u2(OkYΜyD_5[Y$Ku$XG[.]yR_cˈ~s$UUaN>| mu̽XvS\#<84JCj~ǪZ ?J&nE~] .\bBu`+<Vٱ---0;[|=lWd'6_zc9[穭+ŋx2pKoLڶZe磥&ve??қLdckV.N_Ppw[sxk?{(\Z]GA Wi C9!p-0¥ʶ0@jEkm[Y)Cx; x5Ret]rPW-v>A8[K<\_\Mcs9Mmꠥ$1746Blav nm[?R5̅7P'ygl%i(LŽVu?td.GKu5s' R_6;`0H @r%!ʲ,%ye"%ZY;OmnjV9M=h*=tsoZo:wC4-˶(B)棤)@D8`@RNoa w IDATsq$ι3d<-q!,lRw?)V?\e,?F )ͮ@n Xg/m5,W˃_޲P<<"s93_fx>3Ͳf4( .Di!1zN9&59+ 6Ȟ=dg}8Eg &U<0=6pxwMzv3V**ibE ƋCQ8P;AEk-S!:MrG*)5ZUx^o1qg*LOp5Ujv5O&'p:T3$}{'B8wcՍ2ue/t+4+74r=r6!o$XqNrO^2uS aeyfJ?>\%]œh۷?mB5#0y'W.q0'k mZ:42Tw04 `WeTݲy[ZL>fj zBu-`5j&S]ႝ5n*Iy Tde>1=!:E<pԱB!xs*^ne_㉦atdkExD'=TW .SѶETUl0,#h'4nZW˫{Z`4"DS]t>*??XmBaC?s {nIԬJjrXe}T'*_ jc;Xsi*\4gkf߻GVVs@D+qYС:ZZv{n6\TOVzOMjÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆlߪaÆ 6l!~@.3?C8Ν;g:,/pGG?_|_o6555\x|;몪}t:MڭGUkԆkIb T,hpX!o}K.7rlyUe d ZXx idב<>aqݴr( ~?u.PTU*J4BQͷQ5Q7SDQE>-uIkkkޟ=6!;7eYdd!LD !ݣu;}΍EQXY)^N 3k!5~7e *jl\y(ݖX47$7+b҉@?b)d֗x+[q l@(z'^eMɚe(RU%7߸6apEo]o:c'to7w=CO=O|)bd5Co/TT $^͂pg'33;; 獰 z"٬yD"/JNXCCrY?Vk>z>Q6Sq[moy+Gсo!K2z1vX4(;^NfӘUF]X7kzr:YxR߲6\˺-ǖ%drJ-<@Qb~/밴7Iﴓ#"dm}1r[ﴓۛ1 UEc,#'0)f 赮u:/ۈI?`xH)"x{y\<@mm[ DЫ4Woc6TsڐS,y,p}GdA)ڰaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6lذap66lذaÆ^/^GL:f||y_Wwk1o|tww~)dhh5|+(s=Gkk+.޽aBo+Wp1zmVvoί{TuZ4Jslљ(?I/|I(EӅ/I[FRLMӭ8N550e7dgVkEQ< =㟜 ҹ<^/ȸ M$ 73TUT]W9?U4H&9RF66pqٵYZ+Z;m>X*r&v=ĩ?g`Y}NHwpB2f8&(6R(.N6k$uu[UUT2rZnn6ʸW~撜;s(^xs\:EU meh\Sk,r9uFQYecP aY?F7?s2x9~wՉ'[v^ޝkk+ȘgivP5[E5ӥkDS8|NޝMAg-ab*a _xsL<[X^y3bvkmis9 ĘKk<}}$j fw⓺/TTTX,Cy,o 0fL|| ;շ#Ƙ(x~YDӖZXX q>nvZC*ef/ wr8Mb;Ȭܼuȩ.}]9ԃUW">7蛦xmeF>̊I'hѩQܸɴecܣ,I!{>R;(nwڿq-?&YQK2鶌4~KqΜEE̋g dvOkkHDePkŘM]!e.wvUU!q߈ϧz{F=~b^9d#qO6m*Zb6AocTW,-F-r9R ~gg 29z{+b1EG9qokD"'Z.S$)\~Gk}=OKv?!M :5z^]I參K=?o+iWӠ( co+Jkݭ֊F3G|f}-[Oo%=U!q{N+ܒ~| X_\Q)z~S_.bZpVz<a!ٻ* H' w0u(O>q'V<Ь!azZYĵkXS#ȹ=&D2VUw)qE55V=f],fjti3Tܬ4ع دէ+,e0##r4l ӺđgJ [ۏ[H`7hjj.oE*~z%Ug`;9)^WWY\5vC&Ck׼%Vdfݸ[3}4"plڔK::d$YF@jh(QffH8 Y!zw ,U66lNAUilq|1gQfG~?߼`KSSe JW>UZcytn[^6PUzzΕȼxGf* g0TlɰىZ U,f84h d6>mh4e௾E?*ɵeX +] D̺p:7WԪ NMq'V1G|@:2.SN.V=Qg?%9b._Gd]Ӫ_i~X"S8oM~gnv\Y[[[w4VQUb4D^@T59Řb͑XqZF8 T&rر3 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6~:&† 6lذӋ*A@Q>c\ |_?Q'Nݻwe||&~7~7R:t_|E__JKKgO>6GczG<µ;s+HI]czP7/o3,+|!&HA Op\>e~1U!>' 8fCBeR! P]M 3Ft:i @$ 1ѺW0KZsӤoGC PS3աe?͛H%J+5 /Gs Ѻ@  _㬭m"[n wɥ ^<~șe惈O y?#ty8O7ǯ~-Odg쪱;h1#[c͑R7]4BSfM`o)(?Mt2B6jSKQcLM;Rȧh󓭛g*(J\O;H0t8VCT!&j1V_P0g4kb  Vm7 0%' fll;we*//JOg5|WݻwD&~ZzUO_j{2?J5ΕǸWJC"7>`>O oU릩QuCB;}ٍX܃iVUSTXm\![q$T8жs޿?2tn՟S{OrmZ[xY=iPQzߟf׾~M T]` s޲C ,{S2UUxcimwu^/yޜ~?f7M u=;4\X#!pͺFQ M7t?_7՟m?c.Ն;n C'ZS]+C׆̐h엵.QY9z~0ܼ8ͅ-ؔIitmO.>GϷlVA?ڊ l'۞$v9+_zWrjwaop/"q$)XfZe_{_%X-[gWh<, '-zsqlPNȩgO4d:" LxMkqUIg̱FS[#(V{}mq=ڤ5+?2b(v$<!,`zO|G Fs"^=ݿbٓ;UGuzgyO7wyrO_<$KHxi+R&?cl3x5^y&?D:JIhGJC0` ijUvk|eDADU¾ec.M㯮n<3/)'VMF{2chk^|gvfUz6J m?2P)pWgV7?W) DdgqKnCgxmaÆ 6lذwW\رcyz#oI,,'^ /nEnO_aU׃Z7|V0p S;VAoc5wۿ5=E7З*,$<>vvŷIM֣~h"EQU%;<YT|Kv|iM +X4¹o\K^nrr(5jT*g}=[tƺNq&aFgbn^*$y_*G{]S]f.ZMtN0Dڍ0Sf/_q :6}\UE *tQDVޝyЇMDS,k]ټ!`\ vSqۉL*" m=<9S3RV(O |xWm}'C˫wvFYƠu!. |\4IeӘYcP* $tuܶMk>'KlՊ7tYX|:؊Pgr2ܹe4sR~os-c :q"/?M:2z6]gZpUbX}((,ꪥrkz38]0>S>ka%?EUӋtǨt3|T5>S\+[*b< =-3M?T6U\Q}FUn im߅6i8#z?%R[t> >_A4pGy-|s|Q y` 2 hmf ȩ*͕&*Y-Z!~Y])cxop3TTT@4Jjʍ mGvT aO49~$ZXlK UECش~ѣDMOq- SW>bUo"VTF$Iz7֫Vf;pTV*׋[J*‘g|d]^3=>'2{7ʋ=>MuM \$Ͽ ) ##٩3YMה$WJs `BPLYpS+э7E"}z*},!;݊aN"-稪zlq]yx YZ"N[ZDA]~$Iz>GQPcq2 G{],L)p@k]+ΛMZ4rNHKAQ;dVzNES=uTf-b=iљmc.*GmwxxkWXH]] [.,ls bbG`, =lԪv1JU[}o_+ ` 9] lytܽ;˳?ފ{=ng \)6o[* #Χ*L(e}#:0vdk+*fȶf9wxYeΞ*E:1hht*ec8'{lFQxY;t#-,/¬D"]96.%a;U_C:ێwo浪 ?淋;+ 8?@z,fg&{CUT"1 =;w'ѷXSuY (/yV&b =1rmϒbCOaVWGx)GSm;VPPU2:v˝{ kp5 !Xz<[nk2uQ*k38y_CGSrhͮ~zڽ 6lذaÆ 6lذaÆ 6lذaÆ 6lذaÆ 6 6lذaÆrLMF{ _.E^K㪢r/zƚ)?q]dg^_d\ș 'QTh*ś1WCIA$iXLfOc3PXcI/eCŻ,grcLFZO_~O@[C@@)4r3jn#>ǎ9ns9l.ŋSKi/jq-M +Irñe g2D^b:b93lZ>ϲư5zkGI-PgO# 1"h*GႹ^ rld*\38<$IU:FC2#(8_?y<(HR/,Yfe / r]q8'YږlU%WNVw+"kc轻݌t8Rٕ KO. sH]DnV"!"Qbq:|Mm,"i:Gk]$x\jTm9.|( YZጶǷ'pg"+T:8ڲ?TT7g⼧5lԩlܠ? 5|;#o!n狐3pfߞP'䓁=6tn;2o¹9,ZU_l mK^ Hpdp%d?32"flzch1c|ܺ5t[QG80ˮ9Ykب"E>'V>;O__QuUr,ܹGz:+le{qPs" LLFrVF`vMտ]Gڞщ@7ųw uoM&zoȺ/0Qea(o9Zz\;iyDHe $/t8]@``iEU5)D[*;\88rV1 A)c#+;UvDv8nc؇@OxFj1<.!nhۍ<4˂&>^|pc|= tىȹ_bq-%QUԊL3rfCU}Pq.#tTfû hvvZ(wKnw2ڞ7l~k- 7MW9[LtI,,b(5RrhhG}R{֠ trkopO8+| Lڌ~*ywdK"IU˅ʟ(sUKd@j)J[xΒʱK1 9r'&}=N% .u߼P:4iC:k7ҕ3";7rF.gC}h˞|k3wlV 9#g%c:Xz~=IF~[nFGL|k;p+nELMn=b)}D!K덖3<,ul^5ƿȬ~f^u\xzm|}N H,Qɵ>HrPr6YүŪ0I<> oe\^8(JɀIM<PsH kc"`ӗä/b-?OFeD,ԝQb%D+Ze!/u7%doxXaGb7lJQr9V7nبc~p `́mru0u߽O) dd>ҳWyctgXwtHʕ(3\J[qJQU -cا7KUEz9ޭⰉ dmNptmU3փV`_HC,l)7 BOe(|vɠR=Gv*Dǯqj]3Vu+)2nwg]kh4dOyMqV,zzBGtv2KYZbl l_aB43ӛ1/ IDATCUAe44*476w $Uff4x|  )jgL(ԉܱ'pV!LD$(GDi tfqWxwY欴Ou'/DK} \ș[鿧w,(!F.[YE8}2{/'<^2Tㅐ{1)Li>3٣mm\D0w0瘝x5~򓜼&*^Z@q1=.rP*aKTshLna4P Fs ?)Lr}-+AU~ _xHds>HC:∎2^ٱq$tvPdO2)jWW9S]s Y(, w0b.HdC))vֻ`j2imR,cu֟,pjq.p0܌idFcb?)xs9dd;Fl&ɺN9NQd=g?S$QR""dڥ>,KS>F֢:CFs'E )y`Y981PdR*$(-ne5pRhG!|b~m*=DiUש]\"t:7SY.zT3R`e#Wfg 0N߂c({SWqky-iVڦgVռk&+ ]ssteb54}KL;1W({#*;y0sV;a#(J 6O_k"6G+'>Bqh2O)Cx;p6KLQ,ָb|(gy9d%||BFL`UX}, ub685LjbڱN:P: ==# {_;DY/͛[k@k+]SSZ[7EŨ٩m^'P\`¶'3]6?gl$v,'e7b9Ycs/"g}V'Kс=BOG|;x!}w++$KVd Жҵ@Sb;BY3/e.PZJwpZ]LJtK&1⪬ѣO߽B[+Ǵqg'~<;QQ9EH#[Kp{lv~OIg"+4 xXI8)3vߓ8yH\.dR1fWcT},=YBm0$#\%Ԟ0h ~b?qmѢ(2;;Z2Iܜc3?cƻKmKD xҒf{nL-1DWyY~<+WH$|Qdg3d1٣g|, dEQ_,JnunYIeh#9vLAYG9.e='| =#w$Tyb:ȩ ҤzT?؉r^:)!$Qb Qa3I^:3 qk3/'x"LdG~Yes}6. &J4-s8߽5mM0e-.iaS\v2ɡ!d7&4L9:^oBNG͏% O 9N(ǘ*;n'wGssLm޳lUC1 gSHT7A_czLaQ򻻑4>]hַ _Z,Joq5Q֗#.> %i3nVW}<<&i]foYQExVi% < .<$1g l͟X$qvIP5^cӗ #=_ij;H"I{T(N'cqD4Jټv Pݵ _|pZ ȲK}*+*xq:=Rxb1zkal 9 rWaR ux χ8“EnŭcFLNPa07vzqvр*wAwvyy ͇Ȅ"!<.c9Sxf^m?B!n7KKK$ m|sEW%OffHᬬݣƠgN!ՎLA jaFCX,Fԋ|Qf,qXJURWXEE>|츚ηиL^5Oq&/Jc1 kn;iەۂ@rRQRTg#`4j؜Oݿ= f-y,dP(di) -b.tcb0| g׳2.L6>;TB+l&mfy<2 EK,zݷӯ 6^.gN>5LN?'a Q* Lu;^GdďJ1n2 *Lu3mXU0]+/(|x^s+?$$W&uk¹e\A;aZ̄c O N?wᓅRFب7^_g_9II})[okM^+{(.29gzzZi' ذ< ;9g'AaNFV"qumYcJ4<9@I|9 E(*'tEӮ:]LKv^׿NUUէ?i&oN9PL1ϖT}dbfmw,Ⱦ-z(éYT j2Mi++;gY4{ #v0R]5v;񉉬|[ߙYK Y&hw_++ZLBLMMF)]M0W<_^Kxn\{^f5[jM\I9ukYKW;qhs:ZWVngl-91gOoDH@^")x=\+"Uc$ ڏ{|{Y*Rzρ]=Yr(SrakUcg]" k=3'0܌œ[c~2g PSlcDL Ӈ[ ^)%N];3ʊŸ,}]lÍ78^xHQCNTgB?>R B:NV ehs.c8 23ldR:BcZ>Ikp9}"ꃤ0R8uCj#08衬: anXI :ho Peڝv6sb>|sn q޴9nIcwc4vL e/T|mE$B!U_wlLBo/>a|slU<~+r3ɺڒzQV*+'yv7f<<{0 F}PDrܯG9vPTωi#T?ySOe=\gvvfWz}Kc)n-ܦaW+5A)&^+ ZY͊&Iv#(gwlۿwy|e brHWU&a%ȠfVceʶi;Z^  BVӞǫ?}c{{[[)9 #905mft2IcUޤAؾ7L;j$4̮g^WMg l|"+6w-#N4Jj+|[~*qv1" Q2<ܶ!77-wa߈[0nq"58jƶ@VP$Э}ӟuNhC@3ռeN?·Opebes&h4eq;|o[HW=D Ey"k @}sT *j㭍zގI0(klͦTJiI·B ݹÜIݻ}j9}F*KE[ OQZPmZ'n\C.6`P\R jhф2x(j QU?HsSju|s\w[jxoo>2žB x=je}0<^7CXJEFskf|w7*F|Q,a&e;ɉf0“O %اg om4U͝\myŋa|כH+-F(1*s.p!e *-bm?@]l?ի" VԠ;>a IDAT۲>WK/2V%¶&,'^=lTI9kj O cڙo/RaHNT)#\Xe 9[e9ڃAjsMeKf װ A\.'_{ )qJџc3d sCI$K꫄aSX,$q66j6t<ݡ bԽszi5ϛFg(]Q .a&uy gۘ &ͷRds>A$AK8\$TOЛRpYysGCYY{㭮f9SDlV/elV/Ue A\X.獧\d񘙻<3_8dΩ> ;&=,-RX,^jjj&޾Y;c4JBR(IΜ5=sȲ +n7UVjBT&Hr&_ YvoiA?݂xSW6`+׮rpAֶa,74hgUwN"2yBE!o"vSOCxxRᇌ11>6ڷ~[g(*Q5XJAW"\"Q:ZCMh4ah( 2 줟 {c; h.p-f<АW8bL可Q cF}ݳ cĭ$h+rѓezq?H1?Nra(=B๽c+g& OnPQ\sV86xEJvjRK"TZZ̓EMDCQjYrpe([\)njUz%Cp]Zsx6 ZKJD!HVqbRbV›oIeԿmd[b-~'vnSSC&_}e`kzf1֫S Z}v߇HEAT a!Gb-@' I$X_rq͵)J\Xma|o|s$N-g

zrij1*kObC7_l6jIӝBm_O>} E1\OCǖUQJ{6oAxPn\n yrc8Di6\f.}oDC*~Uk+wBԣK*89 :Ak5ꚧR,F3$h|1ke[5j\ݾgd^IQh4k=TaR_20tbH0S9̩żV}ݹ ) sK%<<ѣ@04w$msZ5cL$4ssppwN$vԔG{ʹ#F-s/} ӆcz67s ,ZKo/m܅ R˷Wj,}.j-\JalKW5o`P{c/OoJrWRț3ˍ'qI=RiayɅ-AEZ9gU_Χ$}WUr:o|=a10/~1Vk9c8wNq9JZ[Y{7ӿUܖa˥dwG!v3$'7pb-#-^f}{Zh2bشƴ $-b߾m}k^ &f(m.լ'4-kkVĤ(f+mŜ@5G]_am-Ά]܌?tkM ˤTW'fBxwylB2$)D<$?YڝNLF#u:g)(Кh 2Ý9 x{8JxKh <[^CQ]da2QhbNi[eg_{1Ko,b'}'YI}Xܤ*(,FE7SXTm=Yey?dCy <&V7c3c`2yߏ0gP{do<Ǐ~$ob6Lb1&VV'U<o\'Ub2|:5"+wkYFm6z}wQ n23ry"1L9? E Ja-ЄV" Gxěd+N,s` 3#5,he|+FT !EX {^_ 5~9jYZbf[8ztSԄUnr<ӑ f NRPQ@FÀ2lKf˗qެ25%`2(+?'2Ǚ K4m":9|j#535^67.b<0ƽLmƁXcm,[dwB- %-%Y%Ef`Bi b"K\ ϶|D=Y6ZZpYb4lp9mA nPܚ8bK JJ|l^\"399h(JmOtSE0Z.LzI,ܻVBJK'v-BmΝ%EaVmIL(\kabBMNxC ":Q(?!i¹ xZ6ž5]e5WXS*lRVܬ9_~Ruԕb }U"}?Qߞ#fb7n\+ sIECQrLa(M\?e*gG\?90p`6JcӵwzM@0L.\^")bM<银5,~Gw)KBTTTH$hol$ctzL&:^pI2qr |>OaQvF*( -$aˏ3rR#-\ZLLZSL:?*S.NH]aQxP d7pL"P:Jwn&Zy"OFz%U yJ͹wN[mS7uuOsj/tUx E}FHV,f3 ^]kj~=HopZt -Yb'xZH~>:E^S<²cQZ9./$d"=W19`>a3as$ fwY3Qz7ҊYz j`q.WNLVdz;h25ͺC\,{`/EP\uK>k m2*^~Uo]LOz(EZ~@/_*&U>;npSSS gjxhi)w._px[X<^+XlU!,EI$&%|pJҮ?0s(V-}5hTZ[Y]lL;ɞ1nX[uq+X[UE?)>Ƀ.)^ܝӎ+l{~s~r#κ >, ͚`vD`&[!W/2UR]~ar2Dm"&"@ +=Vhۻt*- wY&z'ʝ;}=i*\vSqp +Tׁ;'࿳HGGGGGGGHNy57>~9v~:::8{,:%j*ݴ3hc2AGL$ڒ.bmaI4c#$}E¼!i LunGe)0d2ёW4Epwm`ϭ3rȀlF0 GGlbRlvQhkE9[eAeY9,YQ~YQ3嗛Hd$u`LȊLT7# g:(f}n]{oưr:XvN7in&p`J p0KN YbYɷiDW/vN*_@V搻p:;l 4 {^$6q˼.5L_FbZrh|v4h})avv^jyVLrU tjk=jc߯%&23t}oQϱ~VW7d92\`u {q`>,uO9ù;_n3cdmڊm25--$#AihHr9!gAYĬ:؆pP,&au;4bn}@i/Dfz T0Ψ1"$Inz م.Rk2m2W.ڂ'IyVL&h+mI8rS^?Ոw$;Ng<6x饦 >`D&ҴM[śoݳ=S=^MT&AKv9mս} A,_^lwK~AgV.K##"%HCQptq2;n'N˿݉ՆC=dnɤMY%jҔb-NZ\2LyCM"('~(wDXA012b$TNuoE;)i]_tR2 CX9m%R0O9a!cHnZc(p1&@/i! G#ٷT7H$dI$6jx,Y~s/F] ƄŀŧnГQA#G>֔F61|;=Ǥnќx$2]]]tuuvqŋssG(,$U?5沈ET^!W_P-ڹ}WЅ$mAqhD.BqrxI$.1nQxLiܴ[|j2c.X忾W-=CH$@dY&av6IP_kfM]YX]%J"2b˝8Ng1܄BHw;:oR,K#h޻x#,/g~EQv>f>]v5У%á_7P}zR$wkq6k3/}Q; j L3iQ ` >.e,.a?øo֬) M(LeJWČ1 &{{ x(k. D>lK3瑮%qIPJ.T5mvs>rCh//GŴsxyhwT lnL\(ML NreFYFsbleiS6E,˄V;!dErءCdPSKŁb6H`IQG1hZflKKܭKe?(4,.{ӵ>p&+yװτA__F#xnsQNQ|8ZPwQ}5Z&Akt1mbp$yvO;),l,IL'}M(cn8Gn$E@NLoޝϷdEa)E*/~JWLLavy컚@Ifgv>o7L+>ѡ}Q}ϴ\ƽ' *+Y~h]dEg()s:EE"nڂbgzE11h]TI߽1盫IQ´P)='Ðg{1A02lILN' ǏZMj͘cKҬtڸ]ϿD"KG۵]n[%&PJD"YiZUn\wЉoth[Z)Vu5Sv/X X\gtԉzg3ޏ2o-V򕌂I`"rjcḌ(2wvb2#V F>W^w6}2&?B#r)$!zD>Xڴ,`/|6kL{x\)5{86e)B,)ɩvu{^b:~{$whH=:>=jg1C`hZZ`a; F6dB8ف[۫dysD".>{yqHn+͊ 8/d w gm[M_p'DMw\ej 2{ʅ˼QdNP==Inߺ@ϟ\y077Rf[%FJlѕ;XmEyEwwfwf+\Bzg/=,ޚo*xH޸NoM ӋQ0ay(\;d٦'Dq$ܞòbZkz#RZ ^WqMj/g H@C<.^Ͻ=3ۿrV*_Bc[11 _#bH̢Ncc9~|[~jqYNSYV`5?rꥶ֏#KnCr"jS=^\*Ge^rmjrͦDb1yai^Do≖>j~:::|3[o r$x+i)&G}x<yVb++Hɤjk9II19ϩϜBlU>T7h׹ti2'Em5LL#Ċb½1|}>.]Jp$r͍fpAJmlr{X/4&\G%~ 0>>N2dqqQѨclLu3.STUDYi3409 ϗG,[\ie[x1<cH)?]D,.T'pfIJu/&~!ChJy>t$&BTT,st(=uj/55i% ~c>; OշUu5G2yrE_ZC=yF7sSΞq .DQ>ːN9qXw/:㋿fžKˋώ^{mtvv+ݼlyA;K].a*=sqƍLNNV.>#V/"##܁sT#X ؙ4wtǑ$.5-CJd:Y]y۸8'tk.c46J &#(.W&Jrrg^} L\X?w$i󝗍\#RX(cvfﯲ}We)qWS+&gxoqQ̢}Y*A b]Ll&C12Ә^jvsLB(+ڬrb8$}&%^ZC -+Sέ}.1ښ|QqhnIfhaX$Uܨ,JYpv*;y_oPe5z) bql6Js𭰐U͒_ٚ/jk TC ZZnie%>fB@D7AmI(9=} ճ<+2nاD|$K;wO@k@iLF0WdӴ};XH%{X[խk$Ge:wv 'nD:9D筣\UCܻ7 ?fE4j}_@liX)7o6vq8~k f36hT;>έd/3F*س  ЈZ_,_,‚y!l>h u C+$Q,VX`TX<óWrQSVܘ8,xܫVHM&tEBQA7k_\_Z$kQIC4YىweQ_8z]D&Qח|@g x7*BBE$p$nw3ciYٙΆVPT4u ŵE"dI&I\HsSS~۴?րy`ߜ.=;BgW_w07ݒ#M>H#tɃ#aEYeb2iUx ܾU߂臝;XzDJM-j˗ٷop'O Q,$h=d$Ҡ`aܠf:"=wjXu`W?gZ^cbn-yVb/"9?x9^w"`TF$ŚX⭑=hwIye*@{cUo8{Z$ǎݻYM12"1>&vGm]7TUh vOJZÙ jY͂o\H,g^!7$h>l '@4<| {m\xNqͥbL&EOo%O2X:z.Fk%E2l; ^!aI(jo>!6 ttl\ Gk0q^~q|.mvl16MMigb-'Xq$ZM-DZu#<p$ɥ \䥭-M"QǾ}gW4`{Di'})/b6P08{>&'Oj:2OK'r/(dU)of^h Yi7<{|}v_},M+N't/џ5Vl(<)#y< 𕯼҅ͦAG86҆F5[z[}7CWWM D"HٻdTW!e3N0uc,/\ZĽk(ߨaU~e7S`'x IalAK]¯xq|'Ot+HV0pؙo$'NkY4o6x0o;b=aq:#,;wwoQ}<1ZZ%67np^soSnrm{|Nn4.}!7V 2nG'c3H|<ހlW]\qF/"5$Kѱ:PW7jo<;X\@ݬqHv66m8HފPăL\*@k@#a6o-Ѩ٫+tﱔ{o;Zs ˅E. N?d21?? /PVV |RM̽iyWU]kvN?&6eH-0u?KU[5%+g|}{e]WGhoob HбEAe== -/#9s8Eͬ!߽ֆx~lUENtc> /K|_ ~w~qhw~Aܣtuv_+Xq6!qG#(N]=N?wirPY9HWW t@__~$IBp׸YӸ-fsAg|}zah+pGh~^rg? yvAӏ8Mߔ8eŧ8$Ϲx9' 6 Fy7~xB!K? YMP{&0?bq>Ûs*j5>uc, IY ra]brxYtzV̫85ru2_,͏_2d$h:d+~ه:TN#9CK_a8bQUg8׿GP]U?^ȑ hx>681yq;m6 СC[v0z'x,EX^|3 CxgFC#[/+sEEۻiӉ#>]y} #*Oq`hPx,Qu%̓hYHxO8\>{ϥV{r,󋕾+4Y$<sUr1,Pm܀8$½E]NBkUUddD׬[Vw4rSi9ÓΑd -u-̬땶lZyp:~1ǿdp12ߦajK; r:%,aq|ӣ˅gdw!ː@Kryީ;ޢq̞+)ꊟ\^ y=4 {V+.'Ç1Yj.I?ۆkTr2^7Rg e"s~(M*o(`= p;̤Re-*Ea^r$)mWQߏW/f⎨ IDATu~awkzG*cUEU ${7LWQe/Z~T'8-m45/[ @9͟OsoE'Z&,]_/z%VkSeR9FNNW߳mE[/8yMf3m|d]_‚yQ#Q?cg\I|/n^`|y.g3-ixv/-!A$6_++c׈s과bѽ^B{ՌJ$_Mνlzfl05VV56"^mν]rhR}{t͇,< dJ^\D@B(x\Mv Ckji.P%"! IΔjlhkFxC7o޽'Ϟ=sVy6b++HGy~6v+o4{)c?LRƇ##t;팓C)\NIq\Ix8eg2ᯪU⵫H$:wyn)twu!Atvu5AUU3++1l6Ɩ L\4o|%Tt*Vil>[T_`u5!1Cg?LC4<=i=A8eO+fzXYpHe_QJu:R< 7 ~+b-a<m-*iUEr8UVXz)SͦI{G7 <}Or ;,,X%<* ^ qsmh"dQ_Ʉf/ݻOFX):ٽkn$4K˟JJp}ܻb]ɟQOجl y=;z~_˅۩`2k:?;z55ŪXﭫFb.Qdy!SYޚgK!477|D&"x疐dż sr 8Bl,̂͆vb)gC#:axV/-qDnMM'@6c,S^yHfb&n(DWipH/kW[ky|T* YJg3l,/ˇ[6~xUnfqc!Txw?"zbjܭ}yrr Ixm6A|-̝X DD$e[<ڴfp=LFkzv nqx>68$#IL&./yj,)O.OmΟ?Ogg'+ڮ&?ճ|b&|;W- r\.sn4=ulAyJ#Hxg#,KʆA[~j?D\.V+|qY x'(&x8Q*uA}^K3/֋E.KËX܁)>Vr>NКH 47brߥ |KHCQU&=dav0ɩXtx\w7M-ˡ-ZL>ۅ?ǚz=E1`6fmD2V/FLs6c%[L5,h1s}~:2a7cni1r&G\8Kl8=<%WTE6t̊Y\LڞߔFLf2e}S*J"mF$޽q :vl. J(@$a߾:9yr[~^rje/+SO@J[ힲ@]Bv-&)3mym#@z#,|WK!3V ZuM:@QX,Nl6nn^C n~6nQ·I:: ?"oc!%kyqgI^M$GG*⹖@&I\D"\.gϞ.+|S_ȑҸN>+Okr':?γV{1{ "@A3 QU^fߞ$Rʉٻct[Mv|ٶ1}+MC}}P~t/c@ߏB字!Ԟ,,iSEA/7!\.M~NqG{:ʲ&>\!>!mn*^ר ׵ }C/e^W_==9 ag<yrsNg~nBDe:[,gǙ_Pg%ɹ핣(U#%~>P  M nfJ~oʑM$j6EI*=Q9ǯ[hj:w`;eܹ}[wH^$Lpn/p388^PU5P.&Vl:_ܡ; -RwAI*O g#F"(>K2EC 46V lڟ*:: ss%_XAn7ᰯZ6 9L$戫Tjk OH~LJLuɪE񯰘+~o,$fff<.̤3g:QW>϶@Q@h∂9g"àgC*%J JT-(d 5PLc} QD|c3,J[eEQN(tmFQ~\. q|q.]>C:̠c?׿˝;naYgP96#!Gr1C)c9^DX/="3BQ֕5n=ҳTQ <Kr88֛!pA-Fqش7amm̺…K?Arϒol$X_7S5Xy+/1pm.B7Oq0J>~O¦ sTC"2)k l`Z+̇%Fu%P~ϷP)u)H)ȓ2-usRy"w!^"_$/ Ii[Xks^2^-% (-~8vRU{TFI`i$S~܌~e \qTJJAQK}|S#Vkhh(F":fbBEUwX5Ypq d).OS)U6FCC;gU)9۷"7;٧IEs+8)g6[#E(I?jⶐ5{/+'{ cm2#nܧ=PPyhq\M`s tf:?}#6* gH?JrfQ0 ZlZ"o`_8(hkq6tTeY,Mg-\,7~fEQg2dN#F($riEU8wcnu4ՖX~! #Oʘ6SRE1og|OPaު {Qzzϟ5z'0~)ɓsK9Bx|.O]r9sDOz$3>ݙw)fpof~ƝNprJ[f1W;9st}2Ndt8 nNIt'brӦ}@N?:Qy~>F tg6b~:|g+:ܚ ZslTw̼!O8~IB_Ua8M8v̬;wߐuUQryixXw?_R`FzZ7r~,x$HɏxDӡj/?:=uy]X۫Fٔ[ݫb-ЭCQByMsswfM$d~Prc_`4t20000000xL] &P/OI:_ -d7AXOp\qZ|!;ZD";+h`TlrxIM"mJVUz'&d&&Z CCD./ bԮ]{hal!:~1q}o#vs팬,˴NֲYx"j*]?'(yKdpXܾG"wQxuaL3t T˜#a 2t2c(=F~tL TIIa H%f[6ڏ$/` IgxzHeY&(I-2(W^qNűCS9|Gb.2WZXS6g̤h)PuH8o.BATDI,HT;@j>G9VlV`U #>Uo'2;su/WMyll9zJY(:,"e }4̹Y"`P(녡70PڏP˵U*'KF7tQJ~U'&dV?sUxZ9 iūC;cc422oL-vΑ[KY |pD_+(R!Yd.\ hp"̝kw'IUQy}-EXb~.AIŸJe¥~e`Ͽ{#AS ٝYelEUԏ mfdYܰ}Gu8ɡ** |Ĺs*6]+,|.!ޙ{XZl̇CCq|-4(w΅#a]{RCeYeM:ZےWbzqL"(, .>ơF#X\(9pX|.P1Sgqi]5P~1,W\O3k܄#勬 e2M;l95d$'ejhz& d 2WveY^rr8w3¹kܼ^k~&ú$(gg+TJ[y;΅L sn"q{C[檅/xjGe[Z"Rڱ27y66$ٰ 7!&}a#פ8CiE^pM`2P C:x/UwRDš /,i ukν{C% J%,y?WǸM22*.VUVÛw o-F@ٚwy蘅`Э2:znOA ݵ*ާ'yƫ>I=1ߜ$ufA`ISaG PLY 3, 9AnTGߺB:hE ٧0W^Q9gIf->-NjHˢ+{2-^,#I&6t| kRJ 8~V2* d2Z-Hn0깁+T99v8k7QhšɋH4=F IDATu/,*$k|Zw>v2,uu.kEh4`*2:6qEi 2WiI[\!:w\}lac*%`s M Nn/T\>R#4N( SZй @pz'mTcdK MC ⸒f,]مb203ÛaixکO;x'⩼s];$iky-;gxVoD4{>ylns88P[U^Y?_9t2"3'g>RAڛ޸N\dUR;K}srl3/LZK(@SQ y5oڷ/ uwL4?? ޼ssL~:Y+ Ե5fgIS(SXfm8콤"ȓ2A_silT8wn?woDX_#hP4Z?kJ^)V+ȒFرc|>\'EJHis%YL2w?g'gDF'G JAt A_ŵ}\geO1ֹ<+4AχK,()DbۙOq49u;_(BzQ{<|5 I_MIrU>,PUnsF:dԶGBJfˉѸ?sg ㇧Q(vN'5Ib|jqKx^|Na}~u(+/gs\&+\{v=w?yH#A-vs>ӹ7yq,Lvqy:2}>>sAb"`x ߖ%& `,ɴx#. QQ4wIlIdE‰ :\?H& Y8Sw έwK}kMs])UOo{pul(֗w \v{2ܹsx<",/' H$>%)H)lHg?{W1d>Ū֡\^I{9LfN\0繲$?vS# % e$&B\|137/׿L*=_`R`г>K+#p3Ż3s^@|a9wK>#YFv ;j;YϮ=Fw޿ȝdY">m(ϳ7O&y7n_"c`E헹4f¯Zic&O׋飏NO|FW;2WHKo—cdWF\4 HRnݻ{u_femo>JĉA?&*G(&Ϯұ\on/NN#i" /1mvfƗe"II);?:ifYvSTTh(`wRhG5-a/RbmoΔǹk^rgOe#9i+:Rrj13j$RGG$5ڜRRvt.pe%Ͼ_=M*ks-WXZ*l.[Gmr)⧧P?[ k nUIn~xZ||?Ѭc9 IlEGӣXlbI Od )hrl}y)qk.1tx.hM9^0i2f'kYgFpO=,s'?kC8@:0Wf".dNK֪ܪid\=JםAoO5X^iQTPkiw!5y@ a hp6P>K(5(ªK( :ꘟN 6X,r~v1wوBO|f.^QYQvd@lju>:yf~osQk$U/:c ILcc#ז5'z}V:#pǨ4CX/Z~RXJPj-Lc2?hi rTHbq0|܈hϹ(s,m 5ud"'{pU8BW6GޜP1?fϘRaLcvuuF__m9~5{O 63D3 b팜ܯE2gk5"9-߿՗_lE\YJ…֪jZX~.NR&D5;KDe¦!THŽ/+Z(}!g7M J%y,-qwRg=?Ƴ+ 'h14d9cjA@kqsd6 d_$` .2?*VT ZԶբ={^Ak3W؋y|G!oۼ[l \ߦffg?Y᫛ yȱq"X-8!Dn8sd?%\z81mC UXn1w uD.h["f5{:ti)2)v{w ]VkԌTv6>Ug0d S ㅟ[6M^d .u:dHVSÊG f d(:_ -djOGu>G[Tf ].`Uz\!Pliu$r@CeCp%5+egKJD ݠ{xWgEz{sdֳ|}vdqD"W#]R H ~narT KDG:Z*HVϒÐ%SPCxʛ\L) u!E V7S^1BuUk3⢕hĖԳ45148Yʻԑc4ƣV pAٮTÃDF0VFss +-o&_1`[)ZW+T3̚*-kelG ze,5oR]!J<6HRU[c֖s{̋m2B=*@2t=vͱCPy^2)k wqgٗNg#8HUD-?eG`1Iq[ FoX qhw'jRu% Ð$~r%4\G_іqLqb R=  vm67n\}%S@i={pD'UtvY$iUWn+n^"_?GN͝FvFj2±w/*ށZP(SP*4M8O`8>NK$Zrֈ^,8Jj/!q4 ޑq^ywܽQˬ1A|7s_'1=ok8)*}5J,p<_Q"74&w3:m:¾ "La/w5Ӿ f/%ƛZ&MƼ>GI2&UUGX\b~nn=˛o s$jD)`&kGԘSHM%')=OPw(*= rniE'0VA8_hiQrhDt\o^׿`AU'n2!6 ]O"w40 9$K#ADHA"}/{tw7ۿE.Ko|ƅEn6x n}:b+ܸ1*EҤ/|a76V~rf. om}h-Σ 4!u^UD7݅Rd-h%*DShFZ@7qG-N=Q*V&Nr`&-~]i ht<}D IDATZG-zg*}$K91^$&I36Z7OP[˶mϗ&SZo¼a!GcR>%T r*:hSțvz< Oa3؈F9{ůVk(<'?Hm 99v5:g=("#*ƗW)G:a!1^kc˕{ O˯hj8UByyb8ΈF",KFQD{lq? )Ne7ybN<1]Hmb|ᗱXnXq[+|6a #-:%/zHx#^V,Ѳ24]zMMGI U8`~[@Sjf U2&[9TEbp1vΟl088{טqM) % N_yu4Z7յ{ijBV`/EBcd$JCX3 Dlid_WjC +V+wn,6o.8rA[IA`XRN^WP|(ք5%. +P2JiIC54nJKhr*yyɂ0_/jlݪLmS qYvj5Wa5c0럽>Vû^v8..CĢǯrw/cܤ.ۮubJW*`SZ{7nжe ǴbxÅכDH kv^dWm96a،oN*:_GiCZhn"Ƚ DSp݄3xAɉgf|x?hT3:_;Wŗwef9X]H={ZF ӧO3=7ʽv";gvq`ᘠy\;\*043X,\CAٖۮ=ro}-FƩGXeʘ鞡X-H?fŔlF8죥hO8RUyVNb3}c]l<-:G}10{pϡ$?~t̽5O&Wr~BgQ1LMz?$ד.pbXP]BC>K0zk^ _ a8SWgY]H慺x\{Qiio)Xa/BCqpzCDV~I߂bxg~1\o7KQbDu5UQu{mzU8e9}:ET!<!Q=v=B_G >hDFEFD#4j5ܻ[tQߡ` '-y Zђ)t, %(?ڎJ6V+9-IۿZ)Z_ * ,Xl3.tq萅!VВ#G0]uY`lb m^TɵgAƦ SZOծ]foljB49pΨ"z,VUTե[UɾV49LDCZ`k3S zɔ[H%T8ZWÜwt4v45kkkN]w~'K8,M/w3u`РCKpf lqt~Da(DM._G^?5ѢYgVq^+:FYЋͨ3[^ӓ(44>[hh Ngv&SnS۸REu dWEXR"^"/3TרSۢc(h{;t! |N=&t,CC9Jj5Β0׈n.G\a=,1;`9smmm2~4HiC_cv8vRq61?"]۴Ʀs+V㿆$|*[)+'롱 ֜=|X#l KY#U8?j+?Ps >> 's}. 晅B0c*!$ R ++[^;6K*no36}oYYd2>nXPlkuD#&uJTJo 95XL Ҵ_dpX7u"R@ Մ]YIUsHn$2ыX;A%49h,a_g6 MѪ1VVvS !GNvXR3M?8OS{ΠXqܓQ;2-#j\Yج$/ͯӺDžFAjN(ʈ:nJJbc*AEgԹl,`P7?L(`e%@kk GߤJ0(֨srjuVں!Z[DJJQ6=p*'J=288[K-$FqV6fakU" DDhZyUgIi*NHKpli4qIמFMM Ii+uJľÆ0:$BǙX(փAhkKZGʊL4G!#sɬe"Ҕ~ 9T@Tnghp7i:9Ss}b'|t:ȸjLLr75#|XY0: Dі^R]Vil16}.Y0ŨjVe+"ANdsI9/GlD1M ̽MvHZUó>fkl6VkaqqdZ|zUMP2$˼̤5P8&bo.'P KKkbW75rŭ~$.a%Te crX4Z坿xm۷sc ;Me@Ь@ ]Xm&CTmmo= y$sx]_IT<-╶3T4cLQ2+o^Wjrϱl:=Eh?ӥ\T_TG gy)ΆZl>zK.~6[y5E!z/6,Qk6O8y:~Ubddż=@PEvLLg!ϝ5Ap}o~ՅV+/`/`3{~,wHĠQiڇ-d|<|8:)((((((((((((((((((((((((((((((((((ZYYY |2wҥKڵ 7U09 kxH'ta ex/ xyV*3-JH.N|:_dfj[dfOa[fn.HEݱՙvѧնw9cz;\A5y/ZX^?6;=Wgg @ K2Y j1I>pF9׮!o[L>Շ\ѹA W5A b)}m3`;Aڎhמ|,fC3e $>LNN.30OU={DxX^bB&zX,9z޹d`$ha9xι~i'UT39?N:oosuH&kV>och3Ҙ L[W &''qż5h%`0G~s"ǎ}p.5΅ID[sMS,&њP㡔?b=ܣdnmEBNG sHLpz54jc@^ҾO%N :_dQyL|NLZ|E.S7*SF"<"+A];YnHi)/9ÞǙH$(.pd ߨ#KxN5KcsuhK}IDh;fn~<^rpsn#bY~gD<M&7fw!~}?9 RVOζKJ9$cO`O"_v;+p>6?cn9ჟL,s?XYFuFۚ/,ow71&<|x?d2{P[{ :uz`4L&3 ښzǓz]l=` Oq K(Ѝ9:?/ԎlgT̀Kh Y e4^So,13#D$9tP^%4/9??tLTWklT)zb_?1L& w۞;W|ըb46׋h홧+TWģ'7C..d#BʯƏ!:KΣAϾ{Zm=_c09f:/w21ۦ?z##(Նm{Y}L me`bn1ZOg;s+\R% ,mLh2{;AΏ}>-VNߗ1؛vU0ޏ;B0<t:Sg:#ʌOSpϨ.3's : `&zsCz`|bٟ֟]a]o:_hΜ@aͯK?3gkl4g_I ?h;2[ry{=?6Y&'D1c&'-4zT'x8mt{iuS8n2^pCn/1~ßXnx2BcRPN&+WD,&ymgI+k3A)+&QX z?s. "L&n>9<)P ^}A7l{ lOAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAA9)Ē;(.{}X• l kC9Є \fqq4 &tv,,ȍ$IO*!kf2NӺt\hy-2071;Gi3wέ m\ 2k$H0U'xoLEWSyPL$ڱk,׷ÞkyZ_aq3lPZ1Ӻ›Lo;*K1˸.ܽ`AaQ=OR̭^rwSN2lԏg禢E,vR@Vx z܁ u6fS x|1^Ru*oTdMu5ܿ7ncP\a?x;Xd13E2*<Rqb66a*c#JDC4Y)K9yhJ2s<RY|yQ We|SSߏE LRE3O1r#{KmH9|č>.$2./20a?ff;w$.s1'DT փy]z1ÇC0f_823E2|ًIL z I}`30s+<\*@z\.ΝIaUDԎZ faI^V]ms3FF:n|oaA_P[hJ ;klbr?+:ϯF~ǨΠ2콌x<ƕ'P{ޖG&*3c >nq\}gg 0XELI+`XL,ʾ޴1(MoOMg8=O#lci&wOa#D{VV$G05AK [ӱ4^߼Nz<{ m*s/GG`qQᮻ:Y]?`vءZ$YbC KSRG o)K^]xv;G or{<2" i5 x{^9׫Pd.5NǨ 2Em1N]>:;>48ƞB}^~wyh:tjl S)Z[b ӯ/Qsl(0X:Mkk FiiG\3[<éwK >n=K@xTb UFp m5t,ͩjv05e4+k}ǔkG?mJ<;wdjjv&33Nr`G'S)~4ұ4U*VVmE4NLOgy+QͥX}O./D8QvUM'?>gC0ojDknNMN3: Wdf2ֱͼnByy[TȞ.p]x-;좪]'-T㡽$斖Ν;JxZbiڗZĝdV/rwVߡrԩ)ssnKY]M0--0ޅG'C 9{[x>N҉n.tQݒ[?5 sSsVvf9'? {YvOmO\|B+tM'3A[hobo`o9I!\>9ē'v}u}`i4m}α1>`%?[U+xUhlP^Ҿ>Mfi܃JbC>SlVN})s<;lm.މ~?t:,kX[cRڮ]K mA'3pwv\M4L.䂹h5NNO.sx7OCnY˽KXT> W0xkv!`汶=vQl\P^5io~hz0u9.6QɼϲVsAG,.D.Fx&KK̽3gCNcakE蠽X ؁p޲h^$=4DcbGc=ũor͉'Ν[Zv&'M#XVX~5jٝV::ZZΟDj2WzeEch|ooa{-E9]]l{o{}P/cno9 9uM{diWhz3n{V\PI\M^[8g sMoeN>x"n۫mڸmKv,_SawD~;<9 2αsNνLkvvl/s+Qq}(ǂk==յ1->򳜻6X;yyM3c\+3AN<ĉϝ BG{;|)7F{Upߢtqqqqqqq߈D" Q{4v`$ɤ;qm[_C/( oje۹W%=!n;B **U*v>"w楺nhz+嶛!H-r=oP(fM3@i%$?kDf"he; fUE>hH[6ɲ4F4xAS39dI"`o';,/4;E; ٨}.pBJ E5jat~|GG(q2!l!^9q1Jh8E}I#K;M"Fu]#o6ȍ,,l0T3{jMU > o7Ks՜Z@8Wu" 0>P{2r]mtT<$#2D9 'R C$V-?<$,XG%4]gQwkl'=nU,9iA+ƖX^jڵk)|x]MjnWj!+MHES}^dNYn 8;v rР_XFEUi۴QYf,ٌT$E,j)s\$Z?6^IG> hD"4_4QA9}cQyƴ5HpgV a4FD{̎^>[%U>?ʋ# Ĕrm>{i?c}Oϲiߘ&(;rRWqeumRd]ǩk(lE:H rn5LQUZ~svS67%硢 hf״VCaٜjzM%-}m;[D[ vkC#Lb%A:N~**5, i 2*ʴ2c~?D"4$?Irl !ԇ4]7ny?^՜CCH Ӄ 5լe^+ "/vGme,fx,C/uq_*F{l*Ah4B¨g5>Q듅#K,~Fҍ|_… Ȳ\vaiY|s1/FϬ>k:Q|LÛ3ol$=!D:({"g) [^9^D;L[l^M+=8^fgQUf1lts#/,))1xo O\km5 >̲BzyX1l8%c)"u ='k)%h19 WGΏkz>O#7elX܌P79l;WQan^ έeͭMM9؞k~| #oUh:smCAZ}MIHGueN!Se4#Q}5E|sghmƑ#gmqZ*>"kA©γ^ϰ}p\<}orCUT kjg@c6Y t;鎅^Nq֑5mM[qc7Vy5yNESP93vPx:G[ETOr)~`qq\WrIVWKuoxVOc3IU`hIPT8n5RpB%J?I1V߃bGQcq;i{+c珘6˶OpKQ802>^VJQ9I ySOq2dLLGO=!fF{ y+aY-kKZnσ4$hLG4nbhkdvwOEr K{ICKf}+Wȼ[S"< lh1T'㚥/v|ӓ]QXfb֖[h!ZYܪR$tP,!Ku&OkXY>QZ k?fg~^XT /RU0Iȴ-c1ةxg!,5:~8>q:?dSÈBA(>z-+T]~7||}hV)j$w7Ts>y7>Gd&Wo0yqd.UI@Dw?$2MijWgsfql*W /`d]oUT5u/š;H G8uo (sJ|  j*>{\AH.00<\VBǙ[@WVRTp[HO/qkˊ!ʤ }v1doOSFRє(`Iw٢ wRhp%܌(=5#eQ4c\s#xkfgx'^[ѿ{e"oy#7Uf7Y'ˈ43Ao8lmTFBeR?9F3r ~ǎ"3V3&'f [Ϭs#^/mvQ|$)´2 щ};j^r9T VU1XvoW9/3@#o=Exֳ.1ET~<;Z6'6Fe_3K*ooߛ"W˾}\fo{o X$XҜGCtؕxʿ(IUD*8a8pNOOQz2q=?.᳇sϾGo8ly[PlҬj;xK5vMm@]ޟU 5F_z;$t~oumί!vyڄ5XLv{1u޿,9 5q7(-3 IDATYQ6L{9>wo,[tхӛ 8sX(=2 %kq㼯9sktNblqEa<9:|dc1C&N>Ru7FEV*+(hqOnxn K/[(r?s* ذUHR{QpKoٞk}{2("l1SEt~ ⮦P+cuoکUfqmqqm\\\\\\\^˿<Ӝ:uG}Çse~クQQS#"BAd4x2\Y?GSga'ʾA [=I&QGbl{'R,zI0TU{wKEPT'ò!$`]mQ jfkLT*d('`mW^d嫣/̑cD'd/8d:<*6E4/.WDdQT(P 47xq3@oX,<[(hF65jû{HSm[ef>qăfGY 9B=M[z&j`1rm:c^.ڿ1 )C&]/c ܷq.`rpQPb1\Q;ZIz%?]I9uB%ϓ)]]iV,gdI"SOB2̺}gj|[.+d uϊSz,mXdsy(~o)UE#4h^*Ud7o$!,oKf%[a]!w4;*?4G3r1yL`\˛wPx*޾>nP=o/=86nlD.zvb{Uo~u~ûU?QCKD@Q~y8Wczߝ,Idծ^Zo%y$YbRј~Cf-1X@`b?.{:pl"{c?]0O+T2$"h@$ 4քrk< rQ;T|(GjNYK7% Gte_f|u>OYQA8:i1)WlW!YH f;;~"lX[IR1CZ]I%cBN3[2vn/j-L}́6[nxPƝŭ*%G2ey7P G,>l]KPUYjfic֛k_eD[msNv܁0xcrɯaXԱ إࠗ0h>KfH&n$T;` W+3>QdWnqw3ٕ[);vIsQ-iY6SΡ(lHL-WKr@{*}m1ǶD(c$nMphgt]ciI_6r9=$hn##!)_,3Om'ahjl0\Ae9HeaPC"C~p{6/js" "2;KN8ڞ컦1227'/QN3BdhX`1߳YC2+s :ʈX׹2 :nX|~ZCF̍[74eH'E?e/[:ŢVEB}}|!lpJlTF@FE8nXc$aH:MႸq5]tTE5EǎqXANgK}6&yM%DȞi!ZZ^A/N}?U2ߗ^%du6Iyy8*Cڅn+DW2`{||>.uq_qm>#ǎ9žVil&\$L,-!$4ϊC-=`c:Ȳ̭B\mzI=[ȉUe6giS%Fd4-UYUFRvݠgw8ɁF[2@yav*Jwy!Z.P{"f3>?xh*irIȓ*ʯŢζ@Zy8* zQlXQU=O Jh2o~YW ~́6qL ]Am3M~ !']23?w<6k3,TD`Ϻp\\\\\\\/sQ__ǎ^… tvvrI~7#6z\Aę?#.*ghp@"re_ǧyg~xL5ne}}yGAr_zǑk_caQ0*>s/v 7+0"|t&Z(+*q1T{?/iXl @>Rru zn;ywM<'p,rOQ,9`+DiYVLZ`IF=Mrax'(}}W|uu%oۧy{bM綮 "xF<,e5:q4j{SfUF+){?xVm}⢮*7y @p$Y^2c$1DA^b)E%n$ae2}_`R@ϡ8+_{P?<+#T'1rrj{8By-lE~ԒQqQٮzzA)$Kh LwJEECbP(TbA4fG?g)n-&?aQ(;tyg{sKV'K :zj˅ 0P_ѯrKa^ MHظQ~YgIjs jJ[\Vy|^t]3[h[}&J79րL;  <łQc~o{q+( i8@[eBClI-_L\a2K<5 < UűICv>.Lk5(2E7S͟%2l]_քr6u"9MCzYH>\,)qnGp#g8t'Ik;A6YcQ͊)Zy~?AtYɩoǜR\ϏrH+(X>UxblT%e9F}lq5fֿ bpNJLr"_8Pg[úq,vqǹW͜[_m{ճ=Ƕ;>{ ֆAA:rӯ\##SSz/F? ?x/jKz5\էXI0!Զ$2Yǵiybߎ1]u>ЯMF2!Qڕo  ( 23N`6q?AdG`CEk:@$" ][+cf Õϓp22w``dI"r4@qQU>/go[CBQ0HShYT(.pHgp yX#Ě2(uN ɼunř^CCb1SU :R _[|+9vȕ6[<2Arpmp+oQ =C+v,>ng'#fbRI2?*1Z$|?}@4*+$wȨ@TɒªA=Bdɋ,,/8mAct~²`i&$X!HXRTEE4NUi3{⳼6  xHWӮ85ުͅn-E ®*=LZ&| Cbܹ:t0Lyr.[k̈́q$'Ǘ{_<ztvuٞ?_p_VQ@W!drD1PFYh vцpӾKD aA"xe _+̅B'ɴqH@x3^=T&(q˾9[_1%b[9s(z;RqxpC:Ao\5o~J䵿wٌv?#trqqqqqq{x -o\njj_*1KKK+VbiԾs[]](eѽ^8_)B-!+#4۷_: "һ,#|>ɍNV(?jgk3ZH/pe rzT$Evm%5FT(;wphikAKqM^:ywkȻ + Ork$lT*B4*U[ǹ?l3zʿE]*_SkBcoWNZX5 xPqdyy|ϳȭ2K Ӳi"SsYu,E/>OmԱ#ܘj(4G^,.QAZ}lj^y6f![LvRb>нFHOK~ dfΆlY"EE9EjT O 7ߌ~T$RIx M UYCP) /f5R]QP2rn[1{c7O 2ރQKʁFgV@б~报7,>O5 6?q*w"?Gk|>y0[Ht\NbO5 1cNbQjyoogC"j|TtVLqZqAq$2:+G|E^x$A S <ӧ xO5DЬbB־ox28f|yq-Ǎl,jkk^zY2ߏd_1>hl5c;m:WZG4l7nм3=l* r|l[ S@D~bj dt9 q4੧:EK֔7DA`@l}.48jYk$6~1(ahTl͊-uyw)`Y}:#Gt{$ Rξh 岲%|~,IAFWXZ?:>9h=o ¡A4D"X6Xcz6CI7㱠þIJjLmaadrT*JUU1z?'}@x.?޽kC~z}ޟO Y²} /~d9?TUs2B|3_0|ψ5^2"V?R}Y.Xņw}ݽ8DFf5Zz!R*"Y[̈́N`$֧\Il#W^ípf<7 j!v{Ht{AIZ$Ifc#'?yId;;YH&6Gzr\qJ1eɆDu~ՖHb*xRI{rJMsUM[;+̅JIy^'O;dI[g6dϐMjC6UEk~5uzHNPWhwCd2 {L;dIj΍ɲ]Aē~j!I30P$.( 8U>ib rvsLE}*Ga$kk{3,.'yxɹA~ބی5 4hLL1e8${b5s>qj?o} qʋ3@/StRTqJ&9SgS5s+vڢD.| EnHfϰ,ӡc$} M;̬Ν(ikGXrF80}6dްlz 9 02wsa7O$KBm[O,ǪxWZw+O :4Ȏx ݽ{X$&s\[d͵qټeNZ3q *Ωz8;A]OsX64?ao][EQ-3[b}? t~,oΛؼ....................................?b\A'W|ᇼ[|/O}; ɠ?q/bD!6!>Urߢ_w,RU_i$Y*)ݴ;DӖaTYgylgAۼRP5d3>m1d7fEجCfB4 h-wLvL#_6&xqGM^jKVa  E9u= %E6V‹sya/I7~*c 5G9IF >T6Qrvd=cY9wmMalpa">&p(abR!+HLɛ͏mYY_d"IϗöENtxi/O.룬OK ōIUT5l;F|T*ZhUUabb5 Ic,zz8;wOg7w7|TtVLq#G0Q<9W4wߚh 2"ß2 .tt.ĥTLT$N.+&eg 릮kQX)ǝs >)}~$W.wX[p[9vًnM]X(YtT@ Uxt&b_Ax,p;LQnj_mfV]8zt@@CR ^aMj&Vk6h2IɤOhoq}u人ANPi@lEyF 1l,(P [4Ę@\MAAih8PYF>O3˕'0u"XĪe2L>,Z em(L6>Hi_&Sz[Y9cIYfľ3]*VgAQU}"K٨+$3 љ5љXI+SÈ42dɋ$QOC+GO-/ĩ:7?n%ǎ8 +g~^ӧ$KH SQpOuu1/|G,,'hhEU)bmΏ{^?-N+y_20OM'Eu45sM{3L IDAT_d?`)\o:m/V }؀#茜Y8 %hiNs_u}VḰLu$1ރ/-WoK z&`rc_! {](Qm&¸m0$sB1| NL aVتi%2Z@l Fn,|#T峈bgίmq~Z$]p[eʂ<#͚͆( fL su," U*~n*_7H$6:n F3k;% ? <^/JGF3[p2~ٴyEc-8Z ~Ԣk7n|"fr\v4-+ЪIc~_S`\`qlA'{&cI&'}53"{YiڈS ny$  u_/#ʎ[FXDpqJL;Mݜgݘpc#uS)=v,8v쬽ƉxS:]Y<"!dk, rxf$+]]HM^(Ee6)5l\dD:iY"(m+OUUod;/X_1?g1h,գuЩلם" 1ǖ7=8C>&(/$.7

N_&ڒ5!SOq:=k [~.tHrЊy;SWp ~/lF 3/DrB4RNPɗoP*A%M뭡8tדgk>)=cuȢ >/&2 mmm9c1.ňD"Fxf~>.|w+5R?m$Up_UUnfoVlˏW455Gy9zB!M!z9'r|z"ß$VxC$hZ_СA/fE#ʦ:GQXO#^s>˗T h&8ֱi&fēOT&RUZ_7h ْb=݇GU͙ ɐI2pOA,w&h $Z퍶z߭kbZK^n]vkjKjע0H5p@&w$dLf23I,<9|yywU6Wfط:;TJxz[]+T_ ρo MlE;ݬƶʨ0E&xPkK'/UyMj? R+ʛϪ5iԤ-'uTĨ ?N.K Աx&ufSۆ$tzTPpH]%SdEG+WSCgCB0ҀIC8 mq5,Qum6Ќ7TKt ǧTE}򸴯q.okveF=ۨIӵẃ6<)p;rdLk{:}:}}]3==jxTWjs?i6o~D)Aw@W7]2PȢIE{j5]CN0~ˣF.?Y uJ uIm}w[OG'NbeTRlDZܮ=*,r&QrOI] # I.TWSŮrTTDm#( ) tE>i[^ >V(v7(Cvٓd-yM]w )jꞍ4m?m? Qߵ)xy-Yf -_TЬYŲ+#׋ǶNKT%T{*IG6 _ǪRգVc;VҖh>NOTxMgJ]R",Baף ¸|+UoohVp(/\iWAWܬ[tō7V2ڹZւA b Yt+,6].r"edRyy{MUB`z}uC]PQ[bYa84e:Irrym0 d6 ^WEn~Q{ec';]Ntvs̋?RFwGY} ǹƒx\=[W/NzV_#q;ȱxM:^d|_mݮ\a-)K No:ݵm7Ǯ: ޮqzR0_wSl.Us X`߯ 7+ GCW)M j~_XY+egK={4nv.TS[k4OT^cU˜gÐNE9R:JU/Ɗ  $=W*DJ]ӟxBRmRa>U䎼nc`l< j^813g7EumMӑWwХ ǪAfhסe+T*`s9O]d1NovȂtH֮^Ǐի'*5e~iZ^1)|GXWѪ9ha/ݯ^VrrYUnA/&:2KKU1U3#&]))se'jĵ-הF$ EG͛:D1x18!91Y߽QmL&#㔖q5T H)=5vrE1'5,L[(NU]Ek=n.(0LJT4DEG51}o$BEZ:q'EO?IZeeYʨGwˣzmszyf[hbkA]?7smF] á:cǨZZ5/2%ZQ+W))@Qٝ}-+],'V._߭1B"Ҋ]^O1cg͔oo۪։UX6d:9<|ǝ.e)> *i_2*'az<r|+7h>껺t4=Rce$坮,uGK.CY*?^YU;PUU[t. ͕iY1ǧ%¡q㊴jݥr4DEQ[3%E2kƽpc1Ĝ[YchUfVz@Ӌj{eZ^+I]M}c8 y>=]c-]IZpZu;׮ iˢw|T\YE?v&п`/T񫔶=1!kjo; m?txf~2g jQQa:zΉtG WV[e zמX LZ$%$_\"GZUY 22LmP:N>OY4"e[ H[ 8O|YZt6?YD*UFe.Gf $cǯK]r+YW)ƮpE&jw}TU"-Zx1Y51w2]"{e0q:cׯ mQsՔ~)V,V܉z nsUZE_aCԔ _e͡4zu_Y*G-'oR˩FP[C~-65&1:- @1487ؽib9Һu =RmjYZ(2ѡ-4Wf5řL)nuڦɓSc_[Oʿu۠[C'>AͥEtciu6[ .q]̦e5͚Y;Synj"YSg%n>/Wi} Ck1x9fRu{\n-YjAZzAT :^(eeQQ\ˣrˑwѢ"qv|<=sJKKu}ryԳ0G._\k УiןaN\ʐ?4vt6zoWK}MdE?;W9~ }b>p6Yr9j^,O|bmXrɻq/t(n׆#G\*J* @}pxFKuA=~h\zhd1J\.JQ׺4ƷTw轁]q8V ,}1e=HLt5rktrvDko*(a}hsY1Y vСxe3X۱}lnz8IRSS.t:%Iglv땯ޥ:Vt_'jO/O._Av%~YQ*[` &+{Fɉo)$cG"<^dnApDN -ǣy&&PqI+ǩwTM-j5-1A3?1QNWᬢTV*Pe*QEEe?TTL cN0 67\*ÐCuPxbya8-oEǨX5Nc7~g~^9CxM,mQնy ۢ|!<ѨWGg\+GW#=P AޣSXZQ*g18ϡ: c+#1Y4kUҩ+TeeJLݩ[wE֤5t`b#CP_+պy56l ]?{`!dMSeGJVTeGG__.#Gc=@m3M̙A~zyO7EL8!֑~joHU_ܩSC4MS^W'|R0rR{li%C~\"u-(n=B=t .KV5)Ǥ1^j۰Y~5FuTvWh۴trt0pȨ[Ĭ1Nu>tHGVPx)<-.Rqq"uuЁm2zsobp(/+Mi+*P={W|f4P Z+:kKRe:r?E${lsÌu;f7 9f ]^k7 ~vrbgkK)#lx 2RFmfY!9ζH]'Tk vkp8nX<}Vjk-/WJJd/)_,U eg}Bw0ק*'z7TAʘXxSB}l4va^ٞ<7]GR{@c®Kxu)GVOΛ`?'Cclx}rŲn͚TC*) q8Iw7f<+E*ύ|2٫`~j>kC(*rh֜~Dnw#:N9 pHNPFqZ+[2_U/6x Zv-Lvв1Jrd8:ۋHNv-U!?)k<1 IDATܧ [c.l? Cy}J#nJUg,П8W.g54HkWEEh N3{f7KI8rlPRRa|@!Փ uۧՓ c֎P*tlob:XSͦ rTæMJ(\WzK^WG_PL]c5u#zXҥKc;M5y_ uiWW+w/ryT.#'PP}r7d_<*;-PGD. áqJjUI~oY٠6C]5)slW4g<+r5%#Pu/ɪl]|IxM/')0egn98Zr!3Sx0 \ڕ?"=MWL-RymXG6 yT_ӢwMW*9:GUnwboZCKCc[0ԥpo껺 GbrF_27lj4-y*q X(a)EY%f(0(Pf'RަG٣;2b猥C+KV*kݞiݞ*Q0^8eYHթn1.|-fïVk̺GUZMt/XZ#1S{\.h;e8+!*|leYgwe˖EN\~z=+ / {ۿ/uk͗賫ёWJByi2~m}uL)a%ݮ [NLTi -zb~ڃ՚Xnnf=}Ūio7JLӾ/USr97Qۍת9lr.J]5Ҝ9+Z%#C٬}p>#-*$b7TdMV^N$`,:{QOǺQa9s}twK6I]'%%tQjvwt6-~CTS^L֖Dq3 s:>>ȸwVC]ӔgHNW~xm q+e=ڴu+NI IߧnvU}/`Vjrʩ)=Q}:}*#d_u^VIReqƴAϵaOhSkk4o`}s_m`5v:Wz|w]phnnSTS5ڛ7M;(\`Ye˔T]S-M'9vڴ]WrwƻmhHhT[Iy1 SOKW[w &Tξc0%+pޟ4IK.1pL:`~nf=vVX>MJ1ci;4yV~z{U4~>mҗ: 17-%e$ӣTThL\ ܼ9VlKdY7#oUT,Kb#}I`c\8x֦luS#iCXڪ^q[& 10T檶]O9Vګ9zsa-*{]ٳG;gy^m6޺p|Tj0O;9'S߫= plEPrrM@c[06g+ݑߨR%eYC7h\[grET[c&(J9k{r9ODwRF,3?_۫kqzgߧE}8]K.q \ow"?uZ8|X3f[4ϩ5=صhmWճ/Q O+}}SMF[s[r,p}o_x ]r%17jɩ}/^({v߭yțվS6l?^Ig:A?lGLI5ʓW=|XuRO̖k+OJ~5w߯4{XYݻzz t/u[ޭ뮸Bv]խ>}' xeصbSþߩ Ʀ^Svj`̴VfKYһKyƠiJhLj:.B}򗿬(o䗡^I5ϕC =s {1PU46ݯv]h!OOөciuEXhȐoO&S޸|"\3dL8Q+)>;Q9oOޮP۬]:)_vo~X/ tuiwoeMv:|~;O'DƄlY݋7￯G`bYݦ5RBj-q5VKȴZCު*M8|XKK |nz~5แ胝;5Gsʏ}'VӦwƿR%qCmWiizϕ:vI%)qdzUvZ^?R|vghC7/iQ9wմ/Tt;~Lm^moa7^S^Jg";U;ޯuo8H|Si:1y].!jn;_]KO<.{WS窒b9s$McƸ k[pLIq z ?dqƷo@ \>5)N״UW>aN'B̶TRFrc>Kؿym]94Ycz=1Q;}Z.9jj-ZMwur$% y E'X~mfd+eӳtYFzD&Z82ˬVi99jnh?śvءgFyGw߭\ܹ32s~a]{o}]w}XM#<gGU3iQ&''Gt>|X6-p}_$())i峲4nܸ0}z2ӧOב#GO _Yh%BB pa0v>L'O֒%K$IO>d۷oÇj\(;,Cֆ^oll׾5l6u];vYl%BB pYeF|ؾoʕ+^zI>O˖-Ӗ-[t ([O?6nܨ]vGfҍ7ި{Gl7p"W8Q g: :! :! :! : wVXL9N]z,4vp͛vZhĉJJJĉu5tm۶骫Ҹq㔒K.DׯWGGǠkՄ 4f͜9Sw}N|X_5w\[nQ]]]Q|X~m=裺[pB%$$ y-8|wu-hԩJNNԩSu뭷hw(\Dъ\*6rU#eYnhu=VBBt:UZZfj˖-JJJ:9쥗^RIIl6&Lŋ+55U+˲twg?YԺ=^BeggBΝ۷+333j~Z7pz{{d͘1Czw;vh̙#8UVVH,L7al]˃F:wء+RNҼy4|ݻW{Ujj^z%9ǑFs *F+rUf U,3X6JKKvzZpeo},RkvަM,aaWx2 JHH^|NVZel6kcǬT0  nɲle]!!'ٳSZg-0ZH;u5w\0 LӌzI{;3*>޲lVffzŇu}YO=[oY7tSkyHagg5i$0 k}ߵl65}t9 rUrUKU1Zb#Wh@ |DA'8,Yba?zofƌcB|ŲlVIII^{euwDsAn[aXoV{ַ,f]yQ뵷[a֖-[>y ð^x[n;Hgl6;:)FJcce,0{{{0,(>j]˃F:7nhl6kܹQ~/ ð~w7K U1b"WhFъ\;vLՒ믿>+BSNUwwn. Ç^ ]6m I3<޳>+sT}ӟ$8oG7߬O~q#>1LԿf[ְ!N1lVV$bوz_ֳl ˲.%WhAт\*F;rUU (Cmm$)33SӧOL~~~IJGa߾}'^{)?ϗeYޮwytttnĉC ,VSSM4I3fО={;};?/˲"!N1RSSUXX(IZ~L gYԩSꪫ4ydI(F^r"rU U1b#WŹ\q08p@R|:u, - |؎?f?KOOWjjjuN$ׇ/c{:x}Y\A%>1$ISLwG?xg}6*SW_|vѣ#'F1tQ6mN-\P_WC\ouk e?_5k <(Iя~#}_[o%ϧ[/Vmm>OWq7gUVVDǎgQ}}fϞ˗bG"F1:t~{!!WFт\*88P jܸqzp8vpjmm߮l=#g9@LeIL 7ܠ 6袋.Tqql٢dݻWorkqڱcϟ/zꩧ﫩I=N>nM{ U1\@s* :0vXIRGGGe%Iiii#&\8֭[_rںuf͚_[FuرczG1uOWԩSuWKmq|Fzgtuiܸqr\ꪫ?I)))/_~Y1a7p *&rU8b#WŹ\N1H>wÇf> {yeffj˖-Zpa2kii; 9}:4pzgp8qFEӟ$IzTTT믿^7s̘eYz$Y7oVCCfΜg̘.LRDnbHǡTffҘ1cމ*rU&8b#WŹ\N1JuTWWKF]8w}z衇-[p/X)))pjl;v.!דkl6)ĉJ񉑗'&IjhhLu)8 NKKK$555I"F1:8 \g*FrU U1ڑ\E \@A'&O%KH|ɨo߮Ç+))IW]uH7oeddh֭BBjY3>>]C=l6>Ϟ|Ԥޘn&I<^Ol-[Lm۶M/,ͦI)Fɓ%Ioڢ7MS5553fH"F1:8 &j=˲V6MdiiUq. WhGs*seY~6zccf鮻رcb+q>X~~)##C[lַ8ߖf_ ~)~?9sDw=(%%E۶mc=z;TKK TRR .8'F}YzWC߯4z뭡SիW+55UNWutt=ܣC)!!AC F:oM4Io֯_okԩ?=wb|C|bb4#WŹ\hg,:ۍoʕ+^zI>O˖-Ӗ-[tss=kF6M>s,?xmÆ 7)IZ|Ə ;w***~Z7pLe]kڿ&N۷u[o?>{v 4aԨ^)))zO~2b#׿n6,-YD ѣGeooۿۈQ|jkkuwfI}]544hʔ)2vut8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8Ct8CtwaZuɣ (.EШynfjh>ĠޕYb.ef)Kjݩv$f64(*&sl 00u5ss.g>|s4tB:l! N[HC'- ` iJuuuO'fOSVVSn2nժU׾?}Ɏ;3&<&cz꩜vi8p`z왡C_bV\ɸe˖*ѣG***2qļ }}+_IYYFW^0ܱ;zok>mA :Y5\wB0Ommm#<2z~?ܹs3mڴ\pMbΝcf93dȐ,Z(ӧOϜ9s` :I܂ r1dݺu1bD?,Y$?3w膇r~2u]~e…y͜936lXddʔ)6lXVX[ou:K.$˖-ˌ3N;m5VC :GZ׿F_ϛ7/B!gyf{ '$;3 ~)M/~ӧg„ '?Bhk>N [:JC'{)+nݺFs7xc?f!0f͚}$IV^ݦȌ3rgfѭg}2u,^8VʪU裏fYzurw4ik>@ [:J׳>ѣG__O}Six|_΅^P(tv[keիWvq? ;\2Zy睼934nlV^>;{:nܸqM9'?矟~җN;-ݺm&SR̩Tʩ]KRSy+)  v~r)y3gΜ| s~x|̃>[o5&LH۷aڵk}5k$;S5gCǶߟ/| @.3LGoc?Oʝwޙ]ve+u]+W'aTsjժr*@iyD^(e=EC']cᾏ%{[J}}ꩧre߶V({嗿F׼Ky饗ZR}}}z$lI>n 9u+՟4UrjժrW?;k*RSy=ZJ}*9(-e4I7x#˖-kv͓O>BO:̚5IڵksP(On6nMb r)m-0V=>&ILo4'yS(RYYh:3*l=ThJC']r!s=S,}.Z\X7eɒ%YdI9sdwlotbdlժU?~|&M#Gf=̺u>S(rQGk| ےZ:/u*# VYzu.\hP(&555 _'yoiӦ塇ʂ {e9S2iҤuQ^SO>쓩SGO?| _ɓ3`f=7ɔ)S2}0`@reeȐ![;ˆF"\rI.\{./λロgg>1clmUR= h#8"ׯkm5G9slvСC3s6]3g6vuL:u| ]]eeeZ~YhQ;hZ:'u*+ ե@lTYM&F{3 6<˛fά7<_~y;z+le}hi&+tn~FKtyos3Z b70 hk< 7u P^MfРA8p`-ZԞvQL>='Nݻ,SNm1X,?a>gvK=2hР| 7ܰѸz*vZ={fС+Wnz˖-KUUU***ңGTTTdĉy6ilKGeeeR_WRVVb^?~; 0 z3y]vok>mA ]$I}}}jjjZuu-N`{֭7t _}P(4YzuFGy$ˡw95557|3$nܹ;vl֯_<0C ɢE2}̙3' ,СC-X sL֭[#Fϒ%K0sC=:h˾Щ5~P(m=XMōkrE,~xv}<#:ujh&M?ߞ_yWr 74MUUU֯_3f'wܑ?>JƎ$nݺu3fL֭[K/4u*]k-kJӫgy&ݺu!>wuW^z'|pNIR__E [:̆@i hvXfϞ0w??6]w]_ٖ.]ڪu)/p@GMmmmZ;3Xzu>w*Tίrj֪r*,nXQ~R˫X&*@WPj9(-:o߾ =&}ɸqrWg mڵk}5k$;S[vm{kܸqZw+huh+;z[矟?O;.*fC~XnLZW[7 ZN-ZUN'yylSjyD^e멬L]]]ZӔhr*PZ4tСC}ܚ}Xanx|1I܊+$nӧOvu׼Y|yo?={܌in[\:y睗N8uK.mm޼y֭[{^g}6IrM7sw47|3k׮M޽|T/&} ԪfѢE)hQRUTj)֩SWW =ZNJN@6lX5k^kv͆>}47o-[E5`ΆGh|ԨQy衇hѢ\Çs@џоyM%4)///+ B{蚗^z)/RëӫW[.-G$fѢE) rۖ!}Y$@/]JRUTj)֩J{(K}5"lR˩@i) ivȧ?̟?5>` B>F'tRbf͚$fڵ{S(r'77{&qb1wyg BN9-3z6|B{KG}}};\qS4תU~f~I)SdYlY{97W_z;GNeee%KgMnr 7dĈU^^;vl=|~ {Ϟ=s]wc7s=Ї>%Kdɒ%۷o̙wܱS;lI=(IM;]ؚb#GbJ IDAT̴irGv-fĈꪫ#g??OE]fwꩧf}ԩS#駟Nyyy/d0`@qzh~dʔ)?~ 0 UUU2dȐgC#orWWU֮]+guV.ݻٸcVԪUVVu`7@WNԑlN@qY~}b?箻츑#GfΜ97t̜9s9sf﨣QGnk>mA [_]]]CC4R S|>+ tNYYY/_窪JYYYn֭3PM/&N|k{V( )+:Z(R(ʹ>S:nkv %{gGoԩNtiuuum2dHGoS:sWC.쒞={@կfժU x <8eee1cF]6RVVoM?/v)}L81?x#_%ٳnC\|y>嗿e~|0`@z#Fdڴi-_S^^=zd}e]w}=o:Z: 栃_{.tP?ʬX"I.dΜ9޽{/gits9'=zt.Fs=P>eڴiYre>STveq=۳$/p0ڮP(P(4;<GR]]O|9C?1_sn/ cX8sg-@P֡N|<PN;w9眓_|1Ν{._/fĉ <|[ʺu2f̘]6Ir 7d~8?|ɩ9眓~:\pA;;:W_}uV^N8!onݺ5߿=Y@B!rJr1dyᇛ̕W^B0~0ǏO̟?6@ O q4t(Q?OS(r'iW(JЎw^{N;b$J ۆ:ct >S(я~4&׎1حzҥ}n}@)(+k[@WVmC 1+,U3}L81wL:usuץ,eee9s7igϞ:th/fʕ[lYRQQ=z"'N /Y{***$'xbnM'pBٳg~?>?|:f۟}!2k֬L0!p@v}};>87v&1W^yeCmaR]]k5VzԪG [Go|;IPh׭_jR,7vܹ;vl֯_<0C ɢE2}̙3' ,СC-X sL֭[#Fϒ%K0sC=:h U{챹3gΜ\xᅭy睗v!fʇ?5*w}wOx9m7<>|x>g]w+< .-ܒ_8p`8 p@B~5{cVԪ]ZUua_o=K.͸q6+X,*eeeg?ɇdkkkSUUgƌy'rw:Ǐ+c6[n]ƌuK/3}殻\|^xa{']vY{F+Ŵ F#ӦMk~n->`VX~YlY.fc?O[nir|}ݛok>m0DUjQt=T*guVꪜq6lX6 [V{Mko裏g0^(ruץ_~Ypa|Fq3gLmmm )S42eJ +V[oݬtUB![o5C G?ь;6zjFv-/`???.k8ȑ#;3O?=oV\EEEΝ}fԩ'O?=G2hР|Kсw޹.쒩SX,*jk>M5 b˨UjQBWj6K4hP3[:h?kqyR(rg6ݻwN8$wlg$P(OOXlPOn?΂ R(2iҤ<yޗYf[nIyyyn&SO /$9裳dɒe]r?yzL0!nyխ[$Ɏ;dP(lښJ]sS[=[(qZUjU'ISSSӪSt.:z@iτ RVV֪O_fM{ ~seee~eō7|]޽{9眜s9\7v،;vk뮍 4(ӦM˴iڴOn͚5S(r'6/}kYjU뗑#GfӧOkk>(e/B~x1_~y.&3g̙377a„L0a7IU:ZU+)KRš$`/lu*@磡Юoe… zkcfڴi۷o}'SNͱ޻!fTUU[n93bښ+P@P@RJ3a„[:z;PR?_>n{\}~ÇGmvܸqW?K~r!'?I>ϧX,K_R{vԪ~Ԫt O窫ʾ긿k6$YfMdvh\s6}Zti֕jkkS[[Nva 2$\pA=r!7n\?dwl1+u]+W'axsɩ_RUTƨUT䯿 fѢE)SKN& v1o޼s=Oh_Lw}9#$?pdnX|1ɹWX$}뮻7˳~m4ٳgmܸqZw+huh+;zA~E5<)Kvm_nok>\r*@Wj9TkU9k(jU(%$཰s*uhB ltW^+B0ַoYlY-ZC9>yԨQGz(-ume-NtyNhqҥK[fWлw$ɫڪy뭷5o%xsȩ_)RUT?jUR̫VRSdS&F{a{Tk h/ܕW^+2g}vf̘dNʷ̚5+&Lh4v{) 9䓛͟??g_hX,;LP)w >|BAS˷+_{7I 6U1?Oo,ښ7 ZN-ZUNeVk*jU(EI^$3k{̩@Q{\pAz禛njϤIo栃G(*{G3yFs'ONuuu***2~mrPZʒ칉Cimҥ5kVy&s9;CɈ#$+Vl̼yr9P(dܸqmͷ5@RBgVVƺuař4iR Bdٲe)r67o^v}-Vyyy~dر9ss7gYpag֬YMz왻+sLo{ɇ>,Y$K,I߾}3gΜ[?>uhjƍ;/#G̠AfyꩧR,3bĈ̞=!fժU?~|&M#Gf=̺u>S(rQGkrcؖԪSBGS@c:z,\XPHMMMjjjnS՛S(mΩ}'SN#<~:ɓ'gzo~)Sd3`TUU.ː!CZyй1!/>y%k>SNIUUUwSQQK.$ .s=ŋwM3z|3ɘ1c6zͶcVԪбԪИN@qY~V9_/u#G̜9s6C̙3۲5K.$\rIcvuL:u| ۂZ:Z+ tu:ln(5kq]mm6 ۂNե6$囘 Th<˛!I6 [2uuu-: Z.555 `%)ļO= `'yy;$F{:@WTVVUkkkky7P4t(auuum@+ ]CuuuO'fOSVVS6X,E.vaȎ;Df͚5z꩜vi8p`z왡C_bV\ɸe˖*ѣG***2qļ mw,f͚ &}ysf֮]ˀҫW ><'OdL| ۂZ6WY=[86ZG5\wB0wQ^eE APftQ2qGwr/UWKCS$lE K+싉;&~+UIIixwFgo"oBf~crnfs̼_>#8ίرcS(ү_o߾yf7o^.\ݛd0aBlْc=6Æ KEEEn̟??=P$I'uQ=ztzG,X|0wCvvM7嗿eFK~K/嗿eV\n-?3pFqf_=:|pVX,\0/ү_&z E ;$ɋ-閤r *UQG'tFgV^'nwPG>w^~WΝGy$?R\\G׾$:&Mʖ-[r뭷Gwߝʜy饗2a„&q9S[[|+y'2w<W}\=]wuyWOfɒ%뮻lٲ[.Їf͚\~b{\q޽{,Y7o^֬Y|#ygr7ychOzUxzU+V<˖-˸qR(=:ӦMK}}}&fʦM2nܸ\x Bn'+W̲e͙3'1bDfΜhm̙1bD֭[338cs4߷oߔ>K.mV^^Z~'6o2{{Meeeǰ;eРA-^*AW:4Inݺ&k-JPȹd8~zd…Ɲs9M B>OItݻwOѣ͛dɒ$i2$'pBkvקūS*^zU6:gM4q`***rꩧ:nG9r_URRWz>~GW^MEEEƌ$"BQmەz#T=_W]WUSa[$IRUU@׮ۣW`w[k*9ut@v7K/~헅 On13H}}}Νd&<@ B?R(|^}}zӧ2矿ox%%%~{Ϟ=s=䤓Nʿۿ[nS::?;: @'DJKKwtNЉ̘1#sut*NЉ<ۿۼĉկ~5^{mk̙mp ԧ>>:OR^^bs)dիWFӧfq>h: 80={}.v֬YI&eo2x|S /C$7oΏ~|3q#%%%G?%K4w5פhWnRYYl=WWƺwt@͘1s6P(䪫Ͼ馛|'BѳZ2k֬\~)**ѣsgŊ)//… _"k`L0![lɱaÆ"7pCϟz(ÇoC夓NJmmm:ꨌ=:O=T?# ,ȃ>;n0?iBcMqqq~,^8<@Ln|>~PH>}z E K  /a5jT/4sL_5wuvc{\q޽{/^O<1I?)vZ|\|Ź{UWWgҤIٲeKn\xIL4)wyg&LGyQ\mmm>+_JfΜٰ6}ocw@)**seo6L0!zkN8L8I>|_mv@{ҫ@ҫ@cEzzh0y|_9眓#FF7n= $_fϞ{ゥl7k֬lڴ)ƍk87xc铕+Wfٲe̙1$9sfFu;p&d䬳ʤIR__chOzUXzUh@'Ml޼9K,I{Mև N8!Ir}5Z[hQ Bq9ӓ$ .l6siW(O|"M+(--M[ZPz {2*/*^NTWWgРA-^eee/^;:`ۮ STTkŘB۾lڴ)Ba_VV+VdժU 6nܘ{Ÿ;SQ\}Е}RZZN;-R`OWxDk 80mt zUmIեsgf̘B/w}3cƌcN/BHqqq{howkzȐ!۸qc?P(+6={l=K/o=B!Ǐo^(x,^^}}}~9z {:*<'-UaoW0NRN@1 `[ヘ |$$ o4^muH:I 4LEEE['֖-[ry_\tE;찔OΡ$y}-/ΤIҽ{{ 1;[`OW:zUv%1H@'؃m=Ȳ:-ITU9jʔ)O~d޽q?q&10SN_9묳ޥ:zUV~:W^y%I2`Τ޽{'IjjjgƍI=MƾV^ݪ}%%%)))@WnKuvʅ:-k{Snˁe˖ۡ3fo+_W9|=Qj*ԮګCWۢW-5 tN楗^Ww}yד}83̿˿1H$yRSS&{֭[hoz _]6GuT/vڌ5jqOϞ=w=mܧ7ꫯΌ3v9$yq;냒TS.t5rK暎NM]~ӯ_,]4G}F߾}sAeyſxG^N!A{hbZM 9t*ao@et"k׮f̘1I~:sntqիWjkkSQQѐ;UTTP(ci׻w~YfM***=;~"zjv]wݕ#GoO72eJN?^5$_җ2k֬7K.MiiNN]]]À޽{7ߕz#TU[rU!VSjt]^k*йȗKO|ڼyO~2_3o޼gz꩙?~Ν#FhweW^Y|y7ܯ%\^{-w\ƍ(nҤITVVf֦O <8~{ӧO77K.mudmѢE L81tP@G(IvXWWV]ׯmO ^{G'͛3jԨmϝw&^jU. $ɚ5kR__o9<@þENr)3fL:蠬X"ׯϑGnɳJJJrg„ 袋2{ :4+W?̝;I\Ϟ=s=䤓Nʿۿm>sg1*Ib {%j\`Ы@ҫ@c:@'//9S3f̘uY~;Ȓ%Kcƌɖ-[v*vر;vǕf;7|̙3g`Ov .ء~|ڃ^:^3 `'Onr9眓*}{$O?t}}ѹ3zN`fnmUVVѽd~ӟ6{֭[~9#ӫW3ÇϤIOl3n9S2`+#GSSS=9묳2pg}s+͛7G?Q>KIIIz葒|͒%K@cTuzU`罜$ΠAZ:8_hyg2dHGC~_}{_nlڴ)'tRN=;HYYY&qfʉ'K}{_N?)//ϱ 64 p :4ҭ[p 9Ϸ[?yN;|ͩʱ3<3tP/^%\l S`Ы;oK.UUU-^ׯ|u tNLn::vEeƍ2eJ^xw}Y`A{\uUy뭷rE/KCc=+"ݻwϒ%Kӟ4˚5k|$EEE?~|VXg޼y֭[nu]ThJ ^uEIZ\t"g}v/^i 6'L̜9@:={kիW7///OL<9'xb/gNQQQTVV6zYfeӦM7n\.†B!7xc铕+Wfٲe@[?{'|:+&MJ}}}Fkj*4OvI*3hРږ^u%I^UqN0 :3g&O{.uuuRzOl޼9K,I{M 2$'pBkhѢ f㊋s'I.\꼀,$ɺuД>5[$uuujZ~} K @'DGٳsGd}In]ݻw4)..ѣ$ӧO[oհV__:9Sr!$I*++iӦ$怖gժU 6nܘ{n=C#}$IIII=5ҧGQCs_pUY'?Bi}{9Ss뭷f)++KnݲjժTUU ._߰^Hp)..n5howkzȐ!tEIJ^r=K/o=B!Ǐo@+Iv%j\`ϤW`odt"~{GSF~8~-[￿a}oƌ߿ޛo$<̓ao$n{{:da{lْ;/z碋.jXSSyTh[zUV:mʙg}7w}w>g}C=UjsOYSzsgdÆ yGRVVְv)dȑ5jTnL81cƌI޽$555|ݍ7&I45o/nGM8U̘1cλ[r5ttmnԩrfٲe9쪮XS3v9욮XWW-5 t=XQQQiӦ)**JPnLP[oNG?Q^}~n5lذ}~e3fL$yRSS&q֭KIr衇6|vuQQwuWF>qLO?}WnA=_믿>ҥKsG7٣bMէQb]}7*ao@e>OP([nL֮]dާO$Ɇ $GqDzTTTd̘1Mb***R(r14ݻw?Y&詨HFq;jȑ@+))+/e֬Y۷o.]f쪮XStXWI @{5܊::`n̙3a[$rHgɛodʣ>$6lXd}ɩ̝;I}zoo߾Ytvn@cTקūS0*UNo9'O΂ ,{lgTTT*ݺu_C6ĕr)3fL:蠬X"ׯϑGnJJJrg„ 袋2{ :4+W?f?t<@S(rnhv_o~{5ӧ5%)zU{%eИN@;rGv~'?ISRR??^ziʚM:5Fʷ׿NMMM ɓ'gڴi)..nyǏaX"=XJJJr饗f0`@[e6lؐB$HEEElSzJnI)`oW tŨQ2{;vlƎq? .\j*4O]WƊ::@']ԽΠ,ׯo6ݩ?_`{ tN6j뛬 22m4h~ :@'R__뮻.??ܷe˖v 6EIJZ@π6kr#??JžnI!v~mݖcfٲe) Nh_~9Ǐ7 `ct"Æ ǎNw |&7pC.弜$ΠAZ=pTTTuRNЉ5*x`.SNӽ{roK.UUU @bt"袋R(קP(d˖-Х%)zuvs(֛3gNnktmڼysf9ӳg :4vZϟls)dիWFӧfzGsYgeٳg}syW$Iuuu UVVΫ 7ܐO}S9裳>(یkRTTͫ[nf {$/nް'<W;:.Na9餓z 0 ЇR\\ug?Yz왳:Q̬Yr嗧(G+V< ./~ׯɳ,X &d˖-9c3lذTTTnCeց6%IRWWM7ݔ|;) |>& Bl @WOC [chˤ` tܟ7.ksW[ni=Xt=/Ή'ذN˃>/8sOL4)[lɭޚ /0IR__I&;̄ #_QW'k\m5*_SZZc9&Uկ~RS@۱OЙUe?N@+//o\|ř>}z/G}t$k֬lڴ)'xbaOtsgʕYlYƍ*СJAIsFw*++iӦ 5eeeYbEVZpoƍyZ;@WW__7+aÆ'9Ӳ7ٯ~ÖUѧkmaoW+2 hSO݇}f{{=|1糿mWst ֩TK#hV` 2e:Yn]N:?A BfϞ$4iR<̬Z*+WO>x I2ϟ~Yre.:;;f͚ s9C>3g F_X` |;ɋ/'SH3SSF` @d*P˚]>OR__{{WWWlٲ%x`.}˖-KPmݖ{{ݻs%\.g9c{kҥ7tSc~Ypa.z뭙1cF6lؐ-[X,fʕcy0b6nܘ%KP($I6oޜJ/| YfMիW? /0K,ɬYrGd_W) ;wnn^ǒ42T9֪ 1qWg'?I{{{ZZZx|c˱kΕW^N8!\sM|tvvfYxq-[>`}YbE֯_~8b1_~y/_\';w̆ zl+ HGGG/RdڴiYlY6lؐ{,7n/C=4guVd*:F*FC'`̼;}3w̝;w5k֬Zj@-3gN=CɊ+uL @#NV4Uz7$R)Ғ, h:DW\.ʵFC'`iJR`L)Iy jztT*u---iooJC'`)&6$cP ԫ$I\NGH4t)6KܛRP]:CTLI:ƨjvNC'a `&VNJjFKKKG(HC'$I\NGGGk`4tjJR`L)Iy j`]0|cKSSSbŊ[n]͛Sfə9sf/_~ʹ瞛9ꨣrWgS1iӦ\s'fҤIrg TW1ɶ^5|Vhx ^{mR(:˻]6o}[sggΝYbEN9<}λ;''뮻2cƌ_e&L믿>'xbl2ZnW\/~t VVhݻwgѢEyӛޔ={#H&N;=7oi_?k^TʢEՕo9?swdӦM & .SQu 'hG\0 VVhe˖e͹su܊+$/λs뭷)ַiӦ9s%to/ 7!6lȽ;gcc}˱rg c 믿>]tQwu+;Ir?}zIo=^:By9쳓$wuא\Yؓ42 QٙŋX,wM/&I֖J7vo۵kW{}LY@uU`<$)Jimm{ j4y'z ow?$9蠃iӦ$O=0h)J)J=!Sa4b6" d[?[X3 2 QsW׿uoԜ)S$I:;;:f׮]I<׼=s \0q'O~rX`h/ST˨ZU IS^}voJIcT 3ת2>4b\`d*P4tFի3qp z/~$[r%wqGf̘$ٱcG:;;}nݚ$c#[A~9svvrg8GsUxRLI:ƨE#fj#Ue*@}h\ pTiBw1O>d|l;L<9wN{{{̙kN{{{ BN>mSL1͛7χyۓǼ9sU,=XU P1Sq*Sax:IR*:薖{ su(*%SZTϧ>$ɧ?tuueII&3LRʕ+{O>x I2ϟyYfM B9眑>UIrzVWW\.c۫\/M4tjβeR(rm{޾{\r%)Y`A=.]ɓ'gݺu[,Y$;vٳs駏ٹ$ITJkk뀯* {'W`dTv4%9'WhT]0~U*>Ϛ5+^{m̛7/sag9sM7W,sg…Ks뭷fƌٰaClْbon+ɫvttTxƍdɒ $͛ST/|!k֬z~I*+kUlgkkYƎ*FC'j<ӗ+2'pB<ӳx,[,}[`A>X"ׯ?b/<˗/ԩSGtԔWݛRΝ;aÆ B::: K=UYjժ{I5Iɜ9s5rf Z^TꝆNä0i0L]$R)niiI{{h@ q+IR.QZh4:04%)M0f1JtttTkUUꇵ*FC'`T555eYtiOǾUVe…sꩧ .#H&N~y׻ޕ$ߜuY~7=KR-Z|͹K$J%-W,\0?G_u> jP`xSJRZhd֪0PTj*//o~IrfѢET*cߊ+$/~('I^[oMSSSoeӦM=]wuysw? oƼ oȆ ri@j O1ɶ^5|Y@*ݽ IDATS?֪: 5kVd֭^y}I?לӧSOM|o) }knng$뮻F 'X@VIRAڪ\36ۯ~$IX޶iӦ) {mmmY~}6nؽm׮]y|<*J<#y71gμy׽ءf14 kU֪@mJtttTꅆN@7ߞB toǓ$tP;mڴc'z郞APW*Jb/3>,F` cZMI)%)A-: I'K/{ /${}('I8$Ν;{on_Q8vZOݻw?i>OG?Qdڵ?$Cbw֪tT*u---iooGUQLm1I:ƠjN@U\ve2uy睙8_K`\y=onniN;-w,]4=P*` /]Ir ({֪4"Oc+̗o̽ޛ>)S$I:;;vJx7o_=裃W,S,|T*T* 8nFO}*wӟ49#JԾV4%PJRZxV?2e:cp>Cڵks'3cƌ$Ɏ;ٙ^cncly_?Sy[2y .ԸO|'?90t_O}eԅ3gvm۶qC}%Sjxz_T`xI5IKkU#SZ0f>kf֬Y};2y޽;3gN1) 9䓻M2%sL6oޜ>mooOWP=]vY>=裃~PQ=s_O2%гx_T72֪2>\)֪GLC'`L,[,>8{oL4)gyfVZ+Wz0'<$?~}g?٬\2]tQ}YfM B9!̙3@X,zr$Ɂ;.x_T(kU P=WG*@-kv@[|y>ڵk0̲eR(rm{޾{\r%)Y`A=.]ɓ'gݺu[,Y$;vٳs駏  $ITJkk^mmmUFӟ4k֬IWWWJ%zk1B!W^ye&LнY ZZF5m͚5YbE B9\};C~YfkUW]yeΜ99ò~l߾=|n^S,sg…Ks뭷fƌٰaClْb+WՇr:::\ O<'?<;v~5Y@uYШ4tFE].hΝsYfeժUC>.(&6$cP Ō3r5 yP`<$)JimmptKK^0~Y@uYZWiԑ$I\NG&kU^P_JRZ V@C'tT*u---ioolgk1kU4t)]Ir4 N5)6)%)Q-_x"|NCF@M*&$`i<#OCF0O$O`iШ4tKO'IJRZ[[Ғ. ڲ}ǕJ15PD ` .u%Ir::<, Ԇ۷'0niPך_JRZޤc$ITJkk뀣[ZZ>E@@]+&hM+IR.1pDݾ/hkkk5T*r5/4IW:0JR/.KXv9IJygjhvUєnjn< gQ(KNOYmݾ}s#FiXKWFT_JR>Om 논P&gjh(&k"mJrTWmmkkKkk뀯ZԱ7PaD}fj3:38rul gwx6?mb -V 7ܐGy$/r9昼χ>LA0X24Ok1F&W۷?2}3OHRZ`_ guI)Jimm;}$%+А.]}s4iRΝ8 w_>wv~.֖۷8o~=$~T=$rYCPpV^}s2eJ~椓NJ<?|3UZ}v9{SSS&L̙3裏X[qyW^y%IrieҤIcz<կ ؑWcӒL<;O$}7|uʻ̛7q{{fjR8㆛#=nsRc*~>pJ;x#2VֿejwE{Zuk^{V#Qc=5ZujmjX>JgjUS~+SZPL$]v%I<}z'x=6n8}cᎭָx;vT}QFjhg{CF؍sR 9gʕYr>9S]xP˙wƸdj?cF0SeL;{dת#񞣝:>up5ϧjjUs-WZuxc>;rN]/?z38/h43f$Inݺ1[nMP;XgqF>go衇fԩt F3 #GȐԲBRT}(2qĜviinn]6_'S`U2Zаp y+裏΅^Kfĉ.L#W`dTjNTꝆNä0i0L:0"6mڔ믿>_|qNӦM_|ZLot* Lot_zv2O֯z/S=]; {*0.]Z) _JPL07MBP9sf3ڭZ2iҤJSSSmo{[}{_c JKKKeͣq ^_inn455UN8_9*BrT~ gD֯z/ӷz~rOַz/[\; F֯z/[\; SSJN8|׾<裹 #H&N;=7oi_?8ՏocyJR-Z|;#6mʅ^7YphfCk{wyٽ{w<#Yrey\}y睗^ziD~&dj2{WO¾L}'WW=ܗ[=\; Їjw1-Zh}V*]vY}O>de„ /,?0hP(T8^vU9蠃*MMMkDơZv7pCP(T?^r㎫455Un&;2~}'Waxdj}2SNUeL譩 ^y}I?ӧSOM|pիS(ܜ>;Ir]wt٤:nϼ} MRqadjMB㑩M/ I/Zd*@m شiS^|$I[[[cRTqƱ, ڵ+=>cTy?w2~&W&WL$WL_26h@U, M2%s̀棤n9Zdj}xdj}KBcK/ P4t*&M3<3J%+W'<$?XG?sϟyYfM B9)~Ƶ3_zyJ%7R(WW95djMB㑩M/ I/Zd*@m YlY Bns=wޝK.$r9 ,ȱ[*PҥK3y[.rKr%Kdǎ={vN?1;fݢE7)6m{[|y6mڔiӦ /C/Z*4ZjИjKT_RT]oƍYdI Bdygښ#8{իswꪫ$sag9~r!c{2X_;3 .~moˌ3aÆlٲ%b17Y7zv<@8㌼y[ޒ~g2eJ֭[SN9e}zw_oS,sYge:uꈜxPOn˖-?ugԩSsUOez'Wapdj}2=ӵ0xr~}Th0LM.i0L: Nä0i0L: Näٻ(g@Oy`TU05+]R׵CZJZ!M.?7*3s&ɲl-O$*i˩i@Au] s|(& :NDA'b@1Q (PLt(& :.*UܴclRSSe2]1׮]S֭c`d7GUTƍ]_Q %*--M&IQQQŚgzwUaL&Ca.bcco8)^өS4{lЪU+ >\SLիW] :┓ScXbE*tK6lfKcygt ƺ4 T⒵R 7sEW`'!!AYYYtu(jӦ:uE:R\_!!!:~ :uKw[/B&Ia(..N&I&Innn:yɓ'5zhZjjԨ"##u)uW\)ɤ>L/Znjժiƌ7yӦM h IDATQ )___ꫯl}M&ɤM8QTv[o9^bJ׮]STTL&^xk۶m>>uСb swu ӧխ[7W/N>O>:~<==զM+<<\{{1ח$!ooo۷OuֵGFF}9s/_n7… oUzBŚd+SvZ2 C_~V*I;v:wٳgk-ԺN߯tm޼Y{Zff+44TQFG Ӻu4tBGyyyС/^lk8pZj^{M0-^Xyyy6l<ӦMѣGm6y睶+88XcǎՑ#G:tHP``]n:Zz,"##mm={Tjj'|R=ΝoF~{?\hѢ>~veѣuy\RAAAڻw$I R@@^u=ݾ}Ν; 6h7O$+~8+$I{jUJJJ8|ۧCjܹsW5ԥKm޼aرc ]IӺudXn0 MSLddRttٳgۮrssSJJJ۷OC <==q̙3%-5jܹs:t߮8}wW55kW $IgϞuߴiSիW!.ժyʕ+˗/W_ Px$i޼y1c/_pY,۵Cjժ9srrr^vMϟ/ZQF eee9)<<\Tlllc _ݻw땐+222ߵV]իW/X0t=h;KVVܞjݺ$9ڵKa8hʮ-X@j߾TRy*~kĉ֭|}}UZ5կ__{VBBM\TrU7T.vRLLL&M tA:tph7 Cjw̆ &ŢN:YfJNNւ ~zܹSN JW׮]eKa ;S>nvYV?TΝb \R'**Jի6m_~EIIIJMMO?]`̏<,V^Çk-ڳf͒&OK.iݺuZ4iKjԨQjٲF͛…  +~k׮ڼy|Iu]rwwW׮]_|Qw֨Q{Q͚5u)رC?=Z"##k„ СC 2D˗/?={ٳzUFbb:t>#}E*Tucccohj޼<==_+..Nwqڵkg>P۶mզM"ŋk%QǏ+$$Danݺ V:utq}gڲe֮]D;+U@eA #W~CA'Pj._5jH:uRRR 0@3f(bѲe4rHIPիX@C\=ؕ_%K詧ܹsuI҉'秶moܹsjPƍս{wEFFj;3gKӟo***ax-]T?uY5ofmҤIQ|I*))IzGնm[͛7OVҹsTvmk̘1ѣGWOA:uJ׿l2֎;kiݺu5kVf;acŋ:tj֬^S:u;Sƍ#Vv(̺=C4i :hӐ:tH~G!___M>ݡءCw^-YP|k׮&O uQ/ o>M2E={=۾}?P/Mf7sPYP<o >nIƏ ?Ժuj*͙3G?]ٳgkٚ5kV2e͛޽{?&M(;;[z=4}tEGGԩSjԨA ?^)))jҤ∈жmtQUZeq5"## @|jVZz5|p?k??|$8q;|^u]\T=(LTl|-[aÆ_~ӼysEGGkRVVvء~);;[Zfݘ/ھw5jHoq7͙3GE5ru(pCcƌщ'Ըqc=sv8W*e*;wW*l9R 6oQqm]vջᆱ7O?!Cݽ8sY]VիWWjnڿ^z_~)D3gٳ7w]tICUzJ!aΜ9Zj<==;N:>g*LH*g*/\u&rU@aq((J?jݺuN{f֬YZhΜ9Zݺu$y{{}]NN$f͚ڵk5v[ (ԩGya͜9SJJJR.]ʙ Wy8S+Rʙ [y8W\P*ә (JLRRݵpB-\ѣG%I˗/ק~*Ys֩SG 4PffN>mkoڴ'Om۶cO:%Iz꒤1cν>9p+00Tw;Wa?ĉ>}- ogyFժUSbbzo>W9S]ྻy*ZrUT=5}wx:jkpKܹ8Set%0 ]vM۶m+OZZ tI-Z(55U>̓,IرcvjժIuƧ[q]^ۧӧա jdS`ߒ>W9S]ྻ3"媜{kp]^ӹZ\䪸ྗ>yLg*@ŕ%ŒG$͙3GEwեKd 8PVU rss0 4YdƎ+%&&oMp**rUrUTDteʩSoʕ+ג4zhp5h TzumٲE˗/i̘1駟twW^%ʊ7|SO>U 6YsV&Mӧ$)<<Ż\"W@E@eZڲ4|p3FAAAjܸ._Çᅲa ѢEƚf),,L=z-kϞ=:~fs@y~3FaHRSSeZd~IIIjذ=_^ׯwX9WˆL;uγg:u>hU(rU7t.sԩSg;vLիWU^=O<z 2D>>>T@@ƍ3g8{+)xjeaԸ/ ORk.d20͟?_{'|? UvvթS'eee;nÆ ҥM ,Pu"WL[x`tˊPFԿ8p@<ݵi&mݺUk׮Ujj>=zTO<ø EDDbhٲeꫯfh"!WP S*55U˖-S͚5 -IR޽mz뭷d2qF؍?.]^ziȑv0h"ժUK{ѧ~+rU$ZI&NyxO(J hĈӧO~mڴI4l0~~~֭$_ݵ$8///Joy\"ISzzS^.p :(f͟?}SRRt%IRppp}eZ~[[NN;Vqʉ\gԸ/3 ߤI$ժU}O8!I]kW~~~~UQ|fI9GIN|3(Q|-[aÆ_~7EI*YIQ$);;a܍7P$:Pqeggkȑjذx W4G)T?,\򓑑+{:8*bJ *PQ ԺuTNB$'''GTfMq7sL͚5XknҥK5{lWʠ@@ ^C IDATT|t%&))IZp.\hwѣ˗O?֬Y#IO?\yyy9{)IMھ>yڶm[q">>^7g6$ 5i)3(99)s(q޴ߑ#G ]T 1W%O\(Jav횶mV`4\mժW˗/+99Yݻww,0ԱcG[ZhT%''">w+= "ISzzcPf |U\<rU3:PqeeebzG%Is̑bQjj$J*eZ0gZZv%I8pݵ8.77W РAU8|G_(9<ʜS0 jͶ˗/kȑղeKq&LPյe-_֞1c觟~ҝwީ^z^,fI2z*rU X|ۃ#Ţ}*$$DCU-uVnZ/vg6'777=cڵ -[*>>^fY %-JLJJ,XHo^UTdRttMnٲE}UUzujڴiͽ}iȐ!񑧧4n89sYL!WhUQjҤ^] (ծ];ڽ{rss秨(M:U^^^>c8gyFԻwoI?~>OlΟ?/L&-[a\nZ&IQ'OVUfMըQCZRdd#wEI*YIQ$);;a܍7*ɓ'mm=zdҶmt 4Hחڶm?h2P-4}t]z$KV^<P:@_W0~7NuU=;w^ÇqZ222q~}O@"WEF -X@<5j0_״i4k,CVΝZ;w.ҥK5{lU+7rU(y9OUmt jڵk:vŋd2ٮL&㏵uV>|Xmڴ$?^۷oWbbF)Sh„ Z֭[ڵkۭꫯ*;;[?8իzf/IO?\Cҿw)ԴiS'OT۶m 5x~f`|Ad%G_͛7k֭zG;<ڦM >\K.Ֆ-[\?Л;rH*7rU@ɫyD lTP~ РA0 {:tve{HVVXhڵ裏/(&&F:urg2 CG.ZjիJNNV$''0 u-Z(55U>$,Iv*00X,ym ͛p0 Z+Mfb"W*J6ǿ% ?.ժiӦd2Zh$̙3vck֬իWKշo_?>u$I[.VTREVi׮]]8p`rssH~~~׬YSVU?sI*p*Utk:@˓aռym۶C۪Ul_9rD/^Dɚ:u6lؠX 4H_%I/_ȑ#e˖v&L j˖-Z|F%߫1c觟~RΝիWRn$IjҤSfQrrS;j8+rUHPS5($8qbƮ]VK.(**Jׯw秔=zTNۿƌ#0$IZZd}[$5lPM8Q}UՠAm߾]jݺ/^찖lV\\c魷ޒ٣Ǐl6+!!t6'HAtʊ\!W~#FCjذVڵksz饗0f2LܔRӐ!C#OOOhܸq:sLInʔoZ>z#)))z榄j޼`}j՛oQlٳGwݻu9t[۞={tqǏק~>}{'ooo=ڽ{֭z_|P'NPRR4vX8p@͚5+mLſRPkV<o]/^/R;Tn]՗_~={hŊڶm|||vA:tph7 Cjw 6(,,LE:uRf͔ hڹsO(kN:i݊+z9y=Z&IW\ѐ!CYfGwyGwu&O];1qDXBO3gt̙3JIIQnJeYeXnilHHBBB<.((Z3K:]9HJwB,@`C@>U5U+rU(SP ORڵϟ?kΝ4i~m Ќ3 VFF"""dXl29RdZիW+,,L_}U6aJJJ'NؽJR^tA?^u͛… 1bx2%0B8s \\\!O?TVڭ$aÆ9\Rhh㕘^z=`(TѣGk7yw ֤I(&&;qC֭[o8f̙9sC{llbcc 7b1A)U䑫P|PP LNNfΜ)0ԿVU{s=,ժUKAAAׯjԨ|ǎa w`^Zw~*+33)sedd8e>3%#(爀`*Hy9iAa&dwPE.-ɵVw]3r n6WӘr oa Ùѷ5ͨ87|<|^uuN\s`_׬]6˗/OSSSxȎ;rK/m3P(dժUYjU˳xWs9w-Gnzhb1Zٶm[J>IC'`ٸqcnVf̘/< h|̘1駟#G_zYjUfϞ}欳jy[WVV$ydOljUHRCEC'`7o^͛lٲ%~{.?ʕ+sI'̝9sf?>~{͛+2ԧ2}wٴi+);{O}}+U:gPR~I}4uA. O>92p 93sojjʳ>$0`@#ӿ[._[755 .3 fС{c=Nuuum%kw:A @w VC ɂ `=NIEOC'.T]]m۶uZ]*JP/_Yf-oyK^gAmK֭ˤI2t?cǎ… _6&I6lؐӧgذa3z|SO=a֭?:*o9gϞ7/ѳ7۶m[T-*Zr|[Jcccַ;o.TUUe۶mm⮸⊜viYfM7eʔ)yRSS;.۷oo}zkn˨Q7}UW]c9&>hwo){o7kΝ;w3gqF***r7:wu,*E V-u@7!tP'wuW.|[j|&}ͪUri%I?e|#[ZY__ٳg1_}?$IsssfϞo~1cFn5;̙;vdܹ+ӧOEK.ɜ9s2y~I:~I}4uA.=*ZrTQwqǵ $ JMMMf͚Vc555I;RNҥKSQQ|;ټys+";w̩rA6I BxY~}֮]ە[}$I.Vd\3dӦM-;z@OVQP4tJo߾I׼5-vޝիW'I:61#Fȉ'$wjlʕ) UVVfʔ)Ink6=c_ɐ!Ct,rVV\i̎;hѢ =iyyܹ3IR]]nluuus}Z~qP*++s''I.\_|e9-ʮ]2iҤr!I:~@9P@SP:Xvm/_</~;vOϥ^2KtAlwC=$~}Ĉ{k_Z8\YjUӧOw}ˬYrWY =ZJC @9 k6nܘoճ3f/πZ=IrHKV6lXjkkd-ksU g*/ (o|c6nܘڜx5jTgICCC*++mݺ5IZ&ȑ#[oٲ%ƍۣW릛nرc_qP:s͔)S^qަMfO?O?t?Vdg?˺u2a„ů3`[ԞV:S{$ISSSJ z*Le:%׋7O>d#Lk׮f„ mbjkkS(rǶ<0`@?<#mlmmm{Ǝ۩x_XtٲeKd/9Ll߾=IW˙ gjOU"IgV rKo>WZpR'nO?t~_'I8$I~rg9˗/o>w}wdԩƦNq ;R(rgvV`t!$I~o3/fÆ I;,Ib(jUb;q9Ԫ+ niӦ,_+3jԨUԪR'qƥ&Z*SWWwkaÆ >Myvڼ]<}{0`@.}ir}{_{챬\2MMM'>?vXwo)g}vjkk3{ 80?O09眓_?&g1ԪPzjUQR'!CdY`A'N'긪XCrtGgҥ:g1ԪoPPn*J@O@'iI:tRR'=KV6lXjkkd-z6 Xxq˗;:޽;k֬ի~lݺ5۷o3~̝;7&Mjdɒ,Y%- ٴiS8v7lؐ/}K;ϦX,~w/dС]?z$ISSSJ PJjUN |x8lذvZ@#G,ٻ+**2mڴ̟??'pB+Vdƌs'f̙m__}={vsO477g73f{IÓuA.@)UMc)uu[t4t)SN9ݱӧg͚5Ytinv/ɾZW\qEvܙN;l \}gڵ9S;>$iP2|N6lذvz=V(WKSΨONP. ?s***rbLP]kUUUI[+ z+WP(䬳j3VYY)S䦛nm,@5&IRWWW\Z*&yk O:FC'(C/NP>~Yx+ƔC=$)W?l߾=x`2ypmر#?p B}_uuuoڍJeَOEt*{,I~y_Odٲe) 6mZBUVeժU-Ϛs_j9Vߵ|1bD7͙3gN1cƤ&~zK7n̥^UVe۷o:묖{eee=$=\Wo N @ghSO=$:thr;wn~dС[ӷo_KfΜ&ߞy+̧>L>Mlw۴i+)ݜ O}}+ޡ֪TA OC'%x\tEwg}6I2pyW/tΛ7/7׽.k׮͘1c^UŋsW穧ʽޛO<1I2`9 ~;v$; YhQ/^q]w],YR4'תTA OC'lْN8!7!&LHlܸ17pC~ȑ#= /0W^ye5kcyk 4(|pmۖe˖7M֭[$Fz?7tSƎf,Z;wnL6mڴ kUu*@ϠVgOv7ߜs=7r7w{W\qE 5k֤C455gM 0rGImmmdkkk${lޝ$cǎT<ݯX,jXrUթ=Z_Eߚ5k2w6͜@>e͚5ݚ |% ڵk;uoΝ;S(R]]jlԩinn544;HPșgPԪt% ؽ{w>ߜݻww.\., ʚ5k^֭[o}+/B+Wp BfΜ>ӿ[._[755 .3?pΐ!C2y}y6mZƌy睹S,O|" .СC~޽;\sMVX7fΝy_>:{ndݺu˳~444dȑyޗ󩬬,.ԪPPN4t2tyy6f̘|oLlܸ1=P9,[,'|r2k֬̚5U <8555zoUUUVXѩ5}i5g>}fժU9ZN<9?|$r^ A @9(u@kjjz՟R t<;wn.\l1&&I;6 tTTT;N˵'Ԫ# ⋹kS(OO{{^:IrYg1bDN<$wݮK^A @[aÆ<9Cramݖ 4('|rN? ͛7gΝ) nwy睹V(jUʕNP~$IZkrzI{,IrAu~.)*媢 $6le]yfڵ9#s}~w$?|$9$=\7rV\-u@innN⋙1cF?elĉYfMw}wdԩݘ=H @ z+Vd[sΌ1"#Gl1bDɢEܜ/}K{[766ӟt} 80[,XBn!?яZڵ+~2mڴq{u/*HC'.=:=Px?mvޝ>ǧX,5yMbd/nݺL4)CM3v,\0 /aÆL>=Æ ѣG瓟dzꩮ'N'N:)ӦM\uU߿ogС-1UUUؘI&eĉ>?Ϥo߾YjUN;$ɟL<9?|$rK53{466矟$innٳo~33f=;};xU1'Nĉ)#( *;ԪR'tYjUI})[nisA6IOٳg97xc$yrA6I^fҥwl޼UW\;wSOm $B!W_}u<_>k׮mЃU:@/p%!)Jlݺݻz$Yg&fĈ9$w[\2BݸL2%Irmu(;jU:BC'{'K.͑G~O>>}-In=PX,<ۼysvܙ$n7:͹Zر#?V#Jӹث= RO}>+ȠAf͚TUU;#Lk׮f„ m֦P(cmy6`~yGR[[%$ijtM;v++~3wL2mڴi jUu*@ϠVe˖ϥ`|+_ɠAvڗگ_qYbE/_}IS:uj/g5kVq) 93;cv!ݯX,jXrUթ=Z_E… seeРAYf]2]`A BnG?jyk׮ijjʴirG?~u_z󦦦\pyg2~z]Az*]owܑ ~ꪫڝ7dȐ|_n*_~y>OgҤI0aB>yٶm[:\s5m)YlYf̘9sdҥ5jT֯_G}4b1˗/S4tS($mwިQZ]My裏οۿ嗿e2bĈwyY`A*++]kڴi3fLjjjrwOX'>,\0CMУU:j֬Y5kV'N'긪X|UN 4t褾Nػvڕ~:mFQz> hnn_ظ(:@/dɒ\|9ꨣя~4C )uJeEC'odĉYvm BtO IDAT(;N~O>dM@7[wacӀ2d>Ç .Y OC'>媫ʅ^ۯ@hL455Ĺ/ z>:{ַ5ѣӷo_AOV54uA.N r)-̙Bj9B!{;5IÓuA.nO&I3|.Yqذa풵7 zn)ЭҨ)uu@)T:͚5k>e͹ꪫ~0sL뗊Լd̒%KRQQ>}d/aÆL>=Æ ѣG瓟dz"8g[՗;׭[I&eС߿Ǝ a/f ]KJyHrH'?#cjY% {# Qe25 I4_ؚf盤#:1Vq&!N"7d:!%1U"ưʽkf~s{8$فO u@=B_O<1'x Bƌ9=ܹ3'tRT*oΒ%K Gu] 4~:7xcݽu/W\r)?~|VXV\\;w;E]$\~9sp;v .{ޜtIYti}\y>|x-[O?=IgΜ9y'r*P?4Z'4+_J>Oeʔ)ij꿿,^8۷oil z3fLVZ{rzhѢ$I&eƌIz:Oҧh tVwww~ꫯ΂ /|!{omm۶WU5ի-oG?~b/^k׮]۷'QSP=TZ'7B!˖-˲ezuwwg̘1馛ry^y=iҤ^cN81IW?acW\qE֭[Kf̘1:y!5j*C>G @#ju9֖իWgӦMٴiSV\9sd˖- rv֭[{^QF%Il@Y|y㎜s93g>׿USVO5G գOQ u7o>… o|#]vY>Od+͚5kX,X,s6\.\.s]_-[䢋.o|FMjSK=Jdf'LP---)JU@}3 tzٸqc~eƌIѣGoٶm[!\{s~{ZQ.\^z)C=OUC;;;FMԾѧ.ܧ&*I3IՕFMꙁNs衇fܸqȆ zy=_|ż'I&Oߟwgԩ\緳΂ rYgsݚ5k`t >}~ԩS+:W,=X{B!SOuͺunݺ^=،92;vHT̙38T*P(Thg]kooq.@oԾѧ.ܧ&*oWLaل$]65gMNzᇳ} gs={ٙGy$B!*]M6eΝ|3I?svܙ{.I2bĈy{֭O?dW5%9Ԏ>N @#Pw֯_}{y7xoҥۿ ̛7/ƍK/4#G?o}[=ǻr%d9siu`hѢ u]s|ǎ袋Օsfʔ)5̒QLŸg T=}*Z'4իWK.IPH;ɓjժ׿NX7PiӦo_ٳgg̙7n\VXwq8[}*/}*N@۲eKVZ۱B7HL81-ʪU_*Wova3gN>O瓟^?oܹ9֖+VgMX>\s59b`ykEo.\?>7pCytvvfҤI?~-ZG K `ffΜ;wyرcVgN6-K,(4+wu;5kVf͚5@Ч@}ҧ05:@' PNښ*UCNXGGGk4j J4$&BSb1 `h3(N dC1%B.0U:(P( tM I v:T@' u@hmmMGGGUbMNh|k׮7ߜ /0'pBF񉴴OOrQGqj]>utt*?]]]>^wm_z Bϱޛŋ+HSSSN9唌?>+VH[[[|\2cǎ?{nvܙN:){R*r7gɒ%~:O(MIhF" ?|{˚5k2o޼}+裏yrꩧE.=+˹ sq{7k׮y睗_~9{n\&>ul kϟ۟0f[[[Ϲ~z:(wyg:<YvmLŋ}~袋z zZ*=XN;J/ AH Jr\83;of}$9眳&Mʌ3$=n-]4B󚛛sYg%I|j @ҧёtuurvl߾=ɮ@ޛtwwg=Ƕmۖ_W<x'TQaxc?|CIsssk&N$yz^O4;ѧULÒtU!ISc[nM>$$FJlٲewO/ tZ'F$m۶%I>={ܷ{֬Yӧub1bq?W.S.ЦO tɓ$7oNgggX~&ɑG_>n͛7O뮽\wu9o=_}>jЫ@3 ;{lF;vT*e̙{)J) >}zϱѣGcs=R냲R)Iv;ݺ3u}+Tf9묳n͚5} ]TA @'1"gyf,Y{ge׭[~:Irgg}k{r^gggy |cNZуbh`P5TA fѢE) 뮻;rE+s͔)Sv;K/ȑ#[V񮮮\r%ټysN>viv-^IL0*?5&h\pο^[[[d=&A;3MMMyv^Fe aӦMK_؛oG}4Ir9qΤI2cƌ$C=4Y0U t!엿eX,[vmoߞ$immtwwg$C^@'^~|;IPܹs{?IC9$ͽ;q@5U t!hΝ?^{-'pB>u$I2jԨ$ɖ-[7Q *Z'  '?Ç?YObb?g@or>;Ui<$ &T%bKKKJRUb`WꙁN0,\0'=X>=zts1mۖ$9;yiݵ^뮻n?w믯uW1Ltuuƹ@Ы@'B|رc|p {SN|+Ir &T%bKKKJRUb`U, +^dC1&$iB.0Uzf -ʿCc=1"gyf,Y{gd׭[~:Irgkw;$]]]io+ zUMS5\~9C|w|@--JP]wݕ=wؑ.(]]];wnLҟ~hJrD? ^F4 GI[[[ B9|ͽ;}ӦMˍ7ޘ/ꫯNsss ueӦM) IRRɓ'l,\0|n<3̤I2,ZHM`Hҫ@4*V(2cƌs17lذ=/^8W\qEr)dYbEfʕ;v@5wg͚YfU1#P*@'M>i>+2Çϲer'I^̙3'O $FJlٲ/wJr\846*{cP.\۟sꩧSOg~8^zi~ h^k֬ӺbbЛrܧ<}N~utti0cO t믿>?p~ӟ=GqDF$y۶mK|Ӻk6]w~o__4PMI*Pa0UZ>5/J"*4>Aiԩ=7lؐ#8"'ONl޼9inn'Iqw{S,V:T`uY\f͚>@l0ư$]UVj5ԎnHdא &T%bKKKJRUb@ӫ@3 ^}՞ףGN{9rdvؑR3gq^TJPNZbh0jիVOmʮA0Ltuuѫ%8N\B6 $A{M|9c$#Fșg%K{!u駟N}0 1zb  81( ,:::  tOӼ⋙={v s;OO) Ypn/Z(|cg$ٱcG.tuueܹ2eʀ_W$r9&L8ZKKKJRqQUaUcP؄$#H8 IDATb߿t=@'. 9s衇f?~|6oޜ͋/Bs=7_җv;oڴisgٙ9sfƍ+V#w\nr?<ն3IՕv{@s TƍҁvÒtU)N@]>.,R)~:?~|>O /g .n!O-˙0aBT >+ٹsg qwIy?yE9P t֭[${]3jԨ$ɖ-[$'T{niDj*io7pE]zUÒtKMPPZ~T!ꙁN JVrj$$IJ V]"aFJc\ tѣ${]m۶$b9ɡ}8)~ {!{W9 Xo=ܰ8I}^_ƪ殈UK/eܸʮ7w]ߩ#FTԩSf͚kye޼y{}ƍ}m?|dǎhGMM{sJ^U]kը;>wmwVm׽Zuڱީ_ukj5cw{=ޫjJ=oԤ>k@D]׫w{cU^^cUWOcu7nXJ곦V;^ tɓ$ׯקP(۷O}r{ή%a(ĪǜBz̩twwg\~IX/ŋ+I^x̘1j઩I}~cNo^}Fݩw.ջWVWV;V5몚ڻCV3V}~罪f,ꭦ&Y 뵦&jonZW^WRXլ9{kjcU@'L6-Iꫯfݺu9#XS*$ӧOW?O%IFU_ijjϰaòv~cڵsNȈ#Ԕ~g0PWkC]XSSPSZ*CZQSkCM`_:v饗榛nʈ#2k֬5*O>d,[,˗/ρx/W\r)?~|VX{gsWfYlYN?$믿9s'_߮i0}G?|K_ꏔ6mZO/˹;7վoߩGjm<5DM 56PWJPWZj*TR[[[ BJdر[ݝo9[ns$?~ IrA;LSSSx]2T7W|Sʔ)ST_}zֆZSSJPSkCM uD] uu੩/eK/T*%I9=ޟ1cF&N7x#>>=z7iҤ̘1#ICUVBP7wꑺZjck^6wꑺZgזX tիW'<^״]6۷ouww)^}Kwww~ꫯ΂ /|!{omVy~-zֆڱשGjjm~-zֆ:e4N`贵eٴiS6mڔ+WfΜ9ٲeK.{u'OvwꕺZjck^^6vwꕺZg׎x t`Й7o^ꪜp 3fLƌ~ye]wN*f C73 %;|0T@'`=ztskmۖ$9<^tueذaٸqc~U]dSKjm~?:ֆdSKjm^u@'`Șg%^}ߗC=4ƍKlذxb'ZRWkC]m {ש%56`'ZRWkC]d N:CƴiӒ$j֭[R$>}>{9rnP()^}ߗkI[ũ^wjI] u1^PS^wjI] uu듽08 GqDN:$=ܳ+Wsf7bĈyo~C?.眝YY Y[*e솛E֙v4imluCA)˳TVZJAkn fEF䟦bد>z3{^>݈SRayIkp_˾}RUU.dHjccHfjcdHjcޜuI 8}ݩ:6l}|ΝRUUnCw߲eK~=Ϧ3UUoÏ;~2W\?g_a{cWjc:e`Зqyޞ*y饗{L0!w!ر#oVF~ƍ?+'O_Çgڵo~3 .kZGڵ+7tSf͚qeȑٿ|lڴ)UUeĉy'҆ 2k֬TU$ywRu-Z+WvNKb G4;s10WLm 310W9a=31TpyGr%_g;wn}|ҥ0`@-[cMO>d.䒌1"ΨQr饗fΜ9ٵkW7o΀r-ryeժUyo m۶-Ǐ̙3̞=;or}嬳ʼyr饗&If̘ۺukVZ/8cǎ>$If͚㚪>ˤI2sTU'YpafΜ#Fd…y.ⴷWUo>@O>ɔ)Sq,[,ӦM;o=۷o1utt䮻SO=_ׇgWWWNggg?8K.u]O< ׵bŊY&^{my۷8}zY~}.^_L4zo7x#gs}kY`A%ٺuk^|u%KdȐ!:uj?Oٻwo&OܫuOUU8qaoiiIKKK5eʔu˗|f$M駟NUU[{}͌3Ru/^UVe͙6mZN8k֬IcƌI]>k;38#GիHNeȐ!3fL93nܸ\2۷o?'|2UU7n̗y晽zW_[.FUW]{'=\׿G|@t&{tIG|݌3r,Y؎;r|yw۷oϰaz}_΂ ֖u5\O?=/h_W3;w<4Cf޽G|~ؒ%K駟u}D|xv_+_J_y/;w̗ 6^@# :@:uj΢EuŋW^[on!'xbs駟fk7O>9[n56mرc (NĦOs=7>h/_~s>?q|ikk3<|0UUG?aqW꫽Zom۶իW磏>ʷ^ϖ-[2q^Ќ6z4hP|ύ7ޘ f9ӳwެ_>rYge=̏X"mmm袋I&OtttC^zu~eĉ0aBF}eÆ Y|yxWbŊTUnW4#A'hr#GkK駟#<iii9眓{'3g<7tS~1c>NӳhѢٳ''t!WUu^{m8_~9]]]SuFy;vl{}dٲe9s4ы>gsfܹ}u.?~}LcA 6,X nz̟wW%/1$Ph@ :t($PH @!A'BN :#IDATt($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BN :t($PH @!A'BߕB.IENDB`PyNN-0.10.0/doc/images/examples/cell_type_demonstration_nest_20170505-150320.png000066400000000000000000005540051415343567000264650ustar00rootroot00000000000000PNG  IHDRdsBIT|d pHYs&? IDATxwXT6gozl@DŎ(j5j [bDXnؒXF+vE4Xb1qfhs_WYWDDDDDDdpd}@DDDDDD@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"""""2PL B""""""ń@1!$"ؼy3ׯ[[[Ȳ';" www YaAӺuk?L Y1k֬wޖ,hӦMDU0 ؈3&W, wxTH? 99GFpp0:tP ێ, ,I$IR  2,ݻ}R Yqȑ<[e(K_uLD`$IBpp07cDFF"&&˖-wTظq#4ih8xݤIƢtCll,,--y; 8cBH}1hV¤IPre=EFgg߶IDErmnnUH  ؈3VbիW111Zٿ?:u'''J*/ зo_eʔA 0adff{Lhh(ׯKKK-[Æ Çr 4*Tʗ/ƍ˾_~M4ѷo_?ܾ}#G,--:uO?ųg4߼y3|}} ԨQsAzzƲQQQҥ *Vsss8;;;Om&Xf 7n X[[qXf Wn-$$BBeX-,99gFڵagg[[[TR}3g3g΄;$IBHHZude[ X~!\]]annGGGk}+H?Gʕann,X@g+V@Z`aa *`̘1HJJҺlRR.\mۢbŊ033C2eеkW?~\:6>QB y&zRJ>>>ػw/=g_F=>ަjZ;w|rԭ[jr[>'O0rHjBHHH?3NJJ &LJ*^^^صk 33sAժUaaa*U`ʕZc-[Ak٠Amyje˖A(Unݺ9}۷N:aڵI$IȀ?ЩS'cΝ:u*4r7˗/ Fڵ 'N@zz:Ԗ_VZspppݻw{n۷;ڷo<}M6 zYQlYgϞSN[.nܸݻCyr wEVp]h;vDJJ ~wt? P3vXDGG?ć~ ϟfffիҰ}vu|h׮={nݺ!==7oFϞ=~\'ODǎaff۷cر(S zꥶ˒%K0i$888`Ȑ!ѬY3i9/W]-[vS;sΡ~6l~/_֚ssХDEN$IBe鑑H;x$I4o\$%% $'M4IȲ,4s I8wڼ &IզW^]Ȳ,6oެ6}۶mB$uvvvҥKj'dY۷oWM[|eY,_\#T+տׯ_/$I={iiijΜ9SȲ,-[G YŅ 4iB$ѰaC[Æ ~6C ,\… B$ѣGܹ#$ICպlZZ$QV- !eYtYmޣGppp_VM믿$Ijժj ooo!IpssؿEԨQCc2dԘ߮];egݪuCCC5&V!Zj%ĶmԦ'&&z KKK#}Ȳ,"##5{2d$ITPAkyOT~6#GYYY/_Ƣf͚>& ʲӵkW%$IJ7VݺuKӱܹsG GGGq]mC7W1Y,ˢqZ?Ojyreabb"Ell7$3gj۟BI?ӹ`Иc YE||tIx:$>@!JuJs[nϝ;'$IڵKȲ,V^$~0"S$amm-^xNV,"%%Eަ,;o֘.dY֘+LMMվ7|#dY֚ܺuKi$y*򜔔u1Nګ!˲Xti˾KB-!?}u[mڴѣGq4o\mڪ2UX૯ѣ>>>Qں/_%Khl[333ƪ;v@ƍѻwo˗z<ڜ9s,UVZj###UQի͛7Ν;ڵ+7o 櫊˗`DEE!!!^R͓$I͛1]w<^my:=zK.#6ʸ*T14o\k֭[{[eD̜9ScGAVEFNoZ-cmo*xorqq;wP~}y˗ׯUKy=ʡ*VQm1WmlllХKWz-ZI&кN^ګ͉'EWUu.VVVc?rָemŐz9m} <<\k2!!?3/wNtSnF7; ˶m0|ϪaѳgO,Zeʔѹnn˵meCX~t隯~l^gQ{bmڴAXX2331x`7:^~ P%{n<{ GŌ3#ԈCWo}fWA^C\\_u+Vѣso^Bxx8كŋ#883f@ƍ9NerL6}t!&&.\Zj:Ӈb.]my|S egGoQ9>g9E, y=\_133Ì3pܽ{ТE lڴ)d ʇtm5ޖ]ttӹ[_DDD4c޽{fϞy>SQB*j׮#F޽{6mgϞ嫪 6m`,]BUJBDFFjg>|Xɦ*a!2;wXYYf͚tR~ZnE᫯Bzz:ۗz^^^Z022/1tP>|jߡ:_[o޼RJEt}y(ݼy5jJ<Ǡ,Ze?tPM6<Ů|1Oכ|Ku^n߾s~.˗G߾}~TRѹݹ\^eY.2zƍ\ˑ7,ENN}^ƕ3?\}XhMv„ B`ĈZJMMU!m~߯mܸgϞUDOnժUCtt4~WDGGZj9aӧ]ͪ'NDZZjmt%'}6o _~%^|KL:$ -w۷5?}iiijq:88@$ӧxu?|8 !̙+|:~ƯAAAz8/_<ڵk۷ocŊjvڕyՠAh-.^VűqB`j2>>gWEQ>J^011.ɓ^_5/xall SSW9s ~)0{lڛoW[[[ڵKMW0vNNN߿?N:oFv-ܹs!Dc%###3ƌ^36Eq'*jlCHŖ >,]Ƿ~ @`{/N: ɈCdd$Zh|{8x Zh777X[[ҥKطo1rHJ;1GsU[>44۷G޽ѵkWT^W\]`gg6p~lܸ=7o>y&`nnǫ:t(N>UV>?*UO8rj*رcq}榵}]vaY&uIsNܹs}vs9|GhԨ<==Ǐc׮]x5LZ M4ATT U]vEZ0~x߿_ԩS8z(z6u~5k cƌQ L߳gO8Jd„ OUpѣE@@ʕ+:f8;wD@@vǚm۶ñl24iwΟ?K.رcprrH[l(4nX5PwXX:t耭[9(E%Rre̛7'Oz011u 6߇j׮:ubŊHJJbܸqj۶m[,\ÇG=`cc{{{|g UVO?v /VX7n ((7nDQlY?ũSyf喯/dYԩSqiӦS3>}:Ο?aaahӦ ʗ/G8z([U͝|9/t)>Q+~LI +++acc6^{-ʗ//D2e}n[իWF +++aee%6l6l% IDAT "r=Ľ{ĴiDͅ077+V~ؿ7otҪ^aϞ=[ ѡC%BBB*ݵƖrJQF ann.ʗ//ƌ#tnOY欭ѣx}Ȳ,ڴigwMѫW/ Efľ}t.y-JbܹaÆFXZZ wwwѹsgvZP 2rHQlYann.j׮-֮]s-Ky)}y-ٕ֭[eBlٲE4h@XXX2eʈAl뫶c|={h۶P077...WlݺUg,YDuɲ1C^99~ٳY|yѱcG믿j,;|QJaff&*W,N*^|͹s9##C\R{{{ann.*W,IJew<|eY̚5Kt,ޟsWvKgmBM6 ???(D D-ļyĽ{4"ϱ0DEM"z̜9f¡CvNDDD/-- U?"*8lCHDDDDzuUk ^ܽ{=~7HT ÒQ !۷xbয়~BaB""""""7DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD a!1bdYF@@JNNԩS1m4OM4AHH6m-Zٳg={61bĈ" B0sLXZZb.1  ::%:&B ===W˚]e˖a֭011vkkkԬY͚5C۶m̛7]vEtt4J*@DDDDD l9r9.'IbccQjU7͛7Gnݲ]'33~~~ҥKU۶m5kb…;wnBJJ dY$IZeFFF9ndff"++K.&m۶رc#F_5jժZ*زe ڷoZӸz*>\SYMҒCUP&LBŲByBrUt5ÄUP*TИ>n8-[]tQM_>ڵkP$&&}b XYYaܸqE: &E@$n޽-–-[~e˖5k<<<)I!_jj.>Rv222`jjtVӡPnjYB 55Ua8pppg P,--5^ !QZaXdXYY; n{ pʮx%/DePPn> 'Nmp ;v͛7˖-Cjj*N @Q3)) +WT_|?gϞ73BÆ k,_\={ē'O~>}G۶mlqÆ 033_5k@<QQQ[ЧOn޼]va޽سg"##1o<=~8Zl ccc4\zi 6DFFN8q~1!,`o_y氲3ƍDDD!R򝄅F׻wINNƲe˰pB 0nnnhҤ }~WFӦM 5֮] OOOxzzfx`h֬0qD`+++lܸ+WDPP-[M6i >ft ժUիakkuw\\\\\γDPP[xBc~tt4N:m۶ |,X{{{/<<<0ozB>>>8p 8s?<@ٲeզ)4 !...OAE8E($Iٳ6^gϞjԨ%K޽{Xp!nܸ={aDDDTX^LM ~选ImM6J4$mbcc6mڼӾMMMQV-kV{ . 33UVUkҥKݴiSL4 gԩSM 6Dll}|:6 }ϟoFm *UJmWTo6sAjr *Vzzz]]]aiiZY닌@jjjl BҡCԯ_?e+*U nʕ1rHDDDϯ(B%""BdlH c*ϝ("ˊho&hNnRdӪ*Y[[_hƍ~vJ.mR###̙3Cg} "##5:QLeYc1/כLeA$dee\\rx4忕Յ>}Ld'Ӌ/ mVTaQ!$śE/NّNNNB !!A5̙3233C)SF܂ p5DFFb߾} Ulzyy׳gOԬY3gTkW~}&@֭[ڵ/P.\ׯ5^훘^zv$"""*(fff2e lܸn‰'O?T+"88X$( Aaǎsơ7HFn:x{{c;v,ܹ+WbΝz*Fϟ#00Pq1xo{ܹ駟󃷷7up᯿_ӧOPT=u6n܈7n 88/^3CZп9s7 __wJSSSغu+-[I&-www _BDŽYZZbСXjv܉)SQu&!!$iOQMDDD/3fI5jO>x1[l+WPn],\s)XBBB0h L<իWG}SNRJHKKN:F6m`jۼy0o<ԫW4cǎ066FDD{E6mk{E˖-jժ_~{c2e 7nd1u {n888UVh߾=T-[If'ܹ ?Gpp0 ͛1rw>Ih(~Ie333=-[bԨQXj-Ɖ'4)h۶mupp(H('LQlY 4@u-.wkժccc:u ={TMٳgs5RRRtNeM`<``@`d}GETS'{wq\\\pܹlFeKA\2kVgϴVeUj111?~j-i&L>]U7l؀|yگ W{eEݻ#W"|!*n6oΞC!fff&*X|կb˖-B\\RSS///GvRX5k///4lvvvQrejśZl#G">>/?ڵkW7gsg)rl[24Ws;vH>}_|Utx).^iӦa̘1/0~"M ={ <<pvvƨQ0c *bӈL2'N Fo%*Ncǀqؼ ͕+̙}7noM;!;7Gv 'OFaooscǎaҥS"hK^z\HV :'xk8&M`,`.90q"g GG믋&FR2 Яڵk*fD%k7!{*`g行ݻ7[F)$+V}CDT<ݹxъ`GǼm# NBC!CUEsHσgiid}xXTDkW`T5oۘ5 f.YZZ;VB6m t ÇG6mABÆׯmOЧpPܒ$]/S$:N\YSSŏ#ZDPY h׮0{l;4"Ƌ'-qb&dX^,<<~=A2,U@*!T?7$+  C8p ֭[777t۷ogAb$#XRqx8qX(_`_"C ժ)R6n,yPI /@͚Ν@Z}"ʛf۷o>8::bȐ!(_PODow~M@Ld(P 奨n矊(Y3`4`<௿6HcccH!YT…N vAYLxOCYfڵ?  ݾW/`PEnx&Ò1}{W '$*>ƌ=II$D%֔-[}pssÏ?~ڵk8x sss}IdPb׫h+xb,5Q^RU`$$2$_+ڍW $&*z iS"ʕ+Ttaaa̟]"+K3ԩ@;*%cI77EڵQ &LP${M;""ILk 8NNNȠ8x; '*Gt<0~<FӍDN/9y@w%ĉ5d$%%Qx4H6j RJ3π-Ÿl/K&9YQCANG?%2!T}6>CXYYppp=Shh(dY322£GT˽|+W?\\\`kkc͚5KD9T YQ=Z@EM`zu )Iу)by@EMPB5j(sٳN$*UF !~'-[R1#JٳgUmoݺc&M-ߏѣGĉX~}GM>T@Æ]8غhZ[^ Zt]ܹsAb8uP~}˕+/S5mĈ6lBBB0}tEDz +`6o###}GETY~Ri"*Ziiw)KK}GEDDWmԨN::::%JݻwjlD9BQL@jO?- ;"oa$_WT2x&<0+z΍Tt(6{vLy]~CvZ|'>jժF:S^BuHNN)wߡJ*9(]taIӵkjq۶Z;"h#{"z5őazXћÊ^t{fUMN?~7obСi$AIY1YZZbС-bbbwOFupwwGF0j" `"E+oB*hYY5iFn-oB Mĉ@ΓUxON͛7J2BjY߽\^ЫW/o-[Ĝ9sj*pݻ>k& !qEUQ"Cs 0|`D+}"C<_XK}W¸8޽;WU1ȑ#q9IUj&M ""B6.\kbΜ9s:) &5~3EO2-R<x{O9PPYff6{U6mܹsV^!!!Z9+Vĵk״ ԩS1zh|y `ees^PP]z<5Z5Eg*;a'.NTwpP 㷽bKoӁCzQ<(YfϞ3g; W.]`„ pj׮ѩL@@;mlٲ4h;mC֭[prrҘkפ5 IDAT.1={Ċ+WbSz[r}௿/з/^IKS `\E{A^.м~ ,\h;>mbHtH%1m4222}AD| `֬Yթ̿C޽{ǫM?r֭[cӦM_ !ћU{"P#"*zggϖܷDU``8-R͒Ȉ͚sO͚56l;;;`\Zuлw" ,㣏>¶mԶSNԮ]ç˗_+\y@E'#C6pɒ,ʋ,`rSmfVp/`}={T8;;cԨQ1cZ۷oŋ? bBHIWWE[lF?!zo]8,,D!5?'*n5F4d3#%w-[zx=z4k,ӧxn߾˗klժ233u͘1HSz:0}:оb,{ TڬLM~}E EQ&D7W̙31tPtjqQlڴ Xn"%ү+WBmmӧ77}GTxثr.0p P#*<<HǏoohHDyWFFFbGZ`kk Ԯ]*T#QR%\x{%*i7mxr }mHcw2H˟*Vq}Nٖ(JB@1D@@_DGG#../_Dҥ///@E. ;z[r20v, к#*|<miiW_7k}t) Rҥѭ[7}AT,<0cGů ѩSu*:P*UJXy8g|FT¥+ cp?d OVb`v)Smۘ {EJdrV[>} 3@ŊH?؉a{HLTX#3`0//}G<oJ;tDDn2HM[QCMɰ<@1doJtE-[f;" ̙&ndVf_``}G_\( qq@ހhrH`x=RT}\щ Uy`Á(V-}GDD%UK'Ne/^ػkasXHNT$wB)·R+0g؈oJHE 9ˡR"|G96ﶟvyvu}vgy߯w6&>vaHV2xFym'[M#7E HDqm2tGK%HJ~ :fφ l'.GÔ)ТD"EOs.s"z#\D9nO=66oo(gy9/kך(gy y2f̘E9r䲟w)J(abɩjԀ;4U fJ*ŝˬkR¿o9pϟOAr=^H~NXXgϞTWW_U׸){}1oovW6W_A6 \ӭ|b.̙C۶miذ!_~% 4`o4ojFQz c&Òd?o >3{ ֨a;%' ߩSa|xAۉD|HJt44mj;V. رcy)X ƍ|tܙ%KڎG\\Px ԩW]ahtM(^v";qt=~ܜeN${fKa6ۉD|9 ~gyC||<W_})SXzy)T4k֌K;w\6nȨQ|RrMfMB|(Ï?=@Rzy;j#7,Ñ#CS 뮻?ҥKcN4xx]ȗv"[4Qz #(Xv";ssPf|їueҥTR-[W_}ҥK z5ܲeKZlxӦMiРueСi >$sYyRR`@N/{m'=ƍ!C`9BC>\FH&} <ĉ &pYG@@ׯ'W^z{aΝz뭗|v{,[,_~7xI&xY/\s$${)A zpKXVDΥRRo_5K}III1cfbW肰hѢiwY*U":::C]2eʰ{s7Rn]Ç8z(禛n6AAA}n 80C#/653Eڵf59~Z0̦Mps׈6 7"nA=֠0.渂*T(Wz m۶K :\Wdg9pl%qmjdd!AӼBK#0_p+xHq\AxuqaJ(A"E.9zx<$''<߱cǸ=xbl+СC9vXvAXXzIpقPr &3N#bW_Q@׮(7 /@xq~ƍyƎUS=J*b i.VV-UF͚5)\0[l!**e˦kSV>px^j\ex3"P4"v̚ݺ{;gA۶ftl]TX;ǚ5f.<@˗/O2e.%z7Fʗ/6}B;˗2et <>YǙ3fjaN${IIKf |yۉD|/%7+WBʶ^j'ѣ޳g;rtAVȧ]ŢME͛DO$*tY?㏰q:;%'+}׭oH.Ȃ{^nd6mwm+޽)rZ$U.9|41oe?9ɓfJ\Pv"V.N2M7N$Yw}G)\0 ;vhѢ̟?0sLk5jDկxLdd$k֬aݺuԨQG$Nƍti5 DUΞ-LQh;%%Av{Y+uu^jC/Pc]Fws=Ǟ={ȗ/_7fqqq\9yǩQɜ9s rN |y4@`Υ;93]5K̹tdd3]z>EQŠ f{ ¯Ν;_xҥ_-$2^/> "00f͚CQJ:uDPPAAATZ+W .<0TG\s'χ FrܷόQW_PݥsG_͛ӧO_ݻ)^DHvW b˖-3ڵkuִu{̴bŊ1uT^/Æ 믿\\< IF\:|4YPkgBAJI1]uw/Yy^xu~| ty-aӦM=̴nFĎ2u?jִFĎ-[̶[QQE;C9 o N#YMS3&652=yv =B_l2n&N}$wڶ |Ҭ^iD숉GaCiDW߷9"F'O6khE#)))ܴ2  $'ٷ;q~3={ 7:}ڼnv;~,5 UFr:GO6lcǎK.iu֍ڵk3l0^~e+<+W.ERуcҶm[^~eN<ɓyX~=5kajZ쥳t)t4bwSOwN#6<{M[ɓmty̜jvqG'OQF=ޠAzYn/٨Q#W~瓓$ihт *0{lgi'>}:?աCФ  ZN-nuℹ VlPf`i\DSF6mʂ .z|…4iBHIIs9s%J{x苈vջ74on;@ǎӈؑ}A>ӈ-n:b^.Q4U(Gq :+WrsC6nȺux?~|ڱݺuY.˃>H\\yaÆ3/X̐/_>^׿ʼn'Xbtԕ;g͂i7JN6D+Tal_4q~ &tVu GӦML{H"L6-ga`` ڵ^z*T-[0fj׮֭[)]tڱg'駟N{bŊ]^᭷/ճ'cM{ [a~I IDATZ_ŽO7 d6mlqtAo߾l}}3tl?7jٲ%-[L{iӦ4hЀu2tP~ (wIZ _eĈ4k֌kRhѬr!CoaZ%+ɰ`lN#<7|t:iDXz07KFHWzjիy<vɭzk׮ͽޛndׯOz7n\!!!y睌=Çg8kbbeK"7X&L0wǚr[WUo_3-H{I*۷"~jM΃{P4|~*RJ:^IɿL2޽;߫Wfǎ;6q7|3~;֭T֠>7`ˉ.ͨڷZ(\v;1K uN#9Ft}hvAlp=WL۶mKoxHNND2\9\1n׿l#.μwfqԎ>jFE(%ڶ5:nXXsWJ!9vE-^-[=v뭷z3gNcnʮ]\E͛#@Nӈؑ_$'F^/t i;=GFx +,VV-UF͚5)\0[l!**e[8իW硇bƌ:u p!&L@PP%4tPZo1x0 YY2-?&ey) Eh\:՜ڶZ 5k;?GQti{=ʗ/O Zn͢EXt) ,YΝ;n('|o9sXd ynݺ <[nsɓ//W̛ۛg΅͛Agrrybon+f;T<ض̖ZʔFr Ggᩧb۶m;wSN1l0/^Lf:6o޼sN_o;~ ;'M7N#b޽Ъx睶ӈof1P48z !C}:CazAXXxrL-[eƋ>3Md浝F 7Cxi"S4"vi"s Hn肰wޤBBBu%o޼у]ڎ'u+tnZdFn*> ~&29|yٲeMh; :j*yJ,GWQf7@|ӈ~& kL3Ľbcͬh3cD]~,Zڵkێ"Wma 5ϐk&'NuRǫy\'gΘ&2Z8lZl!!!)RB QfMΝ{qׯN:QdIBCCw,SKOkf47VvA.&[:dDEA2ӈ9z c=f;%EEEѡC4hg׮] }N#bGR? lvq+G "K.ycKѢEYjAAA-[N:l2ׯ>KGlqV׬}AxYi$pyƠ JVry0h;ÆN"n)9ѤIHIIaРAl2y晴bm۶$/ 0t$"&7a$"̙.~Zlub '''onEaTTEQL ,Hb{m$jԨٶmgKXZd0>K/blIM~,>ʖFAUV:uݻdڳg}tЁyѸqc Bӎ;|0%K^%KСC5S}W {řVF~^f@P4"vViSiDpL:GyҦM*V]wƍ֭5:6o޼z9r$=zy?~qѷo_8sLϻP|Ҟw:53"n+p]|qLW^l'g83Bp$"GWT@8uM4ɒ.W&ݻȟ??[NZmڴ̙3iSAS;wE_ٳigTbbe?3gɓ1cE\ S3ۭL⌼|n+_RzuKeVJбS?K*ELL /Q^'Nz9|EuaJ*AWX4`+ShѴ_BI]ΝᣏϚd|Y/5y앓σ3ezP-HֈQqtAؼys/_νK׮]y駙6mW_m۶5jCll,ʕK{<66C\2?ohѢEql߾VZepn-~Z;g~酇wˋfo6"nBNР\v'rHyPi$FXXsWpĈioժe˖er-GRjU,X2eJmСԮ]uҩS'lB/! '22]ra^}U8-'ݒ̔}Jilvz͹P"+v>h:Mb<ʔΝ G&Mbԩi&Mr]wѵkWl0dDj8p,DĞ ` 3UT#W"~k?Hf8 LLLf͚=^F ,$rm`k͏g۬ʗv;^YHiDP4"5< &M)SSOYHϛqÆ7N#bGJ oP4""h=d;=z &8F_п_rߟlڴжm[[saàxqsGXĭ&OSS"nGسr9~ASEřWn۶-ݿkyk~y67;xMغU$gx|D_L!|YG+b1xe= _ظqf9 \bd *N#b :irիN#b+@ӦP$"aaP2n-U0'[l!!!)RB QfMΝ3g8q" 6TR*Tի3ydRRR,&1c o^v{A[}*سa̘&i֔8F/tYz-VX#G.*nj%WTT:tA >vӎٻw/ݺu~k*T%KK/i&d]&7סCУVWQq'wށ…mY3U7T)iD /-ZO=~tBhh(=n`ǎ~iuؑh¨Ce&'{CJӈi> o;=={ 4DĞsN"r]~g,^ڵkێfҤI0h bŊQXo޼9ܹ3'¹sfdD$U3>0?HftSɉ|ql o ۷k^CXti ,h;F:˗/RJ,Z2eP`A+Fxx8 :|0`{RӦ?}@= ݺB`4"vAǎ[p"hfM  eN#5]3^z~Qٳо}{:tyhܸ1C WD|M*T=ė7PZv{u'zl'o_v\/FF4kg&Y&gϞB  ~kz}3tl޼y2rHz9Dzy?~qѷoKN!x駟Xx1~~k3as VWΝسy3̚v6Md6op&e]iӆX Fppp7Yz520$xعs'z+'!!ِM6,Ym۶QN^cѼ : f:kbbe?A3k7[MQ|i$[P4"v$&7 %m5j;M|֮tm+YٰaUi>cJб%|,U111{Dx^N8qFGGӻwo^z%sUY/70`{ #+ dK7on;=ƙ:N"b{YSn;IֈQqtAXR%Μ9mL۶m395j &&Xʕ+xll,ŋ;~…tؑ-Z0a„єT~WfȉdWwaT[M/΃_~!C`ZMgȎ uGiTV _~|.11u}Ĉk\Ǐst6j ˴izDEEQhQjԨիiӦ > fͺpُL={L;H" ;ÀpMӈ/K/Aʶӈy׳'4m uf뻙a7=BبQ#BBB=zx<$''C3||?D${9z !5kx駩U{]r2g'̢i'f|b<8k^OW<ذL;6k^Oė<'aw$͚yhذ!g֭;wSN1l0Q32;Vf P4"v$%"ÇCӈdNVn{fM;8 2d'Ofԩ:ծ][ZL,}=kղDĞCY3ID8m'_5kJkwEK.\0'Oy6n?V,qݻa6ID9| 2 砸Ӧ9䱝F7+n &&׮]K ,$rdԐ!P4"vxе+rEĭ^l9T$" u@G;vHhh(ӧOp!6l@= /Ǜ2̷v{χ3R.VWÒ%k$"Ӧ?Ea޽III!$$֭K޼yѣ]v/G;z7N#rb|4I{]yhf ]D|jל}7fm&_~ٓ;(Ph9^>pカ3t(Tk;ȵ.fn Ty;1R$"7+5:}^{ {OdĝYo>CM>=8υdv;RtF2n={SOr8U#q/Gє-[jժ.F2"HfBIDY.lqᅦHF͑/Ͼ}h׮O?4E+;vLd$l3o$/_̞IIХ F2d믿Χ~J2ex'YdI1\l!!!)RB QfMΝ{O:E%cْ)<6U#qѣJ5w:tU#M2Tf{WEcz@cnID'V qC`(X.-9Y䥗^dɒ1&MpAΝKƍ˗/RJ,Z2eP`A+Fxx%sqFF-yΝ3{hHwQRR 4"E|I2r?{w[e<~U| IDAT38#G'OM7D XjVq՜Jك?۷W^u]̟?!CСCӎ={,={{)S{fyqLh<_Z$G37G:t<"6d}%orD{řf?O%&FA`4"vxf̀P4"9#/}uV^Mz8Ν;[ɟ?? n:1mڴaɒ%l۶:u/o0i$911-re?@; nZm'gDzhv{N_DR%''r.um+ÑTT)g?w*U=_D ^/'N <<ou~> ѣGٿ?7tSGBԸt 80C#&֭sDD_jW|<:LHFb +m۶5jCll,ʕK{<66C8x 111TP!{<^|E<'NPB5!M)Bҳ\$"CfP$"w N" _~|.11o;KPAZjŜ9s6mYEѢE;СC9vXݱcaaaՋ?S'A@@@ZA`'`y<*v-Yӧ>]<ع_\<7߄M|G-kMak֬!!! >GRjU,X2eJZVV>px^6mzU_?1z# OkVD5@Er^L?W5jnTɷyDBa6Xp!>`ƌvm̞=F3rSL1x^F>~:vDĞU[}=̟vN"s DFF{HNN{4 ^ϛo)^))У   N#bO^\(QvKa.2r$_p٨"1i׬$"|$$@vس|9|5""0L{oDRN0s&EDDDDr] BRA("""""R*ɲe H"*T5k2w܋KLLdذa~ϟn&MpС }t\Nrr2ϊd~^$"(]d(:t@ hڴ)ڵҥKӽ{㒒xٸq#;v䮻ĉlڴpJHH ((xl HFgE2C?/QmLO.] %22FFFf֭[G5|PDDDDDД,6i$RRR4h<2~xqjԨArr2gΜeTq9YlTTEQL ,Hbٹ?#J*tԉ Z*+W khh۳goߞ^zq]w1| Brr2CM;̴bŊ1uT^/Æ 믿r6T^:6o޼z9r$=zy?~qѷo_K;oTRԫWoQF1slDDDDDD Wzjիy<vɭJIHHuiӦ K,a۶mԩSPvbL2ԩSg(c4Ԅ燿^Ort\~^$"Prr2)))|.V"d?WPR%3tlɒ%(U111{Dx^N8vpqn߾=C_7$*^x AD?/QYϋd F:* 88mfsjԨALL +W.X<OZVJ5:/((ӧO1!J#^<YUV̙3iӦ訨(-`hܸ1-bzܹ/fQ`fDDDDDI@@xz!VXAZ* ,`L2>;wrR`Au孷"%%[MC*ABB>nw5ؾ};zbÆ ¨QX"""""&*EDDDDD\vCK q)"""""".PDDDDDĥT BRA("""""R*EDDDDD\JK q)"""""".PDDDDDĥT BRA("""""R*EDDDDD\JK q)"""""".PDDDDDĥT BRA("""""R*EDDDDD\JK q)"""""".PDDDDDĥT BRA("""""R*EDDDDD\JK q)"""""".PDDDDDĥT BRA("""""R*EDDDDD\JK q)"""""".0lݺMRX1R &LHwO?DF(X ŊwqQU?þ+ jdnኙ[VYfhjnU.OYOecԴ23 w4MQSD\ Ev880z 2Es _RDDDDDT8~m۶Ŵis˖kѹsgbHMM|Ǐpr_ /Ve,55Æ C~nݺB3g233:u~a+Wȑ#+*d"""""d}׸~:̙Ȁ(6mذ}ƍcڵ/U^,ضmツ4m^^^/dgg\ׯ]v6߾}{VtDDDDDT ,cgϞhSO=6l+KbĈ$@@@ 99FB&""""ʇ4dff7ŋ @vv6-[Yf!33jnnnL8;;W\DDDDDT ,c{.^x}ߏf͚e |s7& ppp`{33ԫW_~%;ԫW1f5uŮ]wy...۷/,X3"""""EQȰQ*h rrr8@w|{( è4F#|}}qM] VQ///èt|}}4$-- jQiq"""""J3:gފ[888`* Lr5RUtv[cBDDDjJOO,5s{VDDDDDD B"""""J!Q%ł4ŋpppѣG"""믫ft7nT; *DDDD:/g9k.888voA裏={QZt5|Gxw-dž9O>A :wwŤITJBHDDDD,PTFFݺu- {!%%%[n7`9s&bccѪU+زeKAe!UJJ ^{5ԪU hٲ%~'opssC hѢ|ߠA̛7 |||P^=,_<5D۶m#66DOjBhh(o_̟?r};vfΜ6m`ٲe!C 55]f gsGUΝ[GGGK.@z yVpgiڵkѭ[7xxx{)hxMbҥ+O`͚5E!IGQW;{%r2\.r',Jy̨)>}`SN>#СC2d^x?~3gDDD`1[h~a_x ={x.]~Çcƌ0aBcLIIAHHpalݺׯ_իWNJ+0}t>|iii رccY'>>֭͛uV_W{M|pIrwwWEQeΝ`PmuttT>û~֭['''%>>_|Qݻwc'NT-_2lذ|+}(g)5jP-.]888(GkgVXBBb0gZ=ZiҤ/*Zׇ1c$%%Yϊr5888(/_w|O?(tQ9r(w)FwϞ=`0(}kV|W(ʟ eɒ%)\ ~y:;?(NNNvnǭ8NjD!+K|zU89 T&ҍL%aCucKپgN\޽;.]oV~СC]g0 //|ǻvZ9r֭xSNa?(e-ZwMZpuӧѲeKX!wرXcܾ};7 8w5jp.pVZb5k|n? VhlBBBx9rǎի-.\@&M[}Y wwwL&dggߏ B;$ 2";[|MNV7{$ ~{ ;`ǧ/wwX@`0d2{xmX~+Wd2… h޼=}nժU{MΝѻwoL<Ç^CXXM?ThOOR %{^:qڵ|ǯ]dxzz8CX̙lӧѧOx{{Zj ͷC,"2B\6l^Yg˖-qer~fͰw|ǢѸqbo ӬY3=z9VUw{ҊҶm[8qCÆ hСCs!22MҥKjhptt,t7Nov?^`ȑ#q)OXpa7Drr2{9غu+FaL۷ocɒ%8q"4i_~9bذa8z(ك0 2rQΆ*E_|湏&M¾}0f9r1f5ݻw87ȷw\/cXjN>_6{A^鳨 ,gǏGǎmtb033;voɓ'cڵʕ++>XIٱMzǂdaaAXq’mذ?0^xW' VZh|gy3'@>}кuk|'E~ȑ#xYfd2[h]vٳҥ ڶm3fN:k.\]tK/~ޟoY3x`,XӦMC6mpQlݺ5j԰\ܤ=^PwF=ѣGѣG-kժnݺ4 M6ߏ_~実a4ₜߕծ]@n@׮ΝjGCTz> [v4Dt)~v4[V񤥥,̙3%:`ܸq2dH9D?'Oƭ[tRo}܊rb20vX5 =+WvYc~+gUHe9ԍ<-[XGddaP1 ,'~).]T?ֻ^ 99Qtw&N4_n.P sh<=Yj?x{{}|$Zx ҲeKjƍ˷(ɘ>}:M???טYco^sl2339E^ ":jGBtof.SOCvi/Yf(QZӧO佈a9:u*UѣGzyl:+YRg5GGbޙLb$M#!7&YHwL&1BDÆ HիWC$&&geeh4ŋ,~YRRJ4"WMӧOnj3J蘹 3,zyQA\uKH !v& ÇcĉFpp0yȲ$cXj-ϚٶmΜ9Ǘs5_m&(L_\2J0C%j@T)+ IDAT.MgΜ)Vt;vjժ6/^ ~YM2ׯGnT,XZ+0jLbH1@i%bF_Iz%DDڥ1^z"g#C֭]v!<<\\\зo_,X@?LÝHEp;8\RE툈JǼ9=]{v{xx MSaaM"C4QbIĉ1`9ݻwW;2cǛ5k-[Tp4rS$ pt,IL&ǀvqQ7k)Q99+ 4իWtR$%%gϞhР"##vh3Bwwo?$@'+$cf Bwwwbǎ8{,/ 4@>}n:M;Ha݉T;ҳf.s͍i7Emrv5 4SZkذ!f͚ .`˖-Vnى(d\&Ys!yE! 4Y 899`0@QRGd!ɂ!d;>;91I,0k,4l={ĕ+W|r$%%"YswEYq3H3`Æ Xboߎ 6 #F<ȝ8#\Afx2I8CH01I&)kժ 6mB޽ L8v-[իXd ڶmyDt?>RSS8x 4Wda2NNe?evn Qq(HsUGqǏ+-Z`Xj`Μ9D\\ԩ@lӳgO\#GT%~m$ .&YpVd\&i ׯƍcǎE6_ȊC65jzN۰ak) $$7ڵkY;Ѥw&YXrfuL2\Af͚esNϛ\v +Wh׮u۷ǖ-[*:<M$ ԑ,t!*Td2k1zj$&&$%%l @rr2Fc(>vd%$ .'YpЙdP6OѣP@?e\]]mwssw kU!Y]&Y.L4QY&$$`޽Mٹv|Ibݺu0 www@i]h,יI\2JQ岕Wd*& O?͚5￟oOxжm[*DY2oF>}pmϨUyy騵$ls7pqqFg8M`.,$ rي,vx&6ٵk~,Y<==777ܼyW^E1|p?~j\lضm4i|ڵQF |ѺufzzzE& ƚd{IegVDDNjhdQXA4Qq_h\x^:ڴi6m袰1LLx#$ v,.SQ G Ņ 6;$ vIe B`Y/3/&#.%Yp: B B9]\ˤW&Ypd;Ë}E1&}3粃/6;#Hw5 7HF(cccu'*yiL^2 \vwW7&Ұm2BaIv$KF-4?,gX~Y$#. M(+$ %Dz‚bbbvZ\t 999mذAH/;xq(ɂLgf׬YG}Nƍa4q l߾UTQ;<h'= ɂL0粣x1I+ΝŋcӦMpqq~ӧOctQnq3ɂHF+ϝ;'|t 7˖-S9:>vdev$ \W!4W"55PN?~p-ddd;$ v,$ dN2ܦ2]t-ZgEXXoߎ_!!!jG:ɂ$ 2ɂKFIF+?cd32uT8;;c߾}8p }]#=`ǃdnsdAg B???˯0yd!=bcMɂ2ɂ2H۷cuQ#$ Ilk0I؉&YA2Ħ2~:jժy&''&MB:u: **Jtsda}ߕ7 l7H3۷o,ݱcTaÆaÆ 7n5j+W'Ν;裏.T7H4ɂ$ >dk׮_7hw mP Z:x ,\ƍ :8q"UP ݺnVvb'\\\"##,f=`cM`AHQW!BjWihf,==ǶmpuQϫYkݺ5v܉|o0 %g2==v98h/Wx, .eǃ$ >vlEDD`ԩvFDsȑ#k. :vwŠA`,[̲NNNV\:N:~/ggB ʆKFID,.,WrttT5i -[`V;r׾}{}!qd\&Y0It&iBu>s;HNN &&&GID,؉&(\&hnѣѣT?F6l؀K.aժUjHW㑛cMD,8PG20W2Fs]p >gϞO<ݻwEhEgj+#sAȁ: :uի*DDzcpp`>q(ɂ$KF BWWWܾ}3gPF ""x|~Ho8M27Cn c֬Y0K.aҤI8pёXw<6ؤ_\Ll7ɀ4W.\iiiY&233ѵkW4jޘ3gpda݉vt/2Q0/Oļd!FsVR+݋#G -- m۶E=tlI8AP;h5;P/&2`AH\Aj* 25kbto+{˟ˤG7rvKYM&Yin/㩩xU*$ v, 2H1? 4W*?._*U ;$ { z(v`lH4dM60 0  ӝpGI/d\&YpdP0dp8^^^s..._.;}v|׈˗QV-tUĉ  <s΅ ˁKFID 2ɡ \&9h >}:~2dTt&M7ogŃ>cɒ%ؼy3PfM˵qqqѣ7oŋ͛7SgUH;ѼȼU? Bһm2\&9h 46lСC8uࡇB6m /^N:;ֻwot1f͚e9>ea׮]zW_ETTQJ&YppdPm%}*j`Lrܦ2ׯ_Gcر;v,7n],s55** Cͷ<444Xvm+#.#Y0I&n;{KF$cƌAjj*N8d$''}6Ǝvx4T^rرcEPPPkѺukVt ɂ${!s3$+-f͚Y5o| zbdxbF T؉&YppdPؒQ2Mar3 Bggg0+()ֵv޽f!CеkWBrJHegI!$Yinh+WX%&&bܸq Xv ww<<,U ~߿?ׯ@^Bciڴ)V\Yk .LHH@^͛7`ǃd 2Q^N47Ho8Mn#nB>}l )))*DDzSp5i1 +&e'2JsaqF=BD7!$ $Yl.#}*,.inh1gܹ;v !ܻw/Ə>rرc 4,/hҿ\z'=7{I e.&\A_'Oɓ'-ǫV/{2 KzWMˤOrr:%ݔ {3U!Q6`AH$+CHt/N4Qahvt @XX+8JyVr&+6]\# {n{g/X'OdBHH222ХKb„ 3fHLL +==zyXx1._>ؼysG+fى&=Ý·:qTV(Yv=$Ҫl̂Ns`ԩSo#>>iiih޼90l0DEE=?ea׮]ԫW*УG W E&=8Mc]!EalI4d͛7GuY Z 'N9sOMMETTj) 44XvmE*,x?,^'zH/Z2Ϥg!LOOm6\~&)ϫY񥥥aɘ:u*j֬icǎ!77AAA;;;uֈPc+3?E27HO8Ag ‘#Gb׮]:t(`0fΜ [^s.BEaKFى&)lp2Mv6y/.&=\Ael޼jEQSk]̙3裏7!333Ysss)+vIOx?,ɂ2n˟ux S; ݻc:SNq CN0`"Ǽh ݙ0Fsp,kI$ v,uvpF˳=̬--6m+xxxKӦMrb]۷c֭ظq#.^@2"33/^EQd^III]vbޘӧcƌ%z?bǃdQX.sV&+ R8eқ?3K'223gT;JOs… q9~6.>\a#44'$$`0駟w`0 11 6ŋ1vX 1114hV IDATZш8 2DDAɖTgIn߶˜!$IM5=N4MjelK/""SN{h49AeGsݖZjYHH6nhs|ԨQ_>}]|||УG^_j1x}s,Vo`7kқhޤp\.{&-\A8}tC(unݺ6~;>gK.xWEwٳgE-۷{MzSTǃ'D,$ʳPE3ڴi(xxx <<9Fu֩ [2N4ManޤUR~$+aXv-.]d؇d"+zGŞ={*0~!kCH ɂL ̙3h" 2)))3<JS&@͚ى&u;$I 1IF+k,_Ǐy|6m~7#zU8gIo\fGzb4۞c'uݽ]Fˤg+^-Z۷/6oެfh׮`'"+ HN_AzrXlox2Ib"P6`gK1H4W֭[x #-Ml*co(gIOj?3'Wu?ǂL2\AOc۶m1c ""> BCC1b#-;{_pq=;ғs cǃy.v\3v?C Azo><6#v иslION73''O͚?veԑijh4bĈpXbѢkvI/gǃ\&Y0If*ߪ޽?ƚd 89^L@ttv"38ty2靦 B0`; IM~y6֤II?lYfa޽ gǎRde? :9pVo(N4ǚ5ˤwu'+_|VCСC dCQx¯aǃ /X x¯AVr%qcpV￁끘¯qsnܨʚ B ec !x¯aAHzǦX]|PeҺǁsݻLZgE_\&ܦ2X6777!00aܺ%F~xꩢecMZvл7[@E_\&-;?hѢkˤeǎ<|P^2I4WN6 aaaׯ֭[u֡_~7nMvxO;w5iաC@@z2iΝ?cNJw\&[711hݯg.i O|r̛7G1o<,[ آUڵúul۷o:uOOO ,, *DY@#G_|8:x iMZ0i̘~1ZF5믋]?8xN4iMR؏WF2EmVg2 Bшv Bnn ܗ_~޽{ еkW$$$...=z@VV/^QFaٲeuJ,/. 4iT*V!=X񿟹LzMeO?-w|ٲexU.^ѣG#,,g(hʔ)î],[W^}UDEEGfL@t4bު Ē2?-;pw/8&7W OQQ@%/vl*+\,r\> 4m t*vY V?ۼ9'ίJ˜˿.:λw[t&Ow&SYRq/ܽ8shVr2kh=<ˤw(JQZzfiiiP&Lx?Ĕ)S|gr^E<\>,^qnVb)]E$* E.\|yܮ4w vLlV--ҋԩS3ENxP\AXNxW dȐ!Xf DFF"~*=z`Ո$U^;;;Zxa=*wgdd^W!;8~<.=*6+2dL*2IfYcDg0'{?WOa]:%f͹x $5X W/bb;"e^*w؉V.Ç"s.)_e31ghB"ӶaJu sw$f̝˵l4=`.WlQw'o#cƈu0+^F۷N o/rx$k0 sLsa>}!!!+<5" piRKIAAIh\l&ANt~NGG 4}xi`|R>\&=e;vP;wNFtԨU+Q+:e.{\fW8^䰹| xmyerR\|Yܿک[cʹWyc.> GGGaÆERՎ;wܾM%V&`]<&0E2/3}C\^DhHMU;'c6mD.x%3+G0XW\1aDDDXei~Y, |R,(Fǧr.m,^Fi~9ņo>>^e٥s?l":ɽzFk&2}dѷG1 XvT+'O ɄdddK.puuń 0f#xӼmǤo_`j). DXFײqO+[<'O6sy~?$0a9:(wr!<'V& L:o6㑖͛KHd*M& &F4?(:!!:uԎʓG_2'ƍ4||Ԏʚ B34o\0H"zxde%G7 Oܨݭ=Uz4hFয়1'aڼFJh7n3'+W<g شImbg'AA\!;4U#F(u+V(HݡC0}t:tiiihذ!F7|E۷'NDll,|||0x`̝;*E//=v\ݻwS I2D*!A̗ھ]o~bjU#C`,=qqΝb/6V*V;:2r}ڿΧO!}yNNrGGXnn{._t,5 .-wt$I?#lܸׯ_rF8~P(8rJ5bk P\9DFF{UDDDKT*%J$Q a۷! X(/C~mS\l &2e䎎 !_D_l,7Q9Ͳ"C}_$ܹ`ˋ`0#?Ǯ]PJ3ä$ڪ\+VsuIIIA4BR!<<\o1 !n(%D6mu",#0t(AʟX:+KH۷`ћݸ1p,0ap1`Aʟ-;qXײIeKΝ+ޯ^ܨc2H1 h̓'ecZQ 2BA~Я_?ܼy㏑ .Ev!<< =ك;w⫯R;wЬY3[YYI&8}C7 &56#SyرHvEiVr?x ˻wEyBץ ;/2H[jrڛ7^݁3<1PwO\bQϞҥ@۶,pD3CTBP@$dggF .`ŊXz5K.]||< \L\\\pQlN\]xaC31Q ؼYqn8ز&r qS#>^ ݼY Рgo/%$w ͢GkWѫݹ3+6˩XQG66;ƍ|zCj{yb3iϠtl߾k׮ѣGѵkW,];wSS$IBFFVmmW*xѹsg666ظq#>STX>>>4=VJ p:-Z}R,@@%!\vwqq.@@llZwgFsHp$i}&d: &!㏱i&TRCƍ$ÇѾ}") ƢN:3g,Y T.ի<=='k׮P*yٳg Y>R b=ʽЕEҷn3'Tcy|8D'RTt}.;'.ׯIz5oQɫXQw $Nsy&Q{Or͹l仯k[*Y._UVE͚5|m߾]o1իWX IDATZl2xzz\>>>p ԬY...$ | յXnaPPNZ3U%C**w] DE}'6-ȸ颷;)I̥ Οenbx.\~H nE!WW1w$ݻ'nfW_d#ӧcڴira &!8[ܻw/9w9) KKK:u zh>})))*`m UZ@E'tx os.Cb߫!!b]G:z]!][syU" vL 8 CIo>pxm`<1oԊL<'Nw_fffTr Ҷ'թSǏQlY@NN6o GGGxyya}7y.׸Ä\|HKyG̩P2&XlYeKd\N=ϋ5=o~?6mڄ޽{k[jj*T*RRR`oo_"?/LX[[###VVVrC i q RRR2e`۶mݻw a.]pY\t * fc߾}ܹs5j~E߶m[ܸq7n܀B(26!Ls i׸!ZPT(SL풒AAJBxxzΝ;>H9>#ܾ}'N((LKйs琕f͚ilB&MpiT*ԫWOm- IF[""""""]`BXP(g ܹ9v4邥$IЪM;--ڪ-EDDDDD+f>|۷/B@ll,ԩs{znۂ=\Eɭ Z`R LWffw|!m\BFNNNrmYR.!WBCCjϢK<_ͷ#*_|b%|#|!m\B*(acv 3|}}unnnĩSЫW/LĠO>mM45kpE2'OB@&M:JBbb"JeT Ma=$!''7B]rtt֯_ɓ'Oర0h,6߽{w=~-/^޾|rTT ZJ%ע4 L4c ( \p$!,, GL8Qn̙@6m8,\ر]J0j(|W@ͱc;v 6ljQz"""""W8SS+ T(vq7ppp@>}0k֬|Ν+V >>kƄ зo_DDDDDD).LODDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBHDDDDDd)&DDDDDDf !bBc#FRO?֭[CRHIIsDDDDDdЩSn:?&&^^^x1b\{sDDDDDd,`w P\9DFFBRUDDDKa… 9sfAAJBxxB%"""""3ńP1~xL8*Tȷ͹s琕f͚ilB&Mpi}JDDDDDf L6 5jTmP(g ܹ80$!##C666˗/cؼy3 lڪ B>|۷/B@ll,ԩn=z(1G{YI_(J(|(JXXXh|DDDDD|IT*(ԨKL Q^=jľ}cܼy87o\rppp $IB|||犏VMII?HLLa4&pvvP(jlW(QfMcȑpss%N:^zfff"&&}꘹wL߿{{۰233RRpf}:hrd6QQKVS|̝5d02\,vtCpw|&%CرcG#F@1i$c'D Lp.~~rGVVVsі-H8Η|(!+rkL6@AS0!,A+WFʕl3u릱}̙@6m8,\ر6{? 8!w3#*wd(L SD>X=P(pwwGDDիWcĈزe Q/^ʇ b!dŋu5cDB=vZZj#G1z/l哜,wwc:$IB+݃CH,T))fooϡ2bBHf2aBhT*U -[""sP{d4H8d^R/&Ƌ}L{\n)^}yrGJAAAu0!4\xݻwwH/RRR\~^B# |yZXX`ԩrKF?T*&Ddx?L=}*w q+SFU1!$g.=CF[rG@DDD !=&yHU1!$!y@eL͛P*8{ܡhN+?+_T󼌶mbӦM<|θsܡPc|ߗ;z3=BJP*wEݦFXxիWWZk͘^x1BCCj{E|Xj޽~_)DBBޖ{mGۗq咞O>NNNppp@^0)^{ Ɣ)Sd XɃ==݈OĽ۸{+>>7n,B34si=F\|$U+W@P[n(_<^*T()%KOcBƍ7Fݻm6>|wAϞ=5 2=TXvvvhԨ٣޿m65j… 5_F ̞=Æ #UUViѴiS١E8}tϣK.ppp@Ŋ닇HҲ4CFn݊FNNNԩ0m4gN:N:|(S ڵkW^L|puujԨsB@=T*QfMkУGTXhѢ8P1}8#_bݺu1uT3F>}:Yfƾ}޽{vZ!::ȑ#50^r[lݻo>>}qgwE1|p\xx IƎ{ϛKRR Ǐ~C:uХK-O?[QzuIn:ܽ{ 99:۷ <ѣGRP~UJtMI$)44TR*ҹs4RJҜ9s4hBO%I7nH B Q?%R)]t)ߟwÆ m-ZƏ-00PӶ^ꏥ(,-![Uw2.񀋋Qk}Ό U{yzzbʕ+WcƎ!CTqΜ9ʕ+_wll,z衱-$Iޜ jXŋѨQ#X?nٲe=BWVZ [nEttt9wUVEŊ5bƥKP,7n: ԩz2)NHHĉdgg#--M=48 ;nݺܹ3v튎;aϞ=GVV={V`kk[~'''3SLA>}4ի6lޞ])?pssS?)) wAV4yxxZyI:u+!55t !75<1!4^ׯժR$p%JRF1̝sƴeggWy1R(+d`޼yy }: /}u޽֭u,kٲ%piX[[Zd9r$Q~}XYY副8J*+V͛m6uK+++dgg9!C7x*T7 =;޽,nR0i$̜9S@O ꊫWyrC$. gFTTm6tppyмys[u2! ,QjU4m[lYxxx{z IDATܹz_AJ%  XZZbHOO7l0^!!!hԨڵku֩5[ZZbӦMx"7nc̙Z>F@35/]vi̅զ鋂ѵkW ڵ+mۦfΝVZ9dXRqo Ā5222dY9;S.yqq~x׃$wLl pi)㗒RJsT*ܦM~rbTݻ^.J*rcZlQF)k\a!O7ѯ'Ovh|Μ7; ""5kּT5TSC3d B1bJ%|||k׮Je.]9hРd*QkzyҿI ҍ 6!߯G}o|/p=^~15Uk0ƌ#w^ ЩSnݺK+ TRsјꪯsK> 4j$w2bb^D;޽;PM>"5ϗAAA *p3g WR &:#""6KF~:w \~-B##CHiؿJU NNN ^#"ccCFcccOOOpqqAF0x`lذ .hU*;;)))zoy7QLƘ#)JBvDDDhxyyG[oQFa8p $Iĉꊹsʒ&''c8qbe/_ J`ʔ)S+)Icc/=}*ֱam[ ""dtCF{1c`֭EEs ,Z , (looQFڮVZDÆ [ݸq5O. Sa rGC  wKBDat ˗4ܲeKl/},IU[u|/͛sժU0`>^GF+*q'N-[s~t(wT\ii#rGB,wDDgF7d K.œ'On> ;;"qeL֭ѣG:g}I -D_*So/w q:pWqA""2C]Ò~]!L89zÇS'ǩWBCCj₃b߾}رcn޼ @2fee!-- 7oDrPTRѣbŪR SLYb(jcܑ[{gxн6ν5 HbvN)q#Ь6> ]sGֺ50{6̙31{l-[VP̚Q&wŖ-[;jժ:t( NJ3|}}nBw}WcB5k"88#G,9^ (_|bMII)7T4`:xh{m`m-!3GѻP(T ؈ x8Q9fOiӦ/33S&zuFvBCCfL6 ^^^6lzJ;tt;1իWǤIwIIIu?f̘Boobʊ j矁Џ-[@ڷXhPHHN+WÇk *)7nM43m; g j֬/ӦMCDDBCC1dT*$$$5ʕ+ryݺuSoF~Я_?ԪU iiiؾ};N8>M4gf#+K =p@Hٳ@.rGB  3s6WrGBjRo_QH̏' ,--P( IAMDU(Pp%UZ5i;wݻwT*Q~}XÇ)RwPCpss 2iiF&mb˫r֤9# $C厄 I #G N =ϨիSoS(,*Cć_GގwKֳg' rGC:rD\<!H%)1xmcGq㉈ Q'C;6nȢ2ǬY@oy3YGHH[($RѐoCJ;2%O]f!2͛QV-C!sbvܑ˗ESNHQa\|Lo%&v)yPA<D7V"Ce8sa2E\p,sC>  o<`Ŋ|L@n@rGCmm[U+q埋7t˨{uѣGܹshذa2>>>2EFr7য়K ?l^_  iR ŋbN׌/?ě7(?QQ1;mګ'<LjtϨ?_Ǣ2)'/d|}pAΏe51D44T]2W:'rGCdNO;"҆Q'999r@&4HOӵŻ&3S|O"Փ{Hy ɼ\$ >ThT䈢b_ Ц:!$zޓ'_%l6 (U 5埃Cv'=x]ƍ;ҷhQĒ[k葸1ٴimp1FCd4}{1cQ$,DZwV$ >Lb\xJΩS@f6d5Det eP~}̛7y?}{AѴiS<|P(Iߢpѯzæ_wׯg5Wc1qXx>իW^L 4@pp0n߾ʕ+ؽ{#6n_ }$w$I䎄`r%pPѐdd,?ԭ+wDd o|}EEQ[[#"We a.'''C0չsg4mڴ6&L@r ?UV >B5jׯG_~[.kΔ]HK~+Ç9ל$$={~ʖ;"2v@@?iXL/t(991AAJBxx4Z|(&w4iQ~V\{^ԍQl)"B7k u3Lg-ZM?dۿhHKΞe2Hdj$Ih׮aooݻʕ+mΝ;,4kLc4iӧO3d/J{gugF`X_t>$_;ôcЮ( hW10t(0hiiS o:?*J=о}{8::"** ,Q _EAAA:ujPbXޭ[" j֔;*ѣ"Y\b 3X^ۢHۥKGߐL>ӦM; gt V% __W~vʗ/,--q)K=33111ӧO?%%VVVsMƒ6i.E'JO ]XjCYt颿uߋy|/o,<WNll6 */ckɘ8qb233 ct CP<ƶ={ ** FRostt֯_ɓ'O0w:U 8y dJz'?l=(]e2뾺3gDt-vD€}D/`34MIU.i|1m&5X]BhZjwww(]4jժ/h;sLxxxM6G\\.\oootQ0EΥwO?ߧ ן:~䎆t-)I ᎏsz)ZMT,V8Q$dΟ;aoNBxx8nݺ"O߾}{n߿pqq|)Sh wwwDDD`ܸq FYf=nC7kPXt9`HBg7+WD9 OhH׮]|| Ç {oѣWR% * xu#"+66M,Daԓ6mڄVZ!66;v@ff&.\tҲ_"** =³gpu,Y$O2UV8rRRRp],Z_pX`y*\|_O'pE3\7oguȴEF+a$d<>Eczƍգ |8h][èYf!88v킵5-Z/wިZQ CEXR$IT+lR\8ؤɠXSN|toJ17tJ`üԯ$&aCyDUq^6oT"FLjdCF^wy`mm( =,ck-G;7xS>yR () GT_;sv~hHܹ#*FEWqIMtX2'<\T4/B2OFCXlY$%%*U>} RSS Jիњ5/lb6ܹmYrǏEooQLד'=&F,)dCDP&j o>81Q#A ?ɠM6ؿ?6l8x ߏ\٨I>zP#݉s4kז;ORb8Mp0/L_ݺ˗66rGD 1=矁ޒ;"ݿ̞-z}}WW"{.]}&N@ܻw={Ě5kd^Ś5bx +uP  T Ԭ)&!U+qAvq$< CfBTA2IIjhÇbܥK !,WJǏ1*) ڞ=g4&tgOL`: iVƏsnrGC5qC^`ϳgFـprGE1011&ѣEyuԭBCŝd++yc1999F겲1cD%Hc-@99ߊ{by}D%*JVL Ԫ%cΰ]BXlYǣB (S tHBl"Wop(MsX<<\,P,s-LNx} Av.)IT}wYX\ * :$2t0$qR//~^K<*z!&^(]Z6gπ^vdx$ 5JL?pU_MU|<;@ݺ퀭1$1#0P N`L&Ct4g͛93&FmV5jJ*yz %IB\\CW4cP3E`=[ ;pԻiOQ5_?`,֪ɠ2|Qrr2l9hJ^]7 ࣏D^;z?*w@i w;2رmQ׮rGD.'GTC0AܘNWe a`` @P`ɰnMvv6~74iDBB^VҁL1<Ɯp`SIs݊㫯Ms8wNU 8{V+c)1$4%ذh^eBxiܹs~nF57n1cÃbԏ?[V?lNID9wōCL N;$z~nBkՒ;"2t$z?9S*ߛ9C#ec(3*QEh"7h&N||Vt<(sECheg'GE1'$&| zb4$ȰegVݺ\TaHH!+wbctݿ (ЫT;z^J0@T$Ӓ!ndY-> 4o.n98(qs"wm-厈tɨ”̙3@BBrrr4_vMȨ(99b>۔)m2 I .{ Mv6п?P$Ӓ"""D$`21bf|`0\"×"֮ˁ}!`a!wTkF>4h\\\8JizSOtY\"0t>EciS༳(`%y4q}p0а9ۻWM+Q'?3v CbHLd^B25Q_;;)9[ɓ_\ IDAT=-Z 'A{ϞÇ11=vm#"s"N{xdCYBvv6={Juܑh2 t` 1$^=\99b77Dtqе+#;Hqs8xa9[z n݊Wbر(WJ*={{{}pttDTT,XDGGkTV-xzzaÆHI?{w3AA(UTyx؋j#RKpXIe6t(pS)߿m۶ĉhժUcYO NC<1NRѹsI7ot8l$g+Áy{M`۾u6~y '5FF 4As9={ѦM;;;a2TC8uTte˖EBB>DGGDŽ djРԩzD8qQreK.<<>>>AjՐ#,, ]/ֻ)[Vvܩ&* 1ص [ǏE~yGvU~=>..Ӑ55snie)x2˛啪߶J(©S_hڴL:u֭[#>>a+WFFqFDGGCףf͚7oz%-?NbCqՓܳ'а!WӐ |Ӑ5sG4k3/dYqq@n@d$@]D ¥KL{<11V27nƍvnnnXj1bX$gn14I(…bp^ڊ3g\eˀwߕ͛I'˺ySV. q凪OVCQm<~0ۊk_~=βӐoqc1xPiHKbcE"8p`A/8qϟ[SX/3κy%&52/2|"; ɓBMvuBMT9RvҒhQF `baLL :u}?%?~ƍcժUih|$yg4f9M.; \OKhO z}&(Ed6/-[o~S`ׯbccqY<|>_Oiʑ#;hnW[_X-<_ P-;Fi\}σʼnޙ>l=9'Ų&~~btTTCc U61k,4o\b2mٲ0]Z?ll{I5]d'֥kBv{رb>MIL܀3m|W+€mŨfF8U7SRR`ooq{{{HH=".F l>OgK_:ġb@"Ӑ5IIz=˖q Yξ}by꜂CN&MwI{8p $&ӎہGİ+2Ee͇ʕ +2Ry_TIv&  \ld9۷AK1H/O UVEժUQJ<}3gΔdm\εDӦP~={&4  ap۷s čkŴ"KSe|ŊP\pPfM4UE]N?vA,z'NikkX9I *TNv6WbWٲӐVl$7V:&yT Nf͚Yfh?YL|Xpp2Z'>^T>UEbl ""XϜvdض xui(#[8Dzmi$?wލ6mڤ mӦ BCCeDzy'NN72[`-6񢲨bHY-+7n/D-u}{Qde^c]/֭iHT}9{ll󃟟/֭[c֬Yٴ~%e'-3gd NfͲ HJ||Daƍe!k䉘շN"uٽI S{YU8q"M}=ֿxzzbĉo%]9ؖEC{IhB~cƈe'!k,_8%"b PuǏѲeL7oO@6m0axxx`ƌi_ ///,YŋGzf͚L>| 6\]]`񼦚1C,jkW/hą2XCEFV$~oY0@TR^]x8п?~=P ٟ4k d;ȶwPP^y;w^X$dɒ JNC-+88zB1i$ŋuV"""дiScڴi1eDFFb֭kX1oIlի/NB)w~7^3gd'!kr9mNCPͧ<~,~XAx52q ???RJaprrT\}Ahh(ZlEDInIlK@(_$ږ"* j#֔[ xiZiN *hϞmۊE?(; QT=d͙3)));v,d;366ڵkZcu'''^"yMe4Nb[._XNvZH\0NB(Bg͛NCgY.j"%Expvf"~lݻQF lݺ+V3^}UCya3g&*ۣv8y򤥣h۸q_sl/ցc9 <\n\)}8^L a2v#ɣuеkWt'OLm{ &Nm۶k...֭[^Sn]DFFp{a۷oCӡL2*TǏGǎӶKJJBDDt\lf/;'LOOO4j}[-ZYfRhZ __vg2n.Xxja>bNQp0 b3xE&AFS3% Y9_;-*; QuarE_Hشi~  BLL BBBгgtթSpttĠApBk֬<ٳo¶L*q7u4V- 5<x =< L,_m5d)v-P4D/O={-ZN;w ,, C QrttD`` MԠAH7O//Lo|wXH8 yLlOʣ˗N FȤIoNCC}{1 KݐکA8|p hԨ)!C_~ڜ9W_q93ǀkX𐝄^*ժNCb0+; :Ewk[-; QA0b :;x<} 7dO!앒￁ E8׺upIZ)r>c*xSׯs"n*\0eǰ˗UNbx3୷ XjdNj/C Cl|``Sz:Oq *x'aa4D`ɓ{n %%%W[)hNbYyA( ][pP3K͏wsY3hb+Wd'<4TIvb/KNCnT]]&ۤW^yJfܻ'N*; DUKg(јۅz!VL+YÁz%p1oV_'ۤǏˎb,ZʕvL* `N]ǎ"~~JN=?"Fp}ի\.t,8ol{N+WnnnO|xxd(@P0m$WP4=b2(ȂCw Ts%8oZYdzgbYUT+d2Q mSu=KMX`0͚N"GA|}\3gp )chz>ƌl$ĉbz l-A8zhlFPн;6Kt%?^ ez'$d .\=@--]ʢEdT $Sd'fB2*N]OyF˺tIKv":ZUve">>@/2 =mژwTp]E'ФIԣ ?,*U["KQo\/˖58w^gNB,* 0ط<ɨQ0bĈ,KJJX~SѮ]bΜ9?>t"! EQ(FRP#YO NC-t\{{ll,z(mJ `' ) 5K !1f+bW$; ٲE wޑ^dgg5,Yz : .Į]{Oܼy3={ &M½{;`Æ 8|0ϟvr>>>V~z<<\R2j{S%"BT*kRvmzP?;wNvz!Zt׵|$df*VKQT ٳ'vڅ;~qBJ&{؆ŋ/Fs3`ҥ@&k)SAػkK^)vv6&=/s=z$F$DQup˖-ضm<==eGZ6u8a?{^$&IKLV`\ 6&X/GDO\ǕL󲍹 ZY,WMDAXB8bV:w6l.zjՒDe'@Ϟ@lƉ5e'![u`(\DDSuQSbذaq(V)&8xh^v۰x1嗲Shܹ/W6ݿBHێΜaG*X#G}nn{իgϞ_cڢ>|()uظb7swصKq$ˋ Ϛ%; sVʕe'!&LEd'![u); zA۷ocĉpqqaQ ֯:u6\)*r.AA@v@2P^= i$$ɓ-5J,DELÇwyGv(";:VT&$DyhlL)ƴr%PX lO^N( s?啩ѣCe֨z6N5 ;Uڲ^}Uv[rdتehMn?ہŁ -Lcʀ ERmΉ NBj_#F@7nN<ƾ}<}4ݗ_|aJOEe'Ѧ E WݻcIHIov*8|#; zh˖-^^^W:FQF,Dٳe'Q?ECYDhBRӁ;;IH+W͛Y #YeAw^Ҏ@:$w$)ᇲhӲeGq]ƅIG%BQAپsGgDwn~#Xu8\\VZay,Z$4'3ܙ kݻ}9IV),:f P4D9/@ pmepAx غ ¼ȮJ(vlLZ?,L?ky4U|ֲyr2/@@ŊD'sl61K!5n[-Zs'O0qDؽx Mv;ppr֕ĶeW,&(ޝ*"?Vo xxX.YAe2DvRk7{Ǽɥa@@Ν >qOOOKL&Ϻu"&ߊw--/.XCv+EB}NB-ZԫԪ%; ٪bb/HT}?ŋhԨQK(ǏKH$Wr2iXs`Z?e'ѦիREvʫ#Gжd`4`|IV) 0~Xw>"z !,W"c}Au P,P$};PP$ڴhũ/@ӺEA Bbj只{Ot:ܹs!!!2dk)7n ^W"E2ma4lNNNpuu K{f]~ڔqd//^Ξ(Ⱥe{W-'ɑ)))B||<5j"E`Ȑ!ׯL#GD WChѢE#""дiScڴi1eDFFb֭y: %K-hN ``d9y&-  mr,<ؿ_s!ۓ9vXd!Ent:1CEdd$bŊI@ 5{*U rӧBCC4/\<{/ s'T,;!!bR`B`RIHS8l СqHd.2ppwwG6bŊ 9ccc]5[nprrիt-[>b~sX3)qP^;P(NB2EFcr$pLD¯ʤ-ZTIrw}/:s Q7"w]6N<[FYN ;6-_.ʇsޑ̟/wmf]2Da ҉(T \x1*W:u@jYjFcQQQtpuuWWW]3w$$S\x1&; ٪ň+Wd'!-l~Xr%]=z/@R~EQhҶYU+VL2&$$dE=oF+-֬:vEE̺>HRn/ڴʔ$I=.}KcӦh*Κ5 QQQobŊϱsN8p~9::ҥK^59r:u^G:|^ٳtKMѺu2yڗ֤qK._L|Г:Rp mz<9_n=؃@p00huѢE 4k̂i P^]v yHM9(n:tʕd:sFr *F#p0c$DKX YTԩ]^_ނ(T\]]q=|0 p8;;'N4<)) Fbb"祿`$1AiglbEm[I(/?NusoInÆ7`? uN׸asl @BDDDD׸!?5娶 DDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD5nz>˯"E?r֭[|<јD13f 2 2 /d*^ZNQEv[{nܽ{7chӦ 6oޜxƍqUL</ʗ/?Ф f6%%%pHLL8dxxP^|!Sr `k2=ҥKJ(EDDDDDZ@HH+veh`p*""""":6 :!.] $''KHJDDDDDZ!lժU0YV4i~k׮E@@._+WJHKDDDDDZ¢29P&mh իs;d}}}pB~n d*N䧼BByLk\aa8ƍ纝NQzt_v GAMj ` Lmg^I'ەD9BBy^d4s׶*xlFXxIۺfz,$$:;w6+V<|ФSʔ)c1HۜdG BByL]̇ [n/+Wjժ&r xNNNx)z=t:]۰MN= %%7, cY>"EpNC-L:^sYZ6 s.ZjHHH___Ԯ]‰HkXe(J*Grׯc8vQfMzpb""""""64ʴAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAHDDDDDQliDDDDDD!FAD 6 *T#{=y?{^Gv|hذ! ??? '"""""2 Y/?k׮1c *֭[&Xd |>""M6ųg0m4no(G:EQ!ѣG{aԩ8p عs'6mjo(fvZ*T{N{H"ٳ'p\tR={&LXk׮iA֭z#DDDDDD`0T^ŊKxӞI\\#FlٲYns$''nݺGڵq||DDDDDDa0(fzΝ;9~رpttĀr;~~~hذ!ڷo1d{QJJ z=t:]zٙO""""f4s %%NNN9 A IDATAvC6˗u{Ν;aܸq87nTRpvvN~όY1 (^ӧO,;Mc0ڵkc߾}KWXȑ#t]vut::tqN۷o_ǴiпxxxPB8~8:v옶mRR"""mr;&݃c۰f" zQN=(S {-fO?A2eСCh>_H&BYqtt̶8"<xWv ts扈,-...m U*^Zgiiݻ1l^DD/!jQA׸&7FAHDDDDDQlY7n@ӲGOL~̘1(Wy|_כe?/>U~^c͛N:=z=z4ݶDƍr\Y?oK3:v수_}U|L!ZV+WfNC@@@לN<_֭[ʕCnɓ'ma)R{;uQR%899&?ѵkעVZpttDҥѼys$$$`رh׮Ѽys)S%Kć~~III۷/ʗ/TR?th߾=z=^uիWѾ}{+WΨ_>vޝ1߿={m۶ӧ9;v丏 pwwǜ9sҞۿ?z=>}ةSqM%K+~[oE֭[PƍCŊQhQԩS;wLO 6I&prrBڵqȑl>}-´iN:ơC|ذaC;! EA˖-+V2eJZCĉFΝ_aر5j.]n?|7qe۶mp3C 19'O兺u"<<;wDLL >s@ҥh"=ላCnпtC###flݺ;wɓ'7gΝ;W^pߏO>`Сܽ{QQQXtƟիu0 y>ӱe].]BHHǎ(Xd q1b >{EDDZjv8j֬*U/CBB0fL4 .\ĉe˖mMDPPΞ=eƴi3gΠEh׮\#Gw}SNzܹ3RRR{ $''+7|*UBXXXmׯ?#BPeG!""S(MIQD~/ݕB )ŊKrvvV&M2}t.ZhgΜvܩ*TH.](-ZHw}xxx;_~ne޼y(ʼy2e(ϟ?O{~ܹ^WN:kƀe˖u떢˗/=ַo_7Tt颼;#ƌ+QQQiرC)Tr\3t]С(z͛YnqFE?Ѩ/^\ٺukVEtʦMEQ+M65iۜxxx(fYVZSϽ{)ŋW/_( 0@iܸqvժUSVZJ EQ})z^yI^Wnܸ(,^Xʙ3gB ɓ=V~}o߾(N=9E+/^]bRhLׯ__>|x 4i$Ӷr lz$' w@~{5isM7_TR9fС޽{ڿK.qN:^{ UVϣ}ӡ(JZoonrʥpjժ/\qϞ=pvvNNÕ+WPZ5)Sၵk"<<<ӜJ*\r2F\xe˖59;///xxxEh޼9:v숒%Kf1GLL F#҆FEѬY3hٲ%ڴif͚ѣGc۶mBrr2={Phl/]4 x{{{.>>W\AϞ=ѫWǍFc?.\iŝ;wРAtyzzfZy EQի)CF>`AHDDD&)TH4̽rrrB*UҥK3CNƗNvh^^š]v3quuMHܹs)))vrz=vڅ0ڵ 3gĈ#pQT\9t =̙3QR%)R_S_{:>ɍdr 0BM# ES\bT4^hշr!VAȫkMm"J5A{$C$If IH2yڷa)//OfҴi[oy7-[L#FPxx~zն,ZH??n{ܖ5 G6:fǸheÏ޾C n׹s&*++s!AN> ]{@LFo^g?zt1}-.5j>cǷyRQFi޽n\{ڷo Çp84w\quI9rDnm UW]4uuמ={$#۶mӯ~+M>]FRHHHGddfΜ_~Y֭ƍ]^nz뭺k4h :tc;V:{u,~詧rgРAց5樨(9N}4N޽{OǍ`vtȑf=_|Ǝۮ{[`۵xb :TJNNV^^ۺun6*<<\VUrml)S4{lv{nY5;ӤItg?t!/qг^G=m>sL&{/yf-[N>;Cv뗿w׿Ν;_|Q<򈮺fK Ӽyw^mݺUfϞݮᢒ1c~;}:z6nܨ'O#9rH]V_}"""u˗/כo"kZW\qG\}gtw^ cǎ/O ,e]?OnO>~__(++K˗/$)&&FO}4|-ZHO_R&Lp嬪ҧ~ӧk^̛7O+Vܹs /(88X)))-O/~aiҤI_궮dRLL^u#<ҕ%/_Zq1C?5\ŋՍ;Vׯ׺u4f=ܹs[=v:Ţw}W_|񅮽ZgmsV>ckJHHТEԿL&}Gz#"ɤl_vgȑەoY͆:E>}e?UW]{L?n&۬^Z7n͛￿]Ag޽?~ȑ#n Yl>kdۯ_?M0Avnf2ofnxm$+##C555nϟ+Wj͚5JHHД)Sk57W_}zSOW-ZzH _w}^ض`iw͘1CSLQtt6noaÆ5=ޟ.q;wTrr-[4I/GUPPЮUUUi;v?@7pN:r8 nCXf)22Rq/bqkO>y޽M+eeeDž9=u]xD:҅CaÆ Vjj.,,Luv/<<\QQQa:\֪U:4ԩS~bDB PXXx/ C^p:uꔊ_ZӴiӚW\\,Ţ޽{jSmmmh?OݻwQr}ؾ>}tpp{.VO;[5a_7G0`z!_7mQcƌѐ!C嗺k/[nq`_Zb3RSS1cfiÆ zw4{lmn/F6Meee-. Omz:xeCUXXX6 9x!kxhUU^==^{Mvnꫯmswj…ZrҚ=t-gvvKk/7 fծw߭?l:_)]â{=XV~6hnBBBt뭷*77ŠT0ӧOYhhh%##C~]ƐQWee2;vdRbbbyy9NUTT:vfy_m6ƬKo[P=TCs?hѢE$ݮѣG+**5{ViiΞ=8Ifa̙3JHHPPPIræJwܡzK~i'7.FӉTƷ!3gGUYY┕Çk͚5,Yl:tHE/rG?ҠAtaeeeDׯwm{n͙3GsQ\\۷k…"`ڵJOOWNN˕M6=Wd259|ZbΜ9~?ǻ6l&M~[25j^~e{vDDDZRyK_.LnBDD2Nq={H)$D;VJL4^ǎJם;p}!@Ի-H|c={?Iza)!=$#)a@a,a3PCi2#87@n( **{{xCڷO=LH0^njZ5:!.{K&A]t?S̓F$:kà ci|S=l/h=(^}1씙Nq!fHIIFitJF@ܻWS)+KKc6542E dVtSc}]1t>v풲aaiX&(#Gr:{N 2->\O#G`ogD6MKA{yBf+XtJGZW']uQב#ݏ@&I5Jb4ܣWRQe=jzūb)=֥ޣx ˪KWM!WW/ߜ . B7s^tJ'N*nb?~lxȑe0)}\_HԓCx)1뮓͓23uݻsim FFSV)-X`YSBC؂y)77WiiiSVVRRR{ݮXAAA4d+''G&M͛uM7-,,ԴitWk:v옞{9}ڴiSw&d2z })}Ν=J~k<6cĈepo_ߜZgr:];w*99Y˖-SZZ$FGUPPЮUUUi;v6oOII޽{UTT$"IZj,X_ӦMkBCCeҮdKO>)x[ٌ`406|>tH{Xv5nÆ TW]XXϟK:th(9sUWQQ<=0(IwuҴ~6BKCF"cOѣaqӦ1#jC@6 Hݍ[XXx@XQQ!ݮ'O^Ӿ}tR?\7nv!!!JLLԞ={:l 6ߕWJNtdc8i^ߴ3=j6Zr::q1k,P-\Po܎a2R.EI2JrQpHv fPUUfzr-zj]wur8nǐ8m9\,z=9s]/$!!;Ե^{GׯwۇNOfYAAA'UT__֮mѹ!`ZURRҬ.::] ѭު\Wl~8=$Y,X222ڽ?_c-##kӉѵU\\J;vd2)11<N***$IGVppv嶞Paaaad[,Еӽ^l6_7/`=̘1CzW\uv]YYYJNNv0ZZZ"չ̙3ڸqbcc5p@IR>}4m4}ٳe4k֬v;$$ka(zVa=CRRfΜG}TeeeSVV>5kָ[du!Jn]~я~ARIIO=&LI&i:zyM>]7UkJjaf~jF'b(-Xvӕ]s IDATr%$$hӦM0ak$ϟ7|S+VЙ3gԯ_?]wuz5~xuǎ<-^X-R޽~[y?)HZ5x1x19Ns8 nkЭ^xxxʕFONY xmx_^.<(K-< pIJ}V2-Aw{萦?)LҐ!F:}S`׬1^OW]e6C*A ֖6`4qQblb6I\uԷoל "!޽(M9RiicP,.6l0^0gP1(9t &tHW 3$('/ryyFP!=mPc6ӑ#/JJb~jxD7W^)]qE_i1RD /!l6f.:T43gC7Hz{x!~"׷4nQ֧t5FiI]TZ yšC{,[zٽg#{5Β}TUe M=v(Ǐ~Ը[Xl(=iԞRE ў%nfm޼Sfi \{Ʀ!Dڵ˽i Fď@"`޼yUZZ┕k^[x5sL9Rԋ/M6P rk2gyF&!, lҕW555-Biƺ'u 6ʑ#s.@ 39sΝ;e˖)--MTSSѣGk*((mAA&z}-6X,X222ڽ?.B###+.vzHLLT~~*++&ٱcL&[Ӻ馛T[[| <>p$)**l^5Yӵt9Ba7!)x1cjkk+vzDii܆c?^rJJJyf >cTTTn7̔d%((-2 ZECRRfΜG}TeeeSVV>5kָ[du!J~O>o>۷ϵ~ddn6Iݻ5g͙3GqqqRnno߮ ^!-Xvӕr%$$hӦM0akl(g}&ɤիWknˆ  Æ ӤIoTfYF/{キONB!7{p(44Tvu@ziL?-pp!(!0{Gi֭:|Ο?(;VӦMs=t-n:7=UUUTLLRRR{̙3 _\W^yRRRc_7.i4at=ғOJ֭ұcR CozuuW_Ս7ͥ֟'qZtRSS}R}{4k7Ҟ=7HҰaҕW6+h|EYFK5M:9rD#FV30?Yz)/ϽNj |!l6WH11%6xr),O ):%-_p}a/ѣ" x)d 0ʸq͗K~kÇG'O4zbӰPV)(kmCo/7 o+WO?U7@H0!Cru-SUe܋ȆKCСkc@eZJ7nTttnvuוA <\9(ޜ=kǥ'r䈴}{RWg '@XZZ,ZJΝӬYTSS~[W_}×=o]fѣSW'}{h(RYik^tRMԿ"KT~+VhݺuZz-Zz}Q޽}L)BNwҞ=*+}q̼ hoLMQQVZk̙3?6</J/,[g6:eӧ{+/74 ^}~Fl$|=?.q_䪫ҳ>wzwzާnWzzrrrT^^effjڴin_WAA;!ChԩА!Cm6=#ڳgYf駟bsF/oK11Fiz-ӧYu_v挱l5t9v9s(77WiiiSVVvܩ|?v?U^^3gjȑ:x^|EY,jРAu 5~x]}Z`;{NSNզMV~= 7J*/n C}TQaÆR^n[f$t׸/f3HsΕj-mذAJMMuՅiZt?Cg믿^ח_~骫P^^|AwuҴ~6B( 6mڤ &t H$ro%6M8p?Wmmy<)9$$DڳgEt=[Zbu:_~߿커DVYjԉ'ڵ˗p=/D&q{ &e c=w֬W^meٳ5ydcHz|B. ~9dtٲe:p+ٍwUSSӬڵ-+~JHHЫhpx]f6}@k!\:!j/O.۷!%%% ѣ馛ԯ_?mڴs68m9֞]'h>'|x~.wbbUYY6̎;d2OM7ݤZk=zk.͘1Up8TXXٳg6딼f_ Ѓ1dpӵt9V;cGӦMSNNl6>;;[6MfjwCBB \Va=kc=o]^ۿy 6LK,iq4sL=*++S\\taYƵޒ%KC)66V\|ϟ}i߾}###um>?S0a&M ѣz5}txn7t'z4M |xbnF?PttzrTAAۧ}c]VQyy=d25gd2iZz۲aƱc*//O/֢EԻwo駟p=LI n:mݺUVUUcj;կ_?_78nӵ۬['H.E\v!l0qDM8_?%ofR@¤2?!(!2 @vK_»Kk֬с|X2wFʕ+~_7 _•+WXGճ>H-[Ltypc( _~۷f@@`(/k?^ В%KT]]%KT{uG!`_7#yEEE߮x_7 _={裏>R~~-[PM믫^uuunRq8 nWHH @]+'%K׸/{N٣|章@ΝSBB&Ohǥi)(׭A{C@ؿUVV&OT]۷on+==]999*//WBB2335mڴV+--Պ+sNڵKפI;emٲY7߬͛7wy%٘d@i`iРWo#"|cK䧁0''G_%7orss8eee)%%E?~튊siȑJHH۽k2gyFMGFGGw9#`|Q_KK>s_viè(ѳx[,j4嗁?q{ΝZn-[4Iܹs5zh=#*((~:uJ}ƍ[ teiΜ9~_EFe pH'OI# :%>mXP_^843,']vԷociˌ&@ؕ6lؠ`4|-]TǏСC[bxuuuжkoo VU]tLxQRqt̙gZBgXlOwo%,}S1dKCaaVZ-Wqq,vT=c lAA=U_/UV6ŦaG˹sRMf##[-cȫ5,:1$%%%`Zt:uĉN9N\\N1cfiÆ ot1_zfO)6q8FrxJfΟoj HϺpWI=TUU)1]zr- ;S .ʕ+A;o [ 6[ugH'N4.[/UUF7{$;B᪩iV_]]ZU|Akw t8^f7bܻxe]{Sm 55d<tT]][\ڵ-: iVJJJ7uc!bbb$IOnE-n*)e(~ƨ(̅^_pC!11QtXfǎ2LJLLc8p@mm6BBBZ\f6ѳʐQ(==]K.mq v3fVn+++KɮFKKKUTTvBvY}ffL&O}x- @O5,=4sL=*++S\\taYƵޒ%KC)ɴ n߾}r:֭[% ݻ5g͙3GqqqRnno߮ vi/$Гt9&x>^nIII?WmmƍVDٳ碏z|@衤DVYjԉ':&q:p!BUUU kV߫W/8$cdQ_ uzp4v-cHzpx]f6}]N-.kBVU%%%ꢣ;Nq:r ŢKFFE*222^6x]@!11QŪt߱cL&/GVppvVp8TXXءcl6KzzE L*@JOOzj|ݼA 0c W^qveee)99YC$Huuu>F>}4m4}ٳe:piZEBIII9s}Q)..NYYY:|֬YZoɒ%֡C̔dҾ}t:[J.]Z逸҄ 4i$-X@G?ӧo|!cr:aeNN˕LM6͵=ܣknl6dRmm[ݶm۴xb޽[{ٳOk̴Phhv;=+  y׸݇@@ZbG IDAT5nB>s|@IeΟ~+)(HX[]c[t =C D+)V-l68aņ6[ uN,%\.,$Ŗ l6X~n>tキuuҙ3lB@Ґ!ҢE锪RS㽴Zr8UjjY[RkoksC@lOl-p6oO b5pIeqb@!LBs=#0`^z%M2Ewֈ#\L&gI"sC!@ 294sN%''kٲeJKK$h))ɵܛ={kmVϫحXE{jc=N8 CFCIIVkz*ө'Nɓ[VN8kFSj̘1lڰa233~t6h z֬W^tmꫯswj…Zr\=U2!<<\555ꫫ];dju[Izt:RWWtVa=VJJJ7ԵXVbbb$IOns{X,X222ڽ@Qby!Jev!ɤVm;vPDD[=$IQQQnf:%LzWzz.]2A(&$3fPmm^yWnWVV5tPIRii܆cΘ1Ceeeu՝s=#0`^z%9rDwֈ#\jꫵ`;vL=NM6ϟbfSDDEwS-ZC us}A[]A{}A[q}}݀fΝZn-[4Iܹs5zh=#*(([o۷kƍI̙3\999u_裏dX$IÆ ӂ iӦuY)~@bȨ 6(88X0͟?_۷oǽnqF 2%i5ky9IREE4w\WKEׯ3wBWdd[}RRk7{ѵ^۬>))IϟWqq$?Wmmƍ^HHs,D PRR"ڬjtĉn;y'NkQIIL&6,l6B:wy9{yݱcF֬W^tmxnkwNpHRUUc4ž.2R2OҴ{CxxjjjWWWwd[ڶۺS$ǏW^R}}"e25w8n{[!Ըz׆0Kxxc,y)6H)"sXXdžKV=KD)=:͜?^R.ЃjmqXhII$)::mkmۆm#j6=EC@Dj:p!|_V|W|_V#:CbbUYY6̎;d2-cEDD(>>^4zhk׮]1ck=áB͞=X,:wfL^fڼOCt:U__Ϗ݀{عs?A-$v=ZQQQ%I:{\ak3gz-~풤'O*>>^r^uqRRRw^V҂ E7xcw6D lٳoP\\k.}ᇚ0a$Vvv:XIFĉo>=C8p^z%=zT|F:ƞ={4a5J ,ѣGkʔ)ڼyO@`a]V=Sj̘1lڰa233~Zl: 8N6&3f֭[+Rcƌџ g…Zro߮7.VlٲE7p3;ϳ23;sB.s{‰'x饗_ T~9s&Vucbb Œ1NNN8;;*!B!2Btt4111 3 prYnI:I(UͳXOOO6ŏ Xr%.\Tc~\p|+WDSH¬nxx8V+B!ܹs'{gv!L۷K.aXh޼y+WPxqիW8s  ꚏܸqWWDBḸG8i/ZVDJH{OJj,XPF3tQ:uXre]vˋaÆQlY޽KlȚ5kcǎbǪk>NuuuMC(ē\\\KXXMڋ^`UHlJH?!LG==\~~~xxxиqmmۖmRD """Xbwח *dfB!B$L`X=(Z(5kdժU\z'''J.O.]4E*B!p$!gϞˋ%KhF!H0~dNWWWI H:B!Hə3g͛7@ڋxҽ{pssÒ=B!Bd╒"9NNN9Rڊk׮ɓ|!D~<0 Aԋ"k֬DFFJ.B 2*]Bfr !B!B!B8( !BذbŊ_#ӬZ_|;mڴ2e ӫW/a+HP!vSNq䄳3NNNКBݻӪU+._p6p@FqrraÆq}6NNNl۶MGf֭T\ٳK/189դC(BosWhh(Ŋ@~:C[?z(;vٳq>dɒ@=$3ӨQ#ԩÇK.lܸ13,>C(B-[6 ,HBb_Gub9/"ٳgˋ &?vuՕ KxxxN:Ѽys&MD…)P=z :::7nиqc\]]yXxq~۷oӥK *Dܹ[.G_^=4h7oRHF@PPNNN[˓#GWǭa̙<̙VZW_Yȑ#Ԯ]wwwrMժU &((www, ol۶0}{9xWXdܻwvڑ3gN)ԩSy뭷⤣+VcҡCr΍/.\ɉKRV-\]]}OԫWYFΝ8p`1^|֭[7o^Of͸pBh޼9;wNg8zhӦON)Y$| -[$ q7NJ!B!B+ 4?#Gr ~'y߿,_@z[lٳlݺ 0o<͛C\r /_δiӸq1lْ 6LJ[.nT:u*)R#F9π`,X&M$AxlΝ|ӇCQvmƍݮ];)fРAMHHarJBCCQۼ-Zy bL:5ɓXhQm͋l,Y҈3.\(]t>|puu56nhaԪU+Na͚53:uϐ2!/1q8֭[g899W7B!ţG5kR8Hڵks>Çy "##]v/ʗ/Ocy{{CHHH춗_~9h'ׯ_= *U_dIcua޽K|ȕ+W9s&-[ҬY3&NȤI(^xX,^{?͛%Krĉdc Zjq=}GQ^=>d +Bɕ+UNgϞѣGTZ5v;%Kwlʕ}~-zIPP/^dΝ۷2e$z_|7{nN8oѩ̙:пnҥ VW;vHlٲ1jԨx)ڵ@4mڔ;vpyn݊7Ҝ[ =eBwٳg8p !!!L6˗ǫ}vx8HP! #F_~92eЦM 9r`Æ QZ5ZjEzbyZk޼y<ԪU-[KB~uY&;wdɒ\xtѣ)_<Gg{X,L8???Vʍ7Xf YݭQ}TP>}asvvC,Y6m;.{O6l*UAԮ]OOO7onI@@5jԠqԯ__RJřWXvqqN:q:txqΛ#Gm?O-(S ]vÇйsg:t@U/B Ś^i^^^]@*T@@@gώWytɒ%t-MbXCؤ(fJdddo!"=3gN@wҵXxAAAԮ]7ovFҪk׮>,'OSNvށrY~7ϑ#Gpr?f!B!:V0i$9™3g:u* .cǎ\*:t%KpYyX,I!CPht=ݿs&E !]t2e`Xu, '=ŋ>///=.I*MaÆl߾= 2Aw^ ޽Kř:uj=ɚ_{! IDAT~ɓ'ɚ5++Wfǎq ܹs3hРt=CVQt!RFpBt'ZLŅ"Edx q*V>)_|V^{ Q!B,B!2#tK:C!B!2'Ndo΅ > bŊТE YD!B!`` ;Zj.\9rƱcؾ};waۮ;2."=u5IBYxx8fab߿?ڵKrݻ2e C ɴϟ`%-Bhhhewŀ8x jՊ[e+XBd';BޤCRFO' 4RJI3dGPPPSEҭ[7[nf*B$Օ{Ca\]]uL2 jϨ(5kF.]]b͟?Ν;o>^z%\]]\ݻϟ~1a„QQQϟ6m0cƌd'B!=n1W^#44zQX1t0 Zj+M6q9z(=rqPBQtG"Dʝ87DYttt"s.]ܹs7o/^f͚̜9-ZܥJb޼yVP瓊)a;644… (֤-9r$c&?0`h<p4/»Шh0v,|-- 6RA?G3e022+V0g6oތ':ts/^<ݮA\gϞ`.[,Ydal2v{TTu):xxx U9aaСH4Hw4‘AV #: ={B2p ̟/D >C&/***~LSxgر#Y .0vt ?oۺu8pov6www֭ˢEݾ`iժU9?2 u3ՠ4i,.^TNCB~D;h\wpr'lߵkкƙ>>ԫWOO` +!u:ԫnFhA~/m-;"!g`j[;*!Dz3eo߾ݻw85|jiӆkײqF߿'1"^bŊ2p@K\ڵ+Ǐ9֭9uG#m5ܠ*F$Df}ڶUݻW>fK;ݻÅ jUŷ aL!|ܹsу[$v0X,Zk31cX}|5ؾ}{Fd_=???x5M5EFW^I=~0?Sw>́(YPD&8p,n"Fjl#2'0s&^ڟeUdafl-iBSwg͚Eݹr e˖Wkr) > ݺ%_RVDB. `θ<&Gh֯]{?al??!\eKyf78s :ufXo1zAAjuG#]D|mٵ lpU$!bݼ>HqCعsg*Vȏ?(EeTDtү0U1Fi*έRܬ?vvn]5-7I>CxV^M' 1t1c,IYz6hĺHuZaڴ'2Ct4UZ3=HL!]6ھ,G)NDy=c{Aj|N [uJixq !l;7O>=zr+*asը_hĨ6֫"#:FVE9RK:"^|ʜxV)M aL!޽;cƌOʘPu)-2*FV#6;v#Rj.I[Kڟjeu a~Ad-[rǎGa~P G $ukZ 6mTg-8X2mIsOtQI'cwCtTi G ۵mT U¦MBu,Yb.]bΝHo#G/.kIʟ:7ᅦjRwi??]š5~'ҋa%5SU2} &u]!C8}tJ.Ή'}6֭ߧRJo̞JSG{qc5:تh=[zooݻܹ*keK !tsXz5SNeAٹy&W^@tؑcǎ;da(*:~<

\u˕qEEA^a(ZH?Я^v4j;" Bj~tG"lݝ;z<'͙#Frq-ODXZbQ_VU˔#G wn9h'x SER[ʶۿA㒇"#  d樋|~ k= 5j/AݝǤ2 5@PP mzTEdrHرj;剴03(DJݫ*Z砰n*ǎAP|"BŪU*5`bݑ[o0uJruG6eLVUuG#D\VANj޶muGtLEZݫRDkք!gN9.uk'OHDjݻF.}DJʨx>P1x|Ccrs㈶m6m`Rx̿|~L2$~E4"i" CU RSB^NZ’"j;L!4ł?^Oɓ'qEaĉO\.\83t11jgHx;\ٟKarg猽:KAԦXK<}x=-[]wDI+zz1d+߷tR.^Hddd}+VTRΝ6K<̜ J~B$&$wWRd\#΄nE Tegi ͘˖Aں# 1:K,F8q+Wټy3m`5{qфgBD\ }d1cQբC)![x~M  UMt=sGoؠ*KgvC8~xXf Yfeʔ)_jՊ^[\aPV-quuiӦ>}:cO<rӓ#FѣL8J j[Zt93gP7{8>gCjJ9wjԀyU5g &؉̙3RYf%<<B>}]65TpuuSN[s&M7<쳱ǖ(QڵkS\9Y|9cǎԩS㏙{FٴINȸk[P}i<&Cڐ2 UU[<祦=uꨩ&uء}``yp`͛wr1ʕ+ǭ[~oFyɖ-^&MP~}j֬ɸq6mZ쾙3g9Gve֬YӇjժg-2R}}oLqC/_wrrbP:'{xxо}t9ٳg)X`ǝ9scK F˗rya֩ћC@S3v1h/rtG#o6L:Fy5SoݚСC䀇H?޹szΝ$uװ?Cl[ny!w%[ld͚5αcǎb㓢뺸$!aN5,$Dw$VZv|Hf] wށ H`>h@Mx}v.6]zsvv*}9 uKhh( "OP4ڗpUЭh'_!52jδ븎UmK'57u7o*eצM֮]ƍ>2bĈ8iEf͚ZWDҥbI3fD;Dت>S ֎;aOUzѢ0qhPSk Nd_AḶnUEc&NTK3]7ߌbŊQHxapҥ 1c0f̘dbɒ%Q_U5mZ[K؆}2$SB*]t*lCHԫ ɜAaV>RK4j;^LYX1nܸo{XXŊ:Tkj]SRV#<>J")aBU@f*pu~9<˩!ױ,\RDWΠ1|ڽ{.g`XNU@RN,޽ЫZfB  [p ^Bݴi0z4l*F7Sv ba>88::?EY0OzxFآ|.C7Phkנn]UEYBg֭PhDF0e!EFg͂BtGtNULƌٗ۷UF`dC<==8*2ֈ{P$e6EGC%3!I)Yv\.]R#1K/&U#۶kɓ"~HgqC~z֮]Pґ#jES@DY"=<|-[9<U8V_ yF{a8$P2"3vp)Q)GD>03À޽a :*SO{gĈܿ_w(_\ٖ*[Vw4ɓQsXP},^l_Ed˜탎ՒKJFigMtd!4igΜ///\} {v[W% 2s䈚ײaZfBΝSU|Sw4BbZZB:f͚!}x=ݑ[j>MS[q.LժF87aCS7̦NUt۷hNe9r)7-)+qjafQeСx{E|~Çᄷ7 ; ׼Sl ^^ڙz![5k&,, PW\}s"k#櫯̟~;a/ ΞmpL]"Y-_o9XS9ru;wnΟ?O׮]ɗ/+Vŋ,X@wv5^'=NݵKREEطOUݵ1vr0 = *F Sۗ;r)?QݤaÆl۶Mcdi(X ZǩA2mhMH[&MR͝[w4EگmO?Ao>իW5Dd-S#AF0*Uݑ!-^{{@ڞ:vLcg(^\w4Iگ99N FSweƝ;wm?y$vnHx0{wʕGK!W.GV?z;@FI&q= *DDDo&%J W\7Nwxv0Tȑ#!gN$LRFX.\CuG>  X6~8l_ZAb0yd]ę:+wlܸ;wraݻGJ[ʕ]Dؒסo_X$D hU9{0Y}E x ݑ{t)llמ=*m9( 7~j;w" IDATʃ[ <=uG#."BݰJF8' /g0hZ4;aFL}CdϞh"-Z4}tbbb=z4@w%00?03о}{Xti0-6jdU`裏tG" j>ܽ) 7nܹ\;ׯ_vTZ^zѫW/*WL:uq㆖6mDRXv-E!W\ϟ#F`wVTTTpvvN.YARν{jtptpu0eT#GtG"Yxz*FynfF*̢gOV/Swk׮?# ʕ+Ӈ:uJb޼yV8pœ>}8 *aܼy3x0 w؟ZOyȑ#L٩1cS'(R$E R:c U/{'m(c]F,I\=zJ|; WO?U€ݑl!F;| M4ˋ"\te˲hѢ4Ãۧ=+W\r//W\bP`Aʖ-K,Yؿ?-[=.**CѺu]7<<Ih۱ƍݻe (qL2z-4ho{}I5:دHNݣG ?h2cЭb֐1 Cu[s:i.YRw4žCؤIV\o/B#GX<- dxҌ- DZ,\ȑ#l4pA 󊎆v4D&D:eL27[Rzu@!ܹs'믿=#LLɓꟙ+>MRVRTk!%+횴v U/ٳ߇QWtGcf͛o#DF3uܹsC0{w ,]긅dDYRU;vLw$‘ … )Vlaݑd ){m=PU}h٣RBÀ?V8d0~WiTb:\?3>>>ȑCn߾5Gg;Y3(VLw$B_UKL ;afХ |.;~I& w :T|tG#;cǎc̙*#K? `a׮]cΜ9X,ovM.\1*WV/{')+ISYn"m(yKٳ/#/w<7I}~SU}8(YRw4B(4ԩY&ٲeSwxZ=|&/Y;'#^IۺUd IJ͛:?@캣8U{wVMw4ưa쳪BBСCO9}4ݣL2̙Swh͟DeJ8r0A~}5'YWWDٲEÇ3-l;e͚2ef|Hn}s'X;ᨮ^~TgPFݺ:\53qD6m׉ٳ"krpwݑdE0$ 9R͵q23OP"#> g=];x_ jA˖#">SwtBPP~!XdP$ tG7lZbjUݑ3{`ǎ#a~8vLw$Bҥm9;!fYv-0{#:8˖_D5LB0p j˗\~aLH7o^˧; з/d1uW?$e?11*5eH(THw4!m(kARoׯ*h$l=۾=hH 1;}o^k"sG Ho8wNha}HDb&m|ihe|L=B8i$6l؀ʕRJq^:RNɃ;UTaٲeqUNNN^ 6L5Ru$qݹԩ;aVaajモBw[7D aF/s!G4S6kLw ;w.]t~L0gggBBBtR, EaĉO\NE=a*8}:ᛒ(jɷHϠAJqں#q :TjBw$"9~{*GPhH;#GB<.\G1ydϝ;7m۶MuZHLGRV3YjHώr9Yŷs',^,UE@o΅gf`->}:111=Pk%%hTw$mԚ ȚMhQAw$Z~8UkWxUa=S=Yfѯ_? СC9}}*T0yf)Raaa)5QǑ#G2jԨLjWWؼ**;s{}{wݑd;wwo3GҡJ5BojYG'ž=dԎ筷 :ZU}]3k\S?͛i&^}Uz|ٳx"}I=<͕+W~, )wd{ZxxxNv 3juG6oViffvu܂<Æ+}|tG ֭뱘52`<2矇ƍ\i+0pDST_X‰'֭)Z(v_qZbjݺ5K,aj.ܹsɗ/+Wݻd˖YyرcX,š )*Jͳspw:Vyl~XH͟8דOw=pO/U㏡jUј3κpxFEE)Vָ6mJ:u0a7nܠ|\]v1cƌN[pp0m۶m۶(QVXݻB Zq/gᆪ6?3!!:HF*"PpP#)*X.ЛRA0CI&m 3)d̜b̼C߳ۊbً/*>A-Ξ =bKr޹0L3b)y}5@F 7>3DEE)J_|N͛7?D L͚5Ѯ];|vf35j{Æ S0zm,YŅdÈ wLj&HLT: yl lt4TF?߁X%lBzŋc^SV-lڴIƨH Ŝ-D?@L&*pؠްAuґPICUq DiNիYfhժUcƌQ(2QnHU~EHE*?61z]ǎ+gFRI_ep6l@F׼P 5btBvZ+WǏǝΙL&&bUvXHfB?? fdJGY|:+ IP9=xy-ixc˘5k\|C>g#7=4#6N4 UKh(7==ɎzL|HCEGPQ'M'+Wիѿ={Yf=z4B f7Sy a) GeRR˹ bz*JzjsUVR "R^oH11!s=OnxC,t4_ɓEs) y^Nᅨ*J9ʕ+1`""qbҥlAq^W:*^<(Xzk* I4C8a̓dš5kwߡM6~/_FDDR!xC:I,@2wbm?.)5YzhLN{7npUQ?%'OtV?p $$!!!8M _l*mB2r[bF=JGC#}&̙b^R:$՝ǡ܀Ns ݻTO GJMV٢!?kPކO4iy[Z`6:bOBxjX%CEM;m6m6`0o&h.!$ʏ)$'k?!\J)ǏͿQ? 4 qVt$%gCs5k* yZ@HRS@׹(t,[ wƍ7tĉ EFJ([{REH5q;} }h]VXlzu!Ww({,X O#G* lGBĉb5Qh:!:t(;P JGb\Zo|p!zVR~Lš-G71i~ᇑ:uN瓒ҙ3bױc\RIZ-?p{{ڑhyrVhLO<ѐ\…Ot$DtBؿ$$$`޼y 2Rv>e PV{+ /ɂ]!cr=uJnqi:tC=t(Z|¾b97j͆ b!͛J"((8ѻr(222֟9@JGCM' 6DZZaJsZq0}:{7JԠٴ3t71xUb:3S 3/ V ?>Ə={௿ݻwhiȟ&6m9h\h+[≱HCExBhؠ5  acY~֬>{fi_{Ν;;l0LV",RnH11b[$J^;w0KL|F#!wRG <4ioFrwtX4޽[HEʕnR:]. ~)t4\9m1Hͤ۶JGCP6/HMYShH ʖVB8f H鄰}JGǎw|y{{##xC7ɓ'}b޼y`+BBR:V*'Z({う CBKw&`NVyBؿ?{=oOPjU|G]6W` SbN, Fnݺ9C.]иqc,Y.\cr !!͛JGq}>-V$Bz=߀O>Q:r'4BG6-ԝp@p> Āp {۝;w0o<|ײǔ{># t|(_*Gq/v9^lYܾ}[y&bccѿ9<駟VZ9]͛ɓr ROJJGl*G_* ݏ{gʔHի+I^o ̛ܸt$F~~b_++M^+V'Ns4CXre?>N: DצMghbb"L&LXXjiuQcy`d`zmlg`djur(C^^JGC DK+ ZoR]r%W%* >XnL&^Ç^Cl6dfftm1*Vg>`ZZZ?k?OERf&-[* Fmb-jP:$5>{?hXhHXD9&i:!8q"rrrйsg]v(]4^{5=įo>tرL&Q~}/^1ck^ihm( ==ix+o6enݐL3Mt$  2ؑe/FP:R;_xMg) $;;999_ۖK d”)SHIIAƍQL~Æ ҵ d `ِ\bb"TRXo1cƌ"Vt|ne|\֠YW#!9\)ꯣGBup ,^,/kٳ1sL0=AAAҥ 6l؀({_~=, qw T&2JgCXh*WRRdeo.KpޖT&Rڭ[@>5mt4ժ JZ^v"2̴&5dB5kElJ 68\T2w\]vxq,^ݺuCWnT,ժl s?\8J= @ÆĐ o/&PC-ؑ及Z5`.^{MLӘ;WHC /lܸ/^/|Jegٰyfj +-Z 66&LqÇc7z*6T~+}q΂vIeHp\PQ2/@l}'Aڣf \gغu+֭[CgСCOdV>>>Qb_z: ?ŋ@Gݻ>}?sW@˖}"#cBHbwOS'U+1܊SXGGbi DY"*ȣ?!|}`8oسB/l |x}"r/&Kmڈ5cGǁUԨ!Vsװ>Ýy=IպXl]-O=ԭZup*`x}KDiKԶ؜>!ds<ЮЭlA0b+#"*c?,F&&XL, ÞACb[dcpgyQ~ؼ%]:w.~^bf5=DW\99ɢA /6ҷ2eăo-k^鯾`ݺ~jUWF0xHDÄtc1o י QbSbb6%G09DOW^T($غx?{x{:]{c#n+g&-M YS;u xaܾ}@ժİ˗s~+@tlz$aat22O?[+z Go:2>V?۷ ͛|5A"CtG\ѣ0M (H O9z__(Iʕ s!͚ g< |g#z uWz_ǎ#G/9ĉ.*ޕ+K+)qȨƱ;hns˗{JE zf1Z5#"HK_">0PQ *Q#"cW>L5_""""qRDDDDDDńȠB""""""bBf;vlO\*w!~i/;;odggcƌ,+r  m\pQ7۹s'_tb`ĈѣK;~kR :t" n O\\ŲBEBbW>ܘ:wXLL `yΕ-[x\DDDDDDqȨ bbbPL3ٰX,2GEDDDDDFDŽn޼X_+0L6 YYY DJDDDDDF!i&dgg;\nݺԩ6m łO?ssqF%"""""#2aِҵWtرL&Q~}/^1cƸ zjƺJ|jjjטfxyy_Voay!WPQl{Nj۲GÆ ҵaaayd2^p=WHJJrzKTbE߃- @HCX^U,+T,/䪂Fr&bƍ'pܽ{f&)kCHDDDDjsB͆><BC||>>N̙Ʉnݺ>f%HNGL=dÆ .z Gݺu[1b7o.sDDDDDd4\el6jԨ09r$k~wL8Gŵk`6ѨQ#6l1!$"""""2(B """"""aBHDDDDDdPL !A1!$"""""2(&DDDDDDńȠB""""""bBHDDDDDdPL !A1!$"""""2(&DDDDDDńȠB""""""bBHDDDDDdPL !A1!$"""""2(&DDDDDDńȠB""""""bBHDDDDDdPL !A1!$"""""2(&DDDDDDńȠB""""""bBHDDDDDdPL !A1!$"""""2(&DDDDDDńȠB""""""bBHDDDDDdPL !A1!ܹsa6ѬY<Ξ=ݻ#00*T@DDn޼@DDDDDd4&fS:=KHH@ `6QV-N7o`DFF"99 .D͚5q*UJȉHqxѶm[dee᯿r:7w\!..UV<#ڵ+1l0%B&"""""Qڷon݊~;[nE= tǖ-[ `̘1>|8|<^7nsu8yaqȨ\/_Ʈ]= s.,, IIIZhDDDDDd\!􀤤$L>ӦMC&-- Pt<|}}!"""""z)SPB5kyΥ;]s?999X,00L^c6JDDDDDFNNNl6rrr}XĄΟ?իWwABBQaZq%هJCG%&&|. X, r """"Rw"00P0t %$$fa̘1=ztuAdd$/^+رcy9r7oIOLOx {j" sSP,/** rtTTX2`BfM4m2e RRRtRԩSлwo_ 'v܉_Ǐwa&Dy&XV(X^Ke)Q>&fS:#ر#/cZle"22x뭷PF 9rĥ/IjjIB dff&Lby!WPQƕ`e Gjհw^ԭ[&M[o=zc%IDDDDDBrRQXV gِt`Z[nq9DDDD2e(+HJJ ð8dȠCqR\ c61}tr eh_Ξ"CCC <6|8P8wH,}( }/ƕSn""""""bBHDDDDDdPL !؇~Xvcǎ7nG]6.]ҵ|DEEaȑY=j۶-mۦt&DDDD \aro.ҥK]¨رcx]ϐׯcҥ:uؠA`6/B+gXk׆ڴiG::u*&LPtT/DDDD  =+T___ǚ5kjժُL&asI:*++cyf?3gɓ'C[nyz o<B""""*ԥK`6lөS';4nxꩧpuzoߎraƍck֬Aƍƍcʕs4ikܼy>>>8pCFܹ#FrCf_i&sK.\2͛wߟ?pڵkԬYHMM7/~&88ׯpwet}4i___Ԯ]/vzڵk7СC5kbwɒ%1b"""аaCZ XnSO?46mt"1!$"""BU^׮]Cbb"]'OB h߾łE!&&˗k1 7̘1o&Ξ=yaڴi裏 ȓplڴ UV?=l6wÇIZ IDAT?F||<.\//b[n?眗͛e˖ի SOO>8}46oތbԨQ2i$꫈Gnpq/N>3g"**ʞHJ/^GyqqqxW Ν=V+?Ν;ۏL&tvuؿ?ɫ`Wf*UdddgϞdeeCZFٳ }w}SN_DnƌXh}Y@͚5qZ D߾}1vX| N:jժٓ3}).X~aI)::3vyUVNoO%8wl6XӦM\r7W!''%~-䈛ݻf ~}`#!"(U tkӜ9sc=zN缽d29%iٲ%N8k:%4)))֭[; 22˖-f͚ |E !!! *{O s~geq=Xhڷo///,[ PR%TR.\?_?1blܸ/R6k Ο?u-@}7|͛7G l?3j׮]VX?wӢ3@+6jt:vԯ_+z{{UVعs'z @ܹs'z}ihѢXCaBHDŽpOK/ &QF"bo.[.vލ;TRXd `̙DPPw ;v oƫ @ |gg8ڵk'x{ƢEPn]={fO>dbT9p=AO&M0`O1gm۶=z4 9sSNX|9ڴi,L81z1Ǐ֭[cΜ9ׯ:+V`ժUqaРAhժZn%K 555OJBRTVఇ"22Pe"[,AiǏGZZ̙*UݻدKU~}ܹ6m믿:t(֬Y>͚5CM0`~Gki/[n#<^x> &Ll1PLÆ si{Yf!'')Mb޽8wڵk-[bƌZEzh׮^|E-Z`˖-ؼy36m3f`Μ98p}þ}⭷´iТE +گIHHÇ1xB]HY&[aIլV+|||g;> <(`УKZĪjJ{([HNʔQ.w2k!Cc2&lKE1o0e ybAo0)))ŞE֦M;S:U8q"n߾]`oda Oq}=w{>M Bu;}ZH-Zᅬ,O,Q.m9BB*'+jh!O`Bn^Ku4k ͚5S: ;v!CHdU !>cBnD B*'jh!O`¡n!$^ ""6Ii&߿.]Bjj**V-Z[nݻ77t'Rt`BnD 'N7G}?燤$>}SLѣoW_ebXBFI C5ԃ$ŢtDzh.!ݻ7^{5|(W\>|-Zɓ'߱c={G ЦM̙3sٳxWqAgŋ"[dU ɘ^cL8ԍ*%K{mm۶j!{,XCO>h֬]e˖e˖иqcb'x?>pB>}GA)M0{B< +N cBHDDJ\˗/Nj/xB4~xlܸ)۷/6mcs"-- qqqZ*GA׮]aÆpQmbBn$'Gҵe#GoHvz\,:tL7xz°aЩS'CkӦMcuŃ>x[Gd:wc˖-HF{Ր8u$z(/Ʉp9锏eLݻwܹs8p ֮]ڵk{Od;x?6l@BBy@bb" ,,,ϵaaaHJJREREd`)QCe܉ 6GATr `@dBN:5k.^o*TAb*ٳ5jHKK|uFIRbP ĄPD X7|tSL&*U & 6M=lׯ_3<`|'0LD'dddtk\uk6U@J,#Ԑ•gJ ;IVQɱ~ @m$OFN+k-oo2^rfB:uеkW\zWTݻwѽ{wܽ{~-*Wl?' /D/_ȫ'?g.g"RCCH 1>^G#4hCH1ٳg~-BLT2r233uV[vBXX^z% 2uQ:rƍXJJJIAAArdw)L&|Wꫯjժa޽7n&Mo{KB ; lYʍ_KM/bԨQسg}uNl0LV{nmԨFS2FCIoG# }$~H>r^|El6[m=j>^1!$ $:1!$uGUΩSpXy\rE0t=ڤϣ7%TTp:)uEM5k`ȑHHH@&M,ҬY3"9*"5*&yDou,$M''.\ۏL&Eꭘߔ Ƅrw$, HoR/M'C A-qF.*r4'ɹr P:,#K./DݺuECIoGoX&H²@XH.~FݩS':uJ0t( ކdߔ F%,I8ƒn hƎ~ M6ͳLϞ=L2Ʊ*\}rc RyΊ"OtB8rHY2%gB5Nz hn޽{_bٲe4i ___ܺu ׮]CHH ӧO#44T5(s;Cި̓(r9$%wV:0ʐQ݀oJ tɅs &jժG6mtXmNF[/Go܃=H@r\anǎÖ-[pedlݺU\K/a֭;v,֭h<سg{1Ec"RCX 1o ꦷF%#=aBHr@rtMc!>>۶mjř3gk.-[V ul޼1l0ܹ5kotx$jH^; oHXp޼yXd +wٳgѷo_ԨQC 駟TR>|Xҥ1tP>| F'oBOo t@MoJFr!$M'.\3<bdرc+]P~})Sx֭$E$5TvzzJpO$088U۷o#55U\<`pU(CFvVÿ)LoJWr@x hzQvaǎhڴ)Hڵ ;v@ΝPiii(]t㾾J2JBI]*>ɑ*r@rtB|rLooo:t{ԩSp~~~ȧ.}&???_zVlWcjxQ77*~_)%GJGyڶ^N˗/ooٌ'*Mх;,411PJ_+ sӧOnj3=MruX5y7r+9by I9̞=3gT: \Bx]~ԪyسgRRRa2мys_beM5ʍIo t޴#r$m=QQQ2eJV};<}4Lpp0nܸ(Ws=,Q3336mڠjժ.w3\TRsBlLI^I}%G,$1JYo䡹]vه޽[hJuӧ&MׯnݺƥK(jH \f&P~>=Eq}(3\0x/'yh.!l߾k׮իd29]cpC+>QQQذanݺf͚aW:47TЙQPA22MIIIeJHKծ]T$Ԯ]jX,X ,P:,`2!ɡLjH;I[y0!TtC"gz'QIBbsNܸq9o E}ҪV|eoF`6s+5*TP: R i1$@ER  i:!6l݋",,,Gx}f!!SC Qr$ܾt$LI²@rtB7ߨfw#HKSa'pCLyHo!d8,~ IsQ|y%967WCe'Še6.Տ#~gIbAz6ٳgcڴiHMMU:ݑq8R7?Ul\[NXʐH"##x?'yizEpVZεʉ'LH6dTظT7i&6HFIӁR"Mi!utӫW/C-"mc jH;II6oꔑ!{\$ii?) T2|KrtB8}tC-B+;=&~~oRҸ j'z}B9pmY&MBRR1T4!!AȴMaL2~~nVQq 9b@rt?.]lٲ1|p/_[n˗~zC,9/PPULͱ > %J&-  R: 2 M7 ¹sP>طoiBcBnl3~gIQ31&9i:!uԪUKm'&L[nO>W~7,[ ۷oG\\*Ud6..]tAƍdXp!Ο?۷+1TbQ~8*\1Q7\Y(H-={l6&ӧjժ~WEm,YXnо}{,_fͲ89ޓ#DSCI.QQQ2eJV};<}4V^'N@ll,Ξ= hԨ⩧Bbb"ك:u{]PPt 6 **^ׯ_ł}} LJ͒CƘcBȄC}V.G))d`V{`⼼͓;l'r$j+q玾BU7%Q.QBwEdd${9DFF{u( {ѓň^ȖR!Heo7`[LI'l!6ǀ)B*`ba鞌_R]23tB!oL)?6P~IICFIĽ"#JژR!֮lBRX8E,)$OL)ݧn5j#nql !b8\TʛX%WȴZ 'Y>JJK ޓxV/^H O9aÆClڴ G\N同AwE ` !+|dtPQtbb&C޽Hdff=z H2edu֜&w.+rdgs'UzOr2 xxH A d N͛+Wbpvv}>00gΜ02zP0wZBT䐔ZBy_;:J ݻ_IDV GG KйsBWT > "$rH%1!7 ~f9Xm dmNkժWz>""~~~D|OńlG(HN% F@@@RPtB8qo͛1i$RHX@ɘb$V.-!U+߬?d$FR(!)(zM)S@ףGjܹ3\]]1i$?^)1{@GIڋw&M͹(BʓUyUD[(H. ^j4L6 ~!^TB RXw1Y'C_w({-!__(H.CF*4m*u$z=p6Pԑ(:!4rqqa؅D੧Ko_;wٝ;KɅW|<гQ$%zQժ@֦0-- -q]zׯ_.Qdu.жu#1QÇ{|3}NɿQԫ`W|>>>Tn֞ː.]y[ĐU\tRS&rEe(XQ#o =9½{bϞ=z;bhw02fkv#ɓN'B{*sT>=Jn=l$%u+|lANTRU rVr,}}| V.I^wwre#!c! bĊԑٚ¹sb̙jRb23:kJ3!$۹yxiRsY!V'9I>*>):!\d ooo4k [6yAPPЧO;v ;v'|||4G)ܹ#BfKn~r襴$.P!oƄȈ=dtfᕽ9X̕"[P_]u)lذf_BϞ=0aŸz*chm$-bcKYS Fk׀ 偤p֬YRP9f_:u*V___#<<=m1ѭ[oGI]46Kv xܼ)}'qC,K>'Vk׸G) b􀟟>-(zȨmܸј??~p ><7#F;vUlC(nׯ*ٕ+3Ϙ>|ܼ JɅ+5PғySᐿVC;v֭[2y=99YRSS1eL6 5XٳA6mLwvvF˖-iPMYrh[d։˃@ZjŜHV(e&C8XQqe6EΙ34h=z'7ހfϞ-i\x<&!!>fv۷YnY.5HJu1ѱkJ^&[7¯)7"aoHHH.CHF= 9X8@RRt͛zj+={6 y?{PcQ\4˗tRl߾nsnK֝CxH8mJo_QE:ĒKyt _ʽի=ȥKl<$(Ν;h֬B xW_*{\|XHcǎ%j\y433kELZ":T>9ݻvM܀ByphD(HN.\76}N RT>Y銭Òm(nݺHHHSO= `hݺ5NgϊI)펣#ͭG6؄0;;ƍÌ3PڷoKSݺuQ~!!!.Ο?ܹ3PzUl3-:ZdMgZ`I/ߥJ#!s5_YSBR{a'@|Rb"wIFh4hLkժ၉'b͚5 Ν;md0R{_ӧE+ѩS@˖kމb7Юx} [Pl!ؽ{E Z׋|Ǒ#Gly/CZb!)[ ܙ3+$OmJEJ]T? 4̜8̜)ufNy|8z(ڴiShiy!T BK m;wN{'OQG?6[@ ϐV u$$'NcJA|׭}?.zpXre#!sKoBDDeňXd$P"kuݏ|:w: R;.*S`5'_].;}zQ'Q 0P((tʊ Ǹ{w󯳜Kx8Ы$CQXN]pssfR.zJK\Ξ5'upõit+ХQ\DD9>>RGBr)u$qqsRGBjp̙ k;wbΝx0arMfNwC2jW1jEBKRGBroлQhbGRGBrphקO4oǏE-H22܎A0) t~zÁ6m!\8 >OkK Ɂ%Go^]PI#fXa.J>偬M=hkf6m ''GϺ;Щ_o.Kg>?} Qsh }_13))(:!>|8VXQUVa5wY#7.Nlk!|,`nOўdd2ھ]$NŌa7য়AolAqCF'NF5k~_Vs ܺu #w?nd!cGzQNT-n]_HȜ ^lp3Ľߺ{rl $uDHip5@QzuDGG<6%:~1z?tH0O<=;GҖl : *ʺuRGAr+ 6 : 7B۷K СCR`7bcGt_Ecw0"9Y@-ZT*͍uԑ\\ 8brڷWkwK _+x^ZGFX%kqֶm;^uzJw{ YuK_^RHH>>Pcy}oZ.ԑQtBe˖СC{.zgΜ(2ez*4#ЧtU :|{t:l=" _I ŢEz`FY#!9XZl=Rx} [PtB8f߿Gv᧦ [_`<띿$FtNѲ,|L*ڢEb/ .S ,x:ZFx1yԑRtBO?F``ԡ(ŋ@N[qrE&)IVvrsGrkonVbt1<û~]&# 6BS$?Nԩ///P=Jגu_+_[4(̀+7ɓc;HbKs3fow??#!ݾ | Q]^1%K0ydHf _g0뜿$H"ŋӓ۽8v 5?sjդ{hԲ?@@¶m"##~~~sϓ%Lص:?yRյu_ vyK=ԩ~[ZڥÍ8>͚T"( s(6DE6m*@pǂ EeJ駟JsիkmgQtW""8\R~t:`Pċ/J |DOP /H IE`̈́رc8~8Zh!u(k%'}ΝK ݻKr|9iӀT@AW7zr7nm^|MY`Ӵ< mXx} [PtBx!CP73g3P/^lWJ [bFժ}oKZB.+PѐQF{Cѣo^S¦f}yFHJ׮;SMlE a.]HXp!N> ^Fa0`qǎÿoDFFbŊ8p ,XO+,E7bH5Vڱpwކ?^ :m >ɠت{˩^/Ν |0pn]]E#ARGDRھ]Cba!V IDAT6(z!9rÆ ?xM!YL֭C޽₅ >C.]kr\TTz쉌 !((V@+2DTpOl``L_l~M̷S7'׿U#G ʅ^/i#zɠ/$9v )lm+ Y{; >Cř3g x,X1wEHHBKXszԩZ*~A___#<<={X\;w;t)sZ%&NgsLTlDVh]f!9-Mضje ^EbܹQT.^~[,(bh5 y5ih}K=ʕ+zj83gHӊ+1g@ZZ?~p >dx#;vX,&A̯0VuBCmC(V vk)DeKѺ̛bbÁ=DZ/Tbo!ysEIYm1瀞=EZ ˃h`_@l3a,d N/]Ν;zRJxD&MgԫW^^^VfΜ C&g"''mڴ1ygggla ) xM2ׂbuk<EG}-#Ѻ(ؕ+W嗥HbcEY [TIH z=!ٹI4raaZ3&[5-1G56ީUT;m 4h YBM2z=zVEΝI&a>Çѭc4 .\FZ?M#s$3b*RӉ9^%.^^ϲP7o-1FCk$"ۿ_,r?^|\HmMw 4Z-ЧhQ6n.1g 51b.q}ϝ+V\r{/s]V#F(ϴiW^E||xh4ԨQ''':u =.;;QQQ4hP7--dU@w$jٵ&M0vdӚ-V~s伽…w߉=Ο֕:*u=FWWwo1T#$[2A8tH W?%ը,Z0Px>\"XCB&.Nppr[kWy$j2c L6kvx(2!|뭷Ju_mH 4hmۆkbܹ\u֡jժVX={7|3f7"--̛;;;$YYb;,(¡Cbxͅ ;gQ23Ţɢo2:tHXǍ 92+8wNDDɢwo`t"R^{A =] 'pA5y1q_ ] 8+`@! >^,֫ջ$ȄpEVLr}GXp!ݻ-Z`׮]8vVZe͟?ܹ3Pz*W;w)KѧՊE8>Zr5'=] E*V4,] CRE^/ng=S4t(*v~(P"CT~rpx8!v DH^@#Nc<-w!%E ㏼$P׆n=mC Rd֭[q =Æ CUkg(e?`ؾ};6l؀ƍcͅiժ1ydL8^^^ ‚ ƍ?в^fť=^^-M1dZQ7O7`VNgΈuk=Հ[te֖%KX1*/>ۋy_}2Ptqm{=+6oF$#Fm*dwHqhX\5R`y SdB_"44=k|GxW0f H@hh(BCCK<Ǒ#G,Yv#'Eֽ>|(VwSGa,qXeb!RG<XʕHLĉ" [W7)/Aw(/aQQⵖ-ţo_0մʋTz:pR5QV⫽dyc0̅ 9шؼ(/L,? ق"B@7x` <111X~=y ::b+*q#zK%SYYbHE]$%)Ik XF!>~,֬pY].{^b1~9oii,?WNlD$|ݻ{^ 4VBC}}kZbacPll#33~įS!.e!>۷żzmPO52:@<]1m8vpe#lC ;#m۶^zx뭷uVTbx@lisgK!z"9zPF7k} &~~bgfx7ЫJMV"n-zjZCy큓ih_xiV-]u? hxP پ{#1Qno ʕ\VNN_?Q|}ţn]QW,5V>4}W_}>>>B-l2B;v ;v'|||RN3JTNٳgPPiPYPik;`:{r}&?[DDDDDD„FnJ_&_BŲBeBt:۲yu[n`}LѤI_T~Xݻw/}͛󃏏  +!!k.?D5j(D1"*   VQ #Ybx{{cĈeDh[rrrprr©Sп㢢0hРR'RRRFcCh0x`ܘš7o\|UT Z6ڵkW/.]-k׮Epp0~K߃B ۲e ???[l'0|L2%HiӦFll,BCCѵkWDDDDDLXp!ƍwرc {1L<gΜ  { !J9HI !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńPbYYY29GRR\\\GaܸqUѼysϥٶm^{Bj‚ tEHHZmM~J*ظq#͎;еkWxxx`˖-pssCjrc…3f *V___^x0n81M4ʕ+ၯ$_~۶m+\$=&DDDDTzΝ;HHH;wjժK.Ǥaɒ%ؼy39[naҤI:-[0tPlݺl޼g… qE,X3gĦMC-pl۶ uAǎ `/Ǐc˖-p/^ GG'AaȐ!8w̙3f&Fxgwyo6\b=qi#9F={&ǶkG)AzN IDAT$uDDDD$ G995k233ѧOb֬Y૯O? xw1wӧOO?d͞=K,A߾}ʕ+1|p 8&LѣG& eASpE4hrc}֭[ڵk}o߾hٲ%f͚emѢE6lƏ]bʕpqq) 0a^ٳ'N hذ!xb1"W^y%wAɓ'#,, 3Pre@ѫW/l޼q?UV} uM)SOУG=Ξ=o&9q7n\y .\0I1/`ȝجY3cjժU߭4ݡ둙 WWr !L89紤y8y$<==M^s.yj4([ƙ3gvZ&55Cخ];?sС e˰e4o?̾{_YիCGUfN:wޘ2e FeZjj*ƍBSOCvvv)(-s^7{lXh^ebb"jժe\rr2<== B""""h,ߛgI}͛_~P'ՠA,Y]t#-[Y&j׮k׮g?߷o_7{֭[1r"m޼9pU4lb{yyŞs…hٲe֭[_~?[F $$$ʕ+&W.mڴ)=j\DD5j+:;;M68x @h|ٷo_̘1/^,r t:u›o%KaÆx" /t̛7k}O|T5m6|1c`͚5Xn7o]bÆ zӆ;w6 {g}C ?L<:;vlW㏡Mbj֬~w\r;wF֭1{lԩS'%K^zܹ3 ?ЪU+ر۷oGf0{l̛7Ç/J=8p >3̜9Z}F=zt/$-$kpqqAVVVDDD$4TP/Hڷo &`РAR" SLÇ,s:P"+ƌƍ 4@PPܹ#uhDDDDTFVB Q0ooRm9BcD}Y?%a 0HXXƫлwot˗/,QdDDDDD2* Z*.\ ADDDDDD6Le$-- YHXX|+--Md`BhYYY:AK. d[L0!Ç[n%hp4j/7@ͱz'!;;D%""""BudYvo߾Rۯ_?xyy;66puuEDDD[Kg֬Y={vKDDDa0jHvϟ { >&JNNF`` =zƄ0-- {Hnt:zײɄ8dT"Z/o=Q23?,DDDD촐&2dN<1c ::ѹUP}0:"""""RHq-:q(Ӊ^k;!ȍ7TAHLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""bBHDDDDDRLT !J1!$"""""R)&2GPH%SaK B ȑ#QfMC!"""""aB(7"::ϗ:"""""R&JMMŔ)S0m41!М9s_PH deeXWWW˗tRl߾ ,&pat֭4 .\F!$$;v믿n|y"""""Ktf_+nKń4iחXطovڅ1''鈉AժUU<==|m֬Y={vEDDDDdmsŜ9sC4 uja[(_h`0h{seggiiiE=e!MI=*+cB(89sAAAx1}t~%˘BDDDDu\aB(#Gf?ga!""""{:p h4h4RADDDDD*EedR@DDDDD*B""""""bBHDDDDDRLT !J1!$"""""R)&DDDDDD*ńHB""""""033SE ޽{1rHX"tRHDDDDD$k`:صk&OǏ_FvPvm#99ΝÑ#Gpq5 sE5j€,8;;KQk;K;tӧ㥗^CXl1aFh[av)~Xްk;ChNCTT3tRFDVNNp_~|}E-@Zԑ-h4pD)1+?"#9X X\$~ DG͚+p&^^޽@QV-`( 'G(IɸDbbbq!44=ZgH];_27$O`PW/ss͛_3'Em@ժ!@6l`hMw ?(@n< xz>V.c@ZQEO@r2쳢IXǵ''OġCp] )OemSL_|w iii|9?,D3r;%{떸o"z aq'ZHsrCDY{_Tm+YߨQB_T_I aK[Ű!Cģqckiڎ.X=֭[SN!222%ux:x 4i={^zBj0sL(:S4etohˤiYd5 Vfr3$A^.|<>\|(K4hRdaaaSZZF͛kz]&L(y{ge1f/͝kfsծ]Eldse?yҴΞ-}ڿ *oYQ҃ /70{+af*5YY xe)==PǺ')W,K/z*nMIII:zhnӥ?1c,]з (~I<^AS>ZzF-HRn@M+n%^mn?&g&M(Yg‹%f6ʝ:pRXX񔷸q9>eה)S] %%%)00[PPv!I $}ݹտiÆ E*w,/>#=B]qݩ/_ތ?{=QiHiZ oL_Ϣƥ_qՇLoƕ5i"%&1Y y^T0Sπ)SgDǎ^'mvW!4l3i~JwHfIO_Ufffװ n!R=c5nH9=?PS!!!jذvڥ*ݻO>wq$11Qcǎ-TP6nm.z6yTp^];/)8wt;ׄ-޽r(=6nzv.عӬ[6wcԳL@Rg'';t5^=06dW&#~?_r-fܹҚ57A~&${^lر7n\BX|:6Lo:uꤨ(9~w e^* 0@sݻUA?z!}7jӦegϞtʏ4`+ԥ }d7ӄ{5??3w挴x憁;V2 7Ů]ysC&g>ʕL37J򆑛%-[f5P3]/G܁=j9r׮>~Tbɼgff%\.=aHH>CE)O>D={35c3W/Z2ASbk9sLmyPד|z aDDʼwy:w^xA_4uTuU~^|E*>0ӑ?o ̸U͌c}d9Bj…uh@jXi?grfl|l"Xa>yӟO>1ZQʗ73fq㤭[7MJcƘ`!:Z6LZ*hN׿gK#?rdt ;vu9o|'>|-Z'xBǎ￯{hqnȴihʄ(20{tjGU: 3ps^řmxs UZF6m2c7^xARŴ ̞I'4ԇsadsߣY'YJC]8GV53Λgɍ^{M:pۥ.qqqڽ{,Rڵ/iU[~J94#/IGuC2m[󡟐`ƕ `~!o$o.qE盰ae9. a,YެIKL=?L]HH07P٥rZYݼgOne݅ f t=}L^6Iгgt3s|:^nVD{eA^ڶ5n^駦[g; & lI ZK;K>\^Fi p_Jhƪs֯7ubvo7cPϗ pׇ:u]0rsOwHW33eϞoOIMBb'=^,-MM]X,gѫlq='={Tn]oTy@x  M ;2)͕@x1˒֭3.4ݺMK"L@xTGG\cwH7R7WWڕ7n4p5.o:^ 47|2K7;L-]p)>9yjڴFkz8@ScEereo3c3''AiCXTt f\? Ӧ\zus,]K3_!!=Cf{5R  W?ח~ 9S7ҌEmLJ;տe/\h>uÝwJomf=Gzt1%$$(::Z=-Z{x@pIh}'3^qtF 29a3Ezcׯ7!Q#sqdn#ew|ys'mz4j$gصҾ}.):'Ǹ?|a$&&۷lKnm&0DzY|,իpB-\Pt预qRXhNG^n|vLI1OfYmrT乲vwe.x%)q䜺p뭦9֭3o풔~3 1FkcfQvfز8{EfۼtvvmotIKLM8;wδ"/Z$-^lZolf-Tq=ܹS .'|N/b!@x1Kͅߛ n3[&}j@{۷?KؼtA1%e5^3hb3LNTz_Yw䈙rbsCzuSuڷ/X9)8t-30EuÖ-R.}9׸pźd8qB 4P<@icnv'OJ_|a>.5w7ERxKY];s,tN2{wUv ="[x${ }f֭ڶUvar}]4(ibzk\O@X\9EEEiРAU~}okKi 2̖.5!qZ[n1e}-+;‹Ys[Դ5lh7ݺ`k,v pA@/憑;eY ;rt1^C5lP:tЬYBi4km&7T3)IDԣ$,^<|KN63;g7u$FeyvBi Y8QڴtMH0] ,4wYVt1e>#*W6렎kn"{o‹-_\3fG}ru 7xhxo0̿<+gBvb&Q%z}AKRoW_nJ IDATitΦ>3A?풔^fw}XbڹbD;qt=wׇ̘Cw}h޼lLrYŷԯ׸-55U~f̘5k֨iӦڸqUeA^Z^z(_eZ o֡o.xܧe@XxeZ˗K90>ެo勾L yK;Ν3iKS'nwkZ vI|ǁҲeW_CͣΝT_lu%r)ef琐uYӶm۴124 Nz)3.U5kzЅ@s8׍Ki1sn]J|eJ^]qǒb& tSN]hݚ+˲5Lҁ.|4b U64l| aZZ͛wyGVR:utkРA+WW' 7q8;z$%+##' X!ZeZ:tڵn/m]z\x?ofLJ25Jz3iKRv:d>aN3v]nY v)F╙iArׅUMWMҸMJVOk\@f;;wիW/ tna͛KC}Gzꫦ622-<ӵY!!ҭM2 l֬1ۿmn&5kSZ2ˤxk!j2^)) 5ҼyғOJ7ޘřn%O.0hNG^Z^ytA,KڻLPn]Σ qqkY&rqi{՟ '#ô"YcBҶmbeKS[q^~1a}ʩS9`SN67E %.奏.]kxw('N532|k\Bd3F˗/ױcǔQ'NRn]3ݻd{`a4gY#_qذY Nra&2S)}ʕ3v-ZHٗfB f[\ Шt9U^ϖPT2?h—_ɻ~|o+ws߉̾ ӵ0uy47 ܟ V9q x mݦ]viw]=A^Z j(>gΘ6o6Ν޽vm31Ed""L̔Ξ5k֘,1=f,->lۖF\c5cd³gD'6I3f W"5մ.g>5H*lKM5ǯY#Y.sgoe(*˒7auؠYPmlsܩSeBs3oe5t _c %&&~Й3gTn]=:t'_%.Nz53kJeIǏ`xY-9Y:q„A??ӥV-Լ9-eQVܵS9u|yszus3_?ڈq/l8pLnu.\0 !!Um[S¼]zs|hFb\׫m۶ȑ#KG՞={+xҮ[ۭYI0VNkvIPT3Pـȧ[{9RcƌQӦM/{ZZW4d͚5KGɵdFǎqF#,YFOK3K$ZL Һuu-[ 9?P:37{׸SJ3_{=ZB Ir:Wu~) @| jժ)!!$<E-00 &4scfְ-rnRjܸ#ByiرzլYK4^+WTN.{֭[_|QvܩΝ;o߾B-={r|8N1ef*U2kT^|,aizӚ~rL t +09O*sQ}^{쩐ժUK۷׬Yr=?yd=ڹs֭{aA5vBϮBC11%^NEjlɽ:d%֫gky],r~\f&GZt_(Ǝqx tѽ{z( 8H9zh=0##H\ !J03M7ޟjB޽9۲ef}t +W"#MU??Jǎc4^xtO(Fgy&-(y>B˚7oÇkǎ YYYjݺvڥBudm v쐢]Yx~M:^4[HTT k\5k֨M!F;wN{U&MJTE7rHw}jݺ @͞=[6lЄ yCXlk{4(+|/}ݧ]j޼y:{llٲEFRz?x3`}gk4i$=:wx 9ų`G>eriԩ2e٣XĨB :ymۦ3gΨgϞ5j5k"(ӋGŊRTKq=֭[ZSZZ*W8uI6H/K!\zOBR\ͬUz$s|n !P-;"SgJ-'! i!yWg-1cx$u|zى\w\ڻwʕ+zi^*0%o"#]p9>BaÆKhРAٳJ_E !ȧ[i&q_]ݓQtk\)ʜ>}ZOv1Cm|諯eÚ5kwRWev]FԩNSUTQ||+$$K%ӋGrRXKq=ǧ[" vS&E#C !@B ȑ#9r*ө|oծ];+::Z>Ξ={Νґ#ӳF۷oĉՠA5o\WQ]tQƍ+8qvڥ%KxkCB kחFJ1nWrrGG\RZjiȐ!/եKO۫I)5մa͓vΞbbL8Q9lϛ`']`6IZUYSjPV_U B/ٴi222ԪU\բE mذK%u 41deI&?/edH&\Sr8h}^(=ӄ J@YC ,eYJOO/ԱE:wZZZPBPRHJJR֭[.%I.r r_)'^j>%33SYYYy>Wе-5Ҍ3 ul^]?/weY:|%>|X111E:_A$&&jرE:PƏqyG ,@TTX"nڴʕ+u֩O>].~Gu]E:ٳgsNZh!ѣGg9UquJoR(BCCեK{:{l3gٳׯ_VZ gBX{99ϲ,K3gԪU$)] &m۶j߾ _V׮]u-xE !=r_8N9H8C}1b֯_u]z DrT|y䝔 _JK3D~K>Y܁yh<׸1*nGC DB*Z!T<!e'!MUC "«h!@6E WBxl@!^n!yB)!ʲ?x ^G @b !=Bx-wّ#G4rH+44TNSIII)Sk׮QhhZliӦ)++ % i)0%@X̶o߮'СCj޼4ٳGÇ$=?uG=Yd:qB 'iӤŋ]*(tY\.飏>R~|ro>q:v옮\3fhΝ[er|JOO~-J{JAA9[pT|?cft挔xtv6㥞=n(r+k\(5:.22Rٳf̘[*?Y ՓΝ.\0A*V4LJGEImH\#ժ%5jd$#2$U\%1*V|ӄ:e cK˥ɓ'nݺ]e-,K:6 oСڶm>S9#[c@X$u9mݺUW^'No &k׮E~9)??+.P233]nk[/a5j3f+~3fhȑzGQd6;vBkܸq. {|'z衇ԧOW|g;%v?%=Z>>s$&&j̘1}/˥+==DZV5Kb?5EZK@~%]C@i-@T:CE1PvQ .!(`SBB"MQ Z@6E Dh!.!((eyJ E !P6Q Z@ˢ((-@E eBMBB"#GhȑWhhN.ӧOjժr:?~/333cIlرcK젾+( ׸0l8q:͛Q45zh?ǻeeez,nYYY7n\-uEA}Aa5.r_dm۶M?x^yfM6M#F(]Z P5>zv*%MroK'OJaa. @ yi͚5ڶm i\$i Z5o @I zO'5\@x&-[&Y#u$/񏒓N@E ,eYJOO/ԱE> / /k"#C:xP嗜mf駟={z?Y3G\D  ))I:uqC[nUlll/hҤI:u19Yttꔔl%'KNvmfMK=&5i"~أuA)@}AaQWP\,333YDϝ;'If~@XFiƌ:6::H3fjԨk߾}ÇK~7۷O5kּ2_wީRqN?Xb `_uEA}AaD# **J,s߿_vRݺusw8zp8tIx`tN*в,eeeq^2a?~<׾͛7k1bnB8NT1"@speY9sVZ%Izg$I7|%T, 7ܠ8,Fjo:edd+W*>>^S^J@ bq)!l@5bU^]AAAjӦKo ^n: 6LM6UŊUV-u]ڹs%n۶MݺuSHH"##5pKAL0ANS͛79 $iJHHPddլY3빎`׮]u5(88X^{Ə\QWٳJLLT)ө3gylQƿo5nX3 ä2>?qկ__3fڵkbŊ<=W~͛ȑ#zt}wjܸ$jѢ裏*55U'NTZvZ+j4vsA5lPNSkO?9 *!!A-[]wݥ+jҋ/(j֬EZzO;S$۷OuQZTn]XBӧOsWoV߾}u뭷jժU9s^z%=Ӟ}wYz嗳?ު_նm[/ ޶zjrڷsNB }ݗd/-a[+/JҥձcGYf %%ŪVէO`„ 鴶nݚk'tZN,bGѣG-˲uYzw/9u#--ͪ\{]P8t1T\9=C4x`^ZbMmڴjդImݺ5{uz:wX͝;cE鐔ky>O}￯cǎi„ sʣsuU_Z59N/_^uŎ/y)lX|N8Gy$3ghɒ%Wx QXb[~ѣGUreIҡCt1]׺ukmذŃeeeiz衇ԤIK@+jjԨ*VP=#p$ ;ʲ,=ڸq89shڴizGH]AR7nժUZj%I=*"9|/yʃ`IDAT-˲t!/ {ァdꏤ|Љ'rO}$ܹS.Kwywk6mxI]v_(..N5kԀ4|pM4Iu+J8|ox+22"bԮIKKS@@%+T< Y mfv׏!^q %&&j̘1 $̙3JKK?W^yE?Q.\Лog}lkVԧOEDDhɒ%0aUGy|newAX@c\GU=ypHʩ!<3԰a=)gi6`ZzZI5dڵ+2335bߟ(u#00Pyԡ"˨nR{_LLR&%%EݺuSJJ>3UV-9PDDwem`׮]z뭷4|pEڱcΜ9k5kp8ԢE / tk׮]Zd6lURE֭k׮eY:uN:[;m߾]u/3igvѩZ*uLK\.Y UѢE YuɱGED 1}QFF|}1cڴik^KVVNg޽{kŹ.+رCTqEM6?X ,ޚ4iZji>^:ujO:Uѣ\8Bv]wizT~}͘1C֭ӲeԶm[o^cW_UBB{s$jٲ*UG}T4ij֬kU:udO/|PӧOW߾}աC-_\}F=K-uVRΝaÆ)22R-zHӦMD])SԩS:xM^z)..N4|pnL:UÆ S޽յkW%%%?#FxM%=J oVLLhx_|b:vh9|mٲ֭UbE+""8pu1/Eǎ͛_ gԩcX֫zq|V=+ jԨ/ZOڵFٷo_qEouZ*T4h.B)M`SB)!l@6E "M`SB)!l@y+WRRR_}7n,˲J=n&}%v~=锟N%}YmVVhhW8b3F#FJv&Wرc;vdUXQAAA*k%$$ȑ#*_|OVVWwyGݻw/ -Rjժ[Jp8TJ}AAAZrNgvw}WZd5j`OiiizwUNEDDG3==]O=jԨ+ꦛnʕ+ ,ߜ9st- ƍS\\OZj)$$DÆ SVV(::ZQQQzskرU*T5j~tnӇ~XV$I]k;wNΝS={Txx.]={W^j׮+I:tmۦs*::ZwM6^zyUt=\kݻvޭ *))I|xr-G'Oܹsոqc9rD7nu֭[륗^*B@iӦvڒ>}ӱcFSNZ|_U3fUZ5IO͛7+33S ޒ{|'--M*TdڵsmRƍssWΝS:u4d-X@ TVV.\PoP&s_VV$̙3*W֯_/3Ҋ+>+Wɓ'kԨ;v/_|CjҤIKjH҉'t @8effѣj۶m^e˖b)C@@z=zGQFi&hBiŌ+@"ʈ]EA0`I&)..NǎӲetuC׮]5s̫zǒoQAAA5kTVcVZ[o 7ʄX~ƌ8pz)5jHzҺuTf|_s=Ν;~9,,Loڵk뮻N˖-ŋ.I:xV^￿_`az҈#S{9RNҴiJ=C !WiԨQv(?D`?MB6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`SB)!l@6E "M`S6mO?N˻ᆱ9w~ÕtUUfS.\xUWЁpǎJHHP*Uƍkĉo޼Yv[RG۷oNRRRJ,ծ];ԩ^}ի:gڵ~-{ƍt(Ǎ'*44T!!! ? I:rEGGRJ_|II&ػ_W|ydYfWUvKa=ɓ裏Tnݒ,,9gd(%%EJIIQV$I{c>}Z_|Zh!{ :46m # {n 2Dr8kջwc.♘*U(&&Fsշ~f͚)<<\>`k3gYW^yE U 4eʔnFIR>@~z+22Rz_cYFjժF׿"33Sڱc$iѢEr:+IڴieYVXpISO=UViĈ U=Ϲ}vtM UNt|_iڻwo߯Igw}]ѹBH5jH Ҽyru͛UjU=zT= W_}UVҖ-[xb-X ծ][+VPJJ~m=Zz$iڵC)%%EѣGu뭷jСJNNւ 4vX-_\4}t͜9SVҮ]n:9s&S_|rկ_?;v(ÑIҤI?A/RRRdɒs3g? =?66VwyB.֮];=c5kvyE etŊjѢ}YիWOM6՗_~UVСCt:տTXXաC_ ݳgOH:t蠮]jŊ,+߳fR[47nAiْٳg4h *_Tfff߱cl2%&&|||Cf͚*_~ILLԢEq<_x"""pEDDrI͛;CMN:-Ёjժ8q6mڤ~MݺuSϞ=uԩGPPx UTI[l)( PN:ѣGkRZZf-Z-ZО={7(33S?V*UH> ]NvޝӲe4|eddriƍZn$2evء45J~~~t}sVtt 5нޫ?_Zl=g~:u;wVjj:wk{1}PBBe/yCUpp%/^|:Y`/\rʊֻᆱ f͚E* .}`; 3`SB)!l@6E "M`SB)!l@@@ E0%SB0%SB0%SB0%SB0%SB0%SImnzIENDB`PyNN-0.10.0/doc/images/examples/current_injection_neuron_20170505-150317.png000066400000000000000000001222711415343567000256220ustar00rootroot00000000000000PNG  IHDR^sBIT|d pHYs&? IDATxwXW{EAEĊ"K^KԠ b!Q5vAb,$jhbD %좂b ,0| 939("HHB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ńHK1!$"""""RLB""""""-ń[\\$ f͚H$ܹs)DU:JܨYQQQH0!$"p TW?VBvvC,3g΄D"K c(WJJ $ ?5""*9 "RYfa֬YEڵLLLp}mڴAbb",--;D~JKiч !i̜9شiZl毿ʟ(ﴝ>ׯ_*1~m\FDDQ"Z))) vޭ2.]?{jժڵk+~ӟ wwwCGGGަ޹cذaQR=Q^Bhh(5kccc}V8Ϟ==zFFFpssѣG;88 D!AGGyyy A5kbҤIdJ*\,]ڵмys,YФĉF5[[[xyyaӦM Ԯ] 22R~ίߋV띜 ___ԨQC>?GrrRׇn޼mڴTݻo}ߴahԨ̙4믿D"7|+WB"K<22C:u`hh333`ݺu*c)xsrr0k,ԭ[pttĊ+"""ФI3gTx\޽{J*066Fǎw=OwѣQNzɓ'K"=DVZLAaÆE-~ r)6mڄ?ݻwȑ#q-6O|+Wu?999_>>>ǁǏc͚5 ,_FT*EϞ=Q^=sLQLxBݠADD"nڴIe oZz(د_?1++K.((HH$?P,UV޽+fddDT*8p@ׯ_W*d'޽{WnݺsbڵTy]ZZhii)VZU/xM$8qD:uJ+W,xB^)J$q͚5򲜜N:x!իWmmmls%"2JDZ+55PFr;f޽GZy{v 1zh$&&bpssS:RT*ŗ_~۷O^Da?...xOjժ=;wgffiӦ)5o>>>HKKömT?Ao׮]~:PWZ5L0Sy.DDڂCFQVU,O8\4TDׇ-JӧO{Q5Wɓ'_>fϞT/" 2 pwwWY߹sg>|OVHDAxȿܹs… EQDJT^ طoVXU=|pm̝;ǭ[+P_yJuիW@add֬YӧO+}do޼}QaDֲ˗ `[^)IǏ''NPF+l/J[dy͛ m#^|Y~*2%"QK<\`8gNNzUIP5 / =vX-ihsӧO̞IWY%fffESY AT&@qlh֬Yχ 1vXyӧXjߏE!((ӧOG.]yymϯAΝ;|JDM2dtuue\|ȶOoaa Hݛk.Zn DC_WDy|նm[PX.0r͚5*@edd?xb_>;wWݵk|R^3ϖDBB^@PmBE}{z`/^d̘1+\\\`mmw"11'OĆ ߅;$ &M{CL^ɴip9/йsgT^<@RR>z 6ҥK1rH4k zBzc8qfffFFFhӦ :> ׇzƍӚ5kХKx{{W^ptt˗c/ C ABB.]:u 5kē'Op $j,KLK٤Idu/^@*U0~xe2T`ٲeo=ދ/`jjZ@DDDD!x9LLLF!l߾}pttĮ]Ν;_~ y/瑓-Z(l ggg>}X+$-- {2Td2!==>p^xPIzȀ9{RcĉhҤ n݊`bΜ9T} >>X+H0uuu+ * ^/T\V$x\%ilBh:t)))Ȁ5k///*Uzcb-8˗/!"7Ǐ#<<'O^zDDDDDDeE„L0Сڴi>}O< 0eb„ 91 )/.LLN"@GGDLxdfwAYv6? ~ɯVDQ(]ږ|>V^^Ou(V$}HrssW \KO¾}o͛ann^hG"<<ɓX,VۂHNNB}ժU!">}*o/"RSS [[Zs1c ̜9Dpp>ǏϕTIw]]@*硫P0U!NԿTrrMk0V$V~}ݜ9s&W$иYFe2YƜ4hbbbp5ԪUK^j* >F۶msXZZbܸq;wBUT7/_eddȨIeCX22K灋7xQV ZY*`iͩ$?i{_2 zzz}V^xPI$&ШΟFvظ##uzҬʤs2qbϊ{}yFtt4V\ٳg&kը\|VQSSSxzzbڵ6mMt|%:.oVa >8~~4j :iu 8P8äs,?זh:eL0{ưaйsgu$׫W/xxx 44>DӦMm69r˖-S̙:#F۷`xyyᣏ>* 唼'- O..ѣƍKOD"3xPzB%!H0e l.@`*{_zz-VgqCFիWشiV^f͚2220uTɓ'hР&M4 9r'NDBBLLL퍐bơ:#++?\ڴ>hЀp@ǎk7.ٶ}%B בqˏF&~:"##:t(z,e+5X TȈ4[x8r%phF0!~84>!, "bcc۷$fo:DDD-Ç) 23Qի1c sg}@Ԯ]m۶ŢEkرݻ7amm 'OFݺuѣGc޼y _|j׮'PyG!77WiykkkܻwDRȄ7УG 9,Cؽ;qy"""pԭ ?ܾ t\E@&+Gi.5kʕ+JugϞEdd$LLL䏮] \-ZP;11:tP(СtNNN mU˺ix}DQĪU`mm].7fۼ;ػh\*U-Zf[һrr=ҍ-;(e:v///L4 ~~~ u/_D@@ƌ7 Y&ȗ[{u7yyy*ZZZBGGW(>UNǧ ٳgq)4h@ݡط1"?&DDD6:54? ,\DTqI \iP8;;+}m޼9.]BRXj-))Ia@̥ 6ÇQ~w`E-o> "ۇICj oVw*055E˖-i&6nnnH$Jݻ)+90W}{uGCDDD @o޼|ƍ?BĉqٳHNNƎ;(oӹsg,^gΜɓ'1rH%f"?Cpp0f,Y~{˸q|rDEE/I!\b ܹs7VnҤZZz5 .] 44:::rR*0w\7my\=?yLꎆJJW3ͬY;䄸8L2EuԁWWW"<< V׬Y3lܸӧOGpp0lllz?SL&bs6Q"""Mcccٽq^o{ht?5k 6|0CPPmP"33u뀣G3g I!LIIAXXڴiZj^탣#v;;;J*>}1W^LLL`ccӧ#''G W<))W_kꎆã=;wٳgQn]u"cĉhҤ n݊`bΜ9uEΝtl޼Y~s xOQg$2DDDDDi=˖-Cpp04`w%"9WqJR(",, |󍼾{8x ߿_ЀXGE֭z\VmVo CCuGCDDDV á IKcR8 HLLDabb F6+BuU8::"88'O~ ,QQJUYy* Qƍ/uSw4DDDTV +W>똓#1!,=d4//LbdDrr2V Q"cggxIb-úug^%ڟ: &:B!h5N˚5|K)-Z 99wAZw܁ o]ڵkPZ D"@G{:H<}TQe4::mo߾Ça4ʼ!"V\)/EWFʕѢE /Tޟ AUa6HDDDD>RиŸAAA2daÆ Ϟ=ÇvZݻW!1+zBCCC4m۶mÑ#Gl2ŝb[.^z[ѣsMDDDDDG¸8ܹ?;x)ݻKKK… J;v`ԩ5kРA[ +oߎ{A"aÆ_0lذrF2##%%^%5kf͚9iE)yHh,~~~ܹs1ay;ЧO24,:téS۷ ph\,--ѻwouADDDDT, aaaB]YɁTZ6)@zz:1tP|'er *}эFDDDDDjRdx3f 222;wTQQQH$lܸnnn044[lAƍ,X@a? СCajj {{{,_xvYfW^AZ !Q9AHH~'ܽ{Wek׮[n߿?.\>|G.;|HLLN:ooo 4.\@PPM&O$ ,XZ™3g0j(9IIItbBHDDDDTzggg̘1Ceܹsg!00kF۶mh"DEE\(cǎE޽aookkk,\]v ֛WV7n  'NDvaÆ/^Dll,~'@Νxbm9994i{DU q?~}:acc` sZxO;A"!M&AOOJ㼉*tȿ]#zϸGztIlܸnRw֭jçNDGG}HLLĶm pE߿fffjDRJJ9cƌAzz"'"""""m=!!!Xp!K <<Zb:u*Pxyy)9shԨ.\Crr2vUaڵkѣ@OOcǎEΝT1yxx([P>ydT\qqqq1bbccYU@yx/_E ߳r"ˑ?dy2to#""*5ZXXŋի… prrBZZ,n:gϞ/^ 66ǏWcǎƍ&Oý[nֳ[~3aCK.KHIKA%i%؛٣YM4 c8; 2io}~I;y7~=+{ pL28r틩S9|U. AThcc Hg=qa(NB5jpFVзa_Ls:u`o^I^I m6cvQi脰r% &MTEQi)¨A֯_+++៯^*t;}}}y=QEt5lwhn;aB hS;$eK(۵Ww8DDDMϟT{Q ڽA@bb"ׯP~ ;v _}$> fee)/33SaxiqdB$ tttJ?"ҖuaH|^0hxz{BDC`٩eLJL&S,[g[*]ZXX 55UVC(BױY~[ `РA*ۋTTږ('yӌ30s폈$?IFpD+Ƶ ݒ}h 3$"$`Eq CE8PDz;oaԩS[Vkܸ1R)N<~e2Μ9oo+==*$"*7nb365Cˆԩ\Ga˱"NF` uCDQQQ;w.&L /߱c郼<5F~BCCm6\|h߾=”FѽӧJ2*=)t Ri~WWWtI y$&&*M&SXv-QQQHOOǧ~ZpQ"*Oo4)T![xH m;KO.EV"ٳgJue-''}!All,d2tֹ1 KCz_ >'O὇k:\9sСCbĈ}6,X///|G-i Q!B('AD +/l*QH6ս6_ymSw11v"Vks_=*-Qr==CUw8Da<==.>>'Oɓ'aee޽{#44Gm߾]ak )))ppp@tt4.]Ǐ#""زe f̘d 00ƍ#F@rr26m L:Ç/4ݻw+UԩSpqqyקʘF' ˗WCDDQDLL ZhzڮYfĉ1n8` )hI^^!-3 O3髧ϲ!CQ#3'<rr ˕,W\1W  @"H ~LNo5ˢmaߠf7K͸ޥCx-@>~Y)_·/tuM6BBB0p@3F׮]CnHC`` vXh==bkرݻ'Oԭ[/^ļy=z/L8 .ā...hԨQc ӧ_hϟWxCiӦoi<1מ\éSHHM{qyqM^zcQMeCؚ6&6bP= DNtQFap|O}D({RD ϭgϞvZyY7nA>N-NLLTHCWЦZjxA9j(\t .v.:dzD!".<7`KT"E hn#@CcQGc'kckHXF;"zSNPbJi“; &MB˗/1c(^fM_LYjw͉]A(,Gݻq!KчE#WVwTy81l[ 3'=q1|Զ͡DZL\\!YBCC׼ys\tș󭬬ִNJJBFFBUS6lsѣGcǎ'a0==sž}o,_HyϬE}ņЦFH[PYfauCDR++7~G']v İa`dd/"66?sXx1ڶmL4IBUn?[Fpp0q,Yu.F† sN333NHEpذaaccÞzoW_Eȡl+{D'NHaahsc o> ;"0fBLL '''aʔ)puu(SmϟHHHPط0͚5ƍ1}t]kkkԪUK;1D@Ch|(zň#x Ucz迩?8m{ R})X^ۗҡ jM,Cv ADz ,+Qqm;1rH齆_8Ŋ+TCvIe>DSY,nnn8xR]b:o-/aZk?Y c=cuDDTZfZ.k/[}ƪ; T Tá=͛7ڵkoQre$$$իW/xNÇ+# ^^^ sUX#ֶ\b&w߁G^ )s}s U.3CFJ;w033͛71|pT\[nŭ[U1yxx([Tgffy\Sd' l<gsUc豾*TFgHFw7~~~HJJ{*b˺u`ll={Ezzz9Gzkb s0A"*Wcm軱/^۫phtBx (W^SCD=zXJW^LLL`ccӧ#''G V<9Bp`uCDZGO7w;"""=dRJxRիWaeeEGG#77WpѺusprrBzz:6oތ`$%%aÆ jb{4X2 Zyv跩|gϞ˗/ѼysqjkccTn:Avvv'O{B|DtS:NA H}ίC=0@wC#rd ,,vB +: 7,Ӊq$˿*"9hlMӴf믧O>,\/^S2{l8p ?<)g2w\ƏOll,QQQ,YQFȭ=3}]veұcnDD?x 555 99"))~c6ms~Nq:gEp=Lcĉx{{8"!yyxqMk58R RXwhz|| >o۟~ٓ.]H MЅhvqcdH2tu8ͦGL2_|+`L>???w}K/#x8p v uN5q޼y 0E1qD6o+k>˃>5\CLL UUU,_׳f͚r˄n ;v@۷+B!ރmӿmSsr =o;o&Izi:GMGi؎D:v]@;77PPwQ{pp WӬG58&{y衇:t(ĉ׿wСCi۶-/ ^Ty߾}yGYh,ZiӦv(**n#?? zŚ5k1bDZ*4i?#TTT0|p>C"""\gjjLB!,dgr Urɭ(U֔R^[^''~ 4g?|5ba8z:Y73?ӡD%TKo'1s^NB=?Nl>(uܖ=gϦ}255nX !-Cq}S2KYVY[I͌j{ 5lVؿ[O~Ω9OivmgJO~ϟyб `6mYhNnCw}__~ѣGSQQZ!Xh(q՗y ;Ӈcp%pByb0κ$B!ZC:CBK[qG9s˫,77s2rHF&B!['/2&8HHHcǎLxB!B[С|w߿P75j#B!Bϭ2V!#),,Eeܔl&** Ee\ͭ{֭[ǒ%KoLĽ^;vpBvAuu5L>(͛7B`` &L'B!YO(ϭf̘10g̙C`` cǎW^qIL tB!BK[v֍'x+~y/׿Eb1c_筷ӓ_~3gN#::kDGGiӦW!Bqr&$$TRSS߮N;P4Vk5z3f &Lh4b ڴiu]bi{'^!B!.N.oذÇu?NGZZ;wfżKL&]t{aݼy3'99&NȓO>.r͆VUZRB!EA-7믓ʕ+i׮K.cǎ 2]pr!Bq[[!\jW_}5>>>¡'| !B!D Exx7 }Se%&UY >kAU:(/o*/zzAv[;9㶜"!"Zk{Cb>оiiw:ՅxNزEUEH;U4M%_}O? J߾{alV[{<|8\}52/テoչwSt\sJakfunIH8QVzȹŭIuBh6Yx1֭C(#hUrsa*nؠZBFV5Wͩ_~5kEyX?^SW?_~6YRw] |ZUK𷬬eeFՠt50l}q幥V5<}P 7@Tb;hQO-ǭɓ'~zMFtt4_Ι3E9XUV$pr :v,32ѝ^^ Joom]T=:˖2ӱ#/o3l񅊻J5"xJH{a‡sKz:zU""\sg R4?w?| >t<,ۂ:! 櫯b;vpBvAuu5L>I'aÆaÆF?f&,%T/7p`2E:sW]Z?D &ĉra4M\>@ ͚2Emij T9:UWF~jH-_zFcsϪ*5\q*uWoVs-EE`C0kօ&r:!ر#_5III^rr2 syիW3g,YR9t/䯡m۶ 6I'h1NZfr8ǫ IpjU[֭SÌMS-X!ulUI[\Ut&MR/i>/OONUeT/嗫sˍ7_.55l6z*+^+s~nٺyD%0}Z$uܖ eym%(3gdҥt\MFzUU5.M={:2IOW՜)STysr0:t=o<:nq? .lHNx׹뮻;믿{g{wlٲPRbƌ@ 2D/E1[,[FoȹEJ~:j(rz5LHPss۵;m>j luwNK,?&;;QVVV^ >t:ܹ3/楗^ 1bȑ# n=ktڕE,b '8ktxt}?7k\1Սi׮uV;8Bi)#"TdB~ѧ~(ω٬*hڷWII.>٪GwU);p?իU7 k~v:O-_KĨ2~{TTܫW޽UYKguK9ѓ&q6T\u/R=sMHȉOm'~>__5`/᡾94 '9Á_ΒN}Qz-͛?C=ÇYz5>(_6inbcc:t(K.m?ϼy8x }łw>YxBx& NTT5 os،0ÔV5y;3S]hSSa.5WUtQ[߾5[,UU_Vt^v Ж-jѣU~> 2nB*ދl^JeSW2׫ȑjPk'ͦz֭SäUl__*sZ9t{fOM&Ր\S=yQvbZөFcee}Jy>>-M>l'A[25/* .P:c6Z!dU;wo=S]aTyߓɤm6-*JR 7v=97p8p:|f' a pϏ4bbb櫯.СCۗW^瓞NHH0`AUUFQޤIOرc}9j*,TmNuDع{ǎw1XE7=]`n߮:u[#ַf֭Snlۦפ$՛ۥ*?;έ"e32U^RSCF IDATUSU P;B]MjGu jS'eHNJ(U99p@m;wӭ*/}LȹE\$r:!ҥ >2dƍc|G̞=>`ڴi3sL|||غu+O< ,ɓ'3ydX,|lٲYfI'K+SUoFZY07WUbcjWxQRMOO֪nuJ#NJ\\̝;ŋ$kSOo>L&]t{a>| m6 $%%1s,nQ=%%jbs]g_W0U.ΑjgTJ]3,UөV1-,T v*+D 2zbQBU;yx(/DE{/8ȹEbRm9n?͛ԩ\NE!B\lr6!l̚5Gy;:E!B\lrv‘``ժUC!B!ܖ&ǏgծC!B!ܒ[IߩS'O?ѯ_?<[oL/B!3CqNСC-kj!Bq:nq¬,W B!n˭L4ܸS!B!Z'o6=zoooz[o간B!s!>(=g+`˖-̝;l8B!B!hzQ^|E&O+V0{lJJJ\Yˑ B!b#uܖCFm6ox~.H!B!܇['ӦMW_mo)S\vZ |9rn޼~h̙lnህB!"Cx}x뭷Xf `֭dggs뭷$/ӿ~iL&?It: Mcݻwnw.cܹ<3;ݻwsU~mfΜɷ~˨Q~2Z s33N3Loa] %-;|^?⼽:|ZRµxt$WWseP:ɯWmdזhdDHHh}:F[4jQ66mFj"\q[%Yyy9aaa<,^s={ c޼y**bzt4]O[[VƓl5kv<Kfm-ard$o϶*,-iC{ "pjf3fbFQjIs99<O\if3geߟ;I!ܺ:>/-AADĈNH8JBJOǠQt>8(S{&U|SVȐXxkZ ֯s:)ZZUş}>cc}IIIK ,-(Pm-FEߕSd2m[ $6`u:yQ8r/b `Ib"۷wuxBZRm9QsuuuT ;חTdϞ=v`?@>}HIIitJn.ipCx8ɽ۳lffz:JH`u^o~2ۻrsU!!)+L||r5==)#`TvUWMY+*d[?f" ɟ۶\Fm={шQt,|=/9۳K/FntLTݽYmRbϏ uutfrTLs k{Ll R:Ây8+۷? ""TYKJ"(kCCy#/{wGbcA~ۗ{32r88:< Gbu:kM'__NmxkWlƓ?WnAmڜd})-eYa!wDG3<8;5Z_npsDFB"ԴqZI[̐t;2+:7#!!ӵ+WZ^''c4Rtݻϼio4Ah+vjkdYa!TUۛUU㱸8t`4M_99y1ǽzz200M&*vƆ5 ivƤt2'#,P=f3 >z==4XQT<ų uvVH{{*Xx0O9•AAֹ3||ln :sGߟeIIu%=XooKHcIϿ{32Ѐ8T[˻<GO??*:XןsyshedmYڶ\K>" ؝N2kkY]Rcp a{UmAa!^^<dX,u["#9TUq<ˑ:Am۰i;'ذJOłߗsWvߡC7Ηi\gI0St*\r:!2e k֬ᦛn"**v…5Mj6i_72|*++?>6mfqF IJe˸غuk[gvm|}`3hdaVwFG0.{ f]y911XXt)_:Ql|SVF9̎*Z,zxuϞf oTV*v:kq{<6# #vdf0Pm3b. Ojj8tXȤ3&uV+yl ^>kP)~Q)(da\>>e]y9 Iᕼ<䰳Ddl?VT0)5סoPVkG7eeh@O?? c`` ׅr߄&Mx97>}TW3,8H//r]XvPdbqv6ބ lD <11]` C+Ԛ1)5jۖ9Iԙ>KV_nAV=Ş/Ǯixt @x0+rrɲӖ=?tZ/z^ms:iZ}XeSe%Us32q:yKnhSjYxrz<= `M =^ϋ:QaÛZrPm-wl&ۛl2v3>﩮fJZyuuI'LL|6ȈpT/ὂB Vvڲ2ddˉ"9KJ%*۶y ֔\UE;I~=)!=dpӵ+]|}omx/)>9ߗ3%-v_995UXU\^O{O{ڵcLh(wvez.kkjs's۷:thTq?G}lB9 \SVƫyyALZNǗݥ geQtۛAAALI L֔ˋ:AAA f|vVWˋr4l hFNgWssy$+ 1>*++M7െba۶mg} ,\{IC61'#+*x=/Zo{n21m~|zxySe%8$1OO~RY,xxtdzVtӱ~~|ݫO9yy<XU\kyyׯX+**4,&;y.zr:de1:$P8Pn^~,/gBj*uN'1}fIN'Ǐ<ìmϺkʮӧQfw:y9 =.?eil@@564EʦJZ,qwv 9(*b]y9mF:6~džؑ<%% dqBB|ٜNƄNAҨ'81wj0pVlBy#-: ޝ855ee$jןK_}©i%=-&9\[&%$Mʐ ⼽i0/32.,^Xv m0Ĭ;ԐS_j2ԑ#PQy&!M4M_\9Tlt9^uN'n#%_P@fkROtT!^'hdJT׆Qlau:0W< UU<`mlUr5,0TMִ46TVRnhP$&SvlٝNaq:YOg֔3~^.?VM㝮]1 9EZM BBLf[\ʋd!\"+Ja aqqqK__F)sZO?̓>ȪU?~|g7|UW]uז-dʱ8YY\̸0EER]͟ә͂u;T9DzyquH){Ŏ;֒=B*+g/%&Rp,n gJTTJ@)+Mds:XYIgn6;gCe%6VM㫞=y*;դ@9%=uu|QZg%%Νt>wTU1j.vOSHuB8j(;Om1-_UV1tPYv-+Wdƌk MIIa$%%1sLrrrx6l_uO jeNF-/'ח:FfFG2>,* EϞ}BEEG˝:q%>WXJٱ9uu[,ZdW={^ҍRm9nezPm۶x={`Xҥ wuӧO?7of$''ĉy'{ F gnF 0pCxxsӉB!Ηwަ s;tpu8.#uܖ e]ƿoP\F Hm-=mcwPB!h if3RRX۫/Rm9fft Yx1`|؜Nv$B!DK㹄nܷ:W#.rnC?rӯiNp"%'CPY{eB!}l>}b$uܖswmE~W .2)UU;/K2(Bz&!{2--u ܺPHIsrhW$'35*^"B!DVm3fnby?)钹iq[73lܸS2h rssXt)6mrqd쩮f耻۵su8B!{zu^Oc;͍܅8_nZ;6鶲'PjE IDAT| w4JOgΝ>}.7!B@OOՋuu۳EĭEko6J.*ruH" a6mhMwAD8LHMe^FtNCB!hNbcGef255"a 7 33g[nEӑ˹_D+ k6}02$a !B+ӿ?t. Έ  [9r$ :ӧ3k,fϞ֮]ː!C#44o#G4oذaFرc]@M w䳒~B!pcv=x3?۶UN>V ֭./dߟSb2x&%%}ΡCXx1' m۶eذaMz?G94˶ogZTĸ:!BfiHVMcN֦6K\ akҽ{wv;x;we]ܹsyg>|8޽O'agB!95oXr(۪1<)QQ>8Ճr<]; IC夥'z"))?ABxᠶ?? iOgg3?&FA!B\:ׄqMXkjX^Xt,c¸&4!!ze ., .ۗYWWO|}}IMMjŌ3xG񔃴Y2 E!Eu屎YGru5+-ٜnIK"(x~rˬ/ +V ++oS갈"88~㥥[&&&2bzlfʕ,ZbŊ&Wsz:KԻIpGyA"E4q.N_@x8.r-&?LG7__C71F#-,Jº:>SyfZNFlV4Mho??>wq̟?M6aظq# :̚5z-[0`oMM ~~~f|~z,SSn\N cŹ"Jʊ8R^Z'M8ZW>Ԛ%:z=F#F#Ƕpm- Rm9nȑ#v,+_Çu?NGZZ;wfqw;<:^d_bSP [4 E%-%2pԖ~XffeҴ\J3sQp2IFSM/*Gy$Hx^>9{NQl6:uFiڴi/ժU2f͚i̘19r䯎}Wec[pXhQcU!^P^ *zyё?{VYP @9)H1&/]6VP |\ݻ"2je ip߿qE#p:{jUC`ѹsUi7D`Qm(!X,@E "Ek^zQ6mf͚.ӧOkԨQڵBBBd5gΜR~ҥ$;vԾoZh!ooohUy nݪCe˖Sdd={KXۮ]t=I&Uڵծ];-_D_j/nUV%Q/֖&^-[VA?xxx3}toͦM{jLgl62:t0v̞=DPӴiS3yd/`g ]N:l6s=3fl63nܸuZ}1f6/[3|+W]ѣG3f'vfOGlԟ'm޼Y8;\.Sί[dwz9:v쨘-Xٶn:eggv?99YNҊ+*oڴiZjEGGk_gl65h@9996jZ~,Y &zN:RQ+Շ@X|WKM7HҡCtQp %tM/ߟu.Zn-W#G(44TWgΜw7ЪUtKVસXÆ Ӄ>kv ///uA_|sRjz>|X%eѡCaV=:|$Y/*,,>,777g@8]!!!Sffƌ#Z{LӦM$v[&MD՛o^k׮-u;IP>}tw*44Tv㕐O?T]wR5L^^<==K{yy9ϵk}kϞ= $I{5l0effjڷoΞ=B߿_'N^pQ 4PAAN>MT3a t:uʥ?l6bccifPڵu۶lR+2Ɣ矫ս{wݻW+VW_ZŜ_~uIjLc4l05jH5Rƍgijܸ^x!///Q+խxs_{5g[~~iڴ[qf.9o-//τL???sĉ;T"ӣGa>2Q+8zhBs___sic c;fxlDEEe˖;wc?hꫯٳZ>6cPׯ.]#F(::Zf֭[vZWpL2E999ԩSիW/I &\?~6l-[8_H?-nСݻ:wK/:M\#Fhĉѣ[b%Zz%áիWOYYY;wvޭ_]ÇDl:~kg;[rԩoFӧO>SjUw"E'xDDDooos7իWWpEEE^k~v2]t1~~~&88$%%z%cf̘a7onLӦMĉ/頊o߾:.}kLNLxx0!!!SNf%R+(MMVJS/6i$ӦMj<<egAAqկ__~~~j۶.:>@wqK|3g*22R:t5n8+,,L/˱{9EFFK׈#vN͟?2V$I{T_.CuU;vP&MJwÆ 8p` }O޽{+##CW_}֯_M6iȐ!;t7jѢE0a,X-Z(++K۷ow9M7ݤcV;p:wN(IR>}{ѣVf͔u֩o߾5k,8p@u֕$=ZjfΜ1cƔ:Q3g9VzzVZ%IjڴƎuoԁ;M 7r̈8p%ձdpEqAI STT]FqΝ***RLL+##qU=**峍aaajѢK ۷Μ9F顇ҥKUTT[/EppErwwwfV\\,I:ujժm۶nw}_ㄆĉ<~5khJNN㝏Ԑlb@"9rDo׮]2OOOuMݺu?f͚iǎU̸J @B\S6m())IǏW\\=k+Q;w֜9s.il;o}](22gÆ ԩ%y|pEϚ5KIIIzլY3K[nUÆ gogϞ w5}tz뭺vZ-_\AAALm޼YO 2`z.QJJ|*'TNNNZec+\#G, aaazt p,+`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "EUZ lٲV\YYs1{lUɱ_16nܨ ^OFlٲK~rtCkV``ZhW_}չ}Ν;ddor8U6[;OLLĉ/QQQ?l۾}*y+ @W@@⋋n믿.q@jJ+Ϛ5k [n/+=3[ׯeEEE Pzz$飏>n׿/IҎ;$cRSS$Izǵa( @ݺussj۶2Ǘ'xB3gLwu}vgۈ#4rH 2DꪫO_~zǪ}.\0$$D͚5pBeeٹsԩ#Gh̘1z衇4qDmذAv˵tRg\-Rjjf̘ڼy$i˖-Cp:r:ud?~\K.s=uIfΜ9shÆ ڻwnݪSN:[N.߷o^6nرcp8b 1Ν>@ǎy晋LL^=S~.Eqq.\lHq տ M6)?? ו{hjjbcc5zh5iD-[Ԛ5k_N%''nr8zpkeYaESvکsJMMucq~]v+-Z7oiӦ+2o߾3]VFrC:d5lP8p~3j(}G.W.|r+88XAAA Vaaau^^^4hLkVt1Ir=\(""BEEEήМ\^uѫ;v~y ,IDATP.]ԳgO?,,y̝;W[VHHj*g@);}b 4i$eeeIjbdd˹zzzyDرc:v })''Gׯp UnnSn] 6L)))n޽'N(;;[$wwܹsm<ɓ'k:JCM37100P=N>]) ә3g\·_:pرc:q℺v"Xڝ+4h^z'O꣏>;=ze/)??_'OVvd &C\sMU]5SRR_򈌌,w^l6`|&N7:߳EFF:?yy)>>P[ +gݻU\\~_̙3uiM6M СCK3 AnSttz_|\ڱcGcǎe3b^Zѣǯ/edh_|y l5uT1B!!!~c/ѡCh"Iw߭ŋwQݺuTn\^6sWQjB(!X,@E "E?v%eIENDB`PyNN-0.10.0/doc/images/examples/random_distributions.png000066400000000000000000004372161415343567000232410ustar00rootroot00000000000000PNG  IHDRmsBIT|d pHYs&? IDATxy| nܗD#_ * W(mR3hAUiPhohl GY#ݑٝ&ٍJkgyggyF% """"""""QNQQ؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDDDDDDDDd؀EDDDDDDDD6 XDTf&L7|LпKe[ӼyslذT֥ TQZZo-]TѣGur[wܹSIӧO#((eݻwCV|}T .DΝKe]JƍQN899Iӿj888`ӦM8i,bׯj5eӿ;sZK4hŋ*ׇB6˖-MKMME^Pre $$;wƖ-[d_?Ǐ/+%GAナ+bC矇|}}ѡC 3ΝAEC 988ƍR/=|}}vСCxBBB Q>|fx5k#))Im6,[ =^*зtR9sd~士,ɓ :,,jLJJBÆ hPR%~p1"ի]͛[-MD5֭[ܸ?3HKK#UGwqpp#W֭[p@k5 [M{.&LpbŊxꩧ0k,dddŽ7#Gն8T*UѰaCxyy O?4m&{=4hR ^{5YhZn /// .`Y&\]]QNC*+==UV5h2ȑ#矋/R:|M/_FBBBAItt4]")5kѥKx{{O=._,oժ*ڵk;wUCd NȆT*h4̜9o6g/T*Lk׮~ë7x۷o74Vмysdeeaٲe8umۆ_|&LUK<3Zշ]v EJJ mۆ~ dff"::HII? tyyy,-5PTѣ&::wErrr8Yܹs SO=b7筥oVy0~f߾}6?&LqpIETT,JΝ;eLƍ*T@@@&}GGGZq5\t IIIضm n۔\޶[nᩧҥK9rSNѣGj*)ŋuVkVߦTZ5̜9G6mڠK.YYY8z(&MTlذOF.]ddgg#::r)!h"s0`"""発;99wޘ7o^ql{p on4ְg *bŊprr2vҥR_|Q6T~ѢE aϞ=8~8&Lٺ $K~#{@@9=D6C':w,D\\4}ƍBVKEDDlٹs`Y\/6m "!!A|ѺuksN̽ٳGaboJ%KѵkW*ԩ#6m$^qwc_Wo]vx'D DppA588X$$$} OOOѿi[׬Y#Zh!4hڴ8sHIIM4"::Zvwb>`I9k^zYۜ `,brppi'իF)=iӟsNa޽裏0qD)))x71x`!66GEѩS'Հ__},W_}-[f͚iB`ݺuHOO7荡RoOsHOOG\\ѼйuN<&MHӮ]޽{c8uvލ^zIzmqŊq)L6 'N^~g߿_ٳl2$%%a߾}HOO7i0)u߾A^鞌z?/^ Vl| CppΝ;n:|gFMT\۷LhZhZX 0{9vVV̙+V`޽x"}]>c|Z*_. 6`HMMEڵeرc1sLhР'۶m7/ /WbϞ=9s&Əo𪥹ݻ`T^/"N8q3g΄;h۶-7n#G`qㆬwi_/ɓ'1j(RYu%t ]tc0p@t2~ƌ۔oYYYEvJoooT ɓ2e /_^deS&M@_~y4|^zر##+u3<ӧOC 6 -- O?4~W{Ɖ'0ydL09sGb„ xL8pttD޽1f|'طoΝ;'{ &*_ֱv9+֭[QNth֬;+V@ŊQ~}7ُ~"VgDvӛ͛ !{`?;wԳDWBQn]ѲeKw^^pwwWB<|3k,)̃DjդiW^NNNROV+*V(,\\\prr*JTXQ3…  ???q!̙3ZҲ֭[=u{%9rDjqE<]odӦL"y!˗/ՓͿpuu?Bʕ+˞[n'~b֬Y"77Wݻ Z-f̘!y Qvm DzdyMQzuo>!ӧO/B9rD8p@I*B 2DԮ][ܿ_qիHKK3i&h4nȺZZf"((H$Tβ}jE*UٳT*yfYㅻ̔uAԬYSnݺb̙os{`8p@,_\;vLٳGtIxyy+WHam&֮]+?.v!~i,ݻg2̘1C8;;z~A/uZVQp\}G"%%E>|X3Fj|=L1W#bccJ5uƍSm땵c!ye^||pppBH^{zS|Byׯ J%Ν+[qR߻woizDD2e,l |GSL:tMtPTٳMB!Ǝ+eu=],oݺ%]8:: Y1}999qW_Uong ///xbiEÆ ʕ+euW,[h5i$wffPTbi*J@>}DTTl=qqq},u& ~CojZOҴ3fKz`M2E߿_=zT$&& 'H-oK\vMT*.͛';&f̘!jسgnѢEbǎĉbʕjժ${`QX hDn̙h۶^%x'dQ~}Zlnh֬o4iD!((;vĒ%KФIlڴ ޽,XiiiСC{hc.̙33gĔ)SLnWÆ q1@ڵºCoذ!ڶmpDEE}޽;c8p̃gC󈌌DZZ"## 0oooaҥ=z4Ǝ GGG1B6vSNN g}Wƃ0{ltF oO>xg`| EhhYf8<>#,] 3f޽FK,Att4*Ud0O ??G %fAAAqQΟ?駟9::"22R:ց'JcNV ,2M^N7cƌAfd`Q^=,\'O٘TሌD5f߬<ر 88ǏG~~#m۶UVRJصkڵk???R<7FZZf͚MD*1uT|r rsskX?;v#6mڄ=z`ڵwSe?[.6oތl|8z(z-uwĉѳgOҹqF"..N#;;[V#0dl߾?HIIFP0{(]W4i+WyŊL>ӦM3:_RɓzB^K7(sAZtp<(űb C=k.lٲfPPχ3 J'22R+BgFbb"~GQ//6nܨ8?==nnnFt t7csnqnU\Yj`LGGG4jΝ3BCC oҍ.AAAzI/+22?&"CEO0o< #GDnnEprrBݱrJVBϞ=k2OY6mbbb>@qGƂ ^gX"qit]Y!k @uVرӧOLJ~aÆ޽{ܹ3 '((HjPQre|KH+ ?qtt25BJJ ͛ HatW.]Ν;W#-uUi>,.\(k.8q~-W+ǤI4vOOO+ki} _إ?TzY%HHHV5rK.aUTZ  !Ν;,*؀EdӧOGDDȦWX׮]MKMMz\߱cGaضmɯUZ={Ę1c6ltgɓeo>>>9s&֭[Wzjժ>[naԨQӧfϞ.]`ɨZ*/lذ*W#Gbƌ]6֭+YaBO?4ݱc!11v!..Æ Ñ3f [CAV˞͛7!!!x'Ea׮]|3gĤIj*T^ׯ_˞\ !~v޽h߾,M}Ν;ڵkZjǔ)Spi|f盹\]]1dժUCbb"ezI*JBB5kڵk###xKll,:u5jʕ+4iS:w#Gb…رc&W˖-tn:$%%aŏn"2^שS֭Á퍏>ׯ_5`_~ .5_Ǟ{9O>Yws<%͚5C 0uTe4h>#\R3dڴiؽ{7"##1yd4innn8v8 {+** oQF!::HOOǮ]2~ذaR"..8{,V^ŋ.F<<쳸}6~gxyyW_śo?qqq8p >l$tƍhT^wŊ+{nر@AUnpQlٲZV*{}}}ׯڵk8{,T^>>>z*Zj$&&^yӽߣu֕^݋5kl۶-u놡CڴiK"&&^^^4i+0U>;Ĕ)SгgO߿}>bm-[pu4k ...R+*r666zB-кuk$''c˖-ؽ{7Fȕ+Wcǎñc0zhlR %QSm7J@_B A6}…FCOL>`wZnm0kHH4noF9Z4} """"Ċ+ -Z>>>O<"%%hJm&&&ڵk WWW/ڴicZ6+<BرCjqY_|YTPA68 . 6e˖bݺuoǦq`Pܑ#GJE_5H1s7sq5j! c)^z*UQZ5+?CfҤI"88ݽ{W 8P ѽ{wه.]*„͚5ׯWr?==]tUxzzJ*',{O WWWi:{)^Dž0])](ZH UZmڴMsxEXXh4MDDDI&?Z4B >\ԩSGh4('ӥΝݺuMѣGz *@-+ߺu FlRAӍ0` ..."00PkNˠ _աtTc~TTHLLM gw5j˖-5Q奏]JB_^ *`M}rW)O"m&5j$<==hԨXh,9+MꫯD:uhԨlpK.-[ hDhh3ftAܩ,?8ax"9ZjZ>W,뉈,TfoQB!>,T*@ٹ~BVsβ'<<A™3gJ"DDDd&DDD8bV9///DGGK OvpUYؠ xu %""DzGeiӦaΝX`<==pvvV\ٲ*TP '%""ŲWl*'V^ &`4hlFAnnr999h4JɁJ5&??PPTaj5LEDDd<+B ??nnnP:z""zܕr~kN:a󃂂7o^-jPreYXkLNN0̄kY'b,뉈ce=,;/ॗ^Bdd$V^!>:H:|DDD۷ &XpXeddI1-uZ~TV[* Gckǩc~ZԺgKyZ^z25kb͊Y@6m * ,^xiZݱn:_/͛Xv-:wlK*k_d'Sb~ZԺl%?JV*c+iy>u1?6`٨> r `ӦMt`ĈPTBFFejBf ʚz =z@TTك+Wbڴi޽;Ν~?ϟ|Ǘ=XO%e2/*?!@͚5.kaɒ%i/Ɯ9sZj>|8n۷7";;={65jdVڵZ-[-Etu1?yj]VÞj<.KK:؀EVg/{I=aZeOiOiV{>u1?^^IoKMc4N-ZI&)~yj]O+y*@VV) EVwˇnvy<2338޽{pss+&MYRe =X֗lBA4iZe˖)=u:to߾ybŋ#,, Oݾ} B@@ѦMZmlln[87òkY^)9󖈈+6͛HHH@5~I1ܕ+WТE `ƌ{.f͚'N %%w… 1dxww^1و !бcG?~qqqѪU+9rj*/5ʳ8Q2J Yt,ewͰX(IcYo✃%q>n9Wga \2]hڴbS";;GE*UM6Ev퐔rrr0~xt W 0yyyHHHAoq[]v ŤI_[-Xq:7Ƹm}/nlq, <폩cByy9{YwL)+6{:L*{֒bשҪCzlV0,999! d#&&F@۶m5kH]v!==C-?l0X[nE޽֭CJ -GXbZE+]+/reW`^ sn*E3gY(zF `bk9@S׳Z5k\y*땘~Qye\q][eζde JyhnZONij|cydOe Xvիq4ib0/22oݘ7kܸ1j5RSSJmjj*|I8-Z3g'0;V.z4֥ckje$d*Ӗ4+ߺ'7,ZY: 3]bۊS[F:X ǶscX[SET*iS[c-rQc7Szed{nqW쥬/9vJ/^1} ιPyf`q]ҍE%uK ꜕(+[4 aќLQ,)~ÞJTZZ ((`^PPӥ'iiippp,pUY-[T(Lv֜0L4XWYb)˖6NÑ"Qcc7ҏVwb(S,2S :e8ѐX:9& e 3v`iҲ%v`es='z׮]ꅺgM#֣ʽLizaK﫢{ƙ[ր!g,4>ސrYr]WzcY~dz-롬)0VTQ9ޖeuqV(_qqSJijTvv6B \\\0NNNΆb<...R\e)RGA75 RJ)RcI8XoP Aw`|n4dzwԛwPz,wsKfwE3PlNc9!2e%q[Ǝ;0 {(<(ݰaCPJ>+SEe05֖:J5u})m4`ȯuJ^ΆynuV( Ի޺1ֻRws!gyPqަsWm1Z~~d؀e4 rrrda4 rssɑSRšʂ67U?!ed@\ʿ.yLi>&3#k7gôjQ`4* [ +Zm\ψUN|r 7Ω+/+>S.6d.c)]BA>_딗)z˙Yz]V \obR_?ꔗQp]-]-Zr(j?sޙEGy*3#RR+ESE]ݓؖʅ9 cu׽^PXZZ|}}X7o^-jPreY kޔNлAB]sn|ބ2n}*(?w~=F:Yd:MWzg3ᄇVmB$D11ay$o<5&͌t(ˀR9_#)lN1L3g[8L]ݢ+aF9w ㌯QM̞CYyILJ—II(ugemEy`~1 `cn5ȓnξhrGq&~E^,St9w[jǙ_9]1ɲ̌Ư+ŸΘKob[rU֓e؀e*W+RRR!FB~~A}yA"444ffd 77Wn@>! Ynan' q*֏22n29`VVb/^m)SbmRڱFTo#i7Gf˘w0Jy&jm?XrK]i\/t/-SNi9]2zkѽ_/̴83|oa~Xc?qS0gJqZzvP7w^{`slcݺuòep?#Μ9wyG צMbJ ^xAֽ{w[ׯK/y&֮]Ν;[YӔ'Mq-^?oKQ YZ-܍4zƀx`Gq,P~MDYqXdF's'Ⱥj]nLeL;%j,o<ӻ>ޢm(Zڔ.ȭM%u's0=j07P? Jm[9K$7M\7'l)-UZ窽0g_ SwJy(yO.cK9}3VVel1V\8fGI/Ou66`٨ٳgŋJ 6`Æ W_}Z*vލѣGcرpvvFLL fϞmtȐ!pvvƜ9syfTV sej5O>وIJePN  Z3lQQ7eu-zkHڳG6 IDATضl[-Tr JY)J)W>z{fα]s̜GSCŹjOW6>'c_򱸬0Ξ˱k-dmlQYիd0 0 _| 5.&;۝n*\ =ooqnc2Zs{)Q3{>Yk[SV)<|R]C[|GD,8T -'<㭼4N/嗭pLDM] _Q։ *XKD{`o^* *lܹsЫW/TV nnnW- w)tC߾}qM8/^0h4O?-M!""",뉈 ]|26m  >8p&M#GHr Zh̘1wŬYp apB 2/2yݻ#F@vv6bccjSK,뉈 ˎ-[ wPn]˗ԩSGJ*M]vHJJ999?~<:uիW(w^^0h xyy=X+vݻJ*AV~zHZh۶-BCCfiڮ]C6lݻ[ԦDDDD؀eZj!^u;v /_իcȑh4z*nܸ&M,Tߍ7kܸ1j,,6`ٱׯ#//`V<@ʕQbE>| \JJ """B=teasBopx12FWRnl_<+4XQ{[粞,;T;wN6}ʕppp@ ݺuÖ-[p)̏?3gΠGҴ6m ,ŷ`^0NvRNDDHP/Qw-ӕJ=wˎb۶mxg[o7ooRJqaڵhժFwbhذ!'₄[ѣg\ӦMwm)e=Q6`ٱ-Z`ǂ ? $$ӦMClljժؽ{7Fc111={4&ΐ!C9s`ͨVΝÇ=X`kҤ lb2\zlV 5iDDDd,뉈886`Mc46`Mc46`GAΝ777ԯ_~,̩SСCxxx}͛7[x1 hj.DDDs,Уٱc:w'|'N;Ο?˗/Ka\-Z3fݻw1k,8q)))pt|x,\C /wy{ň#زD""z""""6`ٵw^CN 7uTdggѣR iӦh׮0p@@NNƏN:a // 4hJ~Èz""""BhVX7n`ԩ,! ­_111RڶmPYFk.cС {a֭%%DDDe=Q6`ٱt֭ wwwxzzbС>իq4ib|dd$RSSߺ7nXqPղDDDTX`;{,Z-th_ _ 2X>((jRX999W^--"""XXv޽{<,n]] -D]D*EJw-EQj_ZjZbXE>?4{#$܇9s̙s'LΜ9^zaƌ?Ϟ=qqqB mqqq@\\,-- K)eXODDD#0+++@U:tJ ҋWceeWe_2.'NDDJ$orDDdbxO;0*pXq ҋ3,,,>fw%&&޽{0tN,rc21&-8Ξ~m<5]뉈(gLߔN˱XyX+ . zۇGY=vRRRTyt˱e0cLZ܉5=0X#RxhX0Q)cc=e;mBDxbU? ԭ[кuk믪ݻwh۶֠A8;;cϟ/XCd\0蕘!ӏHm_tiy͉ȔzC;N➇k׮Xt)Qn]ٳׯLj#PH#n:ԫW G0}tTV ;wVt7n틶mի1qD8::ґLDDDD؁-\JҥKi&*U 3gD~<ŋǾ}0x` >h޼9ỎW^7|-[DzXODDD< F¨Q ۶mVAAA 2F1q,""""""""2q""""""""",""""""""2i"""""""""|f„ jZ޺/iӦ q],^ް'̙U'""l`'""XHDD&M[[[}]\~'OƐ!CuV4iIII .DnPJ̙3o6iӦC!"""뉈2 |g]6p=պ & ..(V7Dƍ`@||?Wm'''cܸq޽;^z"""*8+ؿ?6l؀3g\a4o\ k׮Uك[}>}clݺ5g 2v`)))߿?uJ*魏ĝ;wPfMu~~~ S VKDDDc=t|0?>n޼?(@ѢE-Z111HLL`ffWWWU> 22@DDDXODDDG`q111ꫯ0zh8;;(T:NKKKt:%DDDD}pqqA߾}3ceex޺xU+++$$$,'>>^ɗ= C21&ӎIHmtiy͉T v`aW^?A||<q +=^^TTaaa 1dܽ{WhAbb"ݻa N/t|DDD/f2I~1XDDd2zq#yXDDDG2ePL-[GťKPlY7pss {1ft˱"IDDMà1iq'p|8,--ѼysL>]#M^`iio[lA%0sLu=c=$ʇc0 ۶mVAAA 2fH뉈$DDDDDDDDd؁EDDDDDDDD&XDDDDDDDDd؁EDDDDDDDD&Xyѷo_T\(Uڵk+Wx"6m ;;; 00w5Xŋ +++xzzbΜ99}(DDDdc=Q*02e :?UV۷1{lԨQG7 "".0yd ?ꢴm۶R &O˗&L8Xb7|7FHH9r$>OR_qơ{pppxGIDDTp1#y[orʡRJp႒a4o\ k׮Uك[Uf>}clݺ5 a'"""J|(::Hܹs5k燰0e9|jDDD{뉈aV>rJDDD}(@ѢE-Z111HLLT򚙙)i,,,=e "v`#/^D߾}QN ˯Ty`iiilN#""XODDD'q' ?34 3mUy`x%_%XN4NDDd PǘwL3'!}ӥ6'""S3{*؁<|M6ÇqA)RDY8AEEEJdܽ{WhAbb"ݻY N/=ы `RwL7b -;`)8a34oW^֭[QBzzۆGY=vRRRTyt˱e0cLZ܉5=0X#RxhX0Q)cc=e;򰔔mGźug0_֭믿"""BI۽{7._m*i 43ϟ~5Oe ى^[1Xo5KۜLYZ7)#yehwŪUT;v1b֭[zaxOjժsJ~Nqơo߾h۶-~^'N<<""(;SNA`˖-زeŋc߾}=v4>{c7>)>ܮPOo0h @NPre :ѫ`'""#Heݺu077GnݔB !((FDDD.֎^c=E"pxzzV秬'"""v`JTT-^hQ"##sVDDDd,DDDq,RCBu:=o_t9u/ǟn2{9QwS.#!ԝr6]4/e_ndX6պ.;0պj/Uw*4""] 2UTA"Ek.U PR%,\P5g! aDDD3XZZv5rqc9K۩0_|T-(4,HJJŃWDDdJQqc}` ݋Ǐ&w=r4 |||,CÇjh4hZDDDHI1ܑ""HIIQ+뉈(b/!⭷1x` T\nnn?sDDD*뉈(/,RÇ#::ʕCHHnܸKv1Q^X'!!Fʕ+UbhԨQnWv`I[ȤL;Ȥ  6#QfMϙ . V 6ƚYi\\Ν xxx5j 57]=O:wy666(Z( 'OBMӲeˠj>fffs*`^:PH4k ΥڛiSHLLĉ+++)R͛7Gddd.Ԟ {b7>z`7>z25]ܰtRI&4ip%ܺu+mFxh4XӼ#;mzu5g}{{{ر{ѣG {QFƌ3?`ڴiz*nݚK7=ƍCҥU鎎?3f@`` c[.:5kZiRR5k#G[nZ*bccqQzb7>z25| #%%cǎl 0`Znwy"Usۦ...Koժ… 9W<&m#ԩlll@`ڵyǏ3}%11qqq(\* Z֯yTD0k,|GErr2^s b7.zc9XOXT޽+V֭[QD G6x?ȑ#:uj.6ox6(** U3ۦgΜARR|}}U[XXaaa&KDP^=-[իWUyt:jժ^nӧѹsg[nT{Ӕ6=<"##QJt666Ajհwܩ8Xo|Xo|dRrcQnrppggg1cȆ h41b*o\\*UJF)""{FG 2x6(!!A\rjlۦ֭V++m۶:m֮]+]v+V͛ebcc# Gڵk+F+WN._K7MmӍ7FWWWP,_\-[&*TN'gΜţ޸덏޸덏L ;(OKIIl}ҘViӦ GbŊɓ'OD`\tfԭ[7j};ܖmb jr1S\.x4jW^h ~ɦMdRti{嚜l+VFN'J͛7R:ucEq1cq1S IDATc=<-3VK.hZu떪˗VHDֲl2 ZmӌN*F&NǕrM ]ٗiԮ][ʗ/,'%%I*U|W\KKK6lXSn6]nh4iذ^ oa޸덏޸덏~UTX!!![hQ^ www… CD =z4/{7n /nܸ%KldaÆw>|+۔d-Z"EEEWL{fD|~={3fP+Wp}lӴs0 ٯ(XXo|Xo|4www6z*"""Pti%=""nnn[nի([j{F^zA 66|$'4͛ѭ[7is1FMZNiʕannǏM6JD]vF9S2홙ׯhh4$''MLLDRRQkjrMT DDD卌@dq1cq1c=| !8ڵ`J`ҥpvvV2alܸ6mR>Ǐ|ظqUYvH'^zXrenT7OnۣQFXr˗/Ǔ'Oжm^wSt]~ 'N@@@ 5kTyOz56}iӛ7ojժHJJ´ifWZUTɍꛤ잧aaaSн{wܺu ~-ի~-txzzzY&p ,]ŊChhΠ?~w|hҤ "##1g$%%(_|.x6pjժ;;;"ٳg#%%'OQxo\Xo:dɒFJ0k֬,{WWW١M6A}6fΜB!2uyl"h`ee)Sz F8w:unݺaǎzy_Y͛Qvm<}˗/ŋ}v|5j"kѢES/nz]иqcxzz"44۷oǹsйsg%ϓ'OҥK#44'дiS7$%%C[i4\ro۷… +ۇ:`޽8rJ(&M.?lmڵ F5 l@@=zm۶*77o ԩS0`k׮,Zl?|d;ѣGG2epIL6 cƌE< B…aiiB!*r:~Kʕqmܺu !!!ؾ};zʓvln\?XԪU ˖-СC'Ob„ Ǐ?y&n݊O?:(QSLɓ'q 4h-[ą O>Exx8+aƍtZl*'../ //^~1c ,X_~z:F?Cu~} t}6KVP!BIС.\ݻwc֭ؿ?zeYJ}g ;N8wwwZ ϟǗ_~Çc޼ynغu+֯_#22[V)\0^>D&/w`$ʻ:w,-Zooo:ti&j1cG̙3tҪ>C8q7NdȐ!,ŋK*ߢhd͚5oNʕ+˾}<ʕoFﰰ0h4r5)]|wbŊTRE5QΝEшVUqk+++پ}6l;;;cޘ1cz2>sqqqQ.]Z(:Nmۦ8Pׯ,CYOv Illlrʪ9sFVݥSN)))2qD%ȺuTغuxzz4h@BBB^8vfY,YcǎV[n{X>m4y7͛'ח?C\NN'ytAlllx2k,b YI"EC;vHJ6l(wܑ~M^:t蠜"_~2p@qrrwwwYh@y4k׮Xt*ҥKQn]-[VN"#&&FoTFٳg<;v &&Cʹ-Yvmtx"ۇ>HylqժU3f &M/bĉ=z4VX u?СCH$ӱ|rƍUu4i2:;c[t:*T /^DaѢEFҥ_sʹD>>>@&MpС'O8bDZ۸pL~Zhjժ!,, cǎСCUvJJ J(… ꫯ_bݺu^3f F0C6lfϞի=zΝ;wơCpQxzzYfY>&p4jHÇ?w;ӧMƟǗwV{^tIoe_v~||}ǯ߿FGG,y߿?~͛7ƍ;ÇqMmfš5koaΝzח˗/;gϞQNI& D||<_sdta899zJZFh>@X"zmceU֬Y#ZVݫM<ਣ+祈QSSD~VܻǏCIxxL:Ut:̞=;ّw*"ҤIٳ*jŋt"ʞ}C7o.C Q ei&WFڹsd/3FV_G^V\)"XѢhd̙m}}}ߡC%GƏ[jU iӦ[nF+WȳgF9,;vÇKʕU j,c|ll3gΈjOF+:u2>#\"xb%ٳgRZ5Yzueݽ{Wf̘!rql٢gggY|?zHoσ1#mٓ'OTuܳggɢjU{\^=yO?TI}QG8jjĉRbEm ., ,ȴ~Il"gϞ͛7ԪUKRRR2&+XZZiիWNKaÆ8󬻸(+SLAÆ 矿t*UR-J*ʲVo333ԬYShѢh֬,Y5k_~ABBڴi*cȐ!ܹ30d- u΅oSL>AAAxwO?!)) ӧOGfpq*TݺuCǎQNЛ,<==,[vf̘apBɓ'cڵطo_-Y(R:+++ٳg(T˗/jժӷmsbҥy&␐ >Oڈʕ+os|طoܹd͛z6sTV Och4,0z]vڵ>l0x{{O>ߵBpqqe___DEEaڴih޼j?VVVx:\~IIIx74{{{TPAĉ;v,N:XH-}d(U*- JV?:POsa,GVT UTo{ ;Ξ=?cƌQ"B"#xwopؽV0FIl. +88k֬gv]PlٲS֮]~ŋ333Ä wM"]|yN\e˖ջXL ;Z-v܉۷RJ={6*T7nR'~?u9w2\ǨY&N>set!m"osDdd$ݻ /x >޸qK.E5UV᯿͛{aaa DHHHuիWҧOSb׮]zCin޼ݺu3>&&666;'f 2ݺuî]p)t m^O'ћi ӧ1{l>|N^}2*RUiѰ?[';iVM[GDyqԩ={6{ԩShҤIg2@6mGkN9ݘgii2e'NVŘ1c28U1/eu<0((qYԬYSǏѢE r {=:~S?<~lk#~e˖E1aTVMoΫ[naΝʣ/*22 4;# ٳ?rШQ#0vLZ!&&nnn/UO uMWǕi_&ge^sdTHdɈyYLl묜?5BϞ=1|,뛐&hx8H&M;Snnn}*-ȑ#xw'N_~f͚ەy2SxqkÆ æM iӦ OcǪ;jҤ 0e_y7`ggϣ\rukFڵ1j(*U 7nk׮}ˬQ֮] 77L/-GY+;ziiK,r;..No FX;rꍄ6mԩSqaJEҦNI&aΝϽdgg>w *`ժUHLLT.(CCCUġCPN՛|]i/eu!̟?R{nծ][;w|(d;M_ߑ#G"99fffJ}+T7IfqСChٲj˗Uϖzo5cǎhҤ Ο??'NTe'2rH4h{6Ǯ F1cƠEJzjW\ѣGgrqybŊ{޽;F~}A5a*UN(Tnܸ{8'22<3ϛ2:9MZױged@ obɒ%z7lؠi IDAT }pE;w]t2ļB V>ŊVw_~kp9 8{Q= 2e=K,Aɒ%hIE!!!ܹs8pM4Q;///e]nݺŋرcRG?~;wĕ+W0zhG^, W|yX/^ѣGYgϞ~: \t úu0x`%ϦM,Xx1Ν;k׮a4iGtܹI;tKKKtϟO?Yf>{}Q2˗Ǯ]pa\p=zYti=z7n{ > ѱcG-[V{vb!oV &d{pppЛD~ĉ(VtR9sׯ_ƍqa%"1 4;w'ObϞ=Jӧbbbо}{?~ׯ_ǎ;еkWlmmcРAX|9_0̙3GyKϞ=q :/_ի7332ƧF`Ĉ8pnܸgbطoHjݺ5N<+W"11Q=~mtt2LDfi"##Q^=*U SNŝ;wrҤK)S"+*#ۗ/_5kҥKt&Ns-[eʔQ6lDQ[bEe'O0tPx$ˎgϢ~<͵Hxyy)/GPP{ĉڵ+ԩ|>>j*<$...ҨQ# ʹLCnN:Uʕ+'* 4K.-ZV3vXU;wV+W?# %-}dĈqxCiv ??? *~ M."o>Qt:)WķijO}v^ۋT^]~r5PzNnj73g+++2$Dٓݸ#Z{{{)R=Zo˗/o-b_|aH1ЋdDR_zaeeHе?(ZV4hJ|-VVVbcc#>>>W_&?OJJŋ+΋Oʗ//VVV.;weիWu,666-V֬Y% www (n*bee%uU~7g5izfL2]7n,wV֧ţ1c(?ii:g-[&bkk+[o&bO/SNU)SFu=~Xxxx̙3VZ2b%Ϛ5klٲbee%uԑ_U'u@V엽I}Oǎ^%88X>Ҿ8www)T)SFz)wQm駟*/B0d̘1c|v?o߾"ҦM+S~yM]DdT7n@ٲeҐqƸu+uz- 4(ۓ1l0ܿ ,xVZ |;;ܮFXODD>b]Zoamm˵acc'O"=mj\lO{)yGH덏?4>q=>c]ڣeldlSb{Ը؞Ɠ֎|8sԸ؞65.0&LVUꭻx"6m ;;; 00w5Xŋ +++xzzbΜ9=xݻwG…akk  ,,̨DDDDa'"""z>v`L4 ׽~:&O!C`֭hҤ Ty.\nݺJ*3g~mӦMS4k k֬Q│W]JDDT1eM#"ە̵oCRRݻӧO+z˗ҥK(V`hܸ1{QD ؼy}Nyfܺu kע}X~=Zj{.<==ѬY3\2:?}TybGbb",--!F4>q=ϔcc=76Mi|MG`cÆ 9s6l@͕ Zhذ!<==vZ%mϞ=A޽U?֭[ףH"-m۶ؼy3uxDDDc=QD֭*U>22wA͚5H*/Z^5j,ӧ|K{oJ?MCB m AKT,UXA.G#Q/Uǃc+ 3uP(z"(h^4mB~nB[Ry#YkZk-BƓO?DNNN$$$KHHB{ZZZ^'..'`0KIIO 6瞓4H >NK $^'%gw\}䦶Ck7@g%&&&`cǎ~L&c0p8ƀL&cS)^_qz=fjf}|%gZn\/$%gw\}䦶O:BO?Mll,SNm0&22Z}555>18΀hqu թ|blkhDDD?,B!vx u$ !MN^ 1}oӦM"~Gjjjp\q1mJKKf'rqQ|bl6c4n MB!+''c6[yB!DK-C:BLqq1J)M__NϞ=ٲe {gϞ䐘Hnؾ}_[n%99YR/v۶mx<;vչyfHJJjvNg-;;!`ޱ|VAAA{Ozz1 CBbʚz!y%׋SJn8oGW_ie"%%hONUUK/e֭>7yyyL:QF1d6mIJe˘3go8Àؽ{7?8qqqrAmF޽V˅htMBIr}SJQ]](t:lB` $O:B̞={5k9eeeDEEqUWq1t:_~%'Ndɒ%֭OEΦ 8wqOʕ+Aד‚ ׯ_!h +` !Dx (߻\.v?M8ŋ߻woW>akywj*~!Cw^Rt҅\P>ncXod !S᚛DSTTfc„ $&&R]]MAA_5OdbѢE>X~W ,`ҤI=tR FuR 6]vioGe޽tܹ.> j >>>~BL M6pBz!-6222`?ӂ Xt_;w.3gRdeeQ\\ٳ|#пzղ/B 2+ (HII={'8qp%pM7zj`0|L+BL6>!?¾w/{S0>(֒nSu% !C:BTuu5Ǐzj}u)ܹ3tڕ,^|E';w 55T"""عsvCsNRRRҿx W_Kmv;S;u"߁.: q+'=0dx %OiB/h9끗k|`ph`1 <^s^z!"tHV1c , ""QF+hy'HIIn:rssꫯ^O\\OXJJJRn6$$$PRR7~[Wɞ9-rorK <rmf !h$7OG`<pE~8ud6㉠[H3x;ߴ5!i+D=裌=~mn7'>33޽{3ϐOff&& cǎR>F)ENe0>4YY.S lUˀeq#_3]^KK>4!M'A⹖9gȗSGRy31 ltCrr \B!* KJJ"--cRUUEzzYyGt_^+t!22'Ms8N.>>qw;uI˫rfim< 1xݛg,b,,ϺB!.<}=_\xiɪSWp:ן_/JE.[oCT8nG}$+s l۶7c2B+KHHvS^^r8z(>~֕Տm ٌh 43)ޭ Cq1 l>{?a=p=6B +''.[o\o,` @Kz x.ZW/hP}عBтB'Xan8lӭ[7,99mۆ!ްdvW͛"))Im8΀[vvv:SBe;+?xy8cVn{>ϩ6Y,9"ZBqegg7wM6[o\_aU͒%~!\صJH"o >@!D}m!׋s'Od"##ꪫf͞=CjeiiiĐlfZYFFVB|ӛJmTWC^{S9J1e1̀5~+`_x3K !Dg=u,'LƊ < -0M9رٻ&# \/Oq1͉'[ѣeee,_{K/EQQ#++>}n:} vd"''Sɐ!Cشi+V`Μ9tE_fĉ޽8rssx<̚5B{`(p8,,XY6^gB!$qKN*-0}Wi/˹X{6M!-ArJ5x`FUVk֬b*++ոqTRRX,*22RWɓ]p+dR{VWYY~aխ[7eXTZZڱcGt*@9&ᆱ@6VY1~dl-}!8 ">m?@ S@ӟOow*:(Krժl6y<Bj^L! 1/&22:騩͒%KXz57|3zbk5rqQ *Bstm6o *g^(u Tvj1B\Xa@q%PYY2bz衕 4$~mlƍTTT0yd:LfcKн{wFőիq\-qyyAj^~*mi: 9g_/7 !}\ |pj^(nMk IDAT>a; !U]]ѣG9py;())\wu~ߟ;wjJDD_lJJJ:ٷo_Pգ+">>Ut׽o_6_RӯʺBI OUӜfW^WV%B5c uW\̙3{yW(-- !!︄***z| he ^h7ÁiւP\`0MBnj|_|;iC_L,.B7 Q>(ׯgҥ 6 Mmm-;g:5.p`4d2iqu թmIꌷv ̦xngxoj?_UrS+R{ghہ[gnsBס KJJ")) c2dټy3 n}555ZLdd$N39jjj؆t>q54"""n`w{o ig dv爨F]!n<O}SC=774 11׋Xa"##m۶~mԂJKK`0nr8ѣGILLm ٌh 4YZyp``,x5>N2@!%''c6ǃp,Xk#ˆ5 z`$%!YB'Xan8ILL[nl߾/n֭$''kQJn۶ c:7oLTTv;N3ݨ:}R&9xGd Hj"}tj)O#nBp"tJ^{R^N8J=(++cݻ^zӧp!RRRfTUU1o<.Rn˛ԩS5jC aӦM,[9sVx< 0ݻwGnn.d۶mݻQr08f#b۱X,M ;=OtWfoqa:"t?^nd !hM>X#=|rӉ PsBQB4]z)sQ\rСTDDW ,0a޽{wh4*@ 0HGt:.]+V4Nlqul6;9zhS5ss기 ?pjfkw*` |\? VsG]{!hP"-!fǎnL5\CYY 3f`ڔEhѢEz袣\`K.e .m!!E:e(..y'Xx1ow}7 :ǣŕn &$$~l0zҶD,Y9S\$pƶw'#"||__=x)TwQ{<6nB"d2uTnfƍ?>qݛgy|23K~;FcM&Cp8رc8Ol0UWWcXo.`U-RWa0ͭ@!Ra曲TY(:{fy0̝ > Y!D%Y0YVN׮]Yj:׈xGt_^+t!22'6`Nm v3(u2LVO!+!>k UVY,:ul~64,Z˘5!D^t`'Npwr ֭[Gyd"66 ,!!MyyOѣ$&&Ė[WV?1f3F1 Ŭhyڳh@42P~4wBudjRp8͛G}WT'b2B$s A1kQl6֭VR۷n۶ CrrO;ݼy3QQQ$%%5:v;N3 C&noyڻXC90$>^{- !ړΙM}tf1+g8/ίogRNRz\ҁb<lٲ|S[[f+={6C҈!//'6//õ V+ZYyy9c0t-MOp?J~,Aj!Dkg=\~6׀!N%3!h)׋ECc={Gzz:,_g1c(++_~deeѧO֭[ϰaHOOM&999L:L ¦MXbs̡K.ZlFF/2'Ndőa֬Y-zf [y-!=,=-vD`4`|1o7Q !mBk _}r\>eW&I=Z٤IlV֯_t:z72áTzzOcǎU:uRZʕ+NSZّ#GT׮]՘1c} M~*[ZAńp}JK֪D-)#T= |ߜXιJ|x.uɔeߵKl6xw#mEz|2+p t4W\W_7|2bz衕 4$~mlƍTTT0yd:LfcڵZYAAݻwgȑZY\\^O|T3R4n ltwvkw1~7^Է/ƍc} o&BIVZPRRÇΝ;uNMMKMM%""/6%%%`۷/(r&u=w8te$:9daBV~z6'^v{ԩ BцHVXlw}@EE^ 1 RRR6X'4K:ZXA^wc?xݎ,!hum9w\¬ (c xV<))86o\/M2uTnfƍcǎ~&'p`4m2؆TJ͎`4+5 DY{T\z)eB6n7k[a> gʾ}x#OY,Z&ZB!t`0ڵ+VBޔDFFP[[wLMMOLdd$HSSS6TNm ps:8qݎAT8c@>oBnjav7{Zj`|v{lAEGѵ`y /,,6yx%GEpHV:qwy''N`ݺut]W7ĿnjA}`0XMyyOѣ$&&POlcfFֱcGX,|?dInkO_}Rnja,'''`1n^@Flêwޡ&-u` Fjc#X!#Fw߱vZ~OLL[nl߾ح[}NNNF)m6<_;ܼy3QQQ$%%5:v;No;vUMU?s J!߀OGꞚ !DwN'\̺-?X,̛=5N,sx+.)R(# r. 1LlB~~>7j(֬YCqqVaGffVFLL yyy>a6>|VjP++//'??t)oc 7a܁MW|ʸVVz9sOh+\@/F(ZkN}MJbT)2[)׋ {1{=)//g>njSO=E~~>dTUU1o3 9r$G&)){e˖EvLE}W4!8!$ B[!ޗ#=?~M9ZQB!dd0袋xjpƍ`>SLfv鮤wőիq\MIVxك92#xYU!Ir5C xr|󍼥P!DȐ0W]]MΝ&66Sٹs'>婩DDDhbSRRӿٷoyYw g]pxkv!-+s=@)i"lh_l.J!D0O<ŋy뭷eСx<-^O\\XJJJ|bUWV?">8/?woHpI+I!ڪpv5Vnj'0|FcɈ,!ƞyϙݛgy|233p8ƀuL&pбcǀqJ)w-f4빓H+@`9L&@B`\e ہI=X E>>>@]E!ą$#ژG}N뵲HNg"##}bkko7kjjt>rnO?&\]v"x1F!Zn0k]POԄk XX 8w FGdɨ,!DKk/^&Xmd"66/@NHHvS^^r8z(>~֕Տ=ٌh"ZF`1Czx<ٺUnhTNNNc4(pW^aqKXu܁#k/tXB^rL:Fyy9ݺuʒQJ}vm۶xHNNݱc_7o&**Fnt:}Ñf\uVVs+@DZXBnhHvv9y8 \_[[˱cǼ[I F#Gqm"ԙC?lkF !ڑE`ҁjkkl~g`СZYZZ111a6>222Zje瓞`ht :tGEmڄ:ìZOxVL3B(^`hp 7 .]ҵkWtRZD8Z OO ˀZMBz4{zWw}0m4***ׯYYYu3l0ӵL&999L:L ¦MXbs̡K.ZlFF/2'Ndőa֬YMj,y#J3~`e*vBO^}NSҥZbEt:Nl5@A׻.)6j1*uyoԏƃ҃lBBA[uSwkRm_Gp{1AUhPVUl6mx.`4V?<~\/ɤ+Lpa FsNsݟSSS}RSSMII Xguu5kvt`3XyӀ/XXX8r䈼Png]\hf |={7eJB&0UZZ @BB߾***'z| POlS$>OB@ۀn~뭛qftj !ڲpPׁuSm7/_cq|v!$XapбcG}&'p`4c2؆TJ6L)c8]-@,ޛ7N7:d1X!D[n uh/=?8 X,tB! Xa*22Z}555>18΀hqu թ|briOo>◀&D Gd~^le?O&#un{m&r&`zѰӹ(<3`0 eg-^z4ҁM/ v)//s\=zDz=L׮]+l]7ޙ9Ϙ \ ʫL??9*KFd ~`4nfdF#]ve%p]\/ϊK8@2CPB:m-׋0Hnؾ}߾[}NNNF)m6<_;ܼy3QQQ$%5~ +αcǸ:`F!D'h0 0j ;;pݼ& \t:9vNcHgg|›Bom-׋06j(֬YCqqVaGffVFLL yyy>a6>|VjP++//'??tIoc  7_'1fq~vr =B; 0ٺUNz-QzCm-{K\/ΏRz{2wOrf '$ \/Ck7@ꫯRYYݰFI~}->Z95`4)#><. X,L;B#v\/. +cc:H GN^s ?xv$ !D#Sۀ7\wu>eW\qbnv /@UUs믿f֭tp`L4ѣG3c >cM`̙Mo\E ^Mvzq`ɩ$> ]Vϴ(t:lB]Hz /eq^$S[G`| |7׿JvdIB&Xm{p8/ѣ_=ŋy衇gᮻbʕW&AorXy$7,"n`I|Çsal6 !.P܉k~ xY7" [W|<뭛uBBd a3ddd0l0سg[oO?kra0(--Eg0m˚",x;+0\l>>rVYGK!xo5 qaX;Гטk<̥܁wݬ|s`#0&>O>Bڤ+xx#F0j(|Ip8ر& Á`p`4d2iu5ZM eM 7I>ޛYKvh>>Fdz -u4o&'ą8';FVOc%R@K \6Ъ.+^!.,jczw;#RHjkkbkjjHNgzkjjƚvNU&D(;fԻOoo.er 7Ix)\M7at:)m7 V q~n7'>P^z'jSZHq: )yU7݄>]^r-zt`A\r NݮM'^P_ii)111 ;vS^^3rqQԎ׶n5M!BB$ˀ_MMLn;aǩ5py+}k7\}ImCk-}Gn:HǻA]eڡ՞r'A=& Bbb"ݺuc~q[n%99YR/v۶mx<x#&#Dx %wQ`g`G6Mq:7 Px Wz m2+[w0N}n|'=PnAARښV}-ܹst=5Lԭ\T_X/cíVIj[>zr#<%Y8TB߫~C#--a\Rbf<]DEE_ŗ_~QP>}eš݋&!(~1K%ި-d"5lrYkGM*7;:RR\Ve@vGw@ʭD[&l~ QB1MkhRzڹU-mhY緡9 Qx1^iπDmlPPX@^-b:Ϟ={ &piFFF?~<Ν˛&틪&!SuԖ-R"Fm) Ȳ|wz@ ٣ 09 <@2M5mlِu?rVGTƄh&c}ZjB8| YûZt%8/ 4{Xѕ^9xQ݀rl!ς:Oll,\\\ Hx^^^7jiwB^>u\ f+ (U|Ca "4lC!ʖJY#75W5(gMø{j(§XB4i,u45dY66XY ݏ!-U5iW>ɳvUS{IF,“;;CjjjJ@KMV¨+R`.edMEh3d@'03,i430?K*7RI. ^ ^UmE*ȳg!@Ay9`d`h"P~uii) ??v)@eg]MD^]''ڣX")+@"ВaD;.֛Cֱ4+iBRdB}W?A}/VxUgJzg1Mo``ܝoy.,SXX###tHh"U^?ʨ/u7R=4QwmTy__+;e B@vUn d%WHY{t5%h\^Xgb_E x{CPyFC*|Ur+SxUWLa_m޽ĥUTZgǗK$PWM mUiεW&ce8 lZ쯯uFB| |/Oc vcTUcbo"ZӀ,rq^*ccLʫծ];4nǎ_v m۶ysfX!  ]WC<2TxofWUgM1 F YC[_&ܪzT+P8ja I.Mn!MG|/u zBy1jl7Tx5.1~Aͱ^kUW۫S]lWO1j~M/2ExT>: Z>rmõ]bzBzzo(F5\ԁEx<<

>^iÇǣGPVVGvtNdd$z={wvutzͣXY_CQ#D#}}}L0K322qYhv5v5.]L۶mq5-ժh֬]vI۷_`塼\i5bfQ9(kzͣXQшX( ReddFixzj9r}vtVEE1amViƎ 333D"ҥKڮNXOtG^(kẂE4"-- vvvJvvv`!55U "z۷oGJJ ,Y贙3gbPc͚5Zq=?ڮ344Đ!CcժU޽;Μ9:h:b=E5bfQ"QXX###tH'>~:Lnݺ\#)$$CEjj*vڅrkZ:)++ ,aee<'O_Is΅5L4X-[oǏֱDP,CţXpQ;;;)_vQ)77 @nn."""иqcmW2d.\mWEܺu 7nDpp0RRRwHJJǏ]Yf())A~~S(]AţXl(ֿ<&"ၛ7o"//~9xxxhf}hj:SΝ;h޼KիN>>x1̴]qz[nD+uD^`Z~=>j*W=׳gO}VVV0`?ݺu y/NWԩSG&M  k?q<==!uaoocccnݪ6֭[w\QoTB^%z֭4ɲh"o>7o'*p&oDEECBB=y̓;akk_+WDvv6/9s0m4u^ g>~@`nnssst2|'h߾=$ 4i1c(ֹqF  OJJBPP`llVZa…(--UY,4mzzzTƍCtt4g.B >@zz:+..رcѾ}{w߭U?ȑ#annKKK=ӊwNP{޵k[nHOOG@@@CH};&X| ,Xz,Yt\zF„ *MLLTꬪFAAmۆׯ#"" ¼yx ݺuCӦM5~^/KZZ DEE!""W^E`` '??ppp@TT_bJ3aĈѣHHH@zz:ӑFq###1b8qΝCfЯ_?^cŋ8v'Oȑ#R0`eeKKj֭wbݻ70m4رcuwaÆ j377xMMMyBj/:뿐HOOGrr2I&MM s{1^ulݺBtt4 _~޻wƘ1c4~N/SfͰ|rDGGҥKok׮   &&nܸ~WNaa!|}}1w\ߗׯ17">>W?s窬ծ))N`Ĉo<pu4ӦMC߾}k]#p58|0N<'֩~Nغu+222A})bN!cg τB!~…Ãw_xe 4-]IRfaa/^جYkڴ)۲e wݻw@ `;wd]ve"H.3/y}6c17ذ3grO8OXYII ‚mݺ1X~~>aC 5twwg֭޽k׎bfmm 7ndL$1WWW㓓Y@@`VVVfwfaalllXhh(3f {wjU6l7nK|2_x Bv}^P eGfҒĉL(Zׯ~ImiӦVZ)7=Zq}X^1С;{,/ߞ={X۶msppPN:88ŋѣG33336vX]v7xbk7o(ֹsg&H/ʺp۷/aG,::y8pwqqq/ eT'44k׎6|p[ϳc_}>''D"믿=FwHڞ!G{5i҄vڱ_~W@ `B{MJJlNNebEDDo>fjj c5ǻ ;㏙5/M>D"v.}W^'2SSS^Zj%%%zYZZ2˗/3___&HT*eF}`K.eL,3gg>|0X|||XxxxfM͛p ,99Yi_]ʕ+Y˖-׭[zjN EsҥKfͧn[մiii8uV^cBTT>CL8QsBCC1k,oxqVڲe zrRC݋,*w(++Ú5kT,r?F||<:w̥cĈ w}{lqǎXp!pu,]O?@6z077ǿ3gp#V¶mӧO#++Ki9㰰0p}aUH$>}кukX[[cӦM(--Eaa!~G;޽{vZ׍1ۣ_~8sL9??J#ʕbǎ?~>///:uㆡ F K0l01W\E0o<|_~%<<<yq .}}}1~)֬YӧO֭[?>ɓ' ę3gpyk¹s&߿?Ξ=[2ܹtݛK333믿^c9yyyppp@1h !"tGիW1qD=/^|7Ƅ 44k v)W?wy"V񮪻w"""B)fG?C|*ea׮]5jRiԩS&MBII N>+W`H$ƟF/]۷odž 5Sc,66ViTRtt4 ޽{*3۹s'/mɒ%[n1~'_\\̌ٱccFɯݩǏ۷oW3z*344d+Wd%%%,++ 2 Bl2+W0ggg+<333YӧcGܸqmذEGGgϲq1z1G}ĜYqq+300PyL___mx& ٍ7c9ߟw\hh(sww;88XΝ;P(d'NҖ-[;W$&1rG`10PXoEgΜaBPiC@@>|Ξ=~'Ndfffl1 &_;ӓ#F=<<ؒ%Kxy۷o>shɒ%ld&XBB+..f&&&ܹsѨQ#hQQƏ_QVVUV/^&L#G[n4Y>Ņ{ߥKܾ}WV9e˰k.DFF7o/7nO,022R{ vvv`pqqkxs2E0Ƹs+[~_q"VT7̝;xQXXvzUN> ___ׯ{W.]>x{{ׯǢEX3Bt[u1_|vލA__D@@sssn$fMN>M68t }vbʔ)*?1ϟaÆժGII BCCbb|`|G?ѧO <G|n߾o߾XZZN:#:RJ(mߨӦM!''{ѣqIiӆ C@ uRRRaÆaܸq\~ 777.ɯӹHKKʕ+1p@bT[q1h֬k׸ޢE ˖qU|vZlٲCaa!JJJx#jݝ{8|p/GСrJ i(BB47@U J&400*fVPPv܉bcذaJJ899[nصkNׯ,OOO_|UvZj@ty999)5mllP(ѣGmb͚5hݺ5@6{\\]zfΝ;y&FQkVG`HMMEjj*=z Çhٲ%٣IIIزe :u///رwǏǪU```!;;W[W///ѭZ +Vcǔ:Gݻ &LP?++ &&&v^﮼CSuSUv4=z4?YgϞE\\j\k}wbcc_sQԸqcddd222`ffVuU,1Ucǎ5^H^u1ŊXf fϞ'N .. @wnȐ!c m344#ܰtRB,\PΘ1J‚iӦprrRxQZpsF+Ws_|н{w cݵGj>бcG|СΫd=z{4RSSX~=oDZ{nЧO0`kk[M u턬,V[MCkeWM'v܉Yfa„ 8v0v::rtƍ+T.//GVVVb& |}LOD#ѐ0xxxuּt[[[ecbb4Ν?y%L:[ܼL4mÆ ç~3dZ -կ_?XZZbػwoӲeK">>μ}ƼyТE o>}:qm:u]vVmϟWf#>>+q77o X/,,TB ΝH~XgϞUYr㥭Xaaa8zhw&7o T ???\R͚VruuUZpqqgY̙3ѿdff֩ ##:ߡW[i%ǣGV;JUhܸ1ounn.Ο?ɓ'׺ \|of!.9so6o͛7y7 VUeȑׯ?`ҥܾ;U>3`ҤI*mbb> .uWݾ};ϟ_;vrν&M>|9s`ƍ]m̊Qj'$&&Σ"99)))hҤ ]jo=3gΠ[nUn߾]rs=/ooodgg#&&7cJ#k*y liiF`!9r$[^zϞ=CXXv-"""4k׮q L4 ټ!Bcƌٳ{|NiӦСCҪ" ͛ycÇ1p@=zw˗b 5PBXX.]dݻpss,ZaaaXf pcd}8}4޽'N`ڴi܄ӦMòep5SdaaS)6׮]$$$`ڵ:u*-[333@߾}cL<ׯ_իW1vXW^dq[&M  sss<۷oի>}:?.|r̟?7oF͑ IcG``ډ`O:~q/\WWW;U:*9s&o,Y غu+֮]YfU{kVZ᧟~qy066j#??d.Ϝ9sxHLL'|7n`ݺuسgf̘QϞ>}:,YC=z46m[}̘13g~8vܹ9=Ky6b~Vp1={׮]ĉFW888HJJ£GT> oJ9r$x&ީҥKo_|<|͕&_t)4i///lٲ/_Fbb"~7={I)$$GݻwǏs}qE$&&?ĸqD"l۶ w}-"!!y&~gjS̙3NBRR\ٳg#22>Y۷#a222e1n|Zjj*z-Z`ŊxW|$|sttc mڴFo۶ ;wč7p ,]]SN \Z޽c>}111˜1cЫWSUVx"Z657 IDAT=̟?.\X׮]Cll,ÍҒjӦ &… 1uT{uUvʽ߱i&\zo03! ˜pDդweFFF2rׯg-Z`,00)M^^z)MΌꫯx;lUê)66yyy1cccֺukw^B3M._;v,'00DpjwaL X,QQ7_~aBsssܹsĄyxx &?/++cM6&gSVZ1X̤R) dYYY[n3+++fbb،3x2WWWfddĤR)eN>|0Xzk5i:ǏgL$1Ton>7e ؿsE!֭[C)=E+>>{͛7.kh+|~aa!7JƎ۷O+O#谚KJJp},Z 6lLHTŋk/@ @׮]ΫEDD ,,L+W}LMMi1Bĉѽ{wG؉va۶m Jnݺu033իW_g?~{Jӧajj(0!F`j3k֭?~<:u(MIȫ[NȈU\bkD!hFZZ7}͡_V\\D"AF\#B4:!B!BHFB!B!z:!B!BHFXB!B!^,B!B!RQ!B!B^DKSQQ"C h:Bc(++H$PHzB! :L BTy !W."'Άk011A~~> ]stN!SAA,,,莬P tNSC;IWPohQPI74sjh9銆tN4bns tN&xe˖011-zoر Jr7m777bS/''|5jDh< !W{B!XLRR{{{`޽dž Dشic\RׯG}Cb̙8uQXXYfqc˗ kkk[={Dtt4ZlbOByEP'BSl z1N:dwd݋j-**BfеkW8pK5j8dk. >{;̄ }Zշ{!͋ahh3ĖΩkh9銆xNt)s tN!&G@f͚!;;[i_EE/|> OOO^'B!otI.^^^(((͛75uzB{B!ڢ9꩙3gbdKU<k֬#44:uBEE"""n:8qthZZ`cc+HMMУGRSSѶm.솴|P(Ă 깆vN |:']PIx__h{:SC;IW4Dhz͛>RSSk.bݺuhԨcg_~A@@ ((;wD^^R-Z?TZݻ7&klx__'!WŦ&)ɓ!!!믿4X yyUQZZv+//SYBHM˫={B^&]DsKG 2.\@BB<"P^^L^R)Wdllj"F9xe˖011-z])1`GVZ}HnӦMpssX, ;rrrQFH$ALLFϑc *_xc!G񞐆K%LMM!J!Jajj'H*OgHV !ޢ9Ꙥ$!00(((޽{ 6 ((7xXlpe֭CϞ=-[AHCSl'ggZVnMB @9{m8YRW>ݕ%D7P'7d1xXY-Lm4m1C!E:ub&Mm۶ƍhҤ F߾}y ߢ"4k ]vŁ2G 99dq >{;@* }Z՗n^~~>$@_ܪnHpi@Z`Z"θV،Q'<EP "]Muz(ݜχT#pMqg$`n5n Irķp 8c-^[į8 nVŦW#:᭷bvvv{Tʆ u֬o߾? BwY&؎;gM8I$VRRR0OHCUQQd[n.+ܿ'`0p[W* g1E, `%~򣢪),//OۗNWb{]h/Wn\u719 `1L_M,WxŦc`OV[ED0V^AHB9ꩂ}RSStY8///===y<==! vIeyF΍WEAA%|. I#Gx&[8>%C<`N萐=!VvR)8Y <,)xRpט ?-vSQ쌒ŋFs`B^ zj̙3f͚w}k֬씎CVVJKKzzz3005RSS4e%(cgfb*܆;@_⎨ênPl nE_t!/{Bt?XbzMwQ``sϟ{{}933iQBHFS!!!:t(RSSk.PXX022R:N$qy PXXCCC!Ǩ+1KQ=煽T0%(8j;i#Uv; C(| kG"D{(z'ly.L=VS"+.\ׅ y(B\\\xѿܹs5pG,D)<<<2/om " Woul226A]CMW%dv"{>(V*EѴr!i(QQQr_u1It=S'@O 1[ޝ7U&%mR;Ze VÈȀ"GpTP###*"AeP7 , (谔,MڒМ IB m<}ڜ{rnܳp7'8 ׌)V~< L&%m"TcG#FyfvZநXת TWWSZZns!ڷoJKNNL#o}F"""|n999 *K@ X 6dpu52c C6u)w-z\X,"/#@gXM13M_U`0 EP ^9i G}$$$PPPoӦMʻyfWB27l@TTq}YVl6m *K@1Uv瀾mu:u7n#ёyuL2oVӧO3:-Kv/))0bZL ,yrҙ؊6QP ^9i 0%%%^i'N`ѢEDFFҥKβeؿ+ߪUعs'YYY bcc(377Ȑ!C\i#Fl6J+--%//L] s!"HwO'Q㓒˙={6>6m撝0`֭c̜9zȕppWm6ٻw/7o&%~vl6|bZ1L{M꜃c|eԽOڄK2OM=S3kA$"ЦBvrrr1ƏMQۀp"4Ilj(K,QW*""Bũe˖ydRj̘1gܹ2 *%%E͝;g#Gǫe2TFF*,,l1l6(֠$CY,l6A̠frjŏ mĠizi2NO"[q*l8nCh)}=8S->j[GPu*V^s3b^F')I,[go+wF[a?{i{`9(aW*㖻"Hlj\vsCQ?ra`I1 !Ħ's`֭& .[o]vy;v,zksΙQ… ҥ 2o<=ʄ HLLd2-[|0  ouBLh !{!$f6C?h2ĨQXyA+ <͚53rHzyի7n8a5 ,\#شiƫ 0qDFɃ>ɓdʔ)|J)֭[:u*qqq̟?t رc!RBW2rzJMֲ|N q$ 8Zs,e;JMDk㏼/YAl6{X/8~(|ꫯnH۵k2 owq*::UVVx>zh9J[dt*??ߕVRRbbbmVc"՞l6ş~?smZ j%sj ̝!PŦ`>|oYkZ j58~ ظeQs]bB_( }U+ώq:uk׮l߾+ᠼV^MYY9 hҤIX,/_J{i׮Æ sǓG}n?"^6;+)πC -k9CE&h<h~c}āCȌ S `5l3!D a6 s9iӆ88ݻGz޽]lٲ^zyv>}`Νu8BBVůhz. ~O#X|Z^ѵ-҆[Z"IVXx1+}L:_wyn3h +_QQaaa^'q^Ls+Dc+ Ӽߕ"Iy\|N3PXwe2{۱c\uU3ƕSOy"%%iӦGVVDDD,`0PYYz\YYI֭}SJy"(c8:V] mYX]!B{!R cJ?f w~]Y\ VЇЦB"=lfȐ!{vNʕ+]il6{qt:Gun *KƦr-mXe4p5^@ZHX᥺'PsX/Y9LJ=`ZL~ WcF,p5֋! Xرc 8cǎb ڵ;8\iTWWSZZns!ڷoȫ\g{0DDDrrrTyRm2Zt4{]p ?QxP 999uI{"X6^ ˗c8)..b'_I0zx+?~CO?|r.zgX(--%!!R7op摷Ы 6Ejjjjb|nӧ* IDATOoPYB4= pkm6 c p#Xojj_O^gܩ=y x/^@. !Lys'?,X$'%ѣMb`qIVq8deeqFӧWǏcXҟ|I J 66\F J1bf|WZii)yyydffޠc s kPYB4'x d#2k|h N{I{"Ѝ1 *"@>"SO&q0<K.%33R|Mo6<ȥ^ʨQKXb~)&33ӕ`0Cvv6YYY 0u[o1sLڶm;b̙رcٶm̟?3h'Nz.Dk) wlL!~c%PX'Lp=uy|h$ jO^`3끭! \ `0y8hش"Bh(J׹)ԑ#GԘ1cTjj2L*22Ru]͚5K8qgܹ2 *%%E͝;g#Gǫe2TFF*,,l1l6(ְ98NA , 5X#OѴXs(wZ=SX,8t'51(#ԗV[^bPBN)}O4.NDD6MqǣW|NjݕM:c#Rۧe4vV^CG&DbS㒿DVU[m;ΞWN-+.7E`FX/Ħ's`֭& .[o]vyݱc$::8ƌ… ҥ 2o<=ʄ HLLd2-[r۴aӻq[h [1s4p+#Dx/D3st^o-4`Yfp3w\.֭[G^\5\ݻy駙2e ˗/8q£ 0~xwμy+wq=_RRBzz:?sA)̳]w ]d;p lWH&^;nhL ^p8]%!DKW_}vGڮ]`P~+mĉh4}V\t:zW\i*>>^effz9zh9J[dt*??ߕVRRbbbmVc|Pڸ v\[FsnT/P117bQE9[.bS@{ ,QX+ cnccu"-M/Ħ'=L߾}isqN:ѵkWoJgСt֯_?RSSyw]iW{ǣI&aXX|+ߧ]v 6̕OVV}vюS}xawz^۫W/eVTTsF9!@Rݔ<j#pڄ\!{!O)EU~>;|ʻvwZ7;wݶm4>/FG +!D xb{HNNʛLYYiQQaaaap8p+2Bǁ[~|B Az\573_p[m y.^$ 0U˗S9|8>LԀ$%%~'ѨJ~p1?E+رl*ƌ@ee%[o0g޺tyn׹U˲UTTE28C?`+&| >M^!Fuu)cO x/^EAdfr|%nY999u10{X/uƍ=c_ChhC oUD X/Og :~UVq{<߾}{(((wӦMONyfW/« 6EjjjjNkT9=ʭ,z>G..SoFNuIӧOGnPEsZ`2;]%{VяJwQQQ^B`qəEq8deeqFӧ|Çgٲe߿ߕj*vIVV+-##Xrss=h42dWڈ#0JKK#33Ժ׹5,!ܹ4LܕK; w`׫e& k#BMXX)cO x/^4ݞ=f ]q F$R4`ItR233)--7xnG!//t>˙={6={;p7 䐝MVV `ݺu[̜9mۺ19s0vXmF||>,>#v{Ds11EwuD#sw2 I[0qcy Hz^E;B WQQ9C6m#;;ճsN޽{{^1Ŗ-[իӇ vG!IJV9WiFڲ֒KDEPQG@)0Wp&a,t:cܪpT% Ӥ#KCk]sbɐBq*է=Bbv &Yz5+VHU9qS7iX/|c0̕Luu5yv;}yu=HDD-''&Z=煚Ec'SA$M잔drm;B999uƝPz*^Hj;y"<8qKdҠ%ܵX/|cX(--%!!R7op6<--Br7l@TT kZl>ӧщ@mk/0~f'J9 BeuƝړ3"^lSb/v, + hsЦX NnVSh)^& XAX,'|A222%77#onn.F!CFl&??ߕVZZJ^^׻unaaиV_a`fs]10De.WISƞ`#^ AD:vΞW18'- h{B"Z #^z#G~>c ɓ)++K/eԨQ\r%XO?*`0Cvv6YYY 0u[o1sLڶm;b̙رcٶm̟?3 Z Im`9p7$ 6`!,eG@'^4'/UJ`0el_Ht*^s_Ց#GԘ1cTjj2L*22Ru]͚5K8qgܹ2 *%%E͝;g#Gǫe2TFF*,,wm6f;c-bQW*Yk=V>NL 2B:S߂JeX/,D &X,*Է^'mA[9[yMZbhMOz`={6ϢETq7niiӆ_~_~A q&Vc`,f\/oDR蹇y|Tn;BB4zR~/&m />,aUcXoz!Z.+YVq D\\z7=jǎ 8h3f DN .K.DFFʼy|;z(&L 11DFF[li|~*yx 8~V&zAHbem~ˊMh} ꣂchX/@Rii)999ر:0߿kݻwO3e/_N9qG 0~xwμy+wq=_RRBzz:?sM֪CVBqq1%wEEP;$d}\!Jbe-o0ox OR"(.BͦfRJԢEM8QFo>Wʕ+NS+RǫLGՑ#G\iK,Q:NJJJTLL]dPڜ38Ts2ׄC1@ y2)ZlX/BPŵf5TZV ǬRB IP=,r8 )INbbi3tP:tJׯիWSVV=IX,,_ܕӮ]; J'++>~6&Zpufj`_k#Bf`k(p0 ];bdX-4`P\\e]\>}g޺tyOTshz]90+IY:[4 p#<mεoZ#\q1|4uX/QX|DVTN :rxx@7߄ ]) B-֋XA9]QQVWWSZZns!ڷoQn]eyOh4sɩw9"ouDͫV L9>-Y,sDsɩ37\YbtYg}@#Z @'~XB\z0ҀڷoOBB^mڴ4㴴4R^y7oތ[XXU "55uZl6].1hk^N.c#MB4%y9<ߐO^gܱZGz| OsHd+yMd~M 56͛m9dD QEȹ|>|8˖-cUVsN\iƒnn.F!CFl&??ߕVZZJ^^;^&C BCkײS5J.g-7 i9 Qaaa=Hb7A=)@?r>+! 3h砅II2e X/O P/Gq~ݻɓ'# :dzh3fOBd?%h B", >gСZ~ʻJ[z5eees=O4 ˛"ejWf`*%D1b! hdD> )Fb8[ܶL&L&>{0L B8H.ڹkݻSl |ҀƎ9眃` ##܁(..vЧOlzwz^+ZDuMk g~q=kDNjEYtS^|r+zrn&%1;聶8h Ws?s3dDbNF~`\{_={RTPrr2eeev)**",,x|qf9.CG͙y59(c hUOR<;C4)e泼؆"CEKII6G&H"IV++\ѣ?0| nk@ee%TVV ,weoA;u?A_ńhv6:`2o-zs5aSB>juR#)n."x ~.Ҁb:vM7|RȚ ? '22ϙ7:s..+mh ! 3 >g-O{͕G&z 8ϝ*&j(\,n?/0W-0qLV,w `S8Pu 0-% dtyaٰZ%<<ІTWWSZZns!ڷoߠzF"""|n999gxt8{\ 2(|H]ev!9N`2AU߾c2ѵf7#0wBmzKI'hdU߾R1B3.eH6GG3Hu (-) o+  x۴iiiiiii((((`͛7p8<ևju0צKis}/[*)UN!BV <6!!IIlw'fyG}>gC=X/|qޜrGLEfth G+k`_СY %zM,X~-K.eÇl2J[j;w$++˕All,eb42dH^&C 6dbhJF=#036q%D1(~ ;e FřHGN;AKlytQͤsjEI v뭷ɕW^Ibb"۶mW^d2ݕG!//t>˙={6={;p3 䐝MVV `ݺu[̜9m(řrv LG[QqS!Dh"a)~fo:QTϟuhTtX.ܵ!^ūwcT/^L!3U@.mV.|Ν]{HliIV6lo&?<ǎ#!!#FcqE'?>sYv-<?0 :ٳg{RO8}Y.]yǜ9s{Y db(0ht jV]2B4 I]12Srԩ l 3 ^ٻ0,W-RՏ5"80!F>˸ @嗳s 8,]&% XA,;;zܹ3~i7qƝMD3s݅F~"1Y@++BS8 mNΣ!D}IN=t;v[o#Z4Տ"<. zcEk}7Fj YB42i"y ٰ\, 巁0]>Ko+!_1"F1/Y8OY[GW6|~ Cms!һZW2q ŋ>._z!ΖL.l6z!:t@TT}eʕ޿g(fƌ~9Kb[V}-L|b2Qެ,73p6^AݽBsl3D# w':5]vlQϪ~8"[M&Jw+(TB16 _;ޗͭ&L&EGSկHϲաx XCqn9x*ڴެ,ccdC~jbP\\Lqq1/yM!c$ XG̙ܹsiժfpx 'xY lII )&wLa2dp܊v L U}V cs]F*Mm']pݩߙL1dt:mlHXoZ)ݳj3ԉih}g|%Zj>w>_h[ådLJJ"-)hL56ʆE0!æMXd >,?~;ݺucԩ|~a詽P;r9p؉6 j`,8ڣAJ`0S.qZ >9;|@r>nPq&$7ڱz0ןwbh=vsj&1?f~<.bwX?x+xZ@%&*B XC^^Zb֭[3n8}QOXĵ{ZU"}dz&HCy{=k 9"8y~vafV|ehÀ'66`0}b{,󤷮4ѲH?;9XZ=h+m. +!Bgfcd=++x?m;v[;ہ]ql6{t(]Ҁ%<|7b2Θ -o.A!o îWSBo?y h| ]R9,-,Pa>K|<>qBE0,ᡨddR8p_S6ћm᭴ʡmHՋ8pm@"T%M ؁. wM]w*pn>5XYSk8Ic=.Er?pXۦ(le~ @LLϚj|W˰kX>v;gOb _.(hq~a{Ц8גSBUak|о3<ɯCaz КÝX&ߑҝޟv,wߧ6_e85aKxu^|}!ojpClzkZ w"ܶngZcSTfS]/W/>bZ`(d(BXwN Խ}8P.wMU5FzZ-XAn@f&%aA֚s NkyϺb}8_ؾzR.gwzkE;ܙG6:"tޝv#}tڕ x̙jd2Qv/D?vn/__vN~:: r;GOyB:Z}vDwow6DP`Ak<j7D7N7^npoq/|@AcXii3Zk_8֎_(.;/*~wPzRBthMEްodwz8o@Ծ9k%ַlKxHNN9t۟ 粥 [5!Ĺ~EӐ% !897Z!>tI𐖖ƚ5kX,nذNGZZi0;v ^_\$z0 !gΓVC"^!DX߲BaӦMۗٳg`֭ |~B!Άz!B#%<Ӈ#Gc6ԩ:+'B$^!Hz` /6ӧxb>L=_ !HB!D,!B!Bj@-B!B!D@,!B!BФK!B!B4ig?禛n'22d ]Ɓ"&&6mp7gϞ&u>3ƍ_hcǎ?k'x^EEE5qv)Rrrr HKKc͚57ߧ@j$hzo@!Aph]vjРA;k,_J۱cjժzG>}^i֭S:NM>Ϙ1CzuС9c H),:|RJ<ڵk Og{L>-YDt:J+))Q111n;z6nܨt:z\iUUUSNꪫ:g{Ml_ 6MfRJөEk@}I0z$hzo)%)bH(z$?{;K]4Q#(X"Qh$أkbChhLb1ذ 6 J bRّai,x>ϳO;wܹ^[Y就Arrrywލh֬V^=t;v(jծ];w˗/5Y+mmX#WiJ&mݻwZjݻfmm ooo߿E*mӮ]#Fi6l.^T54m+%%999eUb+,uu*h8qWmcz!1k|i˗/3DEEaƌ@.]޸ƍhѢ6WWWDGG#55\dHIIuannSSS <ϯm(cTaJ[)<<\rNiiisNe8]v 011 6Ҵ 777z½{ʬeM[IUXp(TaJ[Ljct_E[NJ 4GcԨQ߸ORR222`ggMC899iŰ|rdffb浰qкukٳ駟+WR6U1*2NE㔐;N~8%$$ؗDqҴMFFF2d:u333\z}ڶm0ۗe˄̱x_QƩ*zzmcJO`1 "B(R^%K`ʔ)͛P( }}HOOW[JDiڣt̟?W%{4h_mL"Sqh8X'"*Ne=NySKoQ\VJӦ~_~{///t :t  1mz}E9cpWcJo!dgΜ\./edd2ݲI&ܹ3|}}q1\|C yr@FFʶW^I@dd$>4i֭+q=>STVȏ}ݦ#MISiK[I.X'AJT'MSATo[6kBiڤN۶mѲe26TX_6?&mz뵋+$ׯ"U7=UIOO^^^Xd 222 dii $$$lSU^HQ4퉋Cn`aaCI[5kDRRR~zi6SIh8ٕI45NS;;(.6ViT5kVص#uކ!sX_\닏c*m+V2|IG#ejA@ƍqm/_c$Kڞ$t YYY8}4lmmK\]@vzݕVySIi8ܹs*.]KTMSA\\\piH޸tA-6ViThoPb=>/mKO[K_+±^+ړ'OTҒ{nԪUKԛ8DEEI ӢpIx{{] OOO$$$?cyյӧ*xzzjEQ6iWE(Էo_$&&bϞ=bӧOk.xyyAOOOLצq۷/vZ1MP ((Z2}-ٷm~J&uիRI[UXp(TTe(.zb++*-ZFhٲ%VX!!!;v@޽żnnn8s rrrĴ4m/_Ĕ)S˗++ڞ?a&fbb^zյGƍahhgbhڴ)Ν;'.6M6FJ~-A@DDmۆC0sL1_E'tmqAv)SChhII6NǾ}0qDԭ[AAAr NAӦM. ljjZIUXp(ı^LjcvŐ?X)RjժOU򺹹Jz||9ѦM9rDܞSI&011=>s[:u9d2_X >022ΝLJJJB5#)+66VBޱ:t(pKe4d|'ZjѣGhԨQ#GT)g„ hѢ \ƍСr9{=,]{9 sssXXX`Re1mQ11-!r9,Y/^lA~G!""ƈ#pQ111*'ۿ?Zn4lٲ8r>c̚5Kr~zm۶BOrBBv gggȑ#'55]6BBBpy{E%cǎj?Oܽ{=£GUۃ1p@>}.]B͚5ѭ[7Ɂ+W=z H{˗/qa Tedd RAD:u`ɒ%>n}ꫯ0o<>څW߿={sθ~:&LÇ㯿jժ)Ul2J~5QFx#G`̘1/_KbܹX~?kq :tgΜQTڔUXz#c/yyyQÆ _L߷od2""3gHvڒr>cZp!R*Uo,#KKKQmڴI$m۶ڴiCԨQ# ԭ[;g kצQښ+ӧI:u*ժU J*yf""JMM%kkk۷oQF(I۹s'5nܘr9YYYQ׮])--Mܾn:jРR Tׁ^z4i$R Y[[?}Իw"9kRj$i7oޔ+WH&у$yd2CAAAdaa!vidŋ"/;;_~)0τ I%}С߯^F)wAAATJڷo999!yxxP\\sN:OWi ~zݻ78p@͛I&&&dkkKOhOTZ5;w]ւ $~Lxhh(uڕܜ:v쨲 ~"z]~z{JDԲeK?~>''iɒ%O7 0<==={F~)ۓ5nܘ~wI5c/%\NG|={ԔӉ7w\jڴ)S$v4qD244Ç'NN:GEѣBjٲe*I>,,,ؘ5j$~srrh…@r\\\h׮]8t9;;\.'www *v,%mܸ$TbQbҥKN:*遁ԩS':yJYE)DDgΜ!CCCzUy S)))ILѣAիW'rrttT7ox7rHzWe˖YYYرc)++K̓A_}ۓ1jՊN>-nWo=z4h@&&&Խ{woyܹ*`ΦaÆ}R^=8+9tssI&꾧DleeEbڴiӨA #G%$$sHxcŤ bʕxvA^i˟vI$$$ٳX|9fϞ={!!!/0j(]֭[㣏>o :t(6m$ɿi&tQwFRRU;qDdeeaʕjѣHJJy=oF-ĴGa>|8"##O>Dm_ܹsh"DFFb…={6~<<<`nn… L,e˰eܹsHJJ޽{%u[hLMM | R>ܹsz 6l@ff&ӱ~z4lk;y$vލUVoDT^ݺuÅ ϩTQ)33+ gϞ-+6oތ"''}<<<$3ҰpBlݺ.\@rr2>Sq޽{1qD!""#GĐ!C,1`ܼy=zAY^xΝ;y ѣGcGoٲ&&& A@@ϟ/ "͛#V/_.\˗=zVZZz*:w, .]ŋw%tE}c_z-ZÈQ+W~Gn#F@bb"PfM LMMѳgOo7EyݿGQ[@_|Mv_"Ž;0x`OgϞx3f  Ν;[naɒ%,N+cڵkqmL4 cU\\^z>|J{ /Vۦl۶ iiihݺumONN RJ&*o/qVlmmѾ}{-ǎ˗/cǎy&OOODGGyw_ٳg?`ʔ))Sݻwmڴ =fA͚5{nߘ3gfΜ]v؏%u%tbTf*]xK.cXU`^aiݺ5 >Wb]$OUyPcǎl211۷+lK.deeQ͚5ŴQff&HfԮ] ĄH {&+++H:kɒ%$(99Y744LLLסCڵk*H&?֭K۶m}Զm[""_TJeddիKf),ϟStt_DDAtRR(D}%LF/˼uխ[tttHGG4h iӧOVZt9""3hڵF/^C/"ѣGSݺu)##C۷U9"neoݺ&ʽ occCƍ#{{{ɕZq(Qdd$ mۖ x{{SϞ= М9s$=zrNw.)#..AwQ:H򸺺%9TL,8~rJ:Ç$]tIOZ*,ggg&џI2LJ ,ӧ|r +W?xgXZZҖ-[~~Q(&&iڴidddr&;V֪U+1mڵdee%$iw䜜17}D;OWWWXK.4sL"z}@9ET_R~ ,3ursmd RUҫVJk֬)2V~1Β%KйsgJNqhܸ^&J`7jJZh!`gg=z`ƍhѢ8B}J󃯯/1cƨ䮹waɒ%o m|ׯ֭+^NOOz2oΝѨQ#xxx[n۷/T4DGGcذa> wrݠ7nݻ055!##puuEBB\]]U,*UiÆ yfL<ӧO.Ə/Y/իW6lڵk۷#++ ˖-C=p`Ĉ4hڶm *3ZBtt4/_7,^;v@ppګqFxzzZj*r9rrrׯ_c+gL<{ŪUptuu%cP^=TRw wL\~'OT Fݺu@@vvv*߹<~3gDpp0?~l)V91VP sNCP@P{!=z@WW7vsssqea1O\~}>>8s ׯ/ɗ~A3򊏏'CӦMCÆ Y~VVV8qyHHHҥKѳgOrý{ S{tR5  PwXadd$>J۶m(=N;nܺu pvvUPZ|odd$^4۴iCPH1 ,J}i$Gd2('y/ڴbkPZ[[رc7n-Z'|,X!C`رmNNNrQ{RHEa…no0` 'OwߡN:ro}ťK2~WXXX`ƩSO!"@__k׮M)B˖-C@@N8rrTDZo>ۓ`llPx+%&&Ν;;wеkW֛/)))B@@J].?+WVZ000@VP(UNaDIzbbJժUSYcSUPʕ+?QF066Ƅ }~7~w_8R`}}}888Ƚ%gϞ;w.ϟs'OիWncc*U~))c&kkk6 ݻwǡCp1,Z=Ǝ[͛7'W.^߮84l =.3f̐z+4m!!!zj1U\\N|wwwk?d۩Sp-ܹ@3gĜ9sԖ*yPRRRo%E`)'quuuyA]E=6Tڶm|rj Py& 'Il$%%؀O`1V - ի'ѣG|K.]vrիW% đ#Gu RF ӦM+F߾}l2̛7Or5[n%K{7~N:u`jj۷o_Zn֭[c֬Yx5WҬY3ر6668*}Ν+s%|gYs 4͛.^}:Uf/3aN> & N0/%}W*Ui\Z {.VZqa033tϟ?رc 2zzzԩ6l(!РA āL8N d̞=7nDZDĉAAA-ٳgѭ[7}hh(4h"Upelٲ~)>c 8P.ƍ\zC A6mē~~~ š5kp=|ػw/[c"))  +WGbСźZvm8q*7JNNN_˗/>Q?C)33ׯ_ǵkנP(ׯK'Oua˖-_|4l3fHc_ &&SNETTk.L}["33Syg'&&nܸׯ>|777{Ǐr$  "ԯ__پel۶ QQQ… raٳpttg@Ν[<1c6m`ӦMXpb;wĦMp]̙3ˠA`hh?8uƏg䄁{ŋ\yڵkƍs={aNNNr ;wbC]+::׮]CBBqu\~]8p 1tPܾ}۷oNJ+W_e?6_><<<0b7n>S*cEA=?{GK-RY=9:uRYA\xUmۨe˖dhhH5`zĐ Օ'}DT$Eܕ^JTZ5';wJ>|j֬)←{dkkKrׯO}~wjڴ)Ѿ}퉉KUV%\Nu֥QF˗/(wI&2e/&CCCrqq_U%ǩ}daaAVVVԥK )LuPݺuȈ]ek׮M2L5oHEݱN2%/͜96lHrŅ̙#Y<++jԨ!.:OD4n8rrr"\N+yGaDD+V ڒ'={V~!rvv&\N;v.l 6Аlmmk׮t qU~ycܹs+㔲)͛7SÆ ĄTBZ={A47w.]G&L '''JMM%EW^Mݺu#\Nk.>nݢΝ;7_|?] ͝;O>t-W“'OÃLMMS2FgddСC‚,--iر4c =f{uMСrɃ= DfffTJ>|?xwVDż1V.bccp;{,v트"_z[ZjI&yXvL6 XfM~͛1i$$%%hX888ڵk~6L4鍷$2XAqb͂aeܹ3ܹhd2۷Or;껦SNhڴ)ܹsqU4| !cHa 4NR~|d2̙3TC?5T?5cfqj5SO5S86U~QyWU.ׇBG2 41ƘTI- LAHH$odd$wSSSXYYO>U[ аaCr8;;㧟~Rŋ9r$V #<<\d1ec1ƊG+ԛ8q"Zh!I[xoXx1^|K֭[ 1zh_}Ξ=#==~~~b>"B=pM pssCXXԩS g1!c1ƊV9}4 @w~cѣG1=x@L;~8 @֭ښ$gdjjJbIڳgA B(>1XYҶT㽶'c1Ʊ[XJJ n۳gz {{{1spvvƎ;ĴSN!)) cƌ?vXСCbݻQZ5[L7ߏLM51cxc1V8> 333W^=|?V\]]%kX(ݼysI͛C&m֬2pΝR1cqg1+>eѷo_8p,X[nC~: !!`ggī с$C1-!!2H2c83c/eZn֭[{>}I&>}:O T744COOWYbY} *$yc1Vrc1ƊO`UuA^w^r9 ##C%W@#ˡP(Ԗ+12oAe [}Lz\]]@|T8c1)Q"TӧO'XcL*zg÷V5kքB@jj8_ykA^ &쐝OJeffٳg^fggW`$yؿj׮oXe1co8ell\+Wennα1X  ahh`ʕ+*BBB"wqq ENNJް02/]###8;;Pr|6`ΝP(P(5kVb1 3k,1w9r$zce2zVr|Kr ׯ_!Ŵ'NΝ;aiiիWK\z5i}Ebb"#Ϯ]%E(10 ` Յc#u/mRYb T#zcVb=<+^ܹ3r9ڴiU"""֭.\zrs>x͚59&L/_bٲeUBBB$_իW/D>}3g`֭Xp!N*Av)SChh(ԆLC_d"";>#I^###c1 M B+p+zW'Sd|4z45_1Xa-ֳ2@L\ZjE֤OSttJ۷oSĄ,--LJ?~S АhŊj%''ӈ#ƆLLLݝŠBAxNxB' ׯbc26) Ux:$h.:P$@S}|\c1I ,q>c0Γ7%%c1Vf坁0 |a~*Z51/϶f16px ,VRP-}J^|>10 /W.LL`+--+cJO`"'c\V<\cb`/Sʹ.1\{kRaO7]!cit<ic1|U0A @b9׊1cchųc1|X`d24i-22ݻw)ョO-gÆ hذ!r9O? 9UV =;|cXc_XܓXlY1+ >h"־}{`áCЭ[7deeI1b7n~ mڴtRI>"B=m6q'Oh$1ػzX `!4_c!O]t!777jܸdѣؘ|ǏE*\]]%X(ݼysI͛C&m֬2pΝ &<51Ƙyb= ͘!]&cK+[z5 q=;OZ>~Ob1Ƥ*Rgk`i{aݺu?~<իWDll,?.NW^WBB,-- l<}T/33Ϟ=C4;; [,F{h 3Sc}iŏ?Enlȓf:HE/E#]c*JgeO`ixƏ888/_FTT7ߠz갱+WT D744999*yTʼt\<<ij=[ 󿈨ub1VY͚5 BK:Krߑ=GQc+ DO\‡p)tcﲊY[LFw^3g"%%+V#O>زe k8qwW_}%KKK^ݻwW^ ccc|bZ߾}{nٳ| ӧصk+E)=5io߱5p@;[[͗o+d1:::)jrz]^XOk,=IP1ԫ( xJЩS'<{ 7n}3g`֭Xp!N*Av)SChh( kff >G[R#wfm"`&A<>c0ؤP(y~'f⪕Et-vX[ʃT1 Sb=+UXF cɘ>}:ѳgO,[L ;zhQfM7n$L&Ç燕+W"==زeKhr؆_1K0 Jcxc-{=ű1c<i\YR؏a|1X~|UVj21؄/Qc䮋zcqxwV܃ZON;c1V*c#vo;zc'X `;˰GX+nJKK+z2c,0@0W1ػO` ,-XX3c1VpUyW1cO` $:9 |+c1CMt~p @{zc'۷:u666ر#޽;LMMaee<}Tm6l@Æ !쌟~Im/^`ȑZ*LLLp,ģ:: ]!Δwcq/> 0d{1B<Ϳ&cU>|K"%%XbfϞ A׋Ѿ}{`áCЭ[7deeI1b4n?ڴicҥ|D=z`۶m'O o`:ϰ0!1Ƙx_r;C8kbX1X%DL䐋 5h@L=z4ӃĴǏ n:1-==KRg}F,m߾A={iO|]t<|?F-Tsuuaw%7oLYfjLKKÝ;w4ҶgcǏf"1I&! Ԓ_V[5H[ZKurjn-ڢ -Z*%`0-Đx|39s1q>s>G x9=4ק@PP\f JxH%ЛN]!!B\2UE=DDDĉ;w.DFF.22\L|||wKXX'OeffZ&:o}~VG!xg~1gY OB!D "XUԄ X~=K.esE ]t:SBvF!BC8z?̗_~IHHut[h4Ny˦Ⱥ_5|& 3yКCB꯸RKXzCZeˎ}7u+@m$ !DMPc&ٹs'O]Z(33P|}}8+**DEE"##K-p[6hPr+=cK"|wBgy+zeRXbki!Tv#@<0XbBT{5!֋j6ٳDEEAZZ[T(ܹnen߾Yl (g9f Kv,rV!)S`6=n]2~?Xb$3TXq uG]c{!jB'XU̩S.]Ē%KE 45k֐aϷa IDATiU+| ` b?zBQUX/VeW@8=z4Ν[nDGG'|¯oA@@=+W{73{lڴiðat:OΘ1cHJJo߾l޼O?3fPn]{Dz-޽{ g޼y0u@g$Ql>g=hc-BﯿpѓUFcy\z! QJYlӧT~~~*,,LGY-}T~^W*99Yegg{,wѢEyJөM9sx̗F"""^W={T fY (0Yc}]ʻm˨#,T2(=f2.B!*-6ʮR˱~5>[cgyVz@BQUX/O\/z+3X.![ 008sэoy{9 \W 1LPBTd6/WsXU9Do/|NBQ}Hd ,!J. ,‡}p%2}FEVxwҥ cǎe֬YNR 0>~ԩStޝ~|x_k/gdBx$t&~D:B!uDm6UTTv!СCi? T'N_^i4pB{Zaa W Ne>*((HӖ-[4JII:uJz̟l6+@&sϕKZy+&?@eZhT&i+)))s !8[l2͕]Tc k||FFG}vs:@S0sO)^bB\U- FULNUSZll,r ﷧0p@iz"..˗Ӿ;rssy'|'1L]֞j*ׯϽkO '))/"}Κ, 4+}` B﫯@ f8nrBjh4ɓ'Φ}n:tആyvkV떷m۶,((^,7B`z O~BH}u+o;0`kB!wV5<@ff&ny###͵=%,,'O233K-p+r0#Y O,B+ՙu@>Й;9 IB!B0fvJrr2Ԯ]-NsSXXDzu:=-oie*+1x6$^lYZ!1I9ӝ{.@]`)`G'^!ŒF#wu!!!XF?/^t{ͅ c6={>[h4Ny˦Ⱥkkh?ЍMD0X:u9K{) !DSTTTVUUx K\/vHXp /g! ]>:`Vs3pl0W 9a%WTb.bJJJHJJbǎ\:x7h ֬YCFF=mÆ u]DF#)))V\IBB,oZ7sD]V1`[f!uFVPP@PPB__RZX⺏C?N 4bzϳBXTX/VeW@8{ꩧꫯHHH ''O>C=s=ʕ+޽;ƍ̞=6m0l0{~N3f IIIۗ͛7駟2c ֭kϛ[oٻw/̛7Nz=>p<$h3JB!^cuΗ8l47*cFҘZiB! C*{JՖ9ڷoׯ*44T%''l.ZH5o\t:մiS5gԨQTDDgϞ*==\l6+@&sϕKZy+ʯ{SuDPQQec4do%%%BfseWE)UX۞2B@=* nl&_6!ATX/OIUTTd,GL@  iܯ2N}8I M iG@5#$  !nxd6/Ws+֣ۯ #O NcYCS h$ lY2KKqX_XBTCk,( xr'{d !  ] S,%j0LP-KYSBQyỸ-Lg",F y*<޺[B:ew7H 5ۅB42%D a]XG< >23+?;|N:vV !U[#qh,&p'g=y zbB!j : tHk6 6iZC!BTó DɇĿ 4 rPBq=V5 @dd۱HrssgP333!<<)/aaa@k` U[[;fpB$֋/iwN> 1r$FQN.X( ԁ- !(+x۱ .8l6{,… |h^E.wrM>oY;qHW~+ԉ',mkVz !Fqq1%%%UuG%;I9҂4b tgXĭ>~0 h4>BzQ>2UM٦./pIhh(8]ZPTTӧr*2W8mH9^+ApX>d2u@'a,ԯϏ@*,JOGKY[OZ !lOδi*^S=b$(iszòV'K 6d!OO[&mPK!zQ>V5EDDiiinRSSǣr˻sNJJJ򦧧}v+G-3g3pe"#r@8~mRL"GhD}ud !)S`6=n5U#Klv7&39Af0 8 CǏXŲ֖E045-֋jlРAY {چ 8x III={^?|뮻iFRRRi999\޲n)Chem@,БX8g}<te[VUX_ K\qHXcs1#L ;S4<0``^ORv(]WM*w%//a/9~8cǎ%(({+Wҽ{wƍ={6mڴaذat:ӧOg̘1$%%ѷo_6o̧~ʌ3[=obb"oÇg޽3o?uYkK:B!ĵ~c\`%aXb},km54jDqL E7h@&M(iЀz(?*B C5s]{SB=2%/|NЄM.@,*`i}l 2uH~9j5jDaX%QQnMPgqٕNBQV3&4!պoɓ 4hp41Ox.q97bYP4 Xa˰n[F/dK!:G \˃\X6m,1hu -a˲>Jգ^fki;+^!<˳yx0y^d1Fucg[r)cI)5-mFbF%Xlfʔ)|ǜ9s֭[/ӻw2 f1_9(b`V9~X:G1rRG>X:QX"[s,s n,cF,m >?]txzӧ3e|||ZNl__Mލ;-\g`;+ #Y~|gqYf^h]X >7`:,15g]}pޱ$W#Ҟ',G!%% &ŋ0`6mK.W|}I|RiDIzG 0e_ .I9I6i:6 K*ꑁ]­yMX:tX:[c.p8,'חiӦK' JJJ=.Ǩb}iIDwӻ*|F\K(?S< ԧbhXeKXb{5;{X Y_IYguA"M`WIbIdK8IMMeٲeL0CҲeK&MĖ-[*BT~Ӌ]Y&BH.ل!@,3aYAshؠXfwL&`(jQDBNbUfȥAslw%ua=Ų@X 7lH!5a X  Rۭ[/=RX\ D'Xɞ={CoMǥS+DUV_zLh0K1+;dS;%ou4͖u# j{Vzxtܾ۶ PJ(P t:,WNoEs=\2ZX/DuWY݁\K$ \_yK̶ j ,^AXb{i.]橅e|[18_b0x\vME. !J'XIff&n鑑(8yd%Jq}]y puh'.rCCKZþãf,ֳKcb)>vP雐z̶j*`~ER{ O:qUZ'_.+>B 1r|N&92x.L%&px:yM6 IDATt,or.bYGXo{>gϏO]ck\7?݋:\jQJqYL&&Wd| ` 'Ԯ]-]ُW\ˣyy+RwJ7^Yexz .U߮"(ZZϲ@KXo_yqoj;^uoD6փ}NeSUU WeW[Ryбynkq +3gRޯ}XkK| bu\h(p9gQ)W$ֻvOq+-?h:;nQpn)mmvWk>RJn#ZjEo-f'n_]׬BQiu2\~i;c}吧˾Ki\}JI+˦-CãϬ8l.։t̄eTqMX3evBw'lOb+jKEbc??kZi4c+O5K ,$22DEE]˷-ͪ !D|قC׹.7c !(mF! }k.Nٴi&iq۷hb;wV[Z[!(...ӪD"^!D%& 'tԉٳgSO`6iٲ%㏕\C!B\ B!dpҡC̳>h$66ŋs1>ʮB!z!BTG2K1L2?3gкuk^~ez]UB!HB!Du#XB!B!JtJ!B!B! B!B!U ` !B!B*MB!B!D&Xkf3< ЩS'֯__ժy饗߿?aaahZ.]1ׯAAALNNuqՖƘ1chٲ%zFqs!ҞWo> $"";5kָ_hZZnvLʾ{ZCjjS^iϫ#${zxI:Eʮ9yRRR0a,^i&tRիrrr>}:5">>M6y̗NHH*ϟg֬Y/RfΜ֭[${'^%+V\IZ5j=vڌ1m۶Q|}}W󥤤0p@iz"..˗_*V+:ur;-iҞhhذ!yyy4iۼy3)))[Kd1iϫ#H.ׇ{x}oL2%bϞ=ŹMС:'O$;;m,XyݕPh4H{VDAAOȑ#|Ǝ˨Q[܎K FѳgOve?&y$_{=z믞{x}o\rL"#####QJqJU͒ Pj;RTTZ/ H{VO?VeРA̝;qFǥMϏD @xx8ct֍[ҦMiO/XH%{$ ^QXXHڵu::6R;cw`̘1tڕd@ڳ"&L9y$˗//Ҟ啛K/ċ/Hhh<Ҧe׹sg:wl8p  u<מ|O+NbH%^%+х ձsF.BBBXb8z?̗_~IHH=',,1cƔG|s=|w(=@b'ӊX]G'"3WDFFzt63**zWƱMLBCCLsѯ_?Ν;ǖ-[_KLLСCҞpa.\om_Z)Ņ (**رcKzAÆ 1K{zkO'ړx_17%">>b2ҷoߎF!>>jVsDEEAZZ۱Tic/^d>|kҬY3ҞW6EٳҞ向RcҸqc7nL&Mرc+M4aҦ^oҞ^ ړiH>$WGCW$&&r%,X`O3,^N:9TTܠAXfӭ7lIJJĚU-%%%$%%cV\iC+iϲ9u[ڥKXd h,-[zjV^nn5jΈ#iӲqK駟ꫯ۷=MH>{Z6OwI>BR] Q3|?X/^LZZ7nk׮]*w%// {=>nVƎKPP'Nm۶ԩSqqyfϞM7DjjL?~ׇx/PBxŋդITTTW;vT~meWڈQZv1{}~)^BCCUrrήĚW=ݻw/-ZS^i+[lӧT~~~*,,LGY-gu]n-]Ν:uÕVׇx/dKj^PG: 6L{uZSN*%%qMh4_\u9SNU=T7FZW>𞘘hFQgϞJi&jk~d2UZ6m$/+ׄbѣGFQ?Rj>=J?DXL:~ʮ"d ,Q) V^sJ/j2&&VV%00֭[8jj rnHHK.uJ۽{7<QQQt:7nLBBk֬qg43g/µpIzz:}!$$FM~~S;wһwoBBB _~NӮ>| BCCm<-[nʝwΝ;iܸ[9Z裤?V]nߛkiF!V^M߾} Gz^x'|pHLLPH_?,6mDvtűd'N$++ lQsUQh49s&gϞu;V]h4^~eػw/CeԨQ|7ny96X/stR8u_ʔ)S:Y-k׮zff&wy'qqqn:˰ay߿?111?D~(..v*ҥK 2;i4:DVVYYYdffR^=!Ci&oNÆ ӧSG:--,[4ӂ|^j+^U'k&""ḯ!C~6lڵkټy3GbYh4ڿC]FÈ#x<?<}qƤ3k,NʢE>˖-駟fڴi޽6mзo_~ke1sLy,X@jj*l6X-kkժUXYlْ,?ŋYnO?kײj*6oɓ'4hPާ}l}LeqQH^駟7n#Go ^zlQU0q6lJHHP-ZP&MN^ʣRoq*1c2 nݺjҥKjĉ*44T5h@}ئgK.Jө-[ޞ'66VN{nhYĸMi WO?}65gQ7t2cuUK,QJ)Ubbbڰe˖j޼yNi+VPZR*,,Ly睪~|…yJө͛*))Iխ[W{G=z~XM0Aխ[WI&GyS,Xׯ?;qZZjĉNyZ&MRj*$$XE`GgܸqiӦn>JNN.u`JJѣ PmڴQ۶msʷrJu-ڵkdLL>}JNNVj/_nvn6uAڷoz߿ɱsNuwpUNuwt+%'NP< UnSJ)o[oul{j̘1j̘1N:*<<\M2gU``VS<5bU^ ؼ+`0`5b5y?RJ߿_i4YnQ/tmGy橰0UTTdOcǎ9]v9֭[T RWy~_?TXXSZLL?~t믿?^þ?zh^gV:tpJ;v옺UHH T-[tzY_ze0СCƗ3fƍ+V\k׮Uqqq_S-^뿯P;wTZV?~Xy~fR7|[yT=ƍr47oV:N]p<ݻwWcǎU&MR~jԩNyz VIIIh4ڏ-R7V>>>ǫe0ԢET~~>| RNߏb5bfQ%%%j̙*66Vծ][5jH͘1c;^xbUn]矫M*Nonjذ PIIIܹsNRvǎ[oU:NvmjJ^ʌ6}Ϟ=8ph4jǎe.}O)I&VZ9=D!3Daƌ̝;'Oә״7?oɋ/ %55{ѣGϤI8q"{s}K}Q>C~!wqM4qRUV[}ǏϥK;w7ߐˤIJm 3gΰo>ڷooObȐ!9s}2}'L:W^y0c ^|E>#2{o߾ԩS[g:]t ٳgtR/^̖-[eNu{W *u ĉek[t:l@f >B-ZD-nƍZw}vSJOTT}a֭:ãO>~ dG:t~rI駟cȐ!k.~ /´iӘ2eL_xÔ)SSN_dԪU!C0ydΝ˖-[8|0/=6l[neǎ1`+^I~~>ݺu#335k??[Y,]___vɜ9sx7.ѝ={6z+{aɌ7 6؏'&&rii۶-{_|rMƫJZZ̛7u۶m!!!FÎ;6m`0'x2IYm߾nݺQV-{Z߾}_f]vѫW/{Fwl۶O_[ߚ,cǎW|oQu%_p_w^FMrr2iiitܙQFa4̤aÆAPPO?u*O?{Eӕ):z(֭s[`c_b :pŶb<O|V^͞={ʎ`Ν;{ӵkWvM>}HNN… 6lȪUؿ?/?<+W,ɓyx饗ؿ?˖-~wؿ[n%//|Xk7߰{nWswӲeKә:u*?*3Ů]tS5kM7Ty5}W_}pڶmٳݮ({)o߾ŕU9ܹ9rR ,׳ojܸSYJ)Eqbgmg4f͚es%հaC{ɓ'ڹsRJ"4'&&Ft:땯h4*""if٘_99sj*//ڝ;w*^o֮]RjϞ=nӕVU6U}S/vR꣏>r;rE`TTT [9;st+..VJ)w^f͚fUd""zꩧhʔ){npK.f LTٲe >}6oLOhzN}8p +4+77צ H"Pvv(}ƌԻwowT*5D/(G`9[%C4a4m4QoVQY;w$"bܹsI&')JaWݑ7o$oooZf G`iZH$xbѹQQQB(GDD;#om=zwhРAk׮D" .Pee%T*iȑDD4sLQ|![}n0q)VZhTP]EGx LJV\)UVVR=hڵV***?>LGeիW[S߾}Ei4sL""ڹs'r={$ =zj>t֭z6s? iׯ_opDNjj*%&& *--%Bau$]CFDFΝk2t6Rkm|r 68%_mF`] Yhzm.~ ~!t)Z|9>EXXODRFjkpck…0`MwD֭h?((=/JV&7ݻL^z 2 ..}zo 20vX"-- &M8B s>… ;4X=zĉ; wz={Ly ݻ#66DBB|}}Q^^/bܸq?~pݻwf .Kt x"QXXh6___cժUx0sLj SNE``pgƍ3<ݻXhpQ_ȑ#O}]Tw#KrI݁KΝCJJY{Oݺuc=:`޽f=;~ט/F̛77nD~~>  C'2CV")) F,4L#t-[@c͚5Ajjh4_o1bM7`0`ƌBlj|SNEJJ ;|{ĉ#HpE Ȫ*a!\<䓢JV8t'N۷1c`߾}ҥ(ݻwDbHXk1x`1_(PP_"GEE>*zRz퇄q777mڴAhhpk׮Źs޶m[#kmjZ?W^^`$չs`0sMZj%jCvY)7էOF? msss㏋q{PjGw777L8hPƚ?BZܳ>XRYg ,H$?֭Cee% 1¬Ah裏駟Ɔ 0eZ,O&a޼y?aNNϟG5k,j4$JsNرݺuÒ%Kйsg\r:@'N3gCuu:zꅓ'OYc!_|((([0gܼy:tP3<ʕ+h|嗸|26o سg-Z\\ǣnnnlmPX{gf&W^Ů]/[<^\\ JUo :e9~1cɓXd <'N_4iJeij:f?P9uWWWG(j߾=4^Vm>FLf]&88D$O}qÒ%K0sLݻ'N{H.#!!Ax𫯾ˆ##<777o?>R)Νku_{5z+#G5xh4f7/_Ƙ1cpiKxNx\p}׿DϞ=7!VZGEϞ=1o AAAx lh孮]-?",,sgU`ԩE׮]!\mǚ999f7FݹE}o>ќ;wDΝm'%S{n|P3gΠm۶fQ*;\bvuj~>|Xzu>B,_yzflb꼺tv-i|O<>xVVZ?SH$㏘}>?;wlrIȃYz~̘1T*Esa;wd2-\.^HK.%9ei~O)o׮}הK&L oooy|Mrww7n&g'"2_y'\NnnnXDD_5Ӑ!CᆪK.ɓ'n*>|hNCѣtUڰa) O?%JE}ѩS?V*//Ν;SLL ߿._L{졩S 3,\4 }7AVU[t);vhҥAK.RI&ѹs4j(󳺒9/^L7oN>M*jՊ#Y`SVV]~]t:,Hm۶7xj/ԥK*(( "D"V'Oŋ)++:w,Zx˖-,/ }wte駟wK/Q͜=AAAHG/Ҏ;(99Y(c֬Y@V/ұchɒ%BLz*) JKKӗ_~I!!!9s&۷~:u$h{Ooɓ[)''>H$~ɡb""ϧ;?OrYj}WK4o|j:{=ر#yxxFwvH*mo(Ν;I*҅ ,JYM+WQgvK*+++wNԮ];Qfb3hlKúSNNEGGuܙ6mdVT*握D%OOOMPVjj*M4|||HVٳEk׎iĈR(44TITx}WG=zhw}ۛ_AFIKǏ2Qq^ODԾ}{zWDa"?O?z-߿ϪET3qr߾}IT01{?ڶmK z-tԾ^[k?fj_%s̡R*4p@9r b6ly{{Spp0o3;7//z)>>g8Q1ZuHT G,ꫯH*:#j&7)<];im۶QXX)Jׯ0~ƍG۷'BAAAA WDU4Y~sko߯T5>3Vp$___ݻ7eeeK(}ϙhk׮{"zE1gvI]YYIϏiowQ͂&۷(IC,MG_5uЁJ%ҵkZnM4baj:tH8I_MXD- ڟ JMM%ZM Lnoү_;j+m;vzM~~~Aݺu ݈k׮߻I :vhuQbqsIf߾}nG=x'V_a-hĉ-}VA;i_~VWkJ=[])9}gFw}X)}f+ w VfgFc]:a¦ i۶m+e6rR*o߾&);Xm<sYda ~W[HJJjpΖb aeBf?җ֭[_:wK0}f}ؾ};xZ)~ر.d2ӽ{wלoٻ'o߾(++kKabΕO6m0rf 9{,|}}1zf}Kكg}rL C]v93gϞERR:tJ[nKNNT*5jO|Xʕ+R0,]b۷oc„  'bbbtt{<_}ڵk;w4zKxJe3m4lJ69dȐ.ߖi#77999o۬퍫WãY_YatY~zxroߎӧO#//Mpe{L3gZ]ohV6Yw5iG׻wo\p999*5$<<999:a˖-\ia<$''71g jXd PcӦMطoVXiЮ_+W$1qHIIAbb"cXp!҄|DgyNŒ3VWرcСycv)&_U1g1fq۵kgB4c= ܁TNNNƦMpΝzϭ@6mSOaBѣyf\vMx|dÆ xi& 6 PTT0a͚5TCcqg1D"A6mPRRbvh4{Aqq1&M$JCl߾=Yooo@V#55lO|QQQ(HR|ǏGddٵDGGyyyMU=c=c1ƘZ0˦O˗R)%KCCC1c DFFh4bǎɓ'w^Bd2h4QrjBZaa!gv-l Э[&+c183cن;ԴiӐlذըϛ7O?)) :u¬Y$^P(¾^|D$c1c6܁e5jbcclL6 gƮ]R`JRW*Fs|D>FeeeJVJd6c٢F1"hJճ5sg11ֳX"!! .\N:ỴP(VEKۇEEE p-kJ>eee)/c10ݹs^^^-}V9JXc^{g;i8۷t(**B@@"ѣG1h !ȑ#0Άp!7ojg+[UUJ2򖾜j\\_P]rYGޔ@yy97n:9Ϸ\ήV_9 ?37o5HݻXjJ%QYY*xzz i111DzeD e˖ARaȐ!BZBB6mڄ,!33635l<<<6j\.w`  ZN`ިuF+r:s}-uK]5_ @ xUU+00PN:q?juvUgGq8q"ܹ}uָ~:K?+WgϞx饗ХK;}v!>>^(OP ==HJJBll,ۇkb0 IDAT&$$`HNNƙ3ghшs>췂١rRVt:TqY1p8޳f2!((j1ƚ/ݳa\N­[全(L:U{zmL:(((@uu5:vQFa\|._6m`ʔ)2eY۷o#-- | z=h": CU]lUU`0Άi4}kFXLwig \\_go㽽#~ozqF`vӨjq?ͯX}PVgW/zuvj59WᨁQF u˵w3~Vgs\tMԁP3ٱV[#~2Ct^#| ^]16~1'd課&x%ۦ58ʈ,cՍu۽2{45Uo!2c3gϞERR:tJ[n5˛A jcƌAQQrW\p(Jaҥݾ}&L@`` <==Ǐ7iY3Ueڼ xyyӳN#U Rži΋; 4ϯE]{$1L8޳U7cjX?uX{0k*<\r:cǎEhh(˱i&cŊ?~< ??>,`ӧqajuG|r 11ӧO1uTz qqq8uf̘Z ǎCliRWYT*bΜ9^ߺQ5,ߑ >wjg8VgχѾ77luGXYX ͉i/\ήV_슱{F"""k׮BZJJ T*_]vD"O>DHh(>>^TQˋJJJדD",!͛G#Gz  Шz5C pξN-m=GGɣ%ະjI hl鷒1$!69RwFQjӺݖ]7.7m)Ci:*t:]K1'qפH$hӦ JJJ, :[ 0lذAH۳g1i$Q'ONömۄM6!88Æ 4 yfTUU5GX;~FB?g9 Wwו)d2>utQoc1Ƭ,;U^^[nҥK}v\Z!X&PӐ֭[T5)]Yǧ߿ c΁=?u窲^n"YZ1k><'cvgƛoiXUUC4l-sgMG<@N!S 0溜!ֳ- ;sMwbժUP* >[nE~~oCRR,[LTeˠR0d!-!!ZYYYBZQQ233oj\.#MəG\x{6c>2cO%so>Ϳ°O!兠 J!c pXϚ3'Nĝ;wзo_nׯ_Ǘ_~;<]χ7!!/Frr2Μ9F F̝;!̤3Mn sb>^>j(򢒒!m$H(++KHy&ȑ#m8:dO+JE:J}^1f&G~ڻZ-iZ*--1Zl{+5ʄ1MΏ!3{FVq;vDnp9FV۳g1i$Qɓm6!mӦMưaÄ4F$l޼UUU[-VӤe]kWtd1{޵ԍ^^^ BPP83c6,jhDij5RSSQVV&c"**JT*㈌4{h#//c1 83cYs`95k ??3f 22F;v@FFN<{B*,,,L&3k rjiׯ뇄 Э["hx/āiQ4A0H$c]EXE|N7H_xϱ1Ɯw`ٹ\駟Ƙ1cy%%%SN5k233pssXB^z=-#"Q^\xBB5,=jR{ki awԍ*K<@m:Nc9>~ЎiZ 2~~~ظqc`O6 vҔJ% P*I$Q^[TUUYݪUcMb9UWW{#{1<5ֳXvΝ;4hܹ;v 88s j5TWWH nBhh(oaaYymRfqKOOoTY΄PVV&XskfV㎽pxϱ-^ݶY?101ֳÏڡJ :?3vލΝ;tNCQQ=A GhDDD(?hVnvv6<<<֨zA.[>j\nud*Q}#*ߥe9dzc=qxϱFy~+k*YXv^Ö-["|嗢#GѳgOKҥ `ǎؾ};/W(HOOGjj*}aڵ?>|}} Xx1qh4dddh4bܹʶ1r8; +fO_pa1XvĉH$زe lbv|ȑ /]vaըFǎ`L>윔>-[ЦM,^SLJؾ};ҰdzDGGcԩSٙԿꐥe1\{ƚ?RcFB kbUUUpss`paeee܁^VPFT|9Q˘q9>s=Gc=c·cX1#c5)pa1GX5ʱd0cΆ;c*vѣHMME鉶mbĈpY\ 4^^^P3f ,rJCT",, K.ۘ0a鉘?~I:x!"Խʑ5c񞱖 Yū3ƘcXvf…8pXd "##q! ,@ii)}>}FV~˗/GJJ 1}t߿SN^GZZSNaƌP@q1t᡿1Ƙ3x.e ϋcv]9x UUU.\@ F-J_UH۵kI$O4^OEe5DH[~=I$n޼I~~~4rH`0 q:Ps{:d!-U+]VZ{@=0c͙=kqg̱rlrݻn*tݺuùs焴, :[ 0lذAH۳g1i$Q'ONömۄM6!88Æ 4 yfTUU5Y=o'cyqgiQR1w`9V F(((7ЫW/|Ѣ9,L勊T*5ir5I]c1f{GcMac;5k_|PXX 1biaa!d26P((( @1cM#NGxRwo>^k+\nuG cIzs2cO%sgM yŘ}qXϚρeg^{5lٲ(**—_~):>rHoL*JKKh"cǎ+ #55IIIž}vZ̟?Bބ,^8s 4 222`41w܇Q}CDczW7c̞p9q$ lق-[75hyx0sLaСXhYsJJ `˖-hӦ /^)SIRl߾iiiXd z=zjtԩ*җʄ;+O{1"1f*`080βz:ejܨV7OC-U_*WM+ z}PΘ}sxcMFFϣ-}!1k]xum<ښ1.aΜ9}:ߏSB#--MGDéS0c jddd8v:tФueqaَc=c1Xp ~ O<ռZK/Toy5k^x_0n8TWW#==&L7ni& 6 0̙3k֬iZ21Ƙm83戴,H1ƚ?Bhr9mo4QZZj={P\\I&'O Nm۶ i6mBppРF$l޼UUU c3`نc=cH^TX1ƚw`9rx{{jfwLsZDEEң JEs^?~fr5C-c1f zc1~СbƌhĎ;'Ob޽Jk' !ɠhDrjiׯk Э[fc4{Pc>gZԌ*{k~܁͛'OJJBN0k,dff")) f B^/z[GDDZ1{fZd!t2ӦMD"]4R `1EEJ(oee|D!UUUVFԊ1kXuuuYpg=,e܁d j5TWWH nBhh(oaaYyRfqKOOols@;u7"\Xc[;*XcUJgqt(**B@@"ѣGEy9шQcǎ |-eee0 ٳgGs4]YƚٳK;*3-̓kje܁*++~mcٲe˖-J!ChZdee iEEED||ݬ>-[ЦM,^SLJؾ};ҰdzDGGcԩSW1e#{*(HNgs!c1lXv YjU7nƍ`>X+VhT#s-sVE\jc;chQ3"Q<"1sE\jjf;c6܁c-;c1WRC V1f+^aΜ9}Y\t ,@ZZmۆ❨2)dIDATݻ˗//{ K.SO=S#"aݺu7oxbՙ1ی]c5?BhBCCqu⧟~Osf<1rPznvLPyr9z=,P(LX+Dyc4ی1DZ1g&q]NJd1X]܁堔J%XEE(R`XNEEϔZD!͡!J!l.1čWF1G׉c=cLauR\z82 7=^P[aa!!˅(**嫪­[**ZDyRfqKOO\G 1ӭG1WpI90b=krPѣG͎>|~DD,#G`4;v̬lxxx ,,k,++`͞=rsm'uF-cbg϶whps*kll5w`9Çc֭vލ<$%% i111DzeD/[ * C j%!33^[r?Rcdzc#XϘ+<ڜ13Xv?FII`oq5ԩS7x߿?^}UbѢEѣƎ+P(T$%%!66ڵk1| yxb$''̙3h4Ȁhܹs[3EZ`7c={jRlrU<N]|٦|]vm;n87|>>>XbVXaS1Kq3883GXpg1x"c91pC J6LÇbРAZƘ1c̖6Yr%áT*K>0KݕxWƜ zƘX{ƘG_KֱcGxg >N>ÇU{˗#%%>}:ߏSB#--Չ1v_sDc:{1gXNg͛^nO_|***0k, X~=4 &ǧ+{@x ƜzƘ9c=c#NBӡⱬ, :Th 6i{Aqq1&M$:tضm[\pgՏd9?~Ё!!!qqqh48{,-Z}ѣ !!!f燄UUU(,,L&FP(((x(b5%<ƨV=ff0f?83lsb1Ɯw`9oc?)!B;tdje c0̖ nat,?K&?cL,!HL)-ai)z䜞sz=4}>=:{orn˖-=ܣyizW5<<,IAA$ixxXV^^^{pXY ~*N@aΝ[dQaa$ҥK㶽x$),,/^ n=@6Ye{"@Z*D|-V& Y Q5$Zd="XTUU%߯c\WW_}rss%]`ttTgΜ nddDgϞUEEEB(..V^^^ğ$W ucȂFUHOΞ=[%%%!?.\=`nzX[0*--Uww>,nq~[@ dx 0z֑=$šl٢G}4k###bK-g_s,؁Jw+ۃcsݫؾ}98xb]}zgCgUqq뮄旛K (-"'''fY ="}=+ {VĬG8VZBz뭚5k}7QIIv<:;;Р|Pϟկ~Uk׮ nWPPVY~߯/~* 08J XY 35FƖ/_^zIO=TZZ+W?y5/~am޼YyyyZlo>KPyyyz'+JO?^tA~5xr!09zOT&I`~W& ,kiiQKKK\^zvݺuZn]*S(, 3z'=1q dHBe~J"d ,@bqI]=1c8+ @fBi&UVVHuuuz7tt4?m}LY󨤭rzڧ+d.ғ O>1&bttT[nu-Wrs6EF'M'^1^}7}߯A>ܶff{8{~iYF;vД)StRqB=ܶ5$m{+%HВ@ m۶+9"#X$mOa>I2gϞSjuj2Zn>7ٽ\BVGGx mذAfjƍ:p@g&~avm @bz1C^E0}3%E=wh`!DggLn:=Qeeeg"}ɝ^TTT#2'}"tHc^4SMMJJJB.\|/#/5&jpE܄9ٞecMD !z{{U^^>n\#{]cz h`!Ǎ_ו7eUNNNz&i!cG7I_WD{i8.邤(O,k&\~?44,frd!V oIB^d.uVn'fvpden{<&2tM*++믿2oԯ{fD2444{d.xر# dsy=XKyyz{{ǍUTTL<`UdT沞 zB|`ȑՃMzu%@ Y"ݍKêK~jkkUZZoY!HY3b…jjj͛קjĉz=="g`a߯-[__͛7O?ume{j z`74`i<F F F i駟ӬYTRRŋwߍkm۶)**cڴi*++UTT:qMɮy׮]kIyrLwyf̘!׫^x!VTkwwwE*))ѵ^UVرcqo^;WYsUqqJKKo~S{k8ۜ'룱cݖǕd{?c.]ڸqf̘6544wܹs'|ǣ{NLN{B{٣ 61tRuuu[o_:>lIvjΜ9!W]uUg3gΨU^{|>׎uNej_Ro4o<:uJ<,XCnz%WN8A]VpvޭFܹSkgS^r_ޓ]qkz}yO#Ra<ٳgOpffuVzٳg39̈́:tx<O.^hM}}}}S<%5k9i7}}}c1vk_;9ڱo ;v)((0k֬zXh|믏kMNzcܗd=Yk춬77w3.!Dvޭ2-_<86sL577FFFz@ gj Ԕ)S~X~~֭[zK===QM1RY25ʹլY׎uNeWK4eJպ׎MeWK}x<ҹsbngg^r_ޓczggDKdB}]-X`… u}1F]wMSj͚5YSMMJJJB.\|=t|ِʚ5lhhG}f] ԸO3gΌz%{… :{?zJn8ƓY//رƩrJݖ󞬇D i۫qcc'OukΝڽ{֯_-ZHDbsM~ْʚ}Ommmz嗵i&۷Oq͵9YN/}[ߊSz^G*--Uuu~hŊzgbO'f侼'c)5v[K{7qGc~\K|c4<<}xߗ/_Zzڴq8g>4z2ydK*knjjRSSSF-YD-c=O8Zd9׿Ңz}ߍz^ 6I'O~;ҥK1qBEmyO'Ǝ5Nj춬ܓd=$BpŸ)x\xQG ~uZ1RYs$曳VLkN5]wݥӧ<O^DSŋ;?jllk sn{>1vq٩nz]yOC ,W;\^Hj.UUUԾ*//xJi1vq&ءnz'݉B̞={SO|>8p`UTTGd|`ȍN<(#sL|ʚ9~JKK9M˰k5t钖-[>Hӗk}]o4Vo4c|QkӁOk V۲^"%ޭ)[rgϞؙ3g٩F?=z4d3gΌ{϶6>}Zwyg&ʕ+giΝ1߯vթRt)=zT!yXI*kTW_}UGZ IuSjܬC3pNo*c}%>3ڵK$9Ĭܗd}tNqRce侼'q%1d{@ o|zX3gT[[>!t644h bZJ7t ׿U?8d[j^~e=CV{{?Yk^/~<&5h׾iӦȑ#zUYYÇ[ί~+;wN===zb ͟?_O:QuNvvC=;v1䆥cV^-9^;WVX-ZH:u^z%=zTO>|AIΩq695%=YO֓GsMii)))1/6θLNNN}gjkkʹiL~~1<Gt%qFSQQa 7l^m֮]krrr̉'B<&5oٲ,XL>9s昖?KH؜9s3F'9ڱ Qz9׎5ƘdS^^ň3̒%K޽{CsJ͉Yo'qNݖƸ/z\3`i F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F F ?kIENDB`PyNN-0.10.0/doc/images/examples/random_numbers_neuron_20170505-150323.png000066400000000000000000000707351415343567000251150ustar00rootroot00000000000000PNG  IHDR  isBIT|d pHYs&? IDATx{tT$@&n !)DCH=bIS/zIADdyA."T+ENԺRr,!ܔDHdf~&%&${Hy֚b|w#|l`{c7hB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0 E CPB0THc7@Svuyvlr8W3is\r5xCF ϟWTTTc (22r_ A"PũSt:k=)|K/iz-;W͕(44ԒǏΕ***4`Z֧$lҲZ^R=_XkΝ5[iiڵk賃 zn_{56oR߰Z._344Բ>+c\ma_m6^<<U0?P?hB@`$_{y<\p`$f! E ]&ʻ,~c/ͦaÆ]vM2l6=MOɓ^ocº>5|vݻcWX!^p8tzԅBjp:ݮÇ7v~q8:ujcQ'á3g6v~q82eJcᗦucǎ驧RDDD@̛7O]tUVwFwQ:u~BgΜ{ܐ!CXs4,X~Zv,YRΝ;']@0R0֤I4~x3GTTTNFCTHx^ѣG4tj̘14h *99Y;wT,"HVoFg֬YԦMƦ+==iii+`$2]8qmVKNNmݦ-[XR FA:W^yE?;&3.\Pyy9(n:;vԁ FB)Y1G|M;&ǣI&7M]vɓhѢz<|ڵk BɟDW^zmu9 ڵ$HŊ$>}ZUnܸQz衇$ `BEeڶmj/^,ͦrی3uIԿWrZl|sΚ9s%=5!HEe|bfn:ѣGkÆ ڼyJKK'j֬Y2yZV r5kL.]Rhhhc/ fDD5 -)I lٲ59byMIܹ5וaQQQ  3ZM~hY- &X^ScMzayMIrAk`7S@#! E 0ZTh03"B@`(! 1v>5|vۯ㋋5ao^JIIѮ]}~H!66VǏݻ-..A!H@xu뮫ǣou:v<&M7wU'O֢EjߧOmݺ۷t*!! Hv[F=_zꥷ~\Ν /]JT\\x9IҨQk$>}Zk֬QZZBCC4oBg۶mj/^,ͦrی3uI@d3F{Uttv5gKzjB _/?pq_S{Ջ:YvmڴIӦMSVVʔ\u==\6\͚5yɓCTwϞ= iF?я,)I]t͛-YPP`yMI,yAkFDDX^S'P2 #tfMlرΞ=sv7輀0z̙3:|/^M6;s%ө#FСC4o2 #kѩSjҥ.B?WVV1NScƌѠA|-\Pڹs:tp?0R)SB[r\x1JOOuZZh2 #]=O@%$$(%%Ew}]}wJKK Nrrn6mٲ%ీ!@p(d-cǎ:{S%)::ZZnP!@XZZ3gZx6mڤ;]v)11$@#+N:UڵS||M[YYY>?~\ն{!^Tkʔ)JOOWaaz-\.]x瘲25o޼-Z񨬬>B;x|~w}zww)--瘰C dp?!pϞ=ڽ{w3j(׿ս{ǫmn Z0F¾}o߾5vbŊ{gqqqG[n}r:JHHhP@mek\nhr\.ϯϏN:Um[EEVX0CTTTW;dԨQ:q֮][Zfڀ+j !>׺oٚ3gk,]T/Rc8qJJJ4`uAEEEZj߯EtJf̘\SN.%Kh̘1ڻw-3*!>l޼Y!!5sij˗`h_}5wiyMI M&=5oFkJƍ5.55UC q_EEEWC_<ѣGW_/3g(22R7|,XaÆU9.ڴiM,)))I>wX@!!!Brec=ƌ eddy\NNrrrmoٲ-[e˖}NF:"!D 0?@#1C`(!@ "BEeW!>Lի"""Թsgs=:x`cWXQP8w***RVVcѣs͛7O]tUV~@#YN7xC!!;##C7t~iYcȐ!JLL@C`$a~mWϞ=_]ܹsr:u>X?e0RMi'N(::<(9N1Bj_!k'V\cǎ'yԘ1c4h EEE)??_ .Trrvܩ:46B)؁p߾}TrrxǦ+==iiiȲ%%%2dJJJuVԻVǎuz@#y;4`q\zyE >\ҟ'p j׮]j(!t8 /ۭ رCk֬QRRRir\N>]7*??__'f!22Rݻe>CkJ-ol#"",)I7|5vjyɓ'[^Syk[^!^T땖ӧOkժUU$͘1C*((PN$IW߾}u-e˖WNN:w3gxe0ՁpϞ=lZ~֯__m7lj7=z6lؠ͛7T8qf͚-*!du q999ɩmܹ;wB R( !!D 0 PB@C @ !0|ϑ?P@X_}233իW/EDDsκ{tAk„ j߾"""]v0C#Y=C3O>Qzzz"eee)11Q;vP=jx4tP}>}ڶml 8P;wTnFzQS7PH?M7ݤ~Z]zmۦ<9R͞=[+W 2 #Y}h~AIWϞ=_0(IкuT^^~"HVڜ8qB>ٵkmOJJRii8>`W\cǎi>;~bccmn+,,lP@mxF k'ۧL%''ylYY7o^m{-xTVVV>_0wQ]1,*s'NhذajݺV^]g ŋmpl6P!!ܴi6l`Yݒ 2D%%%ںubbbǏWgYPQQQ>.q>%]㾦R76LC˥I&]ŋ>|:?OקOmݺ۷t*!!@_~3fƎPk6j|:t-ymY^SRyRo׸gyF>7N neddhǎzwTqEEE*..V|||G<]Vw}$Zf#Ö-[WˊGyD?pձcǫܑoV~?Z~tiZ_3f(77Wԩˁpɒ%3fݫheggvkΜ9|BBBj s8̭Ee:_{f~jfUk۵i&M6MYYY*++SRRrssս{w{ſp0կq999ɩe˖Zl-[9"H~!p- HB@C! e2@#1C`(!@ "OHEe={RSSնm[v5vŊ5pɓDNHV>}ZSΝէO}3o`! .Kc\.U꟒umi˖-~07^ZofcS;~07s=Ȩ񘊊Z^ IDAT] Vv#CiiBBy $-\}f9r-Z-)Iwy5-Ee|-EeT\\x9I_j]帍7*??_==^B)wٳgu1IһᆱZ4i$EFFjƌUAA:u$I߿[nE-[T~~rrrԹsg͜9W@##>s:zhط~[o$Wddl6[ѣGkÆ ڼyJKK'j֬Y2"H<&''G999U͝;Ws \0RSz!X0R] x~03"B@`(!@ XT P?Ο?ٳg+55Um۶nWnn㋋5ao^JIIѮ]si0ՁӚ7oۧ>}4hСz75i$-X@NW_p(d-qqq***R핟[oﱫWֶm۔#GJӕٳgkʕ~ !]TBCCվ}z0(IкuT^^^@]0շ6Į]Xm{RRJKKuC R <~bccmn+,,*}<9s'l?~Ǝ[ ~AO>ںuk۷oTBBB{+44},Gⷿ+<<*wT;-!!KgT\\ʻ F<]Vw}?@Yfj{h(!>qJEe~ٳ:v$w߭4i"##5c 檠@:ut9.YDcƌ޽{ln͙3'D {9=zro~[t+22R6v]6mҴiӔ2%%%)77Wݻw 6O0fW^^f͚i߾}`-gZhay`=Ct:-g,)B__W.]jԙwouQ^^o{B)h,B@`(!@ uO⇎@#1C`(! 1#ya]@\tI>:t ө~i˖-u[bE-W~Ntܹ* l߾]6M} Ç]v*07Yf~5j(UTThٲe.]˗_~ iUnG=}tz7nT~~RSSpoHV/*t͜9S'NP||/_#G(''3f(77Wԩ$۷nlRQΝ5s] !]Tc뷿V\oV{ֆ \yfVwڰa6oެRjĉ5k"lMԔYfo!P0![sSr /4^].x&ߧtW_}662u}q%=СN-[5X&LPڵ>@@ A4r'usz箉v/6^=͛.sY;CX'>,Y_/BBB4tP}'>y< :To&M ԩS8p꫆\&HV2駟.\)SH~KӧO֭[kzjm۶Myyy9r$)==] ={V\w@ !!\fBBB4~m͛7رcm6;vֱyyy  [NHB@{n%$$(""ٵkmOJJRii8w@ 0Ջ?~\ն񨰰^c% 4HV?CXVV͛WޢEx|@@ +6^tcl6[K9 oMk_+VTTXV5=^}ﵻ\ZTRYB6gρ>ŋն_pr}l6c P7 .O>ii+u%h֧On/QQQ݂no ݮ믿ڳ~?5yqIR\\ϱ 4Dv[vU h|Ͷy<n_宪r8*((Wϒz>}ܹsUe| O>5br:JHH@}ѣG+11Qׯׁ^ݺuc=":5o\ڵSǎ5ayZnJKKղeK]"""tyuUԧOvT9v…Stt233r*={V<ڴip :Tܿb nZ=z(22R:qxvܩ{O۷oСCR-\PVG}GGZʔ#ͦf͚UO+//O;wqoH@eO~IҸq?rVԩSڸq~_Iڵk'I3rZզMJHHСC5l0O$*))r6mt 7/t:եKk[oiZvwﮜ9}'Gi˖-u֕cjϞ=ZtJKKxW)%%E_u!^SXXXqU|Vf{R_5 `_u#Fh~ڵƍ3fT߽{w>|mQQQڵ:tеF u ׿˿믯rk}ݧ#G(++K_~x} LUQQQe3ghٳg=:j((;;1fҁoVܹs5ef@SG  ֪U+9RӦMOSUk߾:yJJJ4?^٣O;vTZZZ)M4IO=.\P /Pe{~kԩ:u>c=zT;vk&Vc%ƎK.U]p(++KK.U˗/7߬KɲڰaC">Ћ/X븩S*"" vޭK ȐѶmѠ~ Xlex5uT*$$@P<~_-A}Yx㍊ӌ3@e !@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"B"ۭnfxnʝMqVxxƝ2!58 Dv~˥ UPPPg(/4g竨HNS=zдi4|pc}ZJ[n?(%%ESLL߽y<Oc7@SsyEDDԩSr:5SY,+ګrizg-O~bY-ݻ+<<\ϟWhh%5 K\\z}Jܹs-啒ׯ^|ͼ]v:w^YftR6mڤ,?V\\JKK?P˖-Ӹqj{뭷oUzzwÇ+++Kڽ{ڷoP!DNg>yۙᰴA5CCC->me`Ka}۟q݆}l:{xJKKUVVb[N6mҽpܹsG@#+N:UK.ty?y^x5zh[*``)S(==]z뭷rtŀj|ᇚ;wqx !@w}zww)--w߭޽{W^?JBj]H_ͦMfF_vfϞ_>_]3oc.sҥgԨQ_y_uڰaCfQj}=@0_a^mphΜ9F>˽zASԾ3͚5Geee :w|qq ŋ['p%!du :ux*+gdd覛nO?ZǾ˲l裏ԲeKI5p@-_@QnW~&>>^={ԗ_~sw}-ZTAQ@0ĉyURR &h߾}:z^~e;zPn*+WԱcO 6L[ի O?tA9NIҨQzFl:uΜ9cYݒ 2D%%%ںubbbK/)%wqUVǿ{*$(h(bq4SRLʹQJr̬45JOb޲$k(f^j67{?z=ay^UgztvA={j„ JKKSF?\M6ui\vv K \p0#z6MڱcV^eeeE`tt>3:u|+VP@@7n\  !MeƏS999ZlY $%$$())IiiijРmȑ#dٳG/|j~@#96);;~.KLLTbbbKwu<@#]:!D ݁0Aaal6[PV!e2dH~]jkߌ34}]Kd,WjՔ"ob-[ڵkF+22RסCܮmĈZٳcwB#yzp̙ںu-Z(++K ,P˖-c5k̥yvڥwyG.(!Me&L˗?bǫy1c\gر2d6lڀp([F۴iS$ JRTTn}.͑^pZ"HV:uw9%$$駟Vhhh @#]@tRedd,ԩSqU꜀;xFN=zڶm~̾b TX!TŻս{wiժUcرj׮zZB#9V9#Gxl\uUڼyqF}Zv%Iv]Szz!QQQjܸq}l6[tzÇ?WӦMs1Y,ӧX}jԨΝ1cƸ\*!1Nj;vhݺu+_VVΜ9(yyyK.Zvm~#FPÆ 3(&&:w`$Oo*3~xgϞѲeˊ4h$)!!AIIIJKKS T~}կ_|cǎUݺuu}\.!TS'Ţ;ťz=@Y,v~Zjժ\DHJJS/^9m6֯_+w9~w^n0Vj +0RU`$!@ 2@#B`(!@ "BMe! !@ "7MH@XUvѣ#EFF:tKϜ9#G*44TܹS\ !MefΜ[_~jѢ`lR;vPfJkխ[7}wzꩧTvmkرvޭƍ{N0o0a/_.o͛kƌJJJ*uUm6YF}$Oњ2e.]r;eF-mڴ)%)**Jr2ǮYFaaa0(IuQ||>Cwq0aiUN2ٳG-[, .( FpҥЃ>XfLkw?~RuBӛ?ѣGm۶z웗իkQvL@@znj V\vv{l6l*Zʓݻ+((HV*wW.]*~EY,V<BPfR=>|[Ubڷo9%iwSe3O ]&Mи7|So@g޽ck׮͛VpeffkwEDDx>aƍv tf#|X6mҸXm޼Xhk2ۻyyyss-qU;?6MڱcV^eee*,,tWzm999Zzz)_(PkB\TƝgǏS999ZlY $%$$())IiiijР_y#hS^{5l6=]!k%ܹ5|߾}X,JIIQJJJ@hXQժO>DO>,X<)))IM4q]BӁ/p_bbp Zx/^9"H@#! MeFb PB@C! E 0BB@C! E o~Ο?)S{UڵeZVM6lP.]TV-uZjBӛhڴiTllRSSݪ'11QÇw߭ӧKбcܚpF-Rhh~.MOOѣ5vX͙3q@eq([F}||ZZ^ul6M:U/WFt ?\7t֯_oQ]׿.DT)n 'Owy]wyl._~s^O4iR%V/?x||Y.}QM8Q-Z￯^zH0F2q9v͜9SO<$O>:q^yM4I5kּ",2 #9Vvl6[+Wi0`gϞ+R !/j-:|֭[=44Tv]NJ@#9aΝթS:wJZÇ :322dXR5L2 #9KxZVV8Bg[e[o9vVZy@bMe.\ӧO+##Cn:;vL4f(!!AIIIJKKS $IzR.]4}tϺ[vZmݺU/O(FN̞=[Gu]v֮]+I>>%+IDڵS۶mK>>B]OP@,oعs]vEuA{9A <}||*0(IwyWhNBZT:wԩsjFZz5o;w^G ¯J=:\`!6K^~y߯-Za!~aw";[AAAZ~j֬yKFr>}W^%)((ïH='Ow߭nݺW0Frl*S1ULVVΜ9(yyyI.\{WJMMUF<~^$B*6YpN> IҺut1IҘ1c%%%)--M 4$ 8P_ k9K]*@#UE ={=vZV@@,K}bo]Xdd$U@#UE 퓘DU@#]!!Tކ2>Fb PB@C! E 0Bcyŋ{l.*^rJϹ~)I}馛<>g||甤=zx||͘1V+"B@`(!@ T7Frq˗5qDիWO~~~jӦ6loF=zPxxt뭷jl<%B*!C4o< ___]tXŋK3c -X@$o߾ܹF=zNDT !IIIZdI /LIRDDDc_uuz &(--M5to`$Ǧ1<ZbOriXܹsE6پ},bccK]jd~~* #9VSU}UAA/^l|,Y6mڨ^z,8pHg}SN9l6VX5nW  !Meԯ_?/Qvvd+11/!!AIIIJKKS mV\\F)___%''kϞ=zUA <%wɓtR:uJ-Zնm"sv*$$DӧOٳMjѢE>|[5 HUU3gj̙ILL,bp]w鮻r|@e`$Ǧ2~fy_ KqyBzC  %вeKj V\MS^2\B i߾}n;oZo(ݻ{|NIرڻw_=..y,ΝSBB~iVqŰ(Ndff*< B,XW^y󅇇x[hff$)""q7nԧ~k*==]dUPP<+88X-!4fܸq3fL}uM74_llRSSuܹ"l߾]E%;v,Sb(##C5ܹsK !X!.Wbw!۷fϞŋk˗/kɒ%jӦի'Iҙ3g%///uEk׮-6߈#԰aC=3q`$Oo*~/%K(==]~ JJJRZZ4h~;v֭D <%wɓtR:uJ-Zնm"s~CW*bW5V֭[WdUr t-x|^]ǎ<}wpgJWzS4h9?W/_Vpԑ^n5UBɱLy}3!T!D 0 PB@C @ X!0 PB@C! Ŧ2b#JPXXXײB-\7޸k-,,ҥK:_j;wuSz9ayw\|Y'NTz6mhÆ .=sFPsڳgOE. pl6Inʕf_w|-lZh5_nWrru7o55suZpȐ!7o[ݺu֭[gխ[7{3ff͚Y;vԑ#G*s@eF-;wԊ+/$ CW"r`$O2w^EGG߿H{\\xi٣-[kӅ tAA <333^=<<\v]ǏXIe*g`$O?CիkQxE2A %/^Xj"///-((T]%q<[g%{P_Ӫ*擮aaa^pAҵ{oAA,K5uեK;;[̱@e(Á^J\xNzpw\\r_\v7_\v_;aZU~b& eK333SQXG?wA 5kTnnVkYVW߻Vvl6j֬y*Kiii:˪Y]YUjjΝ;W$ln߾]Ee-۷o=?Pf\vܩ6mh?~$ˊQHHl"Iҙ3g +WԀj*tNNujٲeWG <4n8EEEiɒ%ڵk6nܨmJ$A~]vڿx թSG;ZM41nhZ''Ԃ 8%%%QX!Cbz0 E CPBpM/ܫ] @\2tPYVKE?b[藧SNw;mV tIOOju~j׮;jEM:UVU?S}߾}Z:zh5k֨K. nf 6L{kO X,j̙:sLcJSŢ7*++K6mRDDz衟H5j護ґ#G'T˖-*99Y7֤Iz2eӟ_|>'OU~}լYS-Z{<#/+"*///=zT_~VrssuYO?-2ڵk/J~'_AAA]z2n+88Xj֬~ijǎEtMԩSn5k͛Yfm۶_n6M4IqF .ҋ/ %xZnO>Dןg=ڵk$W^G1Bԍ7(?oѣ̝>}F*((=ܣnA[l֭[]SbbϽmA[IDAT,UV3f޽˗/W@@{1!pK^)Sx<""BǏWհaC5JsV\)I Tj秐xA8WϞ=롇${Zx5kMꭷѣGZ5q ̙֭[K.*>>^'N,qCQFE;wgϖY \MB඙3gwсl6mZhڵk+ @ߋmRnݺ[֭$^Z7p3}:tPUvm]tsrJݻW4iDyyyצMa6lۧE… n\5\YWp;u=(!!AC-r쥗^҂ +(&&F5kرcue㣾}*99YZ|\;wZnb+$$̹ׯƍqW޽~ۨQ# >\ z7I&ڲe 200P:v[ W+BOm۶H֭[իW/ 0@͛7}y ]k6m4~xM0A&LЖ-[tQرCo,h+B {dي=3jٲvΝ;+<<\})2'5kP%m.3`}EV%W_}4hx@͚5ӈ#tҥ2_n_9 ꫥ0a5kw^w}V||vm&Rb~0+`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(!@"`(˜}ǞwyGv[] }WcjРA}?hݺu:TˁٳBBBTV-5kLfr?nݺUIdX\ꗞ.ժ*UڵѣGܩS'͟?Rs6lP矝moԩS@* @oʬjo-6GZԢE %''ao^ Rݵgkpyum駟~ҩSf5jԨ*krxY,+kwrssg*77WZr\9N>iӦiС:|uԧO :TYYYJKKӝwީkn r)8qBGȑ#UzuY,|z}~})S($$DZrnݪ͛+((HÇw-nMIII%3w\EGG+00PM4… KT^=j咤ݻwsΪ]o:vM#}5lP՛o'|R۶m$ܹStqjw߭QFĉ/$%&&*))I6mÇk.;ws{yy}α_|񅢢sǎeXI={N͜9SZ~se˖iŊɑ&O\GGGkz]UfӪUtIEGGKt 0Xj˖-tU@\e455UzԸqchÆ  ըQdZ5`jUաC"O>$uAsRSSοwաCff4tP3q_5iD5jЌ3TXXX;v 7nԔ)SsΝݺQFAV |֯,SLQJJJ_裏`)88Xn嘣Fz衇pB5o\#IïEDDP'OtfWˁ044Tfw}Y]vU>}t׭[9J]+ϲeԪU+ծ][AAAO$iiiZ~}`eeeIe5122ȵV^:uT(''GTZZN>@U͚5urDŽi̘18qb{術'Oɓ:uN<)IOpXPP<9Μ9~X7ntS_vuqyyy)88ح\YVZzguy< .is:v옆ٳg+''GNҽ\,ioQtHeE+==wnӥKꫯCX,j۶͛jժ[n)qw՜8q"Ažob)|}}5|m޼5Vdd_KNNV۶mK 織PN>ɓ'l/|bccK[lQaa^z%8qľUŐInZugqF*((P~~ۧ]vI  ӤIUjVU۷׼yԩS'IRΝ5ozHwyԲeKy=:uٳgvEgϞU.]J3n8}g VϞ=G5kk裏б lǎohܸq]nv뭷*ǏkՒ޽{k͚5zH*55U[vfWWCm E CPB0 E CPB0 ƖIENDB`PyNN-0.10.0/doc/images/examples/simple_stdp_neuron_20170505-150331.png000066400000000000000000002534371415343567000244260ustar00rootroot00000000000000PNG  IHDRdуsBIT|d pHYs&? IDATxytTLBH*, B(,bIZ\ )ף_PTTE 1He+BAA$d?޽9&Lf993;~nss@x0G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1G1@͝;W~_6m~֬YMA 7o^D5 dŚ={飆 *!!AM4Q׮]u-hҥ||ab󕑑^z^zY7o=zo/Fᡢqqq%_%&&qƺ t-hŊ*...sF]k׮;Ow}>\}b]$5%ź+o~+u9ɓںu,X;wꪫ*믿^^x5k-\[j֬Mرc裏4{l9rD{Vn4mڴ5w\{5jT}?4|>M&qTTTÇk֭zsϩGzv|>]s5֭$iZtW򊲲T^ T ,X7xC233UvaÆԩ:ur3##5jԨ㮽Z9322۷/, Zn|>nRà$%&&. k=\mV8qZjZj믿.I***աC%%%}z'K}L~=zկ_?իWOguM6UܹsFVZf͚jڴFO>ϱ~z|>z뭥AI[zU>[ ,P>}_/K>VZ8*,XTկ__IIIӧɓ/߯;sNaÆrRTT>O߿VXQr>СCokzg[nE0a^z2￯>}())IǏW\~[^z{ m+Խ{w-X@)))8qW_}U)))ڼysaÆTV?Ih"ƌ#F?~6l)ShРA1Θ1Cݻw̙3|۷nݪ45o޼Ԡ7n8}4qD_|nIw_߷$xzgկ_?;VGo[͛7O&MҔ)St[oU͚5hƌe~}.B>|Xz?P *w?P]vO?;j„ K.ъ+*< rA$$$8~߹馛ŋ;{9̝;μyn?s\s5ɓ'Knw4hIIIq>3'!!޽{s;s~SOܷd9:t}ڴiw233Knz97vv[:kv.S~e˖9>ϩY3n8Szǹ;5jp~矗>jԨ2=z~~g9s8>:tp~ O?u:,qξ}Ǐ;:urիW}Y }: ξ}ӧ݇ׯm6ÇF97vJnK:O<9lڴɩQӠAѣ%:ڵsw}7yrrr-Z8͛7x=쥗^r7o~9>iذsu9K.- w.9m۶u~;/55IHHpKns}>}8~YfMme 3g,stM8˼q~5k1"`[́qiӦw6nXr;^{δiӜiӦ9vӢE ;:tp>|֭9r}EEENF_[p.3fSŋ<./r;s-uѣ|%acǎKn+k,{yG,_7b2dCu]իWkڵڵk^7߬9sTիs=͛7ݻE*,,KK/̯ӧOYF>F%yfwn߾];v,y\ǏرcrJ[NZn,X:uj׬U%hɒ%Zd$)))I{n&MΝ{AT;9oF<@+ZġCg|ݻճgO9rD^z u*..N͛2TPYߙ_*I/\c|>_/1@|> :T[lџg믿.lWݺueuԩ:6Q۴~zz} |wUXXMUVw _ߒy!3ĉZ y\=2ׅ jܹgqK* -YDW^yeзPeB=S*֮][WdC˩Kh͚5A߶ U+q4}t|>1"ϟN:i֭:|p>wȑc=G}T{ {?믿}NjU}-uի"jB ꭷ*sٿf͚%ϧ޽{a_]k֬O~S^?(IGVztkƍwGږgyF6l(;vlup T֭uwWי4i5z2M>|X=CZ|nF=Zcƌ 7ܠ+V(###uNo{~/rȑR}z;_s5j׮|ɀ?Cu, ڰa}Q5mT\rڴi#ɮ?<]{2dHuwhڵvڥW_}UIII~~ /W^Էo_uI>O_~֯_oVǏ?sXBv=\]|jٲk.*,,TZZZLJqq>[jڵ*((P^ /AAZ?6zh}zꩧԮ]; 0@Zҷ~ݻwk͚53fz)Iƍu=]vzKg֬Yڸq^]z/~!9s4tP :T͛77xCÇ… ޽{ӆ tk߾}Zh3<ڵk<}||/^+E]nݺVZ/qF޽[999JLL ;P[o|MaÆJMMՈ#_畷TK_:uL'|R_~>}zQ,_~l٢ ZvԼysWC̘1C{[o 6^Saa4iZcƌѠAN>!_蒐:uuֺ5tP߿B\? ~ZVÇՠAjJ'O.Yo… ~K:uC\r~_);;[gu;<;ӟ_*,,T׮]ꫯꬳ?2;U6m駟]wݥgyFѣNZ N5;<}Gzᇵl2͝;W~_͚5SݕFUze9Ẑ2JMMմi4upob̞={ԦM5Bgk#*rpؿ :q !xgýb)>>^>/ܛT8*,,Tbb~aU'(:+ܛ]nnjժ͈i Q}üXȑ#[n7 \ըQ-Z;?ThP=C?ϸR(e5j 9ٳgkܸqQ] ξ !:?Ow`_XqpoK apD| :ydĽ͢C #Z;?ThC MGzu \t0t0t0tN 9r$ܛ@   hࢃCb ~ȑ#zꩧ┕;vhS6l#Garob \t0t0t0t%ATW=8p4h Iڻwu+--MGՌ3Ժukeee)>>B_U;\4pЁ.:1nTl@]r%˽:q6oެ-ZHzkܹ;vl6n@   hࢃC`h5:v옊ʼo]~_]t)uߎ;4p@թSG 6ȑ#u0leb \t0t0t0tdݻW?Ouj˖-uMWZZ=3fuR||i;\4p! 8 OWVjjj=3ou7СC*,,ԡCqiڹsZh!IZjYfiر}~^,5ㆎ'8Pwy Jn;x𠮺*u]A:k^V IDATŋ5s2_x\2 JR߾}աC-Z(h~eD   hࢃ7xr \z^}US۶m?OuY6oQ\\ &[nQNJݿo>8p@=z(u_JJ` D   hࢃwxrɨ$;vLz^~e+==]O>LO?T 4PjjjM6gϞz5bĈϝ>^;wǃ~>M:U 4(1'N$լY}AC.::::ECCo@ /T+++Kҥ֯__{հaC?$%%IKݗ((((Ala \t0t0t0t&Ny @裏^?DuYYYYէO*=~ٳgk„ ڻw٣?\yyy*((О={wߩYfRϑ TxrrHOOXn@   hࢃ Ur_k5TFʼ/33S]v?wff.rIRYi}>I&JMM… ӱcGlR+W3[Nc@x! >\WӮ]Զm[3F 4PFFFп˨$}W޽֭4=zTjժ*b953t0t0tрc5'NT5_VZ%p Z|y}}L]tѱc½ƻ' pE:::ECCCh1nx annnA׷~[Y$Pt0t0t0tyr K5|*..7qL,y4p{#;vLݻw~?-x p:c/E»[4pЁH1nx-[Tя4pЁ.:xg~|>9o|/R 7::ECCC"Ǹ㙁pϞ=%?;;[wqN]xᅒ롇7]{JX1nxf M6MW\qE׿4emڴ)L[VyX)n  hࢃCd57t[]v-[V;vUN5lP#G54pЁ.:::%A|r= ռys?~\֬YYfiر{[n_tQ͘1C[VVV+vx.::::EC8 pGݻwW~~m&I7nϟ;wEUVb@7tZlÇܶxb ½)@DFCC  CCo߾poN4pЁ.::<\2dɒvG999z'ԲeK-_չsgծ][[ 7ܠ]vz;4p@թSG 6ȑ#umK,b \t0t0t0t8O.=~Ə_?mV~Zh댟{ذaZn .]hzu1mذA?%I{UnT~}ѣ1cZn,Wytz4Nt0t4pE[/ㆋ'´4{9s-[m۶z5m4eggsѣG@駟ӰaJqiڹsZh!IZjYfiرzXk8 O.}OK.+SNSիW{۷WN}/^ Էo_uA-62:::::ECCCǓ7|ƍ=777`@ Z5$۷OP=J=.%%Jg(c    hࢃNœKF{aÆoW:ueiF~vڥ+V 9r̙oY6mRϞ=kĈOg55|pmذAK,)s!Chڻwo!O?_F1bfF   b_0!...fNZD3O-}Wx2?NլYI&iҥ4h<_|1u=VZӧx յkW5iDC```@P_2sNu]ZtFx@[>KMM՚5kʽe4i֮] n:q6oެ-ZHzkܹ;vl(7;` \t0t0t0t@edԨQC7>/^ Էo_uA-Mx,u0t4pg0LۧGKIIQvvv*2Ё.::::EC)!fѹs\CkĈ3ydedd(//kcu}u*:ECCC`bCF"ɉ'$I5k,u_bbbc(V~Uhࢃ \t0t` $IR~~~S~gC4p!:@&͚5$䔺/''G 4d%$$n7t0t0t ==pogp a5+BIjҤRSSp€;v쨖-[jʕ}~w}unnnW\\ܙ!K߫4p!: 5!02d-[{ܶj*}'>|xF~0 F:::ž```Ny ` &O>O{NdM>]ӧOѣG%Isjժ>}'_W >\]vըQ¸4pUjҦM}e޷{njJ}vM4Ik׮UBB }:+yyЁ.:::XkǸ0b@7tX2RC```@ P 7t4pUŒ(kcm \t0t0t0t&;1n$c rXk8 "bЁ.::::U@wCCC```@KF\,Ng4p!v1nc rXk8 "b܁.::::ECCT!"kiࢃ \t0t0t@0d4X]_40t0t0t0t&V;1nc rXk8 ""Ё.::::EC!Ž4pЁ.:: XX2bW IDATtz,:ECCC`bCF"bYekk@b \t0t0t0t%Q.N \t0t0t0tp-/qC%;;ECCC```a DX.::::ECCKF\,N5ACCCC``bC,F (NjcahQf͔Sv͛Wի2?Ӄ’```@ LuHOO/5999ܛ, ~_M2Be477Wk7|SH=C;}_. ?`.d_nl0&ӝ!W󕐐-0raFF&M$I:y:w>[iѣ:묳{Sߓm0RRR4l0}վ}{͝;W{ќ9s*:|ռ@sGJLL <ɓ'5e ԥKV~½ib!xe<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<2өP[/vjԨFٳg6rkԩS.w8Uݺu=eTaaUbk„ СRRRtuo=dծ][Æ r =C{75` 4]vWhڵjذa9G ב0 啪nժU%I;vг>~[>'$$D-[TNԵkW}TY^Bp8+l 8dPBá5kJҕF ]?,T>l05lP׿ԪU+IRӦMe/^=z릧맟~?Rf0Ѡ =$ߟ<Pmgb{{M!IE^CBX.R]z饅G]ֶm[u]-գG߿_?5z "!6|y-^X˖-S@@bcc&M)_b3 0;o6á1 lpc[8wa(''0ʅPZtQT!((B3($$0UZCVVMqSQ!NpNvM 6hѢEJIIѤI<}ݚ3gNŁABpȑ#5rHլYS>;vL SppzL={O> SHHZn?\w 7 IUt=H{=]uU R:uԣGj͚5 СC}]wڴivU`>Bk&}zg5k,͟?߽Vzz>SmذAaW^r:_yyyZvo߮ e]N~g8p@s]CՏ?UV[oa+BO>{%I6M9999s|MYF{رc9ykƍjڴzUh8IԿmݺU?4p@OoFa/_LǏSO=ɓ'+##CoիWd{]Vڵ+TntwqJLL,<6nܨwyG۶mSu7_~XiʕڱcRSS駟J*-Ķ?ۆ ԺukթS]ǏwEEE)""Bk֬)r] XZ^^!3;. YYY$CUl|+G~ߥKe˖eǏwرðlƆ #((x 0HJJʰen79O=GLja)))n7v3w\#22t:Pcɒ%2f<:wٯjl6c˖-ul0 8yh,X>[߾}CzZ˖-3;wamڴ1 0ݻwUT18nݺ&L0 0LbDGG{,c\~!22p8Í={z6=۶mu[*i?3n !y:}Z (#u]wǎ?0 edd_^{{yXXJeddHFd]2emV]}ڵZjW_ձcY|$0Zj:AAAjذHaҰaԴiSլYS5jPvvvY=Us'-\G5qB˶o.өMzի=F֭UJRZ*/DjՔS&B !8OUHyye8|6l{vڥm߾]۷ /Pd}ݮ˗?W˖-sϩYfڽ{$)<<\{… u!}g i1qMBBnݪ{N_l٢0/љIjiթSGG-Ό3o(R*U-[ Yv{I|M|αK%իWÂZ{W\$fQ|D7n믿V&MdԼys>}ڣ~I-[tկ__Ç{ァGyD$) @CCjzոqBgJ~z5Jj޼NԲaÆB7o޼ L$rR~P5\[oUǏ(oFy<֭+ɕP}4l0[N[lѠA>}H~a-_\_?7h@6Mtaeggoӕ{Ç=xjԩIE&Mƍ5h j…5yd}z%IuUjСC^XjU=c7n^uܹS7nԂ ->>^?Q~Y'|R_|{] 믿oь3gIt]O=vܩ^xAymVѣZhK[nղe4qD=gkcǎZ($kȑ#{#/%%EڵS޽#ݮ%KO ڃ>-ZW^j֬{%\D?^ȑ#UF ^Z7tJM4IfR=ܯi4x`9Nu],XG]v5zh3%&&jźoh[0K/o߾^_kҤIzG4ydhB^dlZR۶m;x=>\oG]ٯtR7N۷o׮5kL6mR -/i?3n)˖N8[x۷O7;믚;wdeŒ -^Xk֬ݻpEGG+>>^vVjv4V[nE~sù25ׯLڵkvڙJrCF5n8]V111ku%Zj:r䈶o߮5kĉ7nz衋:1t:eCF+`ha3vƎ{O5k,_9sh̙z'*0B˝!t8-ַ=lq(!<,w_? T3WEiȐ!m68PhzkܸqڼyBCC5`M69Ȑ)=H̵`믛FZ-|WK*]sّ0$??)'G32޽L#1WJ|󲳳%ױoUK%i„ 7nCf)99Y 6(?;ԭ[7hBg駟Vff,YR[_H7{N;—_.pVׇ__N%WRxo'kJbv{iӦsO#""' $,8++KAAA텖 ޽{pBhϞ=+nmJUjժ>}GԩS5w\IRnn^Œ a^^>-X@_|"##uw{QFuV^l6eddiӦ^ĨCJMMuUVM)*n92k].}…vp1N.3k/dBX^=o?{B5kL)))m癢ca8p@ս]0ydM2g%6To\hINNVbba+:u]nl6NgԩS'EGG}QҴpB5h@?GݩS*&&F>|ݫYf)>>^ݻwK' aZ`"""d 8PK,ъ+H1B&M*tV3::Zz4fU^]Æ ӴiL/tBe+4;$%%%~Nf͚r5ݻ0T2L%B;' v?}UV.>ꪫL h7] .lN*Va{QttzJ3 XݻwO>Qƍ,ғp ڲea%Y a޽k۶mjݺuIecRdʂO.b$y ( X:!7;T%B;v,} !Y.!\xqݻW֭+h,j޼z)eddZ~q-]Tնm[&D !\j>=szѣ:xԩk튈0;d,JIGڵk{nN:Vttv˝f|f3fLΚ5#X3'\hr ͛KUƴj>5Aޅv±c,~fTJ| OE IDAT}8\C>rg϶i&;ڳg<}ϢE4dȐB6MPݺu%IZ`>m۶MYYYjܸÇn'WP,.^X գGرCgZ\6MjذGy͚5ݿܹSFRn#(44T˖-߯7j…5_cpڴi={xU^]s_#F(22z쩶m^zھ}7o.6l^hĉjԨQE GYz\/nIll6=zMNR~~ekH (W0}…v\,֪UK'O$կ__۷o$;vL999etP[nQfff{IR:u3ċ 3l]h/+8VCFcccb nZѣ_hŊڵ)1iȐ!Shh4sL(==]/CԨQ#]s55.| }8Ny:uJ4akoFKRjUIRտwy>}ԣGjԩ;wnx?jҥ2 Y:! sn5~2]իWb=ͦ 5muAE駟֫S*>>cu8E.;ut9FqmQ,8qBߋSP|5kL)))[ҬQQQڱce)))?~~=$)88e'O֔)Sk@yHNNVbba<%jrf͚y80 l69 z%$$\: ܹS?c 6L~^vv.c)*'j„ ^9bOxX.!/CEK)Åf]tCy^Zwyt7x^߿Ȅ|3'\hqYS%`s/rEEE:Kh[ѡI:uho^5jPZZ.\ x ݳg#ݮ[oUz*nݺ÷$fcxgL_~{虎9/jɒ%Zbrrr#FhҤICFw徇>Xh='O&!P,\+x,uٔ X2!3f$&N 2ө7M6f`Ʉp͒\gmۦ]};vY%X2!,]tȐ!3gopb&A X2!,pBC,l͘1C+WԡC|Ν&EAg[_PtB8tPZJwu"##8 67c>Ӓ%Kcv(`9vjRXXa%Y:!LNN֤Icv(`92:sL/PÆ <==ݤ&'\h a߾}&J±tB8ydCP ( XBI:v^}U=:r$P}P7 X ֭[խ[7ըQC 0}ڳg^{5CJgnjV``W^Zz"17 8xgoՈ# ׯ__4!"@eÐI X:!ZN8Q|ǎ 7!"@eÙ! X:!ӧp8$I6M{c=nPQP™3g*++KuUnn:wƍz:u0 pgQVXui˖-R۶mխ[7CJ k;C111qiJHH01:b1 7 Xz!CtB'OԐ!CL ayTF "hl6l6uUU6Nvڥ={!` X2!۷$S||BBB԰aCn;h"Um68uz}դI>|Xn1p,N}ԣGjԩ;w{ui=++K4zh߿]o>=ڵժVZ رĨCJMMuzg4m4]pȇWf\( q:~EŰ^}QÆ %IڻwZj7x߬Y3ndddˣl6=zTZvv᳒Jh'NԄ .s8Ş@ٱtB(ݻ{f"I:|ԩQtR顇rM:U}vM8Q=:vxN; !|áxC󓟟a<'+Wٳ7o޼z2;Kx:uho^5jPZZ.\ xLөSBϭQ 5\>}Td|Ν;W={T5zh=Zի^xSb8p2335}t5J˗/׈#7x -JiR'\hgMٳgt5J1116mx )))IIIIΝ;_3rh@I8Vw>Cx1ٳPy=tq"tBاO}ᇅ?c|&D2` ( Xzh-4uT}Wر$iÆ Zny=F2+L,Ο?_j?~]^fM͟?f#!EcxC(]vX!$\xbpBXá%Xޑ"!. } mB;@b;P<|Bf 4H;vԾ}$I֮]krd@.$ţ_V6oެ?StqM6rtBOj޼yzW.Qzz* 7 X:!,-ZXl͘1C+WԡC|Ν&E,>>B;.NUV鮻RddlI%XY:!ϴdĘ Xo;QV-Ja@.$+`099Y&MRNN١X:!9s-[nZm۶xaѢE~~~:tPCӦMSUZ5իWO7|oB2 }8o߾ffSrr6lQ^fMO>^ziÆ 6l*=zT7nu%T`pqb8}…v\,N<ԳgRΚ5Kk֬ѺuԮ] \,=d*toagխުvt*77#80 PBөgyF^{ի0Y P.] r-?hjݺ`ꫯW_}eN|D͚5Kwq?1c[onה)SL)((HC ܹsGʕ+}ϒ\FW^W^yE)))?u7j\ .ąvc,} oW^yE7tL;SW\q*mذAFRխZ$߿O>ѣbcc5uT͝;Wk8i-['SƍSO^8>CxAnZǏKnf-Y׿zjUVGPPvQzbbbԡC˪U^vlQQQ~spp:aE\,3,*^z:p.2]qZ|ڶmo}B4kL)))Y( (TnݺJ"M<ٴ!`6C'\h%''+110|~iʕСFAiڳg~ ^DD RiΝ wݺuk{\WX`uK#;;[^>\"cPL8Q&Lp{e 3ܿqjР֯_&MwަtaթSǣlҥJKKC=. Q^dرCM6$eddhuLO~~~f,:1B'N_.ItuיWN۫FJKK… ՠA=uM+W*..NFazTNBuYv,}(d5j(-_\#F7|Shh͵zjjJSNՌ3tuiڵ%^h\B;J±=C(I}G}T& $%%%~6mlٲrtBؤI%%%iݺuj׮] O/>0aoűtB8|լYSiiiJKKXfH >B;.NwevPe'9a2'o }Y>!?Zj@UVzW PIpf>B;.2:i$͚5K#GTǎ%I_~aٳfpL ( X:!|+;tGW]uFIBŰQá*o׮N>mBDy@q,u]z ?aBDʆP' pܐ1cƸlzW|r]wu7jϞ=JHH0+DT0cxgpkN/Hԩ:u￯J,~fK_Cp] ( X N:{N_~:|&E{W˗/߮kV6cop6_' O?TK.ULL١Xzf7$+;K'3gc=ݻw Xo^NRF$G1)2,yڷoM&O.$ţ믿W_mv(`9Yf5; R_h ׿f;|}(@;G 3#諯'Nx<Eў={JvQnl6Na$+;K'_~!h" 2PfӁTn]waz饗K/)33Sj۶&N;Vd|Ν;W6MjذGy͚5=;vfϞ=:v͛Ν;kj߾}F X:!5k襗^Ν;~zu7-={m۶E.w:7o w߮F7$!p'yjժ)==]$6mIYYYp(77c$n+(("B,>䓚7o^ycbbnZ\aK. UPPneffz Tڻwnݪv6lIê0׿o|}(@;.2O?)66Py5t1"4d)44Tiii9s;I_ohРA+Bk׮-t!Ƈ@I8VwNիBڵkըQ ^a+UݪUJ=z(66VSNܹsBBBԲeKuI]v5c r-Zv.=@Q,6LGւ dٴ~};v&Nx_zJgٔMz]:(55]t:խ[7iΜ9]e˖z5}Rp8\fWut;*+??_]vUNNbccUjU;V#G7kcDFF<**J;vpzjm߾]g׸qc5o\֭;X\6ydM2d%&&ϳtBh4a=TVVZh2YDDd];wTxx~M6MNP]áӧO=&9n鹃sD.$f+&N &x]p8=ၲsQd jѢ2KÇ -]TiiieM6aZxGtO" @eWgXT K!{JUoIa:uRttڷo5j(--M .T ㏻mVݻwעEtqC?`=c1)o|}(@;.LSRRԠAEGG˨d{dXB999Ԉ#4i$!'gŋl2(66VIIIjҤIz( Β }ݧzKvҐ!C4hРJs$%%%nժU5a„"N@y5/8q4`-[ҝ1ʒ :vwjŊԲeKjذ*=&gl2 mY6!?[o{jڴmۦ^{1qvE)]h>B; !.̳@qHay|cxGB>| !(BQ$.:LB;' v !p`=7 !,<p~HG"!EB>k׮YBCCվ}{_^_ѣG+;;ԯt:=~wS1S6=Hlmm2 ũy| a9XpӧgQΝw^z}uSNi6l^~e 0ԯ1h|I>-L[H .+ =Lz_W.6wփ>ѣGk֬Y}'UV)88XԠA >\֭[E GqWbb$9ɓJMM]wN%)!!Azw*$^\R͚5Ӓ%Kիvښ4i3o۶MOVvlsil׶҉%bvCVk/^fQ\m\H[8 M&};~ܵO溒pWo vض+Vb#-s7J;ӵ-Gzkڵ]j}E>_\}d``}vXGz5WP_}#ɓȳBȲj#Oߵ֭/+lCHzjŕXf)##CM6UjՔzԹ;l2m޼Y_U&I? ԩS%)ZNN1;]6[~*d :J:KLv=~ݻ]gg>:%~u$ο'sHjpf;(+MJ'JK>)?u?K?ཟ:YQvp>TY\Ν 9}u=r Q8 iv_C{-m[M6m l¢=Ҿ+a8)ejUo5*<'K?y)m{;5#kv/ {ȳ[yfߐ-7abAdV٤\{y{?=# ڢt}gp.mQbbY\}粂/l>>۱_~YzsOʊ&_rXq.}d]cEN֭sgj.ʏ͠oiٲeۯ_?U^]W^y233u)-[L7x>c}wyG~Ǻbcco=yBCC øqJHH8kNڷo6l.߷ol6עZR*Ui&pwQ ։'de+bn_Y.,a(??_ aկ_?M0Aɒ\tll~G߿}W^ںu~''ϟÇ?WM{.~${/5tP]}rJ˺{6oެ5o\Ç޽{5k,uEK.5$ ''G׿oȑ#+5~BHc)==]իWwܡiӦyܬ !(,L$HG"!EB>| !(BQ$HG"!EB>| !(BQ$HG"!EB>| !(BQ$HG"!EB>| E";;['O֍7ިڵkn^3%Tp WZԡCh$Ç+99Y?ڴi#fJ|p8iӦ)((H 3g)1favpCGUݺukF)))JHH8?h׮]R$tYf ͛+4E E_u-U>L Qhhnfelj'{E?~5ғM%B' QPDyS9W^óRl" ` 6[fv'_!6`?G|&L 3{l.>crssB!BmdXI't4M#))_~e&Oi~_W֭[ǦM|OϞ=Xfx$B!B#AB1MMxWhժA`F /vߝ;XAřgSO=a?:B!Bu䕹t˲HOOOԩ:uM_]]a>:ie B!⿄eFFMbb"?a񪪪ú ^/oEqqxC!B!ZشiӧO/ ЩS'j]}}=r 裏ҿSOQWWGII EEEo#!!gy.pNaa!/f#ƍǀ4ic&>!ogYÇg۶mL2T~i ٴi;w-;v<KvXr%> k׮% ҦMFu]z7׿E^^`>}0e=þo!B!đ#ahll$''Ç3tyqpBƏ|d3''1c+d!B!8@!< *w}477k y`HKK .wB!BK'HQQݻw'>>DZv 0477wB!޽]9s83y뭷+yg-++um~[iiQiB!BYFCSS~k3gp ={6w}!O X?B!B!a //Yff͚C111?4iځ%ip8hp8p:B!p8i?1˲0M8Yx4@xرcZ jkky֭[SVVveeeGbbMB!UѢ@xLII ]v=y~jժ]Xrrr~UTVVEڃpݪsb|ߓ!y>R|=zqd\9p2w\N<̙>ztr`08q" .䭷b„ w!\`ƍߔ/wOn[~ɋ 'C|?s_K(q@xGyt]gԨQ,_ r K/DAAڵ{1&OIKK駟4MOQ !B!B4k,ڷo?;CyǸ|i p8Xt)r O<~\^z-?B!BE,R!~keɨ麎! 2 !q<AΌs !B!M@(B!6%1c傝p0m4y.@sA<ACx̓B!p44gϞ)7n2x7,B!BXCZ4Mv Su\u-@(B!z5qq}HM:/L/>Y0,B!F nw !B!¦EՊ=8$QWsMC)2 !BaSzNH웩:a {P!Bҫu&RGu$99Cuå+A!B!¦*pSUEdB!6bzmuT`ېP!Bؒײ{fHu2)58b|څSu#B!m*D'Q8Pu+ٞJ,x"5gNʩ'kV9?Iu !B!͔&PD*2S)U}s"nժU7\*9?ɲ, !B!j2y:GuM:׼̈pΉ}En͢1$9?Iu%Zꝉ oɨDwr2ժ<2Y UaȒHP!FB!R= mݑrdJ9'"/Q]yx; '9ʒHP!RQ1M3Iؼ m`%?:'\SMR8wa:&*wm wx&xEUnȒHP!o}$$ $*ʖbLz;MuQD+an ˖X:)?r-b92F aŊtIm45k֐{۾L<'o[VVFFFmB!wt)uWy|=v@3TD7۶qd2lxihKz0zhCu]VF G7ȠA~K.x;MӸ{СÏ{#'B^\M]'55ΉںrNbw^&9 Frs"4d{S1%g{a9h@x > &۞q 0 !mA?eг>gs٘,\uN͚u93=I/^źu=TD\n: +f˯4 aGISS1118uB!DKP\]eij :'B:|`݀x߅eq"AuOF };zbrUpq:U,]י:&d <&OLcc#N#F0c ,O<&<GGUMB۔|YD-(PquPg,m k~4Nȳ m;.Ov%:(,<О t5̆w"ax}0j(,Yn_0rHj~_u]x6c]($w&{ǩ{Q8{>f̘#ܥ:d < 󉎎 ={HO|CN !L@U^ǥΉ7DlZ?=, kp|:Ѩ:'ޗI82?}@uN}Oq<5Dz$&C|L'ߙpU0TUUm[neѢE;rҥˁ?u%KqFnve}BC_UOWWܾSuNedޞA16:6i؀㻸8R o哏)M9WVNan':{Ց~꜈ aj1;Xuίf#FPa '{B4+| -% JWrc Vq5~b*\3ϩΉ83N3QuNĭ_ _sϽ€TjnҿyP!hwd@ \޶`Ͽ8OPi`w~eD߿ΉJ\VU(QS3(u:WuX:d B!Z}ֻIݡ:'sY>8e[OUQF}N tSq^p3[\T֙p 1qs桯-,BxPP~yKN RݤTwIeh$G34IcYk~ƍTɷ)Ǟ5=Kw:|9n29}:72F B!Z !!WuJĕ/!KWc)qMSxo4Ic4,fmӑ%"ʧ0RӔߤ:),a]ٰQ4>o|H/Z-+AB!-eZT,ٍ{O_%[*?; IpX,|51IJN(xh[qw&]uNĕ3|&훮:'L@vCz{' : BhzNW$Mth9UZR>O5yVCnǰ9;_NG'JڽAɱtɷ&66^{)Q]i 祤PuPĕmނ4Q_s#lBB!-08۷NzU\]]oT+Zѻ֪N*ōto>7SuQDUTpѮ] 0 $.,_2۵"]uI6ׂM IDATo.c0ÀUTW؂ B!Z@]mF 7AEuܹg75P9 CJrWaZkOn1:dM*:ҩ8b18:' 0i ,aT.V>TP!D5oFA\uNmX˻sՉUnii {P!Z0 SS!e >L]镸 -ǂ$Ίq'AM#v1BuT'PWni꤈ u9qN`8 b} q*Eqcxnobcwl8V`2 !D Դ/ )iꔈjhhKF;HpEB#q8lpa@ׄ&;‡"dx^t qQ:=)q#~!8kjWS%%d*ζ({?1q<ڦ;j!m|gŸkjn:Ei9ZybxԷl?Egh^:'̰Ee2='X~A~:cBaK>\u$7S^uQԭ/%arίEL&|UgE )p6lk%NuR:P{y|꤈5[4Ґ̈́ϮkU'Ei,Y!^3wϞS\uz]]Al˼N"qO)FiHs"/b{^-|u5GnxTG؄Cxc?[;j2W2>=~VuPuYPQ\LT |mYTxeQU)t S|@_SuRX2pN6#^ 8Ŭrtea,|D[\e#jo`7MlTsD0cnƍ{B>py5LDg~-_`SX Frq+ujhNb@.4>g }Xos*yA>ڳAL]7Ή8 l;2AuNą&%Hrn⹬cL>oj&{qPC~]OuX6JuQe1}e?n2:[h2UgELcI_B:c]=: H ^hRyE]9#u%J+X\enc+GucY:UU7djjèU1nz4Bj4{]MON޾ov4b2NR> #XUmV T'EeY}[CZTo,^VQ7qEh<ԩ꤈,IKu2Q]7.@YE!<B!~oW#KS6-{^NoRK%Q? .x~PwԄTTj"륡JOYuVD4WcFSnI/ WNYe An caTq uE:yQP;e-4 k=B2bkIFɃs@XX" hUOmŮ:o Khj*>dO)f=mѻ|iGiBqE.'1qՋmUgEL8\NSs+~>0&^|78sio)ˆ$(oY" 45ǜEǭ9pX:d B8ΜISm z,_  tڇ+I 7וa£gYBuc+<:+"!鎢9SuRX&$~" d|L|5s"$.ל_:',-g ֆ[᰽N,=ɒQ!iͭd #S`A>A𧺖w=X&N h8pÅ?ՁGe6àii4!L#eGJjN~'[7x80TE9Lrb?fEg.s".lqHn%L srvԴQsT:뙥:d Bv!o^؄b\,*]^za#βL,hXEcA;1qUc`h hQ:gi#\TuQg& >h-DHB/6iY> ȱ3"r/DuRęWW5iw٪p9ŹќuFꔣN'UW؃ B@P^:***d- :u/6]|t ӈަ/j5մɲLt M1h4_x6h#U'5ᰁi9pFLJ+t 괣β,Bz0NtWO{U2.nY膉aPxGoMV ~@?IԄ86>~ 7zTѢ189PeY,_\V7`3lZE1-'. At ,fœ+Auae8q=N[_4(7M0注v VqѕLt"9:%bL Q&?_C m: dD0 i*m: 9ʜv\s"q#G7x#U.?eY3m۶1eRSSy9ٴi;w>ɢi(︉{ry#XEJJ-wlq#?5gX,pjarc :vhP$,05I;cњ4Mw iWN4mbhn`@B"|0d!D@x > &̟?5kְpBƏONNӦMW^9,غ_]9`E1A8h r^rVұ;ᇃOӰտ}r"vYc%q@M79@sz\K˂v\;vFdǀB]z555.pB233 iii\p.AX.G"c2˨K[LDbpE^sܣ4Hq@(Uuxk;%?fa9=tLݵ3+mD"RBM#`466t:1b3f`?{͛73`2gٳJLjp8ş2c+-9.5nSSPGliRl,M ;UWqaÍ۞x mlP \tv/B!Q%ax}pmdɒCuh,lh6o4_ޟbyーa?Z/]VNa^&Z}D5ӦD1 iV=SRc:E4<.S1Bw=Ow.,BGXΝ9sx;=Ǿ 4uǏO iiE~ DC# NΰAЄh'N:=*h2a+sV3 l;^Gk$9Fle9d}1E{p Dyt Zi:F148yz IJZ!BD@84͟/lB>֭[SVVveeexC~,$\po?rMI,hϞ7h<w.MB!v=p]wΰ=|9 ׏UV}ڵƒNyVR~z;x|)}GtKNNrP'IWVT#2 haQFNn6KDEB;ܓ\F@TڵcǫNB!G a̜9ә8q"SNSN>OӴNp8Xt)r O<~\^z%vbx^F-KZJ3TJt)fi{/OLg||΍[16|==>DhOu|,!hDdEB!'a~y'%%1{lfϞ}4lb54&Ц lKRNȞ&ytlnCr6d&T~y45+fcYl}>ٱl;/V+BqP(^ȧ)KZe%~/I^?Qo>N~qhW7M{ Ad7avkf)hkU̓|2`ߪVDtȺaØ۽!BBBѢx^B4SI+əLjZx| =KY`Rhw~*y4/Z9t^K d,iݑgORA!B`2 iN)O蘺eAS-LJ_SM@krV 9bOu'q1Y{N#)/ZL SvB!D $hQ[2ZASC:5KHl/U)Ңْg}|mH n0а֋78W]9!.]ֲs[.S:O :IIھ!Ba2r9/m0P] (Z.՘B\\-n$_nhCbjOᎸhE`d緣{(Y#C&ՕjCp;&VAB!8d -ʕ:E)Sa۷DKjj)1~3-4_S*~5 ٘Lk0ww[HU]Te EmlUtYBuR|ֿ?i!∑P8 PܬYz{283/: +)δȱg Qk(O7v'>S8n rU'*a> 8M٭8nVs^= !hd JNeSqGJIM Uنz0[T'*Xz6do[̬gSՉJ}۶-r-B=zwP$.S9嫯Xֽ;ÓT')pBROt!ZdJ=AbFL!Lr%yU3T'*knŦ1^RL7ow]AuR|Z1W\vudȐ]|4:M*MV]4/zÇJi,e, |0:G! -Pi&@8^:M}L9[Ѧ`/iQL^?^sP:Hs,jڅ mw+,T d]>#y㕪J/|7޾o!DK"- ӵFc$A']4z-l\D]L,z9z+ W]w6.vfYdDճ|hR=:L7a}LO#RT znLjr攖rJr$r\\ !8l2 y^4M߶mϣCqTg)4(2:~,Ձ 9&QW@ǎQ]ԙ/5#38t(DS~JVil|u'ÒsTg)iߔv3q;ϴ4ULE()NB&-Prr2YCIJ*'mlVVg?F4IjhMZK\+x_^VP%iidoq*Lߏx[u2 !Kӳ:ϣ{iZŧYa[CuRq H_ QN#8O1!Z K8 +?'J)6n*$Z8 Tc׬T)zkw-,֫NS}?-1!~"Ng볬Y{hjZŲey$֪NRjOi&/6. NLT!8 2 y^HlTu2M'D!}4}u:\S]Vzmh@UD<àBu:.y+!|:w8Yh@i;hۄBo`$'e<`N?]uR_>r IDAT03䍞=U! B@;v3:Ew߭!0)K&Zzb>:O]MޗJ XNi7,{HagK+y/o<:IGآ(1faY\Dc B@qqqk3KK;P} !w@Ixg'eݺ?`J ZE^LPv"c4ODͷ]xq$LݹwwuZR4r\yhCuRSgvs*+b}~]Ml}]J xkW$xm8@(h^jbgсIwc$Ku2qZoma%|{4S'yko]dz۽ZuRy[6:J"E%'cN"zvS֧ݣoWU@(Y2 !Zt*;Xcr Ugծ3FSyӾ{@c|{JZѶ/uY:Kimfn}\'_bL:ٞ\ycƌRif\iAo]:I*7okŲfdIB!D2rN}K[rRRILj".ƩśqTg)S<֭s'IuRÔ:! NRj~l 7~5לŔ)f)/9wNR*>5ƹI\r8VPG<61JB!DLO;C U')nJ ձq&;˦!%&SuR>#&ICx]xD7tW3&LPOo'g:L2}Evv4uu(Eh|5 4*FZ$&guu\i)"-%> Pq~?:IFv;~/λO>v(f yѯcJ!o_:K)k!.1upIJ]丢"e;8}`COѣV23_SU(g$4_L&4fb+R_Ϩ MJKLKC׏_&zx.n#9JultrX*c?e^|NSFaѺzN?%6JLk'4먯ߌ{L:IF9yW'fQTƶ sˊNRԛpy/{O{qҥ>v>"id BX^ϙӥ{@uR˼'QV7-Tڴ)g\Op0ӿ$/ w/NvRԀMDЪU)1̛z$v}_{.ルMN[;kR6 ƪNRdҤwT{t515?-ٳ[K{iョ$JNNC%t{:kvT+R'yaDE PM7-cdg_Yg}ߑ. Jm朚Lڵ+'&f 骳r:P֎+aN'>[݅V]TtBaƎmfէsZ,9y݃GwmϱHPbeeer|t)Jee=#J҄0 cTLsNʈwٓhqv,C#Ai+nm55>>lҏ$Z99"FPT9t%8[.]қ^*?9J7>ػ󸨪{aaAQ\ p*EB+Q,1G[~撦fOddSi[BR⒈," ,#j 0GKsxzs{Vqe<!Y}Ezz׆JLLw,g@pضM*=0߅[M۽aMOŸlpi c0ˎ$U۶Mpq{॔weǑ*ߚ5P8W5F/m/b f:5#eǑjmfMzZmZq7 $:M?uqqA~~y-a'[ n^  ~ AURyo]#;TN㈛緣Cڞiᖆ~#/7}/;TI-}afV̓I w<}$ M&`X%;T~‰oT뱅,‚Ñk Y.0$4E[fnO}<);\4Lg0ؠǹɎ$UOPC7};<|^u`@HR4/ ǜ1uyɹOR?hlC5 $"TU L0(=dǒjPDl>97!2qّJHbO#1o+ˎ%#! Nv$iTU}.vQ4q%%Ɏ$U8.m8R/acƌkqgcAHDT:m}qo7/#s$, gڞ>fkHC);-h^ߏFvvHRXf\x60dG=MՁS{g=z x0#V iggaAHDTǙ f[?=Gnq`DQEzX*m1U?!xr{HR}Գ'R"23m| wF#Iuh KAowZ8Rm9*?IOˎ#U͆7m!+pvV9VGa5>}:&N֭[+VСCmW)))}">f%C80< 4<&4j4..)p@|z0V7)oZk@p9\H}0WD#IU;-1/0c$qDQ0 7DNsXmVڞYXV$̜9^>( M2ۍFc5#"-3һW]k6yM_r }!9٩JJz~~a`)%{~);TwO@|?+r7Ɏ$WO>%;TݡW!;J<BdGj۶0by]]@Ԛ($a_r20hPjB_cG+ ̙3+>CG;[nEk(1iM֭Ѻuk1ںu+~9 fXG+l<;cB:DFdGI FQ|$'ˎ$թ\ !$ \ /jwC BAy }F{K-O#!8 ?LVs'WNƂ 2331ydL4 fRFtttQQQpwbX`Au%",ш8 vGԢEL53z|ѺU!#..Nv$i`  =mhأDR%_  ZG[ێètyiN͞fwsU2asWӭ[7~ؾXמJ^UUT%Qf2PTC܆yF +Kv,y~mPpEnG^^>i|٫OHRE @z2iT<"07oBHNv$\\:0lo6f6踫q U>>U t ,YCRҥ٦(,,jEBB|||`2*uܐW~^OOk6ydL2$"+F#ss;Zu&;Tn*,yyyZ૴¯);TVk:!-8u,l#;T Rxg gFƣpȎ$Maa!>d5t^?a#%VO4Pĵr;wgϞp՟l(cbٕ:m݆\jB#//:jB"ҺBxx@û'B}Pۋ 1.+#5,,cI_#HNN p׽o'nY+HL]]bxcP7=ݳ(#Ipxz DΝSP>p8>Ij'yKՃ=7uؼys&L@nn.}]4il4k֬0KOOR?௾ ?_<:Y:]BdǑo\??+;T}`_a:xd D^ub/<*|k4l#*Ă!**9s@Q<?~HuC 9+o" Ia Pel۶  ȑ#1i$ͯFDTf;&OGȎ"ՈQfͅ߹shm^"??;ѩSSّF]8Qv22`27DʒT\ܟ+;Tj`ԯM!''gsRg3\3_MDt}]tѹsgQz(uɅmUbo7XHCg .޿^9Ev$fx??꺣AmcɃQ|su8Ox!$""Mܹ3O.;TXm­C;^zhLrl6t<dD ƤI]^[Y*[eG# Rq`ˎ#h{oxɏrNՊO>):DD s95MnҤIX6~J O޽]\pF~HH);T3>1QȎ#չsXʽ22=6Vv$:]?rrСN9zG $""EɆÑic';Tҥp/F{ ũ#Iu|deu_4?\4b2[vٳ  M<eXr^ KXihp: ݆6mI}`Tm!^VMv"RR֢8@nȥs KJ=sT(PaD3jSΥu,4h4nOȂvˎ$ ? +1XI0Ȏ$ՈWvc\fM?k l 4#k,T7S l B"""0Lَ֔xvq2KW%?[evu-];,MrLXrnݴ]6n $TtGC ?4=6oj]v:McAHDDFEE(J=MdǑ*#?i[aE=:kXOv$rŅ<30` ّW{AHTreǑI\3 qi/ %g`AHDDF {[d˷&'cۅ #I^Al~Ajŋ)$gHRm)mFB>HIqyCF!FF}_~p8LŋOѹW!^۽ I\קi/rFv$ZnݻSpkl|5{8Rm8G,‹?>O>s۬Vtv!F!,,}fny&$! 1ˎ$Uz@q y2L,4BUUFDFFB]\\\/BCy)ّlh_(kF#ISXxqqc__ N$`7~O|й'wpazgaAHDD!;v@ZZRP8'Pp8gpfnoRCsx{g!7w ԛUV+`O}z8fʹ(DDDr뭷ʎ ŋ^B73 PcHR̜lP|FG.;22ΣE \]} 齺#+й'Z'?UnDDD9Fz}>!#I0h4DxG`#G]v$iTUElQ۹omu2DDD)& 0paّ9|Ⱓ`!#I)Ev tqZ|9. <. z~: B"""UFg+x1Zvna).Əٰ N(^Wf<@?$;TY><[VXXFf3>^v L&~܌㎑3dǑkNX~{l>6ɛl $""" ш,13[gʎ#YFa¤FZR#hvԭӧOh۶m3<xyygϞDDDD@~zzMM0 Yfddʾ$t+{約=$)) 3g΄WBǡC0n8aDll,6mZÉHzl8ވ IDATQIJJB.8v~HR]!Ya{Ƣi!.׿\CESù9ڳyPZsg: jk.s=ѣ+T4?#l?իW#33B!эB 6USyBΔb(,5 qG;ƌ#FUV~_\\ڷo_n{NYj(0]@v4 ~}*Zp!Μ9RKII]wUn{PP 99R&Ud`O?"8Xvy0QlT 9 *ɓ1i$+٭^PP77r!@AAAu$"""k0L /Ȏ"ٳ@)f<=SmN &L???=@QQQ텅P::31 Jg$"""F#=ׯ(R=pfw`N9nfwwJcAxN8%K`޼yHJJpBXV$$$& BJJJ%ۂ+9f:?řF\4yCHDDDDaFyyge IXihDnn" >Mv`AHDDDDa(,L@~v8Cv3WQU >}=3qr"""""3Lʒ$`AHDDDDqFFB"""""3(. %`AHDDDDq]vo-;F)NsrVztdlTm@W͜aAẌ́h߾=pkWR8p۷ @%Jlt=f $$YYY~Onn.G """"""*a5GFFN:9s`֭{}BDFFÉ'p.x饗TU#<_=CE=ッwAn;#:i!8{,~zz,X:O?;#GĂ *>CH%8֞@%Jlt=5W^޽{+ޮ]"==?//^^^85 ’,nZ??@%Jlt%n_s ш ϾO7CFkǏGXXXRb@_"J.OOO&@%Jlt=l65a (((dggWNTq鉜 EQ{fsB!ݝJ{XVo6 +VZl HMMEvv65kVZbyW_}矯pUU]OBDDDD\|s3U#''wy'ׯT^ǎٳ1vXO?+Whذ! <<ڵCǎ닃b_>!DDDDDTCXƲe˰h"ddd:to۷t?EQjnٲ۶mC~~>0rHL4 9{4J].bAHDDDDDQ,4a-T\\ԯ_;weǢj/`hݺ5ШQ# 4Ǐ/޽{~~~2dӯze˖e˖@xx8{z(TCOUUѶmrM}<==ѦMr׎@N8#$$hѢMVr %//'OF>}UUrʫ[>;;< 兞={"..>I ]ԫWOxyy#GwYaXDXXx̙3lڵV1-Z$EK.O=PE[xTEgϞ[iӦklu7|#D.]ܹsҥKū*bbbJa;ІDa4EƍŬYĒ%KİaÄ(==OPѳgOXbEj;ѵkW-M&,X Zn-|||ĉ'jsSaAX۷O("fϞ]P4kLtMb2.{)Džx'K=S={tۅ(bɒ% bQQQeOoooUCj A=#"##lu[NNW0`uc;Іӧ UUQfSO=%TU-~luOqq8wB_~E(rՂ&u넢(bӦMΟ?/L&xǫ3s e^yŋl9sPU_z[:t :vX}``4hPnq~W_ UU_]f={EQիk.4U;w N'>|Ղmn[pPUU;vL!D^^p8c;І UUEFFF111U !5q(sȑKW$|_//2;uT:MΝb$''#-- ;v,_Nʌ/}סCq1c0bjժluw}$&&y򂏏F"lZ ! ~ gϞźuh";lVS>..۷1_]a-rۃ @rrTTVZ$ <vm!33Vt_bNL-pB9sӦMluaZѯ_?6mñh" 6 ہ ӦMömЮ];4l=ƌ~ۃԵ}(ZUv^:-'FnݺaȐ!{-t:@_l3@ff&&OI&l6_u/77xg1g@QTTŋcԩl  lƖ-[0}tԫWFb{а׻BR,kҡAW*,,,}sΡo߾0LذaE\m0a0zk6Q\%{1|سgZh@ ֮]gy'N(߿?v;bbb裏 kPEQNj)eJ\d[pp#Q A޽+}?k NWn/jEFFMĉXd ƌ$$$$(,,jEBB.\6%%00`;И }EEE!??qqqlVSמu Z&""-}޽PQu***<'N`˖-[ʼ /޻2 ""Br8pm&!ƌƍqhҤ ۇcǎI&6mۄLTf{3;lr9rۭV+llVS>""厹w^ W 5)ݘuymEEE",,LtUb2.v]DEE ^_n +]o}ŋn+((~~~W]_K\p?UtW֭EhhÇlu]\\PE<e?B׋!ہV<]?~ WWWuoگ[N*6nXd{:?9 ZhB׋qƉŋ] ^/v-;Uc EQD~ĪU}HLLYfbbƌl6r-X@*ҥKŐ!C7tǣjru&Ç UUŠAĂ DttPUUkhî]NbڴibO>BUU1r{OgGyD7999Bv]tES ֭[ ___OՋa-TTT$ƍ'ŶmdǢj)TUו=*z-lC iiiW=ҥKE- 3>ՐHѶmr&6&N*7n,DxxUہ68p@W 777Ѽyso ^f?'44 ĵ#FKS$PBU""""""r>N*CDDDDDQ,4!F $"""""(DDDDDDłHXi B""""""bAHDDDDDQ,4!F $"""""(DDDDDDłHXyyy>7 AV<޽QQQHMM^8ԯ_~!Sc!""maaP}n݊;^^^|v冑Z,k嶇y100p=tt4Νƍw޸ťt8F ԐǏC=z{MQ~cС{LEQPPPPs=#66thj cǎŞ={=NW{EQprssXjG콼ył .T 4@||=ZMYv-͛xm۶7n_c݌pB?1g<s $"""""(>CHDDDDDQ,4!F $"""""(DDDDDD*;U@aa!\]]eQf"""""لlpww& #;QɁu Z'&YYY0 PUYVxzz"//:Nv"^Ϻ׳5[x=|F: ZdN?~ugYz=u gPr HTcMDDDDDQ,4!F $"""""(DDDDDDł+S bɼugY-u qGB!YVzse""""x<,4!F $"""""(DDDDDDłHXi B""""""bAHDDDDDQ,4!F $"""""(DDDDDDłHXi B""""""bAHDDDDDQ,4!F <==ѦM{l<3z쉸8'$&"""""\e[DEE}4ipI={BǡC0n8aDll,6mO@DDDDDZ!ŋݻcÆ z1x`lܸ= ==jժ jBף:ҟf{\*Xz50}t@~~>*Z_oܸ+-b`Z#J Z IDAT>>>HLLD5 EEE}o\\ڷo_n{NMDDDDDa?~VC>}i& >-°aîޔ^-99F22U<쳘3g(**ŋ1ukNSPP77r!@AAAf'"""""baxxx\fc=!s^mXiaa!E)=vEYk~JÒs `@``` .\AAAHII)d[ɱ+z_ӦMԱjڴiӮy);f :l/y@lll{`@xxx塸_'NԱjĉy';f Be˖پdt:DFFRSSqر2C7 saӦM駟"**tk~$"(11+eG!"jr{XrN*S6l/_Պ ;vƍW`Xr%N> TΝ;Cő#G`X`8L2E""oÆ xwo߾m;wƋ/hɈUQFX|9>34jsſ}EUU[n+磠:uʕ+ADtӈ[ou;@4ݻDLL |M E!dgZQ\\̮u"6l؀^. 0,S !$$9y !4fϞ}w1ĥb}8(]Ć йsg4lڵCÆ ѥKlذ9j)r V$3gr!8Ĕ=󰇐n ݿt 6\Q R!rYSHDDt ,wwix5ΗCLKpY "":DDtS AΝqi Kҥ `޽_ E*S3DDD $"Ƌ/xwpׯ/ }nh)p^^PPZ#XM#::111{ &wK`LL ohiU9R""XM7>z%Dׯ__:che3^JDDu8%/e|م3% FIbIYe-{=.iAD$q=DDt ADDUArCL+ڵkQmj13k֬rKZp)&2Z˱;ni.]./UQRK3vDe &&憇]v""-=󰇐ꌿbZe- ty{ŗL٣HDD2 $""ͨ3OŗL&"Vg !{nHHH@~~>Ѯ];}Z;6l؀ٳg_>zI.] / :: /ҥ ֭[WLKw9ԋyiԒL%8::O!,,,ĬYШQ#s=ؼy3RSSaZqa?5BTT~qHhٳgΜA\\Μ9LU/Bg0DDtSs= 6DǎOW^pss+ω'f,^SL%$ Q?~{yVT笉E"mxsq9O5&"{ԹŠ?Ѽys1?=!"re۰a"Ll 1-)0+ӣخ];š"3ϗTa ""x<*n:,]nQ!@ͮ k=Թ!W?c o]bݲcQS!@ͮ klb ,[ 0`.]~ -['DD7UG5D9u:CCYfؿ?|M$''c…c ""EREz9a դ:C=7n\]ӱnJiKְG*S'{dddm۶֭-Zj?Ν;j/߿]b5ߛVYvh\-\Y g㣪g@=lU$HK*V$oMm_ŪVDhI Ka"`K $! 3If&y=y939^9|b)**ʭbϞ=A g$RSSd2GЯ_?Og|6m7ߨu֪]vW^ k׮UϳX,S֭ _~ f͚={*!!!uR"4)Z3J(;6SW.aʿߋJLLt֩bj%e&?ӤISvmsw˗/7,Zs͛gVٰaCpy#ɜ?\Tvݤ^bfΌd5R?l6}i$S󼴴4g:vtY=lYRRIIIo<>}*2ZΝ;+!!Au ~vvv5V^^^࿪ʂ5ef)::Zk֬#LՙM1cF9##C7nTFFFeL@XF3f.]Zaל}4}B}\뀰~ZxnVw> B[nѱcǴsN<,O.a)q%k扌bn?lbzzNrE@Żxbh*$$ za/=:N>]s˲gbHH}Ź}=@`k :Gqʔ)=3?{(2?𜸸!!!^  tR͛7Oj}nje˖) 馛m۶T !*2e(2?,26^zuPӽ{wsM7zѣG۸q *Cͅ J=wɒ%b- ))ӧނEy{ڳ{(Ν; {($hz_g~R?/P!C(88Xr´e[YRRRԱcGIҤI`۷/rԡCEDDW^W6lؠ~[-[ڵkդI[@UrY x6@z#ҪlΜ9O>q&((ȴlL8ٳPI&jժ;=ӦGAf͚uG1Gqo\&Tb3W/{)hۯ3WܧY{l>dz_g=+]4&&ƫe0U}\/uDZѺtbMnnnky3k,/3 UtFAO,X\Xw(PbY3}\ ol68p@Æ S^ԢE ժUK'N֭[j*mܸQSN՟'5h.3~{CUYҽʑaI-$n+--"-EH=o5SL1;w6u1իW7M65vILL4ǎu+==^jVBWS#ɤ_VW2g,8,D3SzZ0c. AZ4] zvқQ *C@X9Xԝ-==ݭe ˲" qa\lQpKkd׮]&e8jY za@jFQ###="-qaq%8pTc\ uV6ᨕy>#cG{8j[tw8jeHꠏ=~֨Q9r,SF Ԩ\(zᨕm>#V-qǯBb>\L͚5}P@`q5بW+}\ˀ^3Z&>>^3g6wu֭Y!xX@i*rŪ:1""d/ s5bZh|5ט6mژիW'\2gddjE=~ׯ9~Q<,Tpa>' _ a aR3ӭӫ2|5qǯ‹/yǛ_WU?aU3VTu#}\3f6z_7t,KEjRv4i$ze+4hРb޽{zɓ'?A~rrrԻwo+p(?H@-~l6ٺuuV?^[lъ+駟ow?^znSÆ K<~i͊UF4w\v}j۶Ku @E\G%z!+toOkݺun_ 裏4zhMJJҸqh"y睒cǎC1b}]C@_l"ׯ׫]v޽[z?m۶w_ܾajժtرcwO2E?ʚ}yV_W 6@ZZk֬YL1m/$)88XΝ+rٳX, v뾹%~fZ]xŋWgmQz;=}te7oC9^pEn3$$DAAAžŕ ue´uV5nXM4)1C'IGH٣Zj}޽VZUxZZj׮:uӧONZP7ns*##C֭S]nozߏ%ܷo{9?9D?mVkyRbb~a%''k̘1Zn3$Rdئjղe4vX͙3G ӷ~r0Nvzm۶Mmڴ_}JNNuˍߞl6=so<>df{~3fLaaa:zj_Sfff6mR˖-}P#<: ;vf̘G:#\fx s=>_/*s9M2E.^ *::ZW02>u@X6oެlC_TaxXoz_vݹa=Ud)Zn֭[k8p ֭[ZP)2sbh̙jӦZn'j޼y_Ν;z/JF:>ѳg*%%E˗/˵~zmVSL}]=)֪UKرc5p@_7u*;=PVV5jp/pB9C駟O?{Z^>}UVPVRƍᥚ& ?~\~"""矫w޺k5qD_W+l6f9$3f9$ů^~з~oFK.1F.\uʭtzrr$Hj*鰤DI񒤤$EEE9JlzϱJ tDRYfQPQ2=~!={FƍGzuj…u<.!!!o6I,IDIR0htPҡ?mBBU_g#""_ 6u*\I=PVR0xÒZHʓnWttt)MҋTJJJzW:GӧOwf)z_eƍcRLa-[LÇܹsCpT!{zh kԨQߎ'S+`PǛH^cZpCR OO>rdK(()Oܹs1͛7OL eK*55Ung_!Qӧ-^@ ;V٭[elْ?7݌bY(@FЏEEEf{Qt[69rʔ)=35++˭7~ŠJ;dΟ?_b$ӧO#4IIIElY4O1f1]f$S+y~2K(y4g:GDD\W|f~vnv{9Z%qwtMX,.]vm~iuE?Ceϟɓ'9nXt!tOwV`rud%$$tTLL3XxX9|2lb*.oi1w\VHup}{a{R 3[fxzO?e*PǎeZպuk;X IDAT{Wqqqjݺuƌ ɇj3*ǐϣ*n wȂayZ:j𘞞Sbw)˖l>B/m; : 8t͛gVٰaCt'Ԟ={zt8n7IIIvy]7\ҧOb.(}@¢2`ʕ׿Lggg+//+(n+==]v])))5k3ȬVIII2l.pc^f͚쳦Yff֬YVZg5'N4 64 rW_}4hdeec\̽kyggyƄ0O?|X\ᨑ&===lIk׮._@YWUQEG5bn&4pBӧOz_/*s5(33SԶm[}ג 6CIvޭzK=8k߾}:{rss~8q놇nb_FUݻ̓'2eJ#R[sHΝ*^ř.ɪTm}acKC5Wrrr+$'';򈈈PVY7ŕ u#ROz' /`17իW7_Yy'u˗jVX,E^Vĸ}^z믿d˯"32?՟ @Y!nc|wJMMUuwZYYYZzuO=5{liF;wVffNTu>^={,%Puu|Aկ__M6-Y,BYߞTnd\(Mܹs!Of˚Ma֭x,X_OLKK_qյ9sXJO,SPN?~\ƍu5RjjveەR(fY?fSTT/R#&`5k S;3 M/+3KUJL,|o\:C8ydr-}]'ǕlbAʲixx8CLz_/^~e5J]v-ӥ!oUKgW<ݵkWpA{: ,hXd۽XaTtFAײv]noQP'V1  \O.\pԊbJ6>2. WݯbBGuX $%%9ӧ/Gw8a\[&++KWxPKa]066V>BBBKΪߞSis5ւ5ebʂ5w᭷ުŋ~[K,gXrJ/3xXKOSD   *UlѾ}{EDDULv͛7kq/½{뮓buU<U^{"/z_.*Ӿ}{=z~ر:|p)g4W Lb WKnݺ5222qFedd*1 Lz.]TOQmRjjveەRdN2%\)SLԨQ#%$$/bZH6X*1R0 _e@rQQQl^cg?fԩS]&FFFJR6V9)tIV*##C6Ms *--Mњ1c[2ˀb? RDe@HxX*jO_|Qru?tڵk䘧XpD9:[$S@zaϕ!?[Kv]>^9ĴՂY}\Ee4,X~e]Z_!CX|Jʹ:4))I۷WDDbڽ{wP C@XLvs/C2zCFp%$$/sXD68+^ **ߞSQ+W(W_ex) 2VuR'OPXXԩo 2 WK%͖E9f:9ͦ(eee S7$}ᇗ69C:O a9p:v(ժ֭[~(1F͛Fiܹնm[oO'99Y :DFF*&&990bK =dܸq҅ uՀ0))IƍӢEtwJ;:hĈzw]/ Py]-tiž={jÆ rgxi+h͚5Β}Ӌ,T@eA{2ZV\?X_]>gѢEj֬3ƍ+::Z}rss=QU^t!ӧO[U Jرc6`8fSttt~0hcչ3 ,<=cչsgKOOW=ݻrrrsΊ&J9ƍ/sD+eʱԨQ#%''3\a9튋sCyE;x`@ʶrڸq222*[ʊ?g}V<6lֹgΜQ͚5U1:sLEU@%Tvn+%%?W(###nU3]U>9 SO=FGq`;wgb(88حx}99ŋK;hz뭷cڿۧg*77W׉'Jtq-X%ZVUꨨ-- -t;u8/^T^^^*$$m'z"ǟz)eggkjӦ:wL>>U9Gn38k,mܸQr,:f+==]|eCLrW0d!(BP @@" E@!(BPuVEGGm۶ Q&M4`}W=wZE^ժUӑ#GP{+P߿_ٚ4iZh-ZHFқooX֭[:^~},Jczsi֭%?^[N=z(Ξ=`9sFj*uP9\xQqqqzUZ5_WD{Ц/qF%< Сj׮-999 ӧUvT@nntyըQA9ў?= }\aaQVVݫD-[LCy 8P]޽ 5V'xBo$jꮻҜ9sJ=vښgyƭlje-NPP@" !(BPNwqZj`5o\ÇWJJJSRRԯ_?y6mN>Z$|>uQ!!!j۶~eff[2335c }諯uӧOgըQ#YV-Xز۷omݦuQF0a;$ׯ#<.]N:k5vXڵHYڲjغuնm[I&0`"eiSb*jΝVNf͚ĉzwտ-]TÆ sݸqN:)11Q?^z%޽[K,@ͦ'N(**J۷޽{5g-YD7nTXX,YرC/ڷonݺ)55IJg7qD}NJQv4o<1B˗/-ǎS\\Zu]˗//܁t뭷A5k~KvZUNw㕒(uM3gz5k֨SNh˪d֤IԢE hѢE5j|MDz1͚53Ç/t|e˖&;;yj5_~b|wE\X,O:N{V~ĉc>#cZ͊+-K{Vnk֬1$$$8={ִkׇ5+Ο?o>l1fb)7uTb~'籯X,[oy(Yjj-tl׮]VZfceՖgwnn12GդIc+?^!!!&LPHH|QU\_~Ezjذm>FFX?o~lylȐ!Сa%ѧO"vکs΅-6ŢB}Y_~EYYYڱc?j˖-:t͛7… ٳgjԨݻ+==UN>l5ny/gqFuAu)twQ?O:$Ţ͛9yZj $&&*77Wƍs= Y:t1>*ҡC$v>~rssUF oW Wz$іUO<7xCdZu]wiΜ9hSo! 1:Kek֬Y}|||Ieddh:rss$I:sLIRZ┧= \R3gرc5`q*=KB{V~gΜ)} >Gv簠 ezGԷo_M0AmYU(**JTRR.^sIMXr trE۶mSǺuw߭=zh1|.wY8iOѣխ[7[>=Yڳ .} >Gv2>#GAJNNbD[VU:tpy=׿F4K+믿^slq)5jШQsΩf͚aM):tH-Z(kQgFF  hɒ%)(O{zWE=%=+͛;,h-9lذ!هJԩSt)ZJ͚5s~F[1chʔ)ڵkm%@ӦM+''G/Ytիk3f\nn6nܨcV}qIY6l.\˗iӦEОWhʯ`B ˤb{>*B-ԤI_gk׮+s7vޭZ;v,9m ,`>}Z^3JÇСCZtڴiSl9ӿОߘ1ctcϟ׼yԧOBˠ뮻_F믿Ν;y+L\\ |?sLt=HZ͜9Sz駋բEb?CJMwos >͟?_t):uJ#GT֭%I;v,t`իWOwg:2 8Rh cǎڶm$i۶m۷os]v_Wkuui„ zuEuUݺu~_2jժW^'9~={,tΦM4sLg֭_ٳgϙ3gTVb?ڵM6$uҥб#GHrĉ5l05JgVfffk+''j_PE*:uׇ~-ZgՍ7ިSN|SjӦMڴi6nXb͓BBB ?צM?jΝ%}7։'Fο[,b]>?Էo_-\P;vڵk ]μ*/B[^!Shϼ'NhΝԩ$n(UԡCg`cZ5x`͚5K6mҾ}7߸\M6ΗZ?e~ }/jÆ W=zhǎU*IDDo^x]7xl6V^Ν;w~w^;wNe6c!-[֚5k~թSG 6t)9s6l0=SjҤIO<޽{^رc^{M%IK,޽{տ5h@K,1F_}T;wQFWW?ڵknA 5ydƘ"<31cj:qqqgРA֭[ ]cۧ7|SFR-}vڵK&MrԦM]wuW !-O>UN:),,6%X,5kMnIGŋVDDpBuU=^x?^#kȐ!ԩ|M}΀,u*߯;W^ SJJK͚5KfRݕŋaÆ~W6l }ݻ"##׿չKq6l;Sn]vھ}ƌ;jʔ)zG8|ŔCiŊrpΝ5j4iSNz饗1bG*)IťrjթSpylXS,YCtKxAmGر `eXbTvKKA%VVHZnؔA,t85kYy_L}}='OdϹ-~d2ƍ+ooƌ|>|>MMM|>ƎۭkMp\TWWO&##Ǐ!99n޼9ե@XWWǽ{Xt){d21|pΝkimg~~>fqq]ȑ#X,,YlKѣ)..:={p80 :Bn„ 0? ͛7IOO'..>~<8p ?cϢݻw(//',,_~۷ocXn, Vf3ӧO7Ƽs_}fɣG:>5k(**⯿]O5kܺu(&77ŋç~ڵk7o+W,"""""R cذa\.JKKAv? >>'Oyf.]޽{?2}wV>sn7>zj_o@mm->LΜ9cy;!::Nph"֭[gORZZpo?xϟ?Wm"""""a]2v5j7n$))#Fp…ǓEXX|>,YBll,l+쎌 l6L2O+_|.x'COKKKOKK3`ee%ӻu?YYY 4,X`י|VګjbZX,XV51"##Yp!9`|l6ZZZzݚ|\]ر۷o3NJFF A?**MtttprƎK\\s%Μ9Ǐv=^{xNۍ0|jjjhhhULL MMM쓐 Z_zzzիzz/yƨ{wF*++}oݻjkk jvk"""""qccccٰa/^w>^|PC\.;wP__ϴiӌ`'W~g̙3' 566R^^]zӧ5zh^~?@jj*&'RPP@DD_~e~=}fNN{@P V]]d mػw/W\1nhWRRĉ;gy]J( qZ[[yv2-Fz*---l߾mVٳg]¸wQh"*++9uo޼[nq 233),,ݻz\;oXX)))t:HOO?`^l&77-[t߂ ())ҥKSrrr={6ADEEb ֯_o޽[RTTċ/hlld۶m?~<'Izb"""""#""xӧO'669wCtVۙ;w.6fFYnN}RZZʬYH:u*VGb8< 11D-[fl\x1 .$99!C0f8N&MI&u';;_̙3?\y}VV111WTT;m~c$&&rСNR[[ˉ'={6'O$$$`qݸnƍ9e?}EDDDDDDBFϾ&"""""" """"""!JPDDDDD$D)(B@("""""EDDDDDBHR  Q """"""!JPDDDDD$D)(B@("""""EDDDDDBHMV7IENDB`PyNN-0.10.0/doc/images/examples/small_network_nest_np1_20170505-150334.png000066400000000000000000004605471415343567000252120ustar00rootroot00000000000000PNG  IHDR8QsBIT|d pHYs&? IDATxyXTe J*( , *ejf꯷2v̴\}dAEvDveO 338:uygyù<-d2ޑ~0 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S=z<˃T*D"QT :z%%%(--UL&Cii)7n 9Ā)SSeggiӦ=g§IZZLLL>I>,z~^`3XtbzY} {,Lg㛹&Z7m5ktv'AQQ7n<Uܒgmc 5hhv|=z_}ֱ0aLݼh=DAI5jVC3};v?L>{d2n%}`툵=*isnyҬ\ l@Æ='_Zк5a`e_>lـ"=L:}RrSvX -^ #OWS'5?SNWϣ7gN?ѲeKf§|ڀI"0uT :_QΪcOcJ)c rJSfv=u6222{x"!5*®OF9iCS}/9T> 7o5ϏʱcO̹ZnU]cMuP!JɎ82ƫs& wu6Ƹ8iK/آt?u!.+pvU=btR * P#:(.Vv3f ?ܹSw0>9;5;w,wϞ@rr}v@qH)*wq\1(Ij튱cq<8d;U@h?x 22j.d29[P!:GRdx^_B9Hqr sq3&C!Kqt2Ƣ"$$XOwq,Uz_W׳7 u{̪\w*^וSwNTVVc|<VnD0moA*yCÆLA]HSK/vu^yxW]*xX[\ < 狯핇},'(n\_<n.SOIY'H$DeDU*}5WM¢"\Xr11zDDOmժ~ǡϞĺtn8 &X1s#: ۹RnĜ>>^ L̔rE deUv[> ƜŸ HвqKZ=ߞ=_}EBLt6\7C;vuffѣu 5z/Np8FOMf=|b Sll- z :S>ƏGbӋGФ5~‘#pkVe64_!e2KHFfv&Z@~~>s'//)@!ڵ_0aΘb{XX)%&TVM[k45ir&ulLz9)lYֆWFco޳i;w,v33 ػd2|sz^>ZFnٶ-/ ىw$'`3 n^crsu>|4iҤ5<8oE?Sag_kU+>[Xx/B湐03Tg']G}֡ B`r r s:N QQ+Sx^aI!s['+dXvnV]ss=UBA EEUdy&-˶i7J$"2f ," 0ҥoygnhi),S^0I9Iu:}:{9),)ĉ'm6Ip1b'0}&M @!!()-c0a, ;hNL?xuXJ J BGо+7ha{UHqIjz&n]#o>x~I~hG߀D :"096'(5H|tMСy_~e'#QG7O! H*b*+7@~Q> [[q]`겠L~Q>b3cѥe\Cqi1Zmk߬=<<0uTkmMۢUV0Ƶw/pK_x%9y}]OMOqօ۷FkklR@to ދT lY@ȞvWunm7Rol`|8} Cm};PRũ%GfgVϪc0H%Rx9xax ""?vDV 8KqMzI}UIOGjn*V!WkW\Oq@X\Z7hᨰ)eaXZ㽱b ^ܧcbѴ+VPw7mܫ= T;c axZ87k#HP#G¾Gq/|}q-F۫ա?7+ʶKbm@b]q~d K̿ȘU߲E̊6b\ ׮II?]I--,-,iOu#֩|na%7mUiTCƚɀ7Efd{{cJqcu`?2G0zyGOWny?-[aIK.9ڿm\KҸqcKKi}0:z/]Zvo \\q=ljfvK&}}<8:yaA8}΃;*t>ѯM?mDP2 vZtQʈR9=smc =O;/G|O.|R9qBdNVN `VڲkQŹ?'.N!!iSӺ|}u5tzYZBB&&g5UyaiaySٰA2̰vtR5'b6>T۴IL#T%>^1 HDT??- XpA5KLv;Pnn1Cu:Vv#qު;WZ*Ů^-Uqw.pcض HeuxX;mnsS*v4C*{#0%E%E5. !Չ7нewM1}w3"f)"3nܻ’Bd2\>Wq<$$mͿѧP֣,ͼ򏼼O?|ՓReT&cIIptg>S]U#3"Ѭa#x9xᯰ*RD4_׶L aF͔ܪBZmj Nj"yy:)?N`H}{Eb*fdf)ޣ<^GgKTXZBfoIQzbwoDw㌍3cfjŵkm[ae/H C?59ulVM8C&=`iYwIB7@Vݑ&B˳ kˮ L TGŏʦX`H!JACLitQ`&F&zU=(%]Zv33˧iHS52z9+V_\6|=:-/-x.^jSmzʨ*/u ;n쨵fTsݥGC$ETiaW9CrIu+_k|j`ָ̡^0@Te0T[=D̃ޗ}Or@ih0 WevmFFq#:9lRamCWX\'K'Z?ٿ#}v͚5 R*_>p3Q@~-ѷo_)g+c~=:z 0%֮eE+1qtE~I~Jʎc%$Q'C>kp?~ٶNWɞw8hZe~ю'?zYR^ZeY)Cwcw9Ɂptms۲pAoIaN@h/1;99Z?-\>$ vZdU#Z7Պkػw;#O>pttĨQ^u1_ 's`Ba\/^E%EXwe4hګ| A6nb$:]k!l#񂧪C?N9vB_}Uci?Nj^ġC(*=' %l=֞ aIB+*\*KNZ5nѱ4c()}&8XV,.u>$Q-QI" HHP2 )uՖ-eAJ" eˀ￯~JT@r@D;%%rE: &贷χ&*d.*ݦG ,XP^̬:wAq<<3GQXRXVWZ[D#( L ;!(%HpvTNe@X,--gU˗/cРA011 -Z RTاy7s/*-;^{M-+߸Q8k:X&SaaI!8KkwWkoberRCtoս,II}W㞋9fCm=dDqS!*MaiahaB) *˶o $Sd2~ :5->300g}III*smxzz^@hh(vލ+W`…ZaǨQSbڴi '|?,[~=z ,XG*\EEE % <<&NUTiUU/ǭ[* g_"ùٽ{"XvG]wâG_ʵkUO٭Jhr@NNesD*W'$5, p^߾ _U"2pP"bo^ea~Zf60n]}M0NNNX|?sL>oзo_lذ۶mCav%K`ĉkxxx?=fΜ /I;̓>ܹs*_#==%%%e,,,RuZ V ݅Ca]ZvQ9fR,XbLxuR.}ZJ 5 IDATEhىRMa1xK_-+ٳ56Xv´T>d<)jʨv J R<\MP=adxmUq&D'痣݆vqQ]&ínq\db32T?~=:z,ɀ3E^M5)ZSѝ;@ Ě %^^](&FQdX|+WдASr3jj"<=;Uȯ﷈ͬÇEUڊv>?!N>v#hHMVX .ϗmHx2ܦQFa4nn.x 88 Add$:HϹE**5a##j7+SSSaYMѧ@3!՚q+:-/UU* 5p[uC5,p]pWQGwoqgz%"C;ӦBAn^~;OaS?"x;|jr66d"CXyuSQXR+eZhhLjU6.6*> H$X=l5FŝwPZ*0aXx ӪQu];}}{ {biI§?Uzت3t&@߾ӪOUຊ+WŔny;iĔtm.U4Idd2|}Fs=z 1cDyqsfʟQO~ !2RwAqa¾, oa)]`[fUظQdgP.ZvDO qhifֽq6F1a5$]E14]34׬YC)sqqAXXlmmaggAleDEE)TKt+-_|5^_odd^zLf2 gΜAψÀjНyn楥H$ŀ;mN <90uQF> RH$b5S%}۰ 7GD~1@%/.6|L4eT*oWbA twy < [`¸&u]q7fSnj~0VhhP@qi1cnxm /X16O}+ss+/Fc5|peדk1W~a"2=m+X>!!;Ed&n-!0Y5c_3ba!~UU p(^Msp%&Vald޽Ex ~Nkq1p挘"hXV9zhByuk5kL&>_nUzajCa[fD}VHDG]u6{xWmy|[o!888pz뭲 6 =pu̟? *ݥRuw3gzjDEEa֭.S شimۆ[na޼yǬYTRdSa}Zb&gnb͊#b2c/J0L>Uc{Pd*;iZ,:|XLUeHŋEUb3c vpvƍyb\jw@G{ u~f dRYR aԝSDA8Bm5.(]f` #8]ea$mE칹?$deVڪi0mOnAn4ϱƒ%Bmcwno1)g2qAEa9T9~\T!lx_zY*k9` 5@p9)y$`^s\\p2ڝ[sѺ8i@{p^'J ½{e?^kˮϊWY<+WT&x=pDEEaРApqq+к…W_}mbРA>}:{=(Tuc{ݻѣGXWƌ lUUw_ė_~e˖!!!8qZjꅎD_<|aʶEG젪s1&vN020BQIvZ]ԥhR7uk 0_W3Fꂬ6d2xCn:ʼn ;Ç~]˖sj2]jUqS4a^YGŏp)z]+DF,pu2q lu<<ʾ( ǨQ]0k]1JJj^?6[[Pӱ9p3p}lquЪUdD(, ֭|}j;MV*PdUz+ ,;￯X;̞-2^VJ l;b/az隽`%qq?A^U]#>~ppVcJNDkSqݪXp*0hP swa,}""D'|eҢuN^͸q@EV30D {CUC򟹹9|Gbh֨^ wȰp#Ӳ3h˖-JlllHf^zxwzp1m*ؠDuI&aҤIj}rm,X TwNAw\˻˚Dt`jn*Ia!QF%IVk^ ΖJ+'N 惃ź*]I]ߩ5qæ2{{1^Ĉ#n?zqYq^d˪n޻EڿKJ$"c66b͛U^U.V.HCiW!,1;Qӫ{Cm\r!ǁ;hsgIxU%;فq+ʊWWfiL<%JW귰 Ua-2e &M9sk֬[o_>mۆ>|CP`̦ 8]Z^>U!HDD^L& !&|uu.]*(lB}6 ,*)BPJwX1d~;, °(u2X[-Տx(*-İa}L&򾗱z5_?"GH̕ȐDbXèpbW_N^uХ '̌h\3:={& KÇb`i>gO?y"zgDqn(d2DޏTyǧTDDO.(22o&U@_~صkcWvyy|.^Ġv[^ٲ'aMqdd! BMݩsNYH@ oJw+BˊVNjJrEۀ0:#5GƪL ,ĔSt4B C¥MK&7MUS+켱Æl*~I~HIٙg:8C󢪳4zu> n{o۸+E17V8S'~$=.j<6CCoc?F_W?^:@nD Zz'Kt3lV,JJKСrՕ48=-zȴ##\X>9k^WQHǭ%\+{E峴S mJsͼ!`Qe-=D&VJU꒓n@IsډN0 EFFFYI'u"˰i~1"m9 +/S H2*bZVMUݿLn1m."9'2ߕ+'6j. ]4KK=*7-)-Sz4 LJ7e:u>oᷠ0xݪZu1"<< mq8ce[?ᥘk(VgݭÑ(Ka%}Cu08s3P~P *E}Vq|PQ} (~4 ,@UH=U>j" ܰAS{* (2#'W[PSI`@x;eY$$bɓE᥈I){\^&bЕOկվ?؎c$-u ~̔{d" 쫼VX4iCSDeD 2iӦᬺu Q훆Q:9bΝ_ '*V~ ꂵ"$L-^,tW,s_"G}e=㼽.Z*ZѡEف/dΙ#B%$C6{ r'z;>p1"RffxԻ}E#GD & ahl+rrD%Cd$4Kդ(v`o/.*t׶mO>}WWG a~u!H@g+@1BMhiJPKL9{w3}Sdw?=(M48qnnE2 W#ܥzW~}楢yr'L&CP5t5U]PmjKo_>xuBӆM1u~0Z1%\嗖&&OT1:JLLՕA7계U5Do-<&f; vVΈHy4XP˺pA4TwaXPFx &e!€vp0} Q\Z ss^yE}Z! 9[("XtrFRL+;9_bوRY)RPXRh`Ls<! 3*"#Tu9cu11bL?ݸZfll3gܹsŒ3yfb믿PT]i_?ŸĆw߈Έ~c/.J% _m+dz*SScGyꦋVm/bٳb }B+i@حXWn*((-Ar%]*f59.?ӧ+_`ͼ hPL|)2K7V*fᗀ_R@Y :@gΐMo<[n0LIFbN"sѷcρrfްEM<#G2Zbӫs r0}t,8U9vЫTT>^u!P:ŲA0| YYMѬ#_R(֩8Gh@}@Uqܲ)ՉU>O'97-TRqw[dŇc(J$͘4[Q/u+쾱ÀcwcB1A}07v{Ԕ|heUNM>NNj[O4j8G.] d..Nu+WDLL ;333̚5 [~b59O n+uE)֕+@!Yqcv؁z ݘYtxAӀP/%Q1]\) C D.311ZMzv:L&Tn=q8[|yQ bF[n8t&(6/ؕH$0 CPJ,j=#nh7M{ég dDje!<=:~mL`H!syúWSwQG1i$=?G_~X;p6UiۚC痚*+=y-<&XD 9yѬ# $2DW~Pk"/1Q9BQXRH{iEqΩ+%$òǤR`L5q^{w7vWK$oc&<wA |w036Ö [ Hb~xyNkdLpqٻWovza؍k.1y7/8zT:"\*% Ue!bf?` ) 7ԩh{/˺u.6ymBGxymT1k9|Xo}AMBXX\E&vXC9τ <bA+)10^z رCq{i(|SU@ض=o_31g ?6mŋB2*H0(:rd6k$Gv+4ENc a&욀{2> >xkLV۰hb24Tdb[ٰ5b(5DVݔ 9"VI)ΫuXr̚5 R֭S~Hj`tƏ֭[C*pSxuOBڵ #G-6mڄiӦ!22gϞ+F@PJ9"+K\X-\(V{h. 9df&f0]Ye@L GfTdX 5)/sx'8W8d21wsVj@]Sl]& y:QuQEG),U`;w?t8Z:Z ~np."t.иjT EFkCy.yb/0fdL @'^04ЯM?\MP]lYdD"~FAqژ*ϑlV{ELM[2 {5kn&mS*bn|73P`rG} ֻڥ8}5-MLo\ OS0m4켱Y_Ø1۹s+tʛ ))Ur=,z a] D_ V~о:%/UI]&!  *n hhcwEnac08E:£G 5ųB"k׮EVVcX]˃~:VNa-ĬY`jjC!66W]yB/8ॗZqW ~"(_qq$N8bD'w Goe}ŃOOV~m¦MEcm?/` (^ ߛne1U&j6aLۓF]@XXR86WRLT7mq<2RdLL/ԙh3rĜ31g08@.۸R捰ӢI]~EkÆOyR)zk!* <q/t}A)ί^KL'ۘ^Qϔ)ꋯT j>Y"-*pjLf ֦j;bnz_0F쯴3Т(%)}ecſª2>/?8ȕYEc:T"ˎSWf(5D6}q% .ܽg+g鵺p#[g!yãG{>)N܊6?_W]3"]uAk/ tT\G&G dɍ ?^Cg}V._A666Xh˯Te7om۶bcc!Jg 2&&&o~߾}޽;5j[[[_ثk֬9s`jj l7VcXr%&LPHs kG}xݻu:TVĜD O$0&#)7 K/Q{c0n87Fݺ;~~koo{DFa5b(Pf! Zrz^ijgcs9۶~up3< LOkoj1@\WxLDuǍɌ) tEW1bЪq+4mشl[i~ |ڡ(^thi##6BRnb_>x+Faa=4}|зu 6_, 8}"ˤ@4/pB'?=-z"579ESh Pg+g4^b=3!+Ĉs7FU+gDO?U7Ti;[ܨK /ckkݻwGYYcrssq뭷;DJJ {?[l1z;v>t,XW\Ufॗ^Ν;;I o&Ok׮͛mk6Ń:<}vw{uuuehkŸޘ%k/ޟ7+ w)!&WXc^<(2oBSmm$G?|c: C .ckd gڜ[ ^u HCuS  !Wrzk?DO|aH3֙ᒷ4._1k=NR5!%mDbvosc֭#acs_}xmcJ^Q|T6V;`Go(߳U!:im̞M6p~Ɉ+~W3SSl!r Ν/}֭䷤L)(0>p_@[_YPQ{?֍]h+jF'17b.~DEx-_N&3]7zRP+wO5%B8ztg):E>SWDHB.ٳd7P(36X1c58CG9Cj~,f_|s<3,n{DtٌOtPSFFQو|y:aDytT*Yu#ihrYuljPܤSBbqwti-Ֆ//&Ѯ;:Y㚝 B9/~^D€nulHHGɘ!34gZP$#׷1gIo]]v6d2q4씷ȡhU`'ݼ>CqnM:v= xYp#55 O;I3gHY"b=bix9ya $W ЫeIWf$0 6U\BꫯD߿\5߳Q&No|LL 1cϹ'K?Q֑_|x/=z,!pt_t;:{=}?\wti:maxQ{{wXdfVܹz!lDzFmbc{Gm ";ǎ1ȩ0a`صxAJ4il Pu608vAɣGtISim oy?>lB9TjB\v ؼy3ziKq}q ڵ ꫯFAcc#qi-DA@|gyW\ARRR?.a\J Jh?D ɿ?gqVq g bqk2,&ebƄA%Qٸ8`>(nH *T"mÆn9R ;;Wbo.q':D bbyqF@tvշ֣PEp0 ~ f=0dFvHj5L8 BT5 j+¥8ԷߒhEH$^&#o!bCYC+tQ Bu4\z#kBH, *ߚ{T TWxǼyDLJ&#NŸq~pB-7\Gh,U!dKt4qb_k9+l~Z9W&3YD~v@qw'Hq GH]+M_"-DF#:̆nNa__Zl3gΜAvv6f̘ &_Dp{bРA1c֭[KVmltt4~|3f ^|Eڵ ww9v!g8>>ј8q"$ qL0ApLE"U2*Rذ&["@šrYUv==N$Z8%x Z[qҙ0 1ܷe+(c0w.jDDl.xRc^uy| ˍQ}¿r;0qD'6O9$;[{cvM6¼X;wMTb/&iy\SHӌ:%Ch)S[#ou~ kFAtPgV(z׳I*!!"N^[a͘64Aq2$ck=\{]K*$۶#]qw'uhǎpFۜq'䁓$W%cftz eDX]ƒ%=  v Юnԩp>~ۅw:8"2樌+"#q >:3g6ėZ'N1=pntWsX뱄̙3M1wc`p 0l 4B(wqE!??$%Tv҆zRHl|rJ?Wq&,*݊nDKaTCL 8|z!Hm?PO/אɓ’&'`VaZ4`,hEDHvJa jŅD ^,N-Bt` M{akc7y`e(q[q.<{ӳ:9MkzTjq3 ;gYa,`z$'`F(!aЪjERRZ3}G2y@@\,,;{)$s)PıjBA^Z:)@#QXX_C(bAJ 0j4o9s|EX{)L<Ǜ=YT $:\q=$Ԥ{!AjItQ#E/)qÅbX?~=l0}8FjA"u<&o8hk WW",?L(/'R=:nz+N윹>C)R_ WyIy9ٸy+ǨhK0RG^wg EBy!)_.0T*0t>k*1.p<`I+Cy5J̎v[ފC0of΀h%p ȿYeDEnVwYz%)B#1{l$ Y5%QŰw@ziDfDq]1߷bZ盂V'SSo7}!A#1!̭O"h)tvl; {`9*jx5Y]#\PPeB__IJDqҮv`, Yz45aoyw(Egl3~ Feݜ-O'W> 5>^z%_!`HV 2'X{h-G} IDAT;е/' #l_'}'~GZvlkMPReXT"!Qӧ" ZП;m?!n =^|EO_qIJ Q`nhQw+\{n o2'+WwveM2455Y2ԆsqY_T(a?NDoD 92R"|3iu7n-܇ppLd"F $xa~!d*+ad焨( 'HM\޻1L ݱ1#t<<8WsF|zwF.e7Efs3ApḼ2ޘ֌Y#9zɹCa܇פwN.B\^1g.)2*=".EejNXNF3dWRMRUEzU;$:"Еzthd20w8lmlM.q8J$@jY̋'BXL;{,Rqm6UC(668^} Nƭn5xW6`J{/K>~̰un֏ߓQfCӅrr80& ĹhWLCbe"fv?y,Rz1gpv/ؗa30zh̍ТՅYHvtR }Xő54B(jZ |=  #'Ov>OB1gk3꜁IOCz?E>u)f28OYW2t8ODBb9sksRmY9ZUF%ZyN+Vlii G`WWnA8]p <<˥Ma/:Z"K\L83r4{]QVF*9kS,pɿG^lsDpIn.i/c+Z5h| OΙzjV1α /aᰅFp!igSONR,wӅ'vvE$u+p(_!=t%:k ]N$-i+PxMa`. _/M!ee$o/n+u9 8MoW?7|Ǵik)}OLl3s&g(EpޘVGyomNmy䣏>Bdd$#== ֬Y &kjҮjN&~5R!m9Fe#NĊxa (#4(c82*Dr" s,âDMN"5VxY0U Na$@^00 gT턮aUc:vݼKgv]Y9ZT`8ؚHbbEM:?(1X6S=5V\Ewq(wQ|"YԌjB 5M^oaoX{3!y@"-4t4D!-|tovۍ닌d2 +k??$U&$`s<޿},X EEՀ7M܄Ygapszر$Cc|׹zc5X1|1Շ1ҟ}ms-"ދ@ps{S -4 WN,4lVfEvu6f~1N퐔|t6[vs;Wn5!%K`ɒ%dEaa!hDGGÆMY]o• xՠ  666e2p!ñnlD'-L:ڑPBƻލ6!H/ U``tpQbSPթ~;^I%?"6D.tQmJsݟ_U=srp!@FO2!ȑUE[NX9FcJ?eDB6D ι#G5ʋ2>eٝF#v#1>.xg̉0ϐ8lҝ=K@lqMRAVj5:z[sm(:e }wqݹ%zy"d|`zyKHĊDDv:+V`ѭh@?/|r8JI -Eb! HopÆt#G7`7Fpa/+ӝ.&M-X\lt]9̍)Ǎ:B {SDÜyinz`x›ps1wTjjXYYEJt8l* (4'cOŋR ^z饎RM !QUT*T*sNxy~ v"9c¦)(PtKp98qt\RQuQ6,X@R{&Lv& /o X1F4BmĕaJQuAre2F0"wL³PWhS8 8w7Y  %%d3P[êѫ01h">?238^q]1]dc g?>Kb_'#:eΝP*HLT"$DٱvU*TAV@Ch%ۣaaȭѰ=tQ {o, )4k=|r3oLYvˉTC%nH}֭/``ƕa@Z0\3ԕ@ͨxl`T CBvRRzSZIp;"-ƝEP["!䑫W: ܿPjcpdłϝf௿}L;_vA !!=`4푷`AQć'vXEsJQG8a#ԚΔejU=\Mf]SFEpn~ ]捑/B]cZ|zR MEË'?3A=&Ǵqj)QlDbnLX/$U&j?ӮnG~m> 55gnk!&*]dVgb娕f uy)&kڜʺ̋0bJt :Tw]ѫq0 nl3uV`$^Zĺ:m#,v1.mE,3ƢxTDE)day9iFFaUʨTT>>ܚrY0 k۠psĭn܃bvlZxƬkւTBx:yɋ3rIm-i>@USF)!~~φph" FYji]^p娕#zU l䲎=qi§9u`6u aފҙZc)dI"B[e9ۚ%E酮tQz ʘEmK-Z[,'f[ G.ޏ͓6mEjk0|lW/^=,ŮwTmȫ3b~@QQz:dQ;p&~Ĭq,`V\k5m2} .\¡zK=0xg:jgƲq|2ID~#Gp"#8Ⳗ4x\"_օ]O=_DhB0&Q /@WEjÅ⡉uK{X/儱˸qќX2bIⷜ!46|:SS82g+>}:? 8y$<=1]9>=ھOf&0B6 7>_V1: lYRќmmg{g1 mEpL~>q5EHPz5o<.jbE1{9[J;bP1~BN0DqbB6}&S~4]Z@޽o6~7888wEFFV\lsGmj|1'B5De`#/g bnMpWDow+\ᴸ^8@xf,pmUrHwϚe8!43֗" E\ч}X6٤֌Y?sQ98"GEqym'ă:ED{ |b~ڵK[ J%q8L݅a a8~uu8qKG,D"cuF~AAC(kJqqƝ77nnW pm $F91F[Ѡl@[Rj"fg>7c.,$eŗ(jشQDbVS[N$q@x{{\.GSS%A橠GԶ4@ԌڨB=cZ4|gVC!L(O؀pt;:}Y:sɱ;vEq@SOړRΆCvM6}X 椌FTm(p!4Q;B(VhVMM"C(3f ;m6lڴ wu̙#uNPfeq"Ф蟏|"2]k 1zNʞ=/u[[ &0gQ&ߟ"\Vi]YWRGh0'e[$V$"_??ѕvɔe&+n ľ}xgaoo .;s='uEVy2\[?ؕe#oaڠi}ZA I_^g7][ 8|mmS7!ˁ8 [}ڃ8-BXRWm&Z[Q(i7b uuV; #{[{_%~9sDOjB !T(P\mтU@tv駟&3 l$6t!B >1vLM%y.X?~=֏_`=0u\*vkx<9I/L8C#3gp 490(..4k'(;([i4D[HAp4c8NZhS>MVuB}>(67_*6 - M)ėclX8:{a PP Ѻ''',N͟pRL!;..'4|P*m"f|\A}pAjU*""9֯^}աSPO'Oiv)UM+r`Lq@!_2q)<}vdsNt:* /_28u xy TpNA,tՂRvV6>T*8y*ݮ_ M[6wݥڔSk|JJO'Cun]l+G$8L""BƕaوeMUFŃ:M",Jq"emȚdF+s5E䐞>&2W1 g.·uyF.a| 3b' 7Xv.F)U)0~q H6.Hմp>M:'XϦ%+hM'Z},Zxy?!Ta `;>ҁI"^T7WclX'5m枳kTi*l6BB/6HJ% IDATj'h>B`^nkHUc5w(2jjF a`kc'ׅ53>Fa^=3X VƈYCg:Ә6_eC(سgN8*ndY߅a/~IlKzS\* M^xjU*Fs?1 +!̮ܯ碶/ByC9f4|b?hwCI} f&k&_)S6Bk!T!%$ Nc^ubƍ8s nHN&B%S8^p٤JS1oDeBm,f"NF[YI@35n=ǭDz_[%Z8|,^\X :.yVZ% 235"Cc]Oͨq ^puѣ8rbbb6jo`\,DtQ]`lI͍;U;eќrS 8՗lYxpspéS?^|ݘBzR0@^P/Rħ  ~!T*, *JkVƞ^TeTLB6?J9 'v֗l!b65F&vVxU -qs?ABxWϣIlSVY!DRV!4d"&'4ڬ,`K.bf(EϨ֏?6nDߕbE1\hSz3(/;<5E\d24`ؖ$k v6'ۙ JPƘ #G0"pJ ޽{޾ "Y&6!x';d|`j3hPURvсZ7/Ǹq\'3V^J|Y<6N(0j5pRL<ma3M}/f v 97#E)BBX&f4+qˍTɓk8TrMB&dVgb|qLn.`g>7,sYE" !E|JK`+ #vˉKYcuA@ݧps3ꚱEq^Ia@x8i4~Zˁ&|F5vlHγ}pػ5)VSbTUb[bGt功3~˅ Eph r|gرcjjjTR-[d9BKbcq6l$NC6B |1z1%S矁nL,M4V16] 1]ʾ0/Ǥ7J0%}f(P,U?h b9ͤnd0kjkŸ9bH3ݨʨxPP 0|px7!Ž;Doq0i.%QU8]p7.Fz Ht`M74a';0\Sȗ#+\ 1zF33/\֌>Cy@k)|~mauSICLm̙37*ѡ@l߾{/ټz…8{=NnYl+s06`ئNY0p [ y7uu޽@I hCF;a3Ӄ,2 cFB&s5KA*{![GIa\0ap;s-Tt8<FEE9*.ӧskOOLmM7X;fmB8gֆE$J@^^ɗ#ܛF5јd)'4e?l!d5ywk 6\9 ) uuuuʂ*J'@b"0eؖ]ݎCzjM4aBy5a/DDdN  vBB\1kTȚd`S(>_t.7RlS,\iOƆ/{++(d-`ԕvbB!pe2qNvNtPP ݋ 0͘9s& wwwbgt R^W_X8l!ֈ l]Rǯɕɬwٌ{$ jH{P3jHpRu jQV~b)TāfYӧI@=U=``e0TeTR4P@$٦F3 Lj%uءߐ"EC( h*SŔi; Ltήp4rt!LŭC  DD)0&BHS{c.(\XFt}mۄњ.hH$H$̙3v]J|"VATI` NNM  P*\~;g^*6h8RAN;KFP\-CXP@??ABYlڵkX`ܺTW;88 ,,` >쎧2Cj%PU̾9ZM 6Lu m~UGyHMMEMM jjj:lݺUlMz`W 'Ƥ kQ.H_.u$$fcy E I ]Ƙ9ArƬAc?׮y>2Ϊ* paFرc8~8"#;E`"Zf0 Q!4innvR#SAE:,<<AHScR, k!m0k+mߕnhP j5{=nooZmmmm:T*I~>( m좴:{әA*-r}:ݝݱٜnW45b !j[j"rRVTU齆ubضm:+--c=9s=++f/&q[p\P@@] ]d2@nc\+CiĹUqdjAW_>B*M(QeE^V_``PcBE +#F8@&~uuWBABطo!C`Ȑ!G]]}ollRsN^x$%RfPlUF\-' vhS[mpd^Иa4P\**h0 wSFnh mƌ1R0!#TpΝP*R׷SP  ?~H娩֔Tk 1XXl+tSP@BQfU=y lLBy'14ivܸ_aŲBCQ 8L=!k6 SChkk [[[gJuD"`޼y7oئ1*II3kOOi]Pa5 m`pΜ'ԒAv0`Xʴ 6QQf(ZhW VD&oG[!,S 8EN"ˀ ȉ'x⎔ŋbe( &l]P*F\ {+.&&iUBFmh,psbUEC\`#:E|vY} \ *X--WOSD[n;mۆm۶ .| yKJ 9; wMc'iVwR1(,~И ۔QZ? >D$r!G<2Rx9yASD ݻoc˖-mݺ111ؽ{7hQd4;ֆD"ѩp/ݥ HqcwM*BՌ5t}fuu@E4 Q~4]TlשCH$.jserBўQC!\[nχBAR0vV觿- ժ_2'oܒ $Ĩ8F{UeƩf2ɱevy@.2l07B61%;8l6LC!b4b Ē%K?zXl" -iioUA PVDk&F^V){oSP 7rbR"b@Ms |}Ź8Ehʨ@DEEW_ӧ1m4KpyK$~L*hWgBŒ)ue0 $:T04Hm66 ʣƞPQS5 ƹMukQ!T*oE[!nB2*!X,|ؔސOØ@B]J`oohQPZUF?X_o8erCF)ѧFVU*a\T<@i!*BBR\ $)/5]PhqPd2ϏLF"nysL hia IBs6J 77MPTmE ;[ @"TeAB( aC },Y6!!ܥdEJ#liiGrm9A4h#^cW-r;ƖC0>36h`f%VB#`F’Z-)Zצ*ABصk>c|駰"-L47C.#M\S(; |$ kҹ!PCHFpξpw(+:KZ#P1*}RFI5cm"333f'rY6UU@wn^n [+R, Kgr HĀu8)qBH$xgQSS\t RئY$b dZ@SPIUIT5s6n](^cW-rx9Z L},TFi !; e+4*ѠlS ֪0 9XY)B2*4B(0 G&O Hhnk 0 $ (crdW0!dӎO4Bh -UrAEC]agCse@#!CXTm(3@466bϞ=8qV='eI0ol$/Ȯq+o(Gk{+9kl$NwcENP ѕH$ӷQRWB7T8R@_"M_KVJBZ:kZj0zh A_9C(7nę3gpw#((ߵ*I_WM#ʟFz5V"? 2N:z(v?J!vV)eepwʭPPNBEaBRFL$!/C(G<Ω'$ ,+(mVbKU{[˪VT@EҲ)ui۪K_jWzZm[E+:L2Y&9IL23s'95/ '9}^y~颇"b2Zu=wݮ/\eՈ#= \kFV~rOs3|GQ[nŰa4{amqT  f2L>1}~ۇk? $zpYfkJFsG`@h7 -rw`ҥâ"n/܃0ݶm8yJ!dh^}n;)@ di~6 B0+m*LzI>,}݇/Fqt͂F&}e%ԂI&Y4"1G*mQ;ℂl JSZpOIJ>Tf7~y*!m!ɥ3fN."Bn;!B\pP4d0iۧ4l<.Z;8M-'8ZCU%-l68>:wGӼB Nfs ( >'+a!{- ǀ"˖-| IDAT=P?c#z޶Ǎ8\%Y2jCGf$@25Va@H;CW–mB2.3kI:gaɢ!qf&CSEܞͅTUE]F Vw$0C8x}k4;b@hD"{_1zh 6,ǎrՊ˰a_vv0q22 (@yyOK8 inw] /m'9CdzJUU\ϝF3'BXVE]ߏn_Ӊ˗Td/;PW eչ jqzkp7$U- a$TLݓV !Bƣ>ŋ _|1{1,]IEth_'ԋjkf2jfǨk 6aa.O}\?Ƅ o0CGCCr[N˨8 -ԄSW^yEФ#Ch.vtǻ1vXC&5^ƂߵOF[[aa8\!-kWv2 4ʮ]ϑ5o٩!k q1@myUaeh @4joLĢZ#Dcc#裏믿xگS_%=p:>(/+G{9%gB _h}g=R sjwD:P[Qx@(d2Ȁ0>JFUUe0}5J$ !I3ݝgv"KFn7֬Y#zEA}z׵6BÑ3S_ qҨ r"u2L2cP,`,=h)3gj ||J0KF!À"\pG0'A@+=b3zb]:9L8NCf&CA2Ƣ41r7!W\^.= $R_]FK5C0d&sڪV=,2 aA,!ɓq㭷Œ3Ү_p/{@/QݛY62DAẀS \V8vıݯrQ@|a@h555]p8l"s@*ʤذ{nMJ6)NX![N:;q%4dTB;c@hԆ2V+.K@mNgw'8F)Zւp+69_'%xstչ '<]pElfY[nvQ[Nd>N5QfkT:+>ɣNFSS2c@:2 ̟Gp~\d7 BdD♭qL2(//ǔ)ScTdsB;5qQf>~1'wgŦ8a@ؿ]Fs4Eh 63TrweSQd6.C KFeT$Yt)-Z|;x;z,]T!C@KS W.zɵtVnŒ1q8KF8P^]m2KRM5$RvDYCQa1^UU!5y裏/I' o8:c`#,*`e=6*CvAnJߗϱ.U ZC )˃aDl*12&C("~Ty| XZDQ̜93f@<0"D"7eKc&Èx.զ2*PH}]sja'ㆎ~$Rvtw'0 S> 6W"]v~^?#KH>a'ɤ !4)uEЎf514桫 (sgmAI:8 0#;"C@rQbɨnᆞ;)Ey*>,O@דe=W>EUĜ0 iU0W==;D`DZ$2_3w ?;{Xi7unk+PY Vԋ $H&NպmX Mn:ǷȲe˰|r2XdsulWhIc*zVϿUxkO`e@.c>?fǷ'[0tvf(ZqϼEqw܁+V[eCkKD(B,dYl]#E褞Gڌ^!dقx;DB~f 5$Z43m%b?s'" |%K aϞ*+_+f-ݍx֭CKK d7o.n7ܹ% 8 DS #+G唿lmJ] 6h541{lkT ܔSB#ldvE. . ROe-m1 W\_/³hjUѣȮ1Ȇ2"J]VTe̚ !-C:v5^TyLh0J%CMkMIPƦ2mmG` - *N?tC]F3OFåz{.33 yr5fkI# I!c^JfcFg3;Qa1 L5܊Ѧo;aU@u |?0m'M8nq D8==8jQ_NrjmzP+FTa# ̜2 B[NȺ9،|$=ˋ gz]]FEq`& $nE"#4YrmEI HDi^"2zKF3;Qaƣ%b;f>/@b@h3GlܸQki8 dC3®.qajVH. pefl 6*j7-IIcxZ斌ϬP.Iy1 Ț5k0gTTT`͈Fߏ;Sē 1f5͡欍Ah*%dtǁ,wӰM1؈1cDY.ZMm*&Gƀ0yծZdʕXz5}QSj*N?tl޼YI]<,bj#EESɲ 5uu<aR|S??M& oWJGs $̭d:*٘]k)1 91lG -ga֬Y.:t(:;;H0 ɻ́ &zBاn_(gTY~Vic0Y7<һ d#ۤX2j1KFŀ"GƎ;z]qFL8Q*b̦)v 4Z5Qay_Z53qc̿^JFӟS4C%Yd6ce>wK! ֵB0 ȕW^Eރ?7x#ϟ/zxK>h aݽ3E%Jΰ#BR)J%2uRX aWKx@%[L&1{la̚5 ^7x#,X zxBey\fFC@\PsB ZB-2D}bwm)eN S<tt߀= `GBk1 stMرc N8Tp;Wگd4SGW@hVP`}g=W n ⛂M jaCV)}vR) ݔ>r ZN= )苇k(ȁB<4\-jfFO/ j¬qjQ~R]F>p) eN DRUrtwkSLÀd0y$j%<6 33 $ZY˺&{'0n8L>W{{ttG)z.(tnv[(DԔפ522 o7N˜>ۚ܅ 5̿v̘ikR\-]#Gt)j bD[s7L|sl]F~z M6|<ӨǏc\z&/LPg'0erQMGw=ս֛TF?dB~w\rue c ~>"͐(U23k~d2&{衇؈gx1vX?kƌA2$Ly0`&r!yG =J̝la4EW*@m_dk~0U{a @G eǀ^_|1۶mÉ'ǏG0J\g'PS#zuSLzLf493ȀpV +fP%;gHR0 <0˸LLBZŜN'TUE"=RKFe -33":Em8X.jHj+!$y ٘Y©D菳+*_(3b@hh4~7p1?ƃ>C9B\]Fa36O f)h*;ف~ƒQncBfŦ2&3`ر7o~iǰDd S[2m5W`Ƞ݅mқY.Z\%\?8x!$Ɖ9>! LItźژB1 4իqQGaĉX~=֯_׿Z<2Eˁ О=;I.4~ʀ0UcXA ;BIg1CXfBn{g0dΝk_.:(/~dt&zFT@7GVK!fFX{5̀I5NfIJZ2VwlD ~Psծ.q3B= 5@gNZPlq  M%ZC<|AhpHHJp;jkP4TYP`xѣ6!d[?=n2N -}* as|%;d֒Ee)jHZNʴ~"0 $aTU Efw;9`ۦ2c @<tww2 cS F[w%BU{]F *ʐTU) ZC*ԝ8TtrB^Tz I(3j 5!hJƅj5ߟl(C x+} a4MdTsRǀÃA1D<?m@,uvwe=]% bo'T2lƨ*#sS7!n;al2D"s\02PkS0k53b@HBvD:PQV wY2 W/b a0D$a6kDdX eHJX.3;C qB1 $dY.I$ƂYKFK% l 6aD.kDtuG- ۪ȶi4 ^ޔ>Q;?"CB1 $dA+'q;ݨ(KϔR@ld[ :3fd':͝% D*DRUfE`@H¨Y$FU՞-'ROu4tUU6)^HQuHEIdEj]U# ,BU՚ ƀ5 ΚC2Yl0BU@cQA!br@EŜÀ˨ iL}Y. ;H"*T/ӟK!$R s ")ùY40 I(BKF5[N* !&[AqbJ1 ԙP'@ eD`@HB%)JFnP경#eG]QfIN~inJ?),K@([Qf`@HBRF!Z !,Hq[k !ɪT3jnS@P~ rB E.HYL2]F%,T"~X!L$c"Bf~|/PMNdjN; %6),MQU.@|[ld2g5 Jd4LbŊyWTYa(-,zݾP!blN2.gBa0bx~T+Q4ե!Tu2*sb€˨yKzv[M=Xd,i*j/. 9%ӟK3d?%eu3?EX]՟6! %c@3{@U:kFɨ afY2j`,(uTX.wRu: TF yd;# 1(Dl 0OƳnAY>OɒQ(Iљ IDATDUI(ɶ _jB !B@y xD$;Qe0Z[Q+ i1FEItn_醷…Dhr#1%`P|@([QBQxDP.jv"rZ em6f A~Ъ5V˸B '!üId UUp88N\.4 $ڗMDJƓxs盨rUI\B/֮N}eqo|LcqE |^q@j\I$H$H&D w bV?%8T>E C8BDDDD%(ub@X$BNgOL2f a6"L*731 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""b@HDDDDDdS l!M1 $""""")DDDDDD6ŀȦB""""""*=*L2D(;N.H$L&^*$|>NĀȅB! 2D0 P]]-z%agLZ[[QYYw!$EQ vE$ +_(U_p8#G2;hEN/r<ӀB 0@%QdDDDDDD6ŀȦX2JDDDDECUUa UVV4T DDDDT40D  DöX2JDDDDDdS9/[RN'-[ /4P|P>~4773TBFzZǡ*z4x }3P(S2Rs\0&""""")DDDDDD6ŀȦpW3\~p:կ~v/X Tz!L08SuDTƌ'ѣ ""2@EE^י-v>,/^+V?'9s栭ʹ1 $")/zDDD8s0zhy}ƍ1k,TVVbܸqXhpN/RSO=ؽ{7N'{9uY_5k0ec„ XjUL0wu 2ƍã>x~?ܹsqqaը?7d=D$^*LU\.N<؟cF/瞋 /[nų>z ^{mw뭷1g|袋[nŊ+dɒ@Rj*|K_G}G]]]P|fϞs9眃wy'1uPg"!vDDTzTPcZ~cڴiXlYn\zX`&NSO=oSO!<\qaԨQq9näI0w\\{o㪫ĉq7cĈXn]hkkC"QԔxZ HHD[8c쏑KܹÇua>g}6-[سg/ )8kE[ADDd'~;}ٴԩS~z?ǬY*>h\tE=s}a޼y5kƌؼysmgM>=.]+Wʕ+qe?@[[.]fL6 F9炬PMPPXW7Q1ظXغhmjjDd PUU@[g7q$V#qÒQA֯_W_}B$T$@Ʋ""""2KFMv=  ;q{xhk׮ʼn'(rDD"@EB""""!4ٳ>)S^/lذmmm9s&VXQ('Ib -9,YqI'W_}~6l~_w)~|><O=;I$nDDDvV[[kCÒQy\wu=3f P(s-7$! Dё Bk0 4G_8q"c֬Y=ݻ7rDEI/-/Qѣ!"""?ꨣ, \C !KF?~<&LȮb1-d@HDDvptWU/bw߰a;<qp:x饗DuE?͛{~{=^s y#FQ}8TTT{ui 0m4䱐1ZON9s&J{~'zxDb̀sѣqw{7nĬYPYYqaѢE=g⩧޽N=:,TVV/ `͚52e 1aZ*v&L W\q qGsַp۔{xEHݸ\.yx_sŅ^[g[ok6[qua3g>\tE[bŊXdIO [j/ᣏ>W_磮nPŦ2 iVUX|9&O,hTDd̢*IcK$˜eC6m-[5vwK/ł 'No~uYXz5<π\ŋsm4i>׿ܹs{~6*7ߌ֭ka@h^_bرxgHd98<+8 D1]lp=`ٸ{]e|ӟs^Y__c=v3cƌo߾=-@O?oj9ԩS~ghiiRq`@hu֥tbȑ4i2=kA!"R,C69;o;30gr-Ӯ OEZoWp8z]ݫ1s?Ád29"y1șg)zD҉p!$""9òyf뮻0mڴ^SN9۶ms{#GuuuiMgK?x[imܸs ʘwa|'&H>AC2 $""2e .^]ofX`lق;v_YSg}6|A|Gشiϟkman/ڵkrJ'C=n e|G;wb˖-سgOAKb@h. s?P(wmۆn G}4>GH$a`oG2LM:ׯG]]f͚SN9˗/G;wƎYfK/M7݄ʴΖ>}:{9<쳘:u*/_+W.=ܴiO3fp`8Slٲ?d=MBL( ~a_=cذaJ$Df0:x""""2B̜93g= "D@ywYkHn΀z Hfb@HD0CHDDD$B"&3Cݰ(ςl,5Cv('>$?v`@hd2_Xz5cĉXd Ə+B,!d@HDD5j!=Zdʕx'_ }5SLc=&pdDp !XZ䩧#<ٳg㪫OƧ~Z+}VN'\.WAd ̀WYY`0|JlfţߟNjxDdn׹l_d,ٷo&Md2inٲeX|yAd !?#yφ h4y^$|qL-_+V= c@hN86lq.0}o? vgde0ɉB""2ŴO6Q;dɒ%:EQ KB,]?яo>$Ig}z  }۝3 $ɸ!Ā7eM`">^~eҥK}v7!zxDBtw('&|3 IVZ34Q`P,%""b!x**F1$f-u{gM6 x2*vLDDT`HosfIV -r5`.߷o#"/M_C@"!vLDDTibr^/BBl۶ ӦMuӱm|{Y!5CDb  82CH4` -zFq)'SjeAKFQ#MdÀ"7q뭷\ىn]FɶRBe0z07 ddԔE^̚5 ƍو>¨Q?QĈ5%"Ȝ!Ի2 $0 Gßglٲ/ʓm)V&^DDTfa/z$ƀB>?ODHxz@ȒQ""*TP鮔2Cw!gx"uB aݺuhiiA2LnҥFE$KFHJBAJ !uG@ -裏b1bF s`@H!)"iB1 ʕ+_7|I#s !gMJR ̀0BE:::pT!$""#J!ԗI0 LǀP< 믋,V(Hk!$"B1T{ꓠ\/x,ȤId:uj&.\(hdֻN |PHHlN IDD1bBK DF c@hGyUUUX~=֯_vU@Q X2JDDF&*e0Ўy,ըEEA.,!EUUCnڤUC<}`J}!Y!e`$[PKwiqʨLVBkflM0 ޽{K/UV Ed(7ACIh*O|{FwL$!'w/0aP_À"k׮y睇'O?Ŕ)Sk.SN9E,iLX2JDDFQ '** YCXVflPȒt(P^.zDm',r뭷orY{gi CO$č2DDdX" oא3`P)SR˚vw1 1w\@YY"p{<:8s| p raQ kHS B/&ӎr B|u~8⋞D r 0ce(l׼zaF5 h0˃XIƖKX=v€"z*6n,^/1o'wQE 5 t9=1b]GR6,%Ym sů[PUlk&z(4HD nǐ5zШ2lL[֖T,|hl`>qRN2j'aՆ~(;Gm)O/{@g~ &x4M?rBU(TW />m'q|S=oLg"-wͱPQF""vʺ/b߾}F$B}?Z_Ѳdd€^#X)IdԈ Y@2yHGwTF,5K/^{ Cw"ڵk1~x##  aD1۟rR Y2J2MKGDgw`C8𭆷rpZ?=h3" L&fj Lu(V_@(90 4\p8я~vq}Zy0΀lxܬda kMH _b$m;12L`lP/B}bGb1ʀɒ[)N0>F!xDb^2q Wb!$ %K/lSL=2ĘFjY/WPGʃEEA hq,5_ !ʐL!} a)gdJMe:9zOR>QNT" .ă>|]w!{ɨԾ!4_ !ʐL!4ȷ6gtS3zW큔#~3E֬Y}k.W^xA( !gM+CȃɂB|iE:}W<GlMez^\kgaX@r@g^Ec@hvTgtȐ!hkk0"1}eJ<@ IDAT!4_5,-~oEqBm |QK Kœфo\}E hxH* .'(!BL4 {]ݐ鋅ʺ8t;,ruAuwه(+E- soUs}3J/^JBA ]AIU՞m"|n Z2:HI*Z>'(Rʰ˨X -r 7g?-[cXt)n\gԀP<GB hijj:b/~0r.wg_De%}^QÉ2gY[O8cz ֖j@H<L$'-[yECNxE͛?8_:?ƕW^)zx)[]%>܏/zHItE|2_xPB@\߾1Ԕ \~9n!A5IZ`Q"=}ewi)`c(Z:uS xǕ/y"3`@hc޽hnnF Ν;1w\òeT %0ChlBYKr77n= C<rΑjP?u=xb/x55 |Ckw+jݫ^*G(磯MZc !w@OQ^7#9N 0rHTUUE!!| ` Ue~FY4UT'35} si !_'QMϔTgO+Tւ|"'kkz2z\+ڗ?WjɨáM)I%\DߗYSvO*4a8ܹшS eƍڟ[ Q@s3LjI^<\uա,`Oh\c)d#1+53H!Q(xyQU-H1J pWz"uy{*v|E_/ _"vtw0aO1KF].`ѣ7|^Q|?nIШbXրpw(oCe-,&,6|NG1c? Gb<a`PHm>cxrG<1[z5_ aMe }zZSUUԄV7zJ2_Y3`cz}m Q`LH;Tm aїw~B\piv:9r$:,wq_O:[)5Cy>p3" 4{mL!j Ģ,LXg}B5gJRvBT@:y`XL DVWV2o osKqyP^V^PH2d`,NYϿm*FyY9<.=+WO?}V^$@*=^CxdвzKjgn 9EY<z(+k_v&X;X xBʅrٰ>/~+sv_=9*7Vxb/OdyOpQ -"Š2m=Tf._4 ֊R#(|G[<}#[eɨb1-8Gz>7@Me(<.OOhjFV;gI$7>iKP[^;J|%@A2BD@`?FG vcnn1cuOs!ìYw~;2zP/`&+fl2x2ˋHgVg1ګu4,}2'>jO53p}%挳zPU7J]K71b1N[-& ɾǹrn^4e ajmb&ѡ-9vu|^!Cϟ].(phf@W/ˡe!L&ʁ%&Zn]wڅ[n_~9N;4;#'|wu-[@pkR{ w\kc? O$ [ّl͈uTTh)EZw|`'z\-KDz: }K[I*ZD8m'nU;0o(ޓ̦o |'ҲŴ־ |$ `!+ G,JF6 =(qB_mfަº:ϝ;F=T&&*ƣi(O{,zRBwc'Nu2Dq˚)2UcyS`@h3<~;VZ/;SN#<Gݗ1!H >{VC%(wig-um(}&SH>e(c2YH_Hkrͥ' ̳F?0p>W,A'p9\=kCcx޷ihT{[Z#}X"CEW2jumu{ $5g A=jd ζn7![S}_ezj YSc6ak+jGvǙ_Sh럺!pI9iIh7}{@a"9KIw6p$(IkA-%yw0s^Ϝ9 }c 1 !>:f0X ^?5+O{25-(7PvGY'Fd(reQJ}`!3$Wy@e'*b۶2 P z" 4M @I+ekg<[F8|x~\ -͵Xȗn%z2Bn aj@@Myz$oP0Op ʑޛGIRiOdDdZKWWд"*Ȣoo`\Qot (02d\qaFq( "(@wM/kmqcˌ̪>]Us}dI~_B>7El|,ǂ2 \Xv-߯J]vZ-a >'KŅAqMbd*zqJ|$MRcAP%CUބA=BҵqiMd]T, E7'w:w)'-{Te8haZWDi?U˒RBPWca#An$蚹ҘP{A%쇟IJs3b_3!4BAU:b1a0 QCh5I nsr๜,Om*ӳ|d ^x0 _34lkpbY2Hooxߍ[o'pƍl˲ i윍TG]r(mbo6SdJ&=6!夁$C< }~_ع8 N~w5!h8sq{~O^,ha4g9Diov:ܲBYtw@89dGnB6Tb|)v "bn/A?aSB !K2~m}իo c4\Q5~H9 E_0YY'^ tBe&T X5ZZ x!{_ic]cZaHFrԎR]ôeHczI2z }eY$i SZy1膓!/,1}75] i R,r 1>`|+_ 7܀n]tPqaF$Xʠ&ERFAIo [ӺXEٛ !}}!bVMA{{Rm.9d(P yh\4$bf `|Wqmha4ӷafƞ͛NWo~3g jU,̀0A{pŃo,E aC8ycq0xh:& cPQe@mdžw2n=~i$qqn3@HΥ @l{! b6Sr! BUXI.½wB9_L7p/SCnR,X~,Ey׿C\}Ձbq@1B~*uuܶ4 g;1)I/HLe!׈dqwŇopmXgxS+BQ7އ4.$O5p(ɥIy֭gb,$\ೖcẢ-> #U,aQSeᰘ/~R0m@5U*B7躌c[>$I WC( fmFT_#Y"1ׂZƇ|v6<9+E=\FU>c8"0䔺, r "/~ }CHe 83}dRn0̠5H?Z:5#k"'Ň9nSSl0 XyjHL¹95u;;p[#'vp&le]ql CH0 $8J{IF_]?pOs< m;):O5R03.ZF˫04N2!$`:[O~m5\L70j*E0}k_:. C(W۵(JEt 1i$a0s YjlM՘>emmxp}, /|f;B ^}5W ?4@j0rXXf).=&177B?) abmi cDf hlQ&IF wێ ZC>!~4q\CK0UmzI"5KFy]kD  2:F $p53Qr sBAPύ TT4{ou n]|Sob2F|XQ\\5_9::[dcRIFI9dtDǻDR#2@H׶o,9& m߭ē܆d j}WEh`=ٍX8>>(- aIFi"(w!t|pج$3MEbi\y@^p\m`8 & / < Tvmؕ!lM*Hlz߽Q=YNP.^v\r!iZa 6"[<}48.u3 QW^qQ%qd!RY* 3{ ^'M+McA$?4v%4iO!=d1}%Mh(,BY.gِhRw 6 @)'%ns4YR>sĄ;w=ӄ阨Gd4K6t08H†${4W(nao JFXׁf aRۉ,v@]7{LARNػ[lvlY%wdiLO}ZZmŽ j!"l\C\CH'~z}сpꩧjif:8d4-ixW%SrIT rAX2JPtL'! ؟qfP,nU߁Y4QGYkZg<t-ff}5dpj[/XC={s&td)O!k?2(1׻o!⢻.~n-o~1}1fY|_J~scG8nÐRk) 2T@XCնwsn^VS{Y ̐ LF|$|nhN\|\)¥e@Hqg/F^ tKj^m`47W\2*d  IDATrիC(#ѯч㰉K.]o=` :4mHm{`BگYu$&MEk%X*}HFEbN}xsVm$FXo<iXC .\(Y۰x A >):'I"6G}pl$w?maf>sgp__KC14BTѾ}Cd1eT^54e@nc{Ƚ'B`847md* KF !hX~,6Cd'/N2l*t,]p'߰ KFrq0tsÑJ9 |9Y2j%r&OZ%e)|RBb{9!C8^_2&^&@2^lAn8hhy IkE(#p\kF2N2CX Sz0/d pLxiVZZB<7} 9HncLh!\w^wRɘ8,5?6(bue573a7{ژeuWi ܝ I>YݪR阁zAj=S  W*ό.OxvYoҘC(%@n: Jts1 @H om*Ӌ!,Dij=aX杕!t\'`s'7¾w>w'.¥e@r ?p~8Sq]w m\|1tjBƼ6q5bdgGY* cUs^j <$GbA.EEdC[:VWdcl'YAf#ՙ!Ii4.O`~p O@a `I6%2WՃ !n8-e=0L&CDicVY9UD?9O6,qwj=qyftgSk]>}[n*Ízuss~3~Uz2ѾSI:Ӳ|A^A0Kl5]F!U _B] bkzy$/z}TCim'2Ch6B#&^. l/C(d\\};pN_`d_ ڑKllHcac.R?FJ%wy8P,7?>˚0?4'¶F]cR|!Jw@2څ!,MV@a\BLI$Myd&ymGk/u߄'~q. UKʼ&Kud6m*v]cTŒׄ$ 33cc1  ]n ْ?/ϩ>n{0YѪs:GiKɾZt f ox [+7^ +R=6aY?1dƯ0m@gBZ @HrcIe3Ik*H dY疍{@\9sAГ\F !m<}:z@8C(Bm3}׶p[nsl&/:Sc "€P\G4 )."\|Ÿ<@xuk_/ ;lNet?`f<*paLTF4, j!IհL !mFU.((_chmż\y'C{+_ u{%rZ edq! xY1}I :vX ځ݂gJFÀ0C2J&;wf?´I9m$ G ',eadY$Sb[;Mx'ghR,CY^skpƱgxt_YM>QeFA$44 2YΕ؞E2[3KvB(x?Cc% :QRQ3tB)'ADhG^BӀ[@C]8@lܹU=9G7Zh{hCPD| BFe4Mn ˀpb8S"?Se˖|%Ӿd4)]hmrNN.gMl<. XXQ\nc;& 9; *'ē!l| ل$ɷdT JFy@M:L s0K IF/{2x?c\F+Og~׮lD>; n1Pe~ 03ȹ3jA:{ukY(-26xٟ%okp/~aVpMÒ,sl0gO!dtdd%1 !YLeCw]8p t^]i5Fg60XF5 ha2|b{};VY!ϟ9B==xd7 d`.e,Ek;7 ֮];୭yR "` !Mؕ|%11 #ʈHmḆѺ߄ !HoYFV0$'uUm=ZNmUe Y="(2ɅYfic:;={\p5.CY$Fd6x¶KM琗E/Cp=x(SB8ŹC u]dno-B./xS4ON6ۺ7Qd60lwvxs ]&{V*xz,È 2"\;s\s5|c?|=D!%&hD;35 vlp=h70\#S e*Cqf07HZa,  o 8S}@$H''uLcAQs|b?R|2~C$MJF)m'jyF%kp 쟢KFtLZQ?@'V< VϹf29|ˎ~Y$i Ğ,ax)wT&׾NAc2:dpaG2Jmz 'u3KFE X4曁kYXPQ)BlۉL2!Pw "* |NӀ\@KP҇ @h;v|ԙ+g3ٰj(> \cYZb.Rs9O~uc>'u]>{(Ams}HFKH~߈2xgg>pH o8ڴme-u, E8#BҼNCd<ɒl xqc x0cLrmB۵=iU^tu!lȼb7!!d"+ ' x*db̃d67HFeS!/cN y{~z,a0|_ym>p8}msTCw{/pOġPi>} UKPM |:RΝMenV{{ qnÛ7y:f4RA2*uEBl*bC( #Iwݾ 5Vƕycˎi4Mh Bb=`]eT w]gX_ 1/w}7fff033|8zn,2 VrNпd(=aȅOrb>>GѦm{+Kqz1#dĨj/赟>6dpwq|<} !/ZQV˒03G~dT ݓOz>."lu}5Ǡ鬀0|6|֭o|<̱;1gp8ѷd&'BIBˎd cc^} ^2JGi'eQ֗dR1^d4 ~z;aS1kMNkMKN-h+H2JY3'(ЛxtPtlޠ_Yz6(&KssAR6 MeB bő}VE>@)Zx}\O2#(4+?ohxs>xxP}37-h6WNi9e@H>ÃaJA+oF`X̸~TB.@AQA wU3d@([q\@q{Xc 3n#I2'BD$H6 T&e@dMh1 5NslMl%BYN cc?ZzhTN2zP ̘sM6x@H߭:&˓+q{#;0Z]0ѝ!LG!^Oc<9JoOѦv/ $aM؞Ǽ߉-=96ȓ p\:)~HvCL [+%n.fR"_3 km;w]W^}Q@,,CHzRy6qѬPI(%4(C՛&eK5t _b 72F[ !kӞ@%_iZ ƙl\q?VM|m FOq36 USj(e%04(%[`&=rͶ 2dtEqa0|I$DR<+H jf f :30 e it)@Bp@  q2p8x8sVdtBMcѸ&tпaQ.T#G# 4O|x _O & OF!tL%Le$|J5U;>J&(x[oWgCe!Hib5.c8J5U(OBP2!+-KO'Q(\?w~5?z/vPhmX5t-€дMܻ^<4!]CZ*VWƺjp4&Jի -=L̕W2?>y aPňnŔnmjeoUg}

MozPc];!} Y5fYd @1U"dlmޟM_ĜETb0Cly߀d[^C Jk-c/4 7 $ sP竸}l4Iw}~u& kqm'Tr 3DR41>N>OGÓ K2:>k)MO#yH(v]7s x}& CH{!$<2EI8> 8Hkfnm', ش).)iJK3SIWYh9l{,C399.JFhMWg0ҽEl) Dp# q17n24 T} kGXcrm TjYwQLjm1A˱0V|j5di :DD [8h0CcY@X-hp"^g8>={Mva'J̴a*3H; L$S%]b.R\r%ؾ};9tI8餓paa71MDmL@na&dhÝh⋓+*4% ;Sp̀&+Oo ! ldLA+^e:Ww8@ a,TMK&JPW}0v+d+^س4&v>w{`n. g&‚T"*h-8ޤҹ xmAJ)RM++c=#*13 r7MXQ<ݞ&itΓ@蔱ݵ(YMex۬gV%"s$ ׼bY{d34=qS-+# g5r, X[A\f2L焜,&! Pb<5jk`)_CH!gٶ|2=KUCKij ggp>_&rn19Œv7kME7pXQCta(  IDAT CuVl!$SF5_]@hU̒Q1crgy zP ! [{Agck,ԹO\ϚwS!|vI9U4}ldtϞda@vMc.D 6Z\YI^S{ R2f 7ez!$`_~h( 3˟ 2%++2ʵ0AjG+##ɠ*Fr&u 5cYZ@6X]Y0eo*& iM HF~/{nڦhs ogmnuFARRSzng==i|'@l"˝`^?^߱4" kNOцވ$pPplyQ1 $IF3bYkEDz\@NQcc!,JE!q%-Ch J+nk32JƚB^;?p'N;eiK_<<*(=Ée@Q.qYgK/%\38r/3ZW_m.wc.wl+I$( K ۀuoa䴘}d0frPM5Ў otQZq~Nd`NQ¶FI.+_w?ߧHJ hV3`(C {@UիIpz:3l({fAĹ0 0)Kv$"'* L!gv2PbNZu r][rz$M}Ń QĐ#P eu&kּ7͍,- i#R p8AcB70EK ^Jsڨ}} `VMf2ZF;vIW;*(D͘ǿ{md4,/E(=Q49sLN뮪-$_Ҷ#- ?g,~CK "uf+װRaetLw<}ZvP@dZjT L UCH `O?>`01B  *wL5*, =qycb 0y0-'2jRCwBٿx'z"^ŋ^x0 a ]k`dk^ctrXXϓe2^z:ts2* VYzo*]0$̲J'asJZ\6Q~0Ttjp•'`Jb[uZ0 -8E0 'IF烀FR a`<-[E۱a6bt< $tEDc* |d.I@jl$ 8qݛwD D0$J4(#p d{ JGBN2!TVӨCSJ*'XJ9f^,GlC .Hn 1FdP !ISe cgf1f e&ʕ+{a %?I 5kxFBWU={^A*ߢNyFpmKv `dX)3]}wP5UqqCZLty Ll^<^KF/p TF^a Iuv]Qh>$ +x`~vj{v9eGjk2jj !:p>7}c8>C 0ͣRauL!R=Y8)E´ \X2ڍ!+}G Gy=EΙƧC=BU] g{{!bKRdh1aC%FT|݊ކ| ۷0D5Xиb`jAGa0$d& ueǫ;-I2Yj( !͉ ˘VQhU4Uo[P޶-1=~jXϓz FX~G>_Nխ6 בu^' l \p83qz +S|C(@X@jVg/5fWB~ ػ{8 CZT/ Y,ǂu 2t'~Od&]?$@73v7i-z(ġ}_Fm'i =a aF@/ϗ{ 뤰me'K=0 AϦXJSzDF;'+Mkx!:%̷%=ϛ6ާԈbze;;2 8I2읟~mEf$q=#*q@\]$P.4KCI.1P!-faÒp3WΗ7Yp3ss~#_1ѾB[!lmo24?/|x{!CH- kl8yFA,Zp۳+-cN? ΝDZP4jBƺuoSB-$>IZBޱzۑ{i8kK!,e4`3uvlO@˱r_qbQF<`Kjg90xH];2I1eddgV% 76;wMiOST4Un;1 8HG?Jle6+1Νl*4nRxP psAd(B>/+סilv-*}1H:`W`@mW9I鵸mm3C *4ƕe̴g )45T+H.kW< b;z.b.R{I\LMMsx< 4m9T^6@^Uf;lBīJ{moLZC߹&jqٲW@m>\-QTUH:ZYHftؠpFp !ƅ2upΫAUA1VuU";G$6p9%_-εD1x?jV+ x_a##-r>hAMSz6nnWakb8" yݸs@l $]y'V]isችOx6\FzfMH2%@?W*QhVpnh4HFsfiPB=trvfIfLHo zJe[F?vΤPi@vym;C{Z{p ]yj !\YZ-%1HNK,NJa5]y#dTu*J]'%4|}a@EgUu6 i:WWVϊ>C(UTP[˰e kÑnL{WոzV;Tn|FyrnJFeL2X j8zp?zp@zd3cm0ɨި@Pvm@(@wS6:~8,ax#pt1X`wL]vAA3Z,bliApzگq w a^cO$ERiW4 qmmD|.jHv4:N)jEAlAѨ-\ۉn2P1:gn VKSXk66eoS=z=(g jFVpO6K ɞ[XJdMXFw@H)nu t$-C8ݞ;_N(4fOVF͗%]Av僥Rf'B6KbR񝍝K6 ;?gkFľ,4B"cq4>d2[ 6@'%@/f*KF7n6c[ƍ!V_I EV+D1[U7{fMT␉Q, ,,CxcWǚn B7YpEvCZ6ں-ݕLFbf7K-fRz(Ch;6.%]y\>xc69صȾ s!_zsO, ,Ѥ>iBbzw= W`0CO߲\!Va%_IoQ7zˀpc.RoƧ?iԸs~~g7iLDQ:BJj5!OL$'Rq܊x}A& r !׃Cl[\ ]JVyɨa İk !\dSR#u%WQk11h+ˀO~2~d~yv."& ` A cl,?Hl[T|O+gs  $ WG 6%$np=wt߄Xd~YaDޞ{d('HF]TdmiiFBE`uc ΄nEvL3dL aG ?LَBaisQ[zSET5( [$qi4QƏa4;2 c#ySk3 )B1׿@0 ?Xvhf6Ώ}_C>V!LP oQ[ WJo1 !yt-5-d^i+^^d5 5$b*- YsVAwz(V Ocl $®5 zp5vl Y ؂v_{B,b= 1iZr!|܄b{dRt[E~<8QҼՕթn]gJֽcb2 \K}vr!8餓pI'ݻo|cb^aTZ=y1=VT^>x-yT40Y$MՄ*Ŷ^O kC#SwV+`64 jd@e(;s!,HC.98x[׾4S_>#2" 0wTj+9mo9-vR!iطN(vB aN6`T !_ /Y/)l6Bl2 2ǻΪcacбC1Z!Ԑ=xE'1IqM2jFl7Mf?f° k?Aj4u{[O?*tωr+K+|$wbtBMcrJE/c;|cN[ IDAT0@(@I.U5_kwW]*\ TUV=;_,}j5P69.R]cFMeHD1y*a| *rBZ38~++Ev jdgǴ a jQAcGnz2לK9_CHAutRΗava`|DI !d@1Xk֬?/Gy$?x|[ºu밖-dsNݑ!\1>4aQOHBG}wX2j9V*F@UP]+ugR3~Hל! aA[0[iPN V~9! _`kE4D@Sf a[p9dol1ا8@Wv#coQ( lAQ%q=9!4C)IAc1b P>_yW ahx_Le$Q˸;҇0'n#&&_ ] ,u+VrK! ClcmOTb`f24†ԎRjh1EPpЃW_|hI.ε~ ;j7noIgkǏij x R,^2o3DQY9m.žj! 45ث S]kԺ!,5l-'H2fÁj9LԆ:w1 Rym'(~qboETb_o{aؼ1[3 8FsC:%vP{}:Bґs}@Xk (y5PU &&*M-ZTiBQKh[o !s*$6VVz_ҹƙd$."us! {'7_J% Z-=Y$Ņ!?>:0h؂ 1ݛ߱EKFm.fh4n~}{0pd/y㎷[$pr ]*f({fT.n6 $0gLCD_d,=3 fi jẒ6]\ms>02U CI/mkqa$Bd.n fqM7O,˸馛SOz]FMD a 9F2~@0s\1:p>ho('f(Xm~0՘^tYC8LĨ2JY4'11m3 5 h7[$0g L*I,i}Gvxh mdž:QPZl}jI.nYVW KF5 Y\ӟ1y5`4 u TFYBIFGa/TA?ש-H&{rZhȳۖ_?eE! DWBW`IZ؊v@{]e<# D0)u!t|ĴR19EEYD]/|r䤩Me%rx}uyC?6אm6*J!qoMs޾!dwLN2a;2 *!|{[+`rhMe Z8rqTC:rBFhZQ ix 1^< VBo{t'1iho*nhj$V¦uhLHy'`!(!ЄwO{^kZ[o[Z{nhc?׵/=ԪU}~[`6[7 ELzH쩳]pL'Q:HUkbL!B#<@Zs{d/CQ4)]sEN\ZO^'1.b?~Ӹ⋇qjwqZ(C(PNhOBxWr_67B7a .(P2L$Ū M$Id8}MIGw0t~MX)8jDGKϕɒutbBY檒Qj*Sd`Ȼ>fGIFi"qLل[2 dB&6W9K2:S&irY8~2yhRHZ*L;>$ tŁOao@H,j3*M{qZ$NXfi,ullA8J*SjJQ3la<|ϣ~*SD~| secdeʊ a"'ay9UQza.,C&0pc'?@"Nٳqna ! }"!^52D^D)J&59~li)l52hc'xAL,q_6o&c$p]qͤ ?`% 7/@N`Sm4T2J a;*jqXb&YHgS Wr 5FFO0/@⥜a{%-ɯA'sb=re\f<۶e_)e$u7d2^C{wڵz<.}#"LOO?d g)(@MLe0eK$5@zibH%7~+%Uɻ.qʉcQW2Yw67&a2CX ^$ )G'.5drFA-[k?+k*y1a Dy@lg%)H]Y`E~ dF?s<^d}rx5%?t`АtC%c? 'Z!<c{8* !{. %*l|I%B(28AƮܸ7e4[Ӳ0 UTQ G1qLރV@(=mUaaWV2PX,n .R7[ % lCjDͩ0^K1aCHcgcJZ9ZLjRP;CvpWCx[_|{KB?~<Ӊ9؝M.nRy둤 ЬK%7JuT$ Iҹj@8ߟ@D3=Wre~믽gO -Ҟ6 b؁?Рptfi/x*7 uOiV-[+,TypN?xDsʦ2NEMM8nF4|~MD@U\#E^LecOF6\( dцBf?Wy@4V[Р@'? KFOmS7p܊a3n8W?ahLjh,W j:Q7s:iՐ(>gMh>"q,.0E@pd+u\=_]: SEjr y}B2:(ru95B,~9٠,L?:2  l4[!xiBLU@88@$0ӠIF@ {e7>vasfM4ل!{=rB2 Bpp/?}9^% %/^08 !ucGO ci2 ~CHa0JʐvFa^6Ó UlNK>NvEj͡ B n~8g%n4!\qS4.>/h,^PCH]geVCxX2s5IǙ8 ]l46O hbq\N?eAb$Э;{@?JMq`v~rj{^֚qQ jT4mHq'!"\i@[[B28}ty$r]. "_k_3 wU2@X;~"٥ꜘ7S&{KCs<4AOaUm)^Cg<>~d҅l1,9J 4Ӫ]0NrR0ܭ2v ]dԶ4@`VM W!;! rZ-28CΟ`uXPP|?яKЮ`]Ujkg_F٘Q.ű#2UPʱ@p.qCتpj0] 81V)t{ O 2(*!d%b筡 _ܢyCq8< x C8DO3~;&l/ö." }( )Q9DXC9OȵgmCVwN,UaH_ 7>8L$Ze@ rBz?M yЙzB*|bC+eE@8_/ݵ8/~g>3zӟ?\pIkDJdЛkhjn_)X n'>X,anX( 2dA."d_t,k^b}*B1a ^z͊2'-e%~"cQ05XA 8pBg~ϪSLXe%۷[ !3j˨?ЋGBzTIfXAee={-=h4xѐB';QW]8ecq߆ jQ! a1#B _%/r ;n?1bᙪMAax2DDT@+94t2@x0xZ%pE4IqbFKv)`WkW 7Vn$ u}p+J!,>,3ʲ Ti侦L*+oN~g) ~0pX`c'|9=8V<-I6@wSm>)2Uk p\!-j*/v!/[|pQy1iZ#G1B#r0)k+pBzbv4MMeQ9+: ci!!PoCeRqĒ+AhE|a|$<µ0SI/]t8K^\tE׫_j|3~#.KLF I2J7E x{ m_tfz9>MrG%U cVⰚdԋ\HJa[ dԍ98^ o*3T2 1.|:.qR"Hu4I˙Hf;dKfĂb0h!t,ClA6!v]`G5C' !W5T&J F>e$laWVUzx+K1JF"Ϡ3S5 W@qKeNC00!y=Ă j~| ev`_ A.q G>}钎O癎~..x9~q9Vyb\CϛX/‚ aݼ fa^@0Wz}pb^ ש2[pܵpHf*e{5a!l ]a؊q~Lc5@̬`'[(siPɨniC8y*A! ])gumgױa jt=CH߿.ݱmJ!LPaUx}_ ݛW6"Yo(2t2Oy<9~&$ &-w>oȀ^L>Kz M1pJkpЍI_"\I4 yɨu >0!Z$uNUoZjkUp-њDZ^@YCeM0sх:DNJ?KJpDNʏϭe;g @Jl <esyA"xW^k/xGp8wj^(\,}j C.1Y 7M|So3- IDAT ~sr . 3xKM4ϐ }BSY,e.O VʬRt=`KD|u`![y=R-*x gk|+ɿ0?xX![(D fѵ\R!1WOkr>D>O9j{.֑_Mפ !7CªKO2f"R,QYpǫJF7?gQSI$!q6 @kxz-%Tf6>KT<3)r˼Ȏ֪Zɋu_z7x#nFM{B#ɈkյBjV&,XǶ av6{]6a!d{l(j26.N}0h N\BY :)bDJQ/t>`7#v%\+!MuI/BjCGyӞE?XKAxV*F!~!,ZST_dUi"MeZ:B!tC"=B!d :}pZ5hj5abJ83IDLbvi QOg3%X' !/Ůp [*r뀒%!<Ǔ,G䩥G>#G$'e]p H 3uʨ?:p4ga1TX.3]=# ]fCL[V@{Ȑ ?'cµb0SyMO@^{-99f%! L38r`@cް^[I"7!dBSqh2#85\Y0.I]l x!0T½};ֱw݌D~aY 3謋c?abFޗF!k(JY:bCLQ ޱcdo(xe~~P?߲@WV.=OQ9r>~~ W]u*<9|ӀO"&zb!fJc ljngq & D׵& TcU2J v <+.8Ǖ/$V@$T%c'4 P]fm{J^K! CsBkt`%($\mH3 vCw \ Y?T2CHZӨIs4HTf!B7$Q:aFf:M1aO@9h,jucKqNQB|_hٛ>eia0a66CSn:lb®TPy @H) a5+uĞV2GuR# n73iqIOj|``PQPrEdiR9 *0KVn6d />l|jl/$O~]Yrw" !P SBWLcdT22Z pLen ?%}ps8֭c_o{-8ƛf|Ώ[\He^?Ze?c!k49F`E)B!+|T2**> ?Lsg=ap\` &aIoZAEs#odzYh!Q=`GK>} HFe o xkg#B4ChۀPLI%IBHͯZh(?f~a8;--U<`Ҿ{^QUƯ_w^,//cyy{EExӛ޴V 2@ tbc'8>̣мmP9=*y-H5NPTtyhsE.$^I;FFp0d57|Ň>4*z蝇ća~@==!Q= < D|Ģ fQ@_V1ف( Ne]NH{uK:|CX%-2Udg6hg3 xvR@H6f*q:IuIuIJFs^y[KY(M 7*m@}`e~.LBay{^WVϏ} F% &Q IvZXOTzTn1iI&klUQnB\]P#2 i @Ȫ9Xguz=_e*'I_WfiQ l;M2t}=S/0bٌX/ p ZnXi݀ibiBzϰ=$~!Y=qJI ٗXSN 'nu=+|V|eH$p5v2CHa/znLQ(fj3=@f敓*W1d]X@ ( H‚ ÈyB!cu^HAO 80> 09鐄j@Ȯtzsxzwp"Sn8q{OmS?яpu3Lwg8n>~q0=mr_^& bq?x!I @bfB T"#n.8]C8.CHʶ8SSKQP~@d^ 1_@7ʚ\@+AeԏxJn#ib )txpL}:x;F!+Sl!=Z>7ES܈> y="q&я3rYsi}dt\@Xe?  !kG>Y'fEn2 ȸs6_*B/35q:j&ͣkCkAǵm@P\F"`JЗ y@Vvg~xK;>9g6?s'#ˆaMDǟG{c)U%&L`SmD3NIv8*mRů M(@(F&")/ReP6ecNh;푦2TVMaaC]裏F3@\ew3Âq:CH_AԌ: 9;1\ LkWfY~.ҿUa[(CgٺFB\f|bkuuaeQrLdĉ0peǪ U55/u 1}ٱnca{~}%ˁFl &z~ t8 ce%UC8 C[ \:aw7&/@ ]Ȕ! epP4RE5KrY2* :#dK<: 5@h"S"D0NcIQ1!LՒ*]F.6` 5ibft0Ɔa=ryScځ 'pJQJFs !c*cjXX!4`!L$dGh8D7~FՂ?*踝u}êB-N>nj?N؉X"1pq3PTY.g]{SSd(={2a aT`'C. h ,ImJFw4w&I,DVQ4\1~*(y]?_0<"",cB5^״1۩4qa#- YClRLoy׽:򚲁AGhReaQ2J?sqW<'fy{R ڋJFiLg=H |K{R(v ,V ts>Y dY#=d?*-D>#mdtZ /|!cb뢒!A94{ueG+1zaMaXEUc H r&ji5 >m!<+7UNPsLN:f׀0 ~K}z'ku@xk6}n\uU>?okdW^MlÞ=2doJo\[k|FC511ALHFӪ>HIL(QYDzD|Na(g^q !Ad({=r\, &JF9`fK%bױlrLehB?!!Id |a.j-38بW ٢퓞8-  UU5D5CpyYuT1 MPbhJĊ#!r"x hO_?ϪbllF*pfrEiwyP=܃7񍘘HN$^>#w`;ßݯK5zcg8 qud*em#54ҷ0´U1A7P_, CuA syR_N!|xa|gw9!d*l&X@xߛKQAdgэ8htZ2eh(PE+J)g刏/Dı@d] NPlϧҥQ&ݚ,ׄ탪f1l~t=VlD-5kJ4" b 7;NCy϶b[%j5RaeU o UQYMV$anVh'0@&jXWl~WJ޽BeBa1<~gm8 FJFfulPЅ2%ׁ } 7[ssl0chF@D(L!G3h5dQt;!pi,s& K8L<n| <4ԕZ*.Ș&} X}T8*2dq$ HPSr,@M,K{Iݴ104Œ/#,:c*SU(T=z%~ gm*O8@ABŐI SV3J q'~?uDIBzmt)C(a닕,ם9 \Q[no${f] 88Lbx5I-p$CX(4D26@T1:#"7# H1d2XQ)34l߆LM秷ʪS6!hy\Ze S!3pm@#t0sr,;)Cu|޴`z{6~!<u%xKc'tyv@iA~8ˑ$.V=) >)O! h^2ML+NJ82@8N..Օ\  7p13gA V* O j>s@< %!gN {m=R2j5PdRcg#N;[!$-s_TYCTfjKLk!dg{wND"Z 2&`8 ,Yes% Ģ<4IÔ1Θ<#'c}yqV%p!dT@T2j{"S$!便 au7d+ (\;CXe*Xp0IJIm@f.5eLaN]H]FAACivKfN4-q$U !_#{1a J5[4Y$SSP23@vaH9~ oG^ Bq;/8Q*c+= ]x %40M`q!4@mC]??FSG%߻[1G6H]NͲO|h?"ĴB%Jrxc$CCbbie|czf5CkqB;zi:?8>o CNq!LjD-CQյA9Egu yl𛀟7Rv}7fs3}+Nv\&%pn7uv0P2NZ\)ЂđhKGMXV@JF&GN`!w<]\vj^|.Ո󀽏!͆0Mr2N`Y<6ɀD˒3?sN]z9#QHCwmT^iW{}v eNGg^q$1`Lل[/Z/3@%HPW 4C9O2:V#R3@HdIo``!6tc ?: `*K㺌$\ S6<("xE4eC _U2ANu [Q <9oŶƶbajc) I}2UaٜB̞n|FGrX3r"~|-[.k(8pz 0Ljܩ1L1Bcژ|>eo6nBпJ5JC48q>Yp~•Eqd4* d fc kr x~51 2BڋlCr\*T[F⪨ژ~7ʠO\F_wEϾ^'k\7{n] A-"C7bu M+Oi=~IOQ\s5[quAQ\xއ͛7n8!iuq8I$[6*ıx uQ2@l3=[ 5a@'<~R !!ldi a竟^AT:QkطކX( Tx)S H@Xɨ Ab2>X~^?73*ndCg Q3!,4X"P8ɨıvr%^ w8Ch>y'oNJر#o 0FN븚dteHc'=: Pm`&;̐I]C禒nTWNy\FHpJ9"}0Yh4VgY0 LI* i1fY2JÏ4II-P@xƶ:a;LfX9S';*Ȅp}" !52 -AF=_V앱BT~L6>CV0s6?eX8IJ2:_%[}Q =JS"*@Xdȳ4eD*Qsf3DƝx/k h|SiۯI8X$l<Rq# !-DMhX5<0};d`Æl !4!wc7Tib(& Yu8=۷tSn*c@`hᇫBS1M',;߉={p{u^ 4L2qy)J 'C;jϭC8l?I!&;zTJM^eh&_r,Z@H.PP< H.eɏ̓M7݄O}S=?~| }Vpv<+!·w雞Š&P3 sB, 1ImF"tVJ@H%uT A38 ytN QGQ( #Ǒ{eنŊ5lǒRW ac%oN3pxƧ%:V L6%{)n %1ߟOZ:x?wE ]"r=V1+0'% s4l5! }qLeL Ib`-<]WeNFq;0cH3V&6z !en7u5KE Cz8zZPp z^0 )Ug=~yOQ,//c׮]z$|o i??U۶w/;4M#(`eT2J-iMm4XÑBv]`?Aߓ1y{x7^E$YkMV. II; ++qb6aF~ʓ_T.8pڅxA~4ȹ-VE+BrB6|?b- ih @+}ZxpA 1hb" ɐdTҨ\cmRx uZ2JY%Vy]"o>Mܼfx~TJwD$lo1)P!C? Ŋ8-LNd*Cju `<`%I/&x%U^:'pRG2ZxO3ѱ !=w2>>jH}.u`߾Lp8puZtN_U2,S+Р,,+lп(+AؠqBd< Cְwx`ths߉KsYkS%"/naa<ǧa Wo q!ȳC}ݭݘϕLe d3Bv0z{o>9N W|5}RļEfK GTBsH>&[R<:| QeLAf1@3gA+Cr]d93Ϫ2< 4e>ŜD-/ *)`a;[;=aYf"[rNɟ@^.1jCHaq(ӺR ^BMe݀HF' +8evZj+y^b@trHR\j3*\z @>߉xQt!a; 9O`əKD}kY!/^_ C%@6b_OId>ã$E@( d&(h^ O(Y~ 753m@2C8:BBdvfcB4F< 6ҵy0a1s^X o`iJV( LLdm~>sh5d^%tڣG?oU¦L0cxs 27}0_-6.Mh"qvk}42hu&i"'ݫVkr 1E^jPMr'p:u÷sC{{=@3ؖ^VgΗIs_Ixb_'? UUqWtIDehj' t6ȃ"xߌ=u9);̸=4韞&kMԵd) L|PfXLBz (ɨ8WUڐ 18Nl2 l64\lx ZGq'7_MRˀfTKFY@r.V6>y@H{4\7%,(4VpH0 9 Nd'T3AkE@fsߖsLhqr]$4ksS3ǥ3~F¨PD ^lgi5'ϗ4)7{UoQD́ciYطߊ.c7Ƞa񴧭+: A1^50QT ~j/7jDC{5DE 4gϩyW=UuN8y]î=]k#8o'%T:&H&I*)@=o{ h9Ş !u("z+n޽Jr{Zi+TJ0͆yC +Qӏc钛lFӿ4ۖI#3=UlN Yp"L{MIbyaYA*!Th(3rvY /#DpȀ0f% ƚJΘCCi0~y N8Xc# CBgr՝Fז\47P2ջ=U 睔mr9;8q~c sX- %}LcTZY]X:YD(6e9d2֖e3S,\3쫦p*$9y$%D2ld$%VpBQhe) 45麳'lՆJlįrWθ}.6o|K:HjcGSp춌Ek9?CKnXкbY;ph2+Ee8@1 !e0!3{U39pН#t3s9D C1x-zZL`r`62 (VޟBtػ\&Zs7D rĝ]/܏_2g`WJ 4yi:y`}˻VWeKi{`@TH cE</'ڬ8Ee* 7[!l!'1QSCB u) &ݗه*'%; Pyf0(@6ІDiV~=\"ysc҈;XRHEGg+6i;vs^ɨ_|KW"F ŒFx!g1fXo9c7R0 nճvт4~;b^ 6WqV\`񲁀 т':gM~ ߵuJJbqNXo[:}53r2_n=RGGzYBNY9x8Amn0|eYt~ɨߢ|  fd ܕ"kɟ&$q IDAT2 +1? k9'aQ6s0x Nڼ-yX.P@1Sq"m C;s8,k`A5@!DA!tmf Y  (=N>8(Hs|OroN HQ59 }bVk1!8 |4BD2WP((&dY$۷<(݀0!˒{& *gE&VlXB*HSS@8ޱO}CK^G?&*d!E| a6Â72s9|ڽ]{`aX.}-P.F8!d W 8Eꃙ'<6c}٧ tj&}N{%N4WȹZ@>lV"cY -lnK8hnݺX$n(h[Mp \nIu !\ye@`{ҩ}c4*!dL!%!UFSlozD&Ch!'u] 7KFi6kpӤu~syo!eܢsL90=vM݂ P34[o)VFC(͊۞)ꮛ>pKI4&818 o9FjsstNoˠcmIqSm6 : тGMhQX;8,bo,!3xrlc{ń~ҭ #eaeT 0͠˭:C&뮸b(؛ދ 8!$ϟ2jXz_a.: mxw2"^@w: Dx=0\ B#[Gl_ʰKd\#fˠT ]B|j.d20 mB_/Amٹ\O>g!)Geh4;4 ĉ(+o?d4%~ CțqX|OA}fǞ>IEG fwdNh" G3`&.Kq݁c9Ȝ &3 `-LF!v\/A|OnNdp-|9U=R)Ssv㶌{ B|6-_Nn~qP ۜpF$B1"?z)h @/etO#]hol[2jhjPm@a$H l1t0O ("#3aeYDEv @¶ֶ7FCJF^SPѩ6ZǑE'&IɰJraQ$ui@1۶pSD7c !u|ë[˥P 52jjh7+/KUQ_2*sB7r" ac_?G79bϻ,a31 u$[nH>d4"2^ $utHFYd4{ ȚbAfB>)CZ񛪺YLd x |vNi  ӀnJMY_"*Nd#_Ĥ})Sv궃NMc,wždpozo_L&^~9 W<5 NsKz\N-Gܻ N 2_+S1IG6 lVxa33a*5&D17Oe a,@Ic6V:M~kkTj78z.n@1@+&JOTWxy/^zw,XG=㔕9!`C$u%'HzB'GǗ2o$@h2'Pδnv|όiǞ]TUg!\T&%kvͣ)c9OU"bs _h+~%=Z#с thBL' :| `n"m֢b4ON{{-*dMl0 ܪz7g&.zsYi6/ !. ?-QW-1?$/䤄4ZiXxTY c3NПxZ`uRhdÊ&WlHF[ZQF`R ! *|W"t}fB9i7]V^nv/םFM  cq!n+cc@ן|=-H9>w \8F\e )R+rg!emPkKL眏@S#бܱf;4ٍ7ވ~uTs/6,Ke S)jLIb}NJp^[4}` ~߹o"KFmI6cuz e~'f76F$Lyr,F fSӨ)54f`0抸a >~o8 #F:ۀfjxEod$Ri=l5 zlbcpݨlIz57]y8T Ko\˨ aXGc,'I|O{ze,Zfn22CHi3x| x8n&U&%FKN(v^lYP0 lD'=8vŇ+7Pg|1!Dd4 %M;1Y9Z x˝oMs-)(@  2,!&0StO۔f &tl]mnVNQa} C<{GCm),p^P,!4.-މ7;ǏazڑSIPcڄj}Ǿo>HA==Xop$x* 0,~օX Wp"]c#<AFΠqvmBٹl9n! m`F=03Y+*3!s%(f~Q*-Xs7ϡ+ࡷ>dk7`*q!zOR9*iZ <'AV鸥`V Ҝ|J&\m3fanW+Tԭ !M q=v?vinW\w]x{;Y -fGGqp-ҩr$U%eK_ l`U/B"&!X̑3FFKkUR-VH]醖3H ,< ud^l;!=YRO2gR]]9# m`bw0=B*U+ȹ^Mo (/2dL܇pusBZ|20]?(^m:E9tPFS'P7u|zI>Ǿ>[Vr|ϟz>7@? uم>T.Po?kySw$5!2ժ! ?ܸa]ma/6Z~,faLDKkŲȹą}˝=)ppQ,!$\ѮK"LU+=@h3-͢IdyA(NSs3z,3T-b{>7㝑30RǀN6P2: CHz>#gWHaN P`@dBΌ#=ugmqY.YjJ!hm96}Q>|,׿5~admi;-Z֮n7-/ٍU`yu[{ӣ6! Ũ+xϟ]LT@(IX^cʸe f3Mʧ$N !q~y_Vz]Y=&V:ocBZTf& i ]Σl_a@HpZ!h0s!*%P*:[O ȉ8c`h jAUF8D)all~&,Je8CN_D~/5pr.e,"aBZnRcG_ q[dm_awx^ێѹuvl@* K {A e-0K&8cYzk\]RNQ*t;L4 -ehutƴ;'y֐>2<<^2=0 ¬ lxQX26j#ɲA?PDd ,#{OT5THZ%52>0kI_-,C He7cxwr7TCZ=C (D RcSR i)*NM: l6 !@KU)7X~/( sh[@Xk^^&}CWiBzoi[ C)*3CHV(qz%!ϓ`{.* U[/C΂Ӳ@m؂Y$.Cmtӈ*CHĈ#+gqe@_-!O$2" /&/f@%;O?OW2d096WX?67Be\0p!%q ~@H%B[*" d1-`M/ m"'-xr9I>he؁pz|+ʋyjdX-Q㔔ΝV+@+}RΣia|z-N( +PqA.@8,71Cs@V+`UM://Chr T*xl~g-Iv%=kt7~>dS<"Z s9=+*Z 0WCSm?6X2ջx/gkJJ}RQ+C*_ae n_*q 2! {Y(bx`F Jz\ M ,]T'bFhe}8q9V>ȇ#y‘J2یX nEhUI%w|NCB΂ kϪ IqP;x躗!\:mG]6x}\@)C!ZByn^,.λBQPHՊCHתFw۴?ȣl 9j-w9Dm)cB*["9뚁ps؀Пnl12NbBP5L` @ Gx pf0C8Wd6{Ps!L&X[#Sc)q0q9$d|Q5?_{;d'L/P8U au!#jx7*@vi/~u=6 Codauk ǷC1',7X~+fb@VQw~&p"4o|a{Or hldžaX/8y$p:~KqArZj׾aeɆwi s~5ĩܗه˧.G*j L ȑ̞ڜG. +^ ­Zx O0(;O!t 9PխxsƲęi4"dY0k;͔d Fa#F3g׮~g$4`n: +ˑPR1+HǀvD)?'E~ps;dTDοJHes% dJ0>"& AU %}(UMgN _|xTu?xu?GQRw7M, (%\"bV-l5!" !"j*i#-ЖM:U# #]q8|aYiQ?CHظm$m'^{HF;ɏ|!^jwny9^O2J]W\}?D_ Vg TeC(KdRgm}I0A{#ߡшN}"9I6 $A lvM =m ڱj;4-܂Rn?OOuyz͚U hm@$ o!fuN톪yY9k(^ sR 8srp a"`Xe)@!ɨIBIadTFk} P1Z- %aWBľУHF 1n !W4vLU,;V{k`B?CvthN$ /.۹2V;^oT+!²GFL9gXQ' 3Ar>7k$u)y,{164khM@o&r^D{3򇺈@&ruSلV8C8,TB#=u@<%4 3dLQ@, 6>lzDF ͡ruOex0xb@1, G6@Z@p@vZ47ft 3Da !ͿqoD{w]R#eY^x#ba N3{*4DFUȳjM bf%T~0<01Eavk)O4s x%E&JsN J1.- 15 mz@#cڽn;Շ]B!Fā y> EٶBsiB:!600V*o6pFU =~f21n,; aDy dBkxʂ܂w/'M?0 L>A9lۀ0+g*L$'i;vzl&8zիpwbaao}[|SSSx+_;L |km9BI8'%WT g݀ߏp*7Rk5"Y$Y>>zA ժ҄-X9pf. ]2o{SZq2xAKkXt7qG b)gih^ ѨTU3 П/QH4LmXRBx_QQ8ucd\)4^rn, BM~ani-$ `M I\[NHH{ƍҘzN /ɻB\| N4R!c';X§j#YJL|3ruuKбKKiml1D~V hIXk^yHVʣR!fn@H׬GH,\V[Ja*.ڀT{ #ajR $NhG6w7HsrtMԨd4e/#,]TfDQ2Mh=qBZY?!aKbE v %@ߖGas_ ˕_"#LG|fovAw+dG2ڨ+gF唘cEr&4͑V#R͢纖}VrkkΦ(\28rqAQUX]K8 :l99qt`LDKhMl,lyu`rrn4 D5 2 Tl'l@X^w$Y9;prS7]PO!,Fv,7C2 ^Ϣ-B`9Ȝ 1"bȥ{o* "@8YϨouիXSpz+(ťbLB/\V d4t~0 JzTL‘#)aCHAO:=w8#®`@dԲ,TU\2y z^+"gjNd[3''BE]ߕniQ2G2PΣe9Y~{=tHnn7Q`HP0-of3~=}G`ABH@J{5$_X. `\fz0T$2v DbZ.2:Դ̾{?$ͣvp$.ЩbY+xq)WyH>£+,!-sӬt <˿qW^㮻 ׽#2:!r": 9,BD!4]~sGUeCXT6WSh}6Yhiɂ+qq>S5!A`%01lky/e.) ,B9 YUC$` TgpդR;pT@dAm'>O݄:3_(dTDȜWd0KKY)4[d{iuĦyX[3R92/Pb $ X/ٹA6dB(3;őrv^ rL$&g/5ֺ8v$[n!i4z-8"LQ{ލ3>uIE9yl,dXŞS)ezZRq$k?p/aI*w@B[ x#Bhֺآ jKRw:M$$=S!TjǘX6 )8{x!m$V+vδL2AOv* d4 {݃w; j;8Zqr)j}X&WޛmE#'vk{m&k{n~x[ߊ%?j@,>,,, 9Ri40M<x>M 90zHFitFU,j CXV@_%P96}.)`!„~$(`JWB :fћa$,-v=HxQr/$" τV6IP" c'' v$qԘYX6ZXܨ^%b8 GKkM̞=ɟ m9bv%N‡Pq(#j97D8x&Ju{lJ60rq !z}ֹΌ QDd_.qljCPP iTVmlM;E{j^\t$(ՆSBjH: :ူPɬNrc{S%8AY"+wl r[-[E0B=+j\ cP58SӃo(8+w0@8rZ p޹C -h0K,֗e> 3h, nm[{? 0p%~߅ xA]Q:cة@xqe 0m OJuznVS" 9h D=ʆWý| t,. sl9CRLҭ6a"| B"jj]7!hî.?M2*3uKݻ˥ߚ݋ 2%K"4R@4WFs3k/+ԕQвט~1M\2up)@pCؓ2Miu\i7|Eou#Yr::f ݎk.D jo2_kJTnQ@HGЀհu Bi6hΠe=+cK@ @,zBLL1=DX833@9OOZچIF;D2b؝ڍI0sk5x֎`HdԑҡŐu=&`Bb4af Y%rg>/Mbeet2,RR.EsFLT |Oʄ`{pّ֕:.]qnhQQszp*U6Sw]\^&2 !&X<¶FL: p0|"Y_r?5\0A {r[D֌0[oşOqwo_}ة@,~3u]׾/c޽(xEQB=, R<{"n"k^2IbSL5*Ej8J?9hԮ B3 Xkl. %p3a`p@hY.Ԗ'1ScEuCJ\rեRYSjPBw S2LG\WpBl7B]B"ڥnϝAc9uYZ!FG1߇+E(C풜@? ,A]=@h B7CI1Jnqi^%J]]Xj?'ސѺR˰8p'0Ȟ_uN^j#: q8Y3ӻhiG8P)IBW)v~s8;6!@?8sᇁ?޽Z9<Gkk^BU%oXsל Z95ā (kb-/mMeXDQ [n:eD™T2壐9ٞ{&qRSe}@jIb:X\f9|j{] I V+5ձkG ޅ2 W6_WꈲmBY:'cW"췅^Exj', 5.a} IF{ ŪOP> l6 {hysWm( O ZƓξ{:|R"h/ |A0߯X$ؿH$ݙX묁y4&4a=gʎ2ϲ}ξ=n\}ո[|Nw8khQR P.$ox>؅e *͎:T=}(t2k)^FgEGdנHa@B{fRE4ʭlڎcEujps:[I{a- nl$̢|2/۬$@4_iӺQaBF Dc 9'3ث&Bwcp5 ׇ;1 F|[ 01ȼ3 н!Ӫ=RH o)}#baq q3'Cڰ8`PG!O6 a{7CHaMÇv+Bz| ~@8JPyE9R@HbK뫈tH`. & HfXyA$yd2`+Hd?S)`4Gr,6|<o 9Y*1`:y7Ș%bE@08 U6e։Z)^js5:.*`:zc<1(B?LUJ7|XMwɥ]y2=W&NѦDBp8U'Ԕ]nCBI"k?m7__ӎ.ЦF(CBaI\mnة@,#<E)|k_; Jd3M29ϙb~DG'Qo ':5`ZێM j誃ssniO+ \fIYId$/=~oeXdG^P&b\MT]GJȈM@M1i6=.]Щ4M}0 NCCWڅl;I36hi%~@HHc&ɐ{w@]uaAr%4x@$a[.%𺃯CRL"FP۔`$ܵ@*Bܷ_v\1}'1;KC=Pх)vaRL6(.zE-!'W, [HVih-J2RnjB7 E2NMVc dnD ONlֈ:n ma)i @0 c@`&4Je9%5y:&Y{:^1ؘ7wԑcXk= 0V*붆kh-HP`5MC)(aMFֺM#D!@ZE&Ky;?5JFjhdLDJLam #@.h bR IDAToccDfÎWu1?= <]eT*({^攩KNc3T>Q*M Γ[DZQv!۱BDۨ]~X@x!u" n;qd>|w@yx@\HkI1ʊ(\P2U> v"mCmgy[*"س ܒѠm"A H@2ucv$nHccd M* Ө䷩|/!vf _H- ` 5^*{F?su5+b `s"XLQa<*><cY NIg:8=8aIs1圊! xFAݶ smj @pBIծ]=1洞h(de299wh-Cof*3khhQ]'˖Jh5DBu:6q]];g3gXTPфGY[`ae}@H{- YmIh>hٹrdAh>HqZ`(=__iE܋FZzag]}ZAtjz!<e+JvPUU_^z~ uNރ 5ž_o+o1J٦ل&J0 YY( ӼP DfOE 57CP&uSNN:N3=^1Vj8/2&"a33>dʹDّ͠ q<1WXC4g3}\ CM$&%(DEc` 57pb5+(d$YEG#9w^43IeYg.waPV/-_p晟͛:@RHٲ%<V#+-o^Y]#!$:׽o#h0 W!;J{.=,zZ@y[ۀŐĞ= \\{NP։Z)6X2T q; Oo==iپ@*'X^Uol' YvaZ!wda6OX-E/x3: =woD:(q1VDW6?ȉؓރlv |/9nw}xߏh4>(8F{Fhm a&4>jM_6ȁ([lʠŽٰ[3PJM!1๧ q 끀r@bݲ*Q= ]DZkaQA_N;E7$ Dbch. \ w?xp; ߦRSH D vKtb轧A CxYa ɽv4?(bv226܅/{w%IY fdRKv; 4 *pd8T1n]Wjsapz{^EY22:;3Uhf/(x̪Y#s@WV>Yby"y"ۏmT+B`v~I*QgP*_^K_C(۷##+z3jasfswKhj#>-fMЯ 7p?]v-_\$[%=|'s 7o.VvkW u8 ,x{Fk^q~x>[ho W! ԛ֋W `{^gµ<RKn[ W!l(0;Uw6Zր0|䰝axWrCv< (޷'@lznsG^z]tq=]=USV-X'/s‹R[zp}P*Dޙ[cj@η7gMʹ}Lx.McQ:HP<Y7O|mQz>6 m{hwׯ V}=-\o$]ؠXu{}];V˽vZ<ғz}BzOn:t-yjщ|!8P^tah08A5vౖw2W{eԺCkV u>"Iaʿۼ9oǺugνCXoqܚUWji1Wի(._UU ڸq§>]^Nbպ^zCʏ$O̔-=2 yO 39LwΝ\.:zwŭVLj5s6S9f Qсm'V!|DѾ5B~jn#?.ڵAmWǭ}.kEk0 nw4j;qn:<.N#uX*+nW9qXQpaъceѵQT..b2u;P82}4_fpe6·^KRKGwk\d\^15OBi-kV_kv΋q1PŋW0tҪ;-agɌv['u CCC~$?^U"z!P}ֹ~p=;̭vbcJ.wtƣwa|OLk͚_Z7_Z c0> //[R^8 ΀0 [~mhal y `Kf ~;[,,ǿapJgX `wok!p[w6z|WnUָwW\xQ<ˡZw ^ )>q7bxն:D}}X9S#֬Hh2N==3@!b;CClw`qtՍN֑S5 <W{r <X1?w9~ b>|Gvû]|>V=׵*RÊG?z1GFWUܦ7OFe}[qn£o_"a8 bs `kf+h@t YSVC?j!//d@q#po٫ P1 Dn_u-^狳w{sC׬ !\?Nlkv:aʟ7Cs wwݹ+=A5:xU9@ܸ:U=o;8yYO^ _uwqcp꥗Zw12 ܹΝo{ }HWdT=|tr/ l?W"c8 leSqũXԿG^vƧl[ 8>|T]+ T>2ӏ:x&پ w+PKzܚي{>X޽?HᅍG[,E;G]sm%krah~;۶hpws|+Ӌr|߼t3yᑪ)ǜycGF/8w@8v+6c͸"]mݰ$8cieuf6m &2ڲ%Ύp͢5"a HphpxOaƟƓO]܇;uTvk3 "{{H]8XʫV'εkMxGN6ۿC+4{d4F} MJ%L" X;$8`կֿQyݺ[1>Eca3۷_t Ζֶ'9ncaztp2΅?3ߝwዏ7LkiMvb⵳'G7ەE' ];|nRw&|r1z6m?zpȹKoL?ʿken:<}я~tS{1-I-iGO ȁgstppݶlyd[OzތҹߪU{+m6X{j 2:hiqaLti+_|5ehWxO;6/݌/u`Koӱw-zP)ՓBvAM> GsXз1;@w_a޻`-{!|B =kFAҶm/?<: MAWB'a4+K֯-K̻YOr3UNYe9t:" D%SQ:-KEoWj}QG+)8PKwW7_~|yEV}}T UEʋ)6Yxdxn )u.pOh4m[i?C/uYn`hX} m-ۆ&Vtij;ë7wgQ 'X4Jw= k>ZCʦw^z=CCijg8gDWok ԲeK0bbزt+ူ;/Nipj#<-YzG1m6(9HOcP(NoP+Spw8Ȩ(]Z></~I% `LKڼt3=(7qY9I}}v=mt޼t3~!8C0yQFn wχW ;n¨3mG]T)  CPx#N[n?Xubމ!lǶep3w0|\ߪ_ھ|;}^!/p/o:< :ގe 7-݄_xx޻s-6o_mc>>A[CXF_W{ܾb;꫁/}´hѢ"_c3ǖzLQ5m[Pw\;nqx` 8yA~`~Sj_4yUXf⁉D)6mۂ֫ ?> 3 z}ņzǂG /44 |>F]$Ce˂{l@G/huțC8ofv['zZr`%R==wB]5/*U^yw8ek{x+4p9+<?ZUV!sQa;؎<e][EΕ;qW.U>Gp7qVm !?6 ]o;sjw'gNJxGp~^q"sǥm/8}^xm۹"x Ӂb؁+v'';p\휄,*pg.Y3;VVot_P9m w1yP9-W s|Rx3 O¹LyǏc!OZq//A sߡ"Lh89VpWvWKsepA%xڱ#xn8$? x".S0\e4ܧp;rt96g:#<ζ_k ?|s>4F^6\|!/apwNͿՃWy%Uk:*|{ \}W>OLTg ?}Wi*svgC' kN?=x!xi%)X/A͑Էpap[wUt9]x7]"<<&Q`f5`Kf ~O_`QN'/~!wkqٕ3E;sg<]4 V=nFᩃOq@ئp!}Ѿu,qXN': ;W~<Π+~,*c" .`-E!tܣ8C}7ߵWD>$fEG=e`[mt)Q*d|>8Ţ{n?JvEkQUZP(B9-q3_>ܴWV}OWZٷXևd0qެl57 8 )ݻ}u}uݻw[2jܭgzq: T]_&Vc[}z6הs$p|۾H<,#LOŝJ055UWF}OW[ƭ`>QQq?G[sܤ,M,2&2nSgfqWt}OWD>$tm}ԶxB,q2&2nSgfqWt}OWD>$tm}ԶxB˅K\7NcttVu]/3UeO|n_Y&'|KYʨ/jS]} >ָ!Lݧ7/:9.h+pf+N-\)u]?J'ZAKF> ȸMMKtŭ:_%k 6us\fr\TrLW)ر=!3%:-QK1SCt|Diq帱QW>KDDDDDPR$a||"OiƔlwfJ;EĘj%NQV^syGLt+8 J%!`xxLccc(JV橣bR,c]!-q4cJLջz3]bbL(I+ TֹL#&b:օ^R4FGGn]Ȉd橣>tBDD1cM4?RB(JC&02 P*tFDD1cM4?ҡGwDe]{bjj{j]aQX t2[jyd2U뺘gDD 5ѰW>2J\g7ϥ+ڡ*8#Tm}I$I'e[N*mR cccd2F&JɎKߔxLbhx㰏Pݶ%XN$Tvkl;|ZPBAYw]\w-{REw|7%ڡ*8#Tm}I$I'eq8M*.*c9n=C&Լ\Dհ:TMDŽz0!v7mk[_!eDIemFt\TF>2Jmru?}~Ov\OwsDŽz0!v7mk[_!eDIemָBjS68u?{REw|KW<&ԃ 1CUqGn[I,s'PO*m5MR4FGGn]HG:TMDŽz0!v7mk[_!eDIemָ \Tv:&T*U,\,|Ov\OwcB=C;T}궵/2w" ܶ[#2n.* !!Ͷuէ~Tӝ)P&UammK"$̝HB=,#ֈʨ帱QW!$"""""J(%a||=i_"ZN0\) @'\1ʤN)*^b6UiۛrH4u]1?t׹YqPF|q=O*P-_(jtz'ϗ7ƹ?|ވN)*^b0lQ״MfemrL\ov(#8Errﯙ CgϞru?}窿';-󋚧xMAV:Temo2-#mwe`z]纷g]}CiHτكALOOGN:ף;#]q68u?W=i_+WrPDoooݟLUw]###u_4{21yׄdMUk&269M]}Wf u{{u7y;_mxb\zIRӧ*dRʨ~**O$nu0UߓEST& +]m2_Ӷ7喑i2c0s۳ۡznS+2{PJA__ BGFE<\l+B';-󋚧xMAV:Temo2-#mwe`z]纷g]}Cܦ2^%p@h9UB"""""Ux%$yyCiʦXEY$;-}ֲ-q196J.IR1d2 #`ll RIwhh:˞z'ŹoZ6[%z2c#*h••LcS,{iklۖ8aɌTn=C&Լ\Ą)Vt=NfsߴlmK0~yLd&%r\|oj6*β'lq͖m/ɱlg4\5bMgٓ\d8M[fKܶY؈It:Q[{u122bc6*β'lq͖m/ɱ23umX,ONRbQwhh:˞z'ŹoZ6[%z2c3qEe,g[ffS,{iklۖ8aiL?ǍO׼55?җwˍp"w>aff \xG>12"""""J*Oo8S~|K/a߾}#""""PWUطooߎN: ]t>g?tyyCeOrݒ=L&Œ$wj>"2v끚PzfLԆU*066L&ad2T*%2d=uK0KީmjENoo>K/>s=]7/;Nצ%ye˞%{OM%IXԌ}Dd:s\qQEVZ뮻~w333G?kk L!`jjjgbbbB˦ġ'n&SbI;5csz7Ȩ"sO`z{{ww[4EN. ?c:.{aR?5)$aS36\YlG~>|@Ay:CS"q1d=uK0Kީmj<8쳱uV\px1ɗN1:: u~.FFF=zaJ:.{aR?5)$aS36\&-3o}׾' >?;Nצ b<99J'%Dơ'n&SbI;5cszv\TFl68pBu]ظq#DZsN:ttmpyr٬֫Kġ'n&SbI;5cszV*pBqزeKՀ׿5^(]n,DDDD7 >c[qW <։DgS;64ܢTFufUE&# 8 8{1|Ć w^۷޽{i&|ߌW68 ?WE-#>U"+SDt\ҳtejL[E]Xnl*:33l IDATq"}ۑDžy|J444T+p5~7v܉;w~7>cϞ=JUw]###RQi7O1H[F|EV>։DgS;64ܢTFufUE&# ER$}?JTtk2꺮Jʓ}U2j2cT|LqJϦvНm(2muibEMnG:#JqQE"'toߎ|+L}׻ޅ{wqGG醋TN<\lVveħ]dcjKTz6LmCiK-Me]g&_Uor;^L\TFqw}77ߌ[k@HDDDDd3B|xpqMoz:N  qrIXt?c51ɱu"ndzL/8M֗8IK[lK@{&W'2?/˸O}+JC&02 P*t6 u"ndzL/8M֗8IK[lK]&l_\?]vN<Ďӭ\e4ULcb&dCl[y*^6"e%NҖ2wkRqMGF.2&vyd2뺘0 c51ɱu"ndzL/8M֗8IK[lK@&|dT/r\|oj&jbL!cDS_q. /qXٖ8bM*q-Jlg4\5c51ɱu"ndzL/8M֗8IK[lK]&l޼wg>kKUw]###<1Lq+O%fz|QĹl2Ib]Rf[54[LٰaC&emX,OHNRbQwh1['VJsd`}ĺ̶Śs\pQ%mm\ߵ1Lq+O%fz|QĹl2Ib]Rf[BSq@h9n,DDDD76~"""""" pQN̔Ͼ}yyqnU}ɖ63*m!+MyI:Y&ocTO;56J?"'GI_w㔮LK }Uqo mT L#m6fzȊ1j\TF-Wkc WqP Ws!JtU`j_L{[ȌUgަn2ĭO`zɬo1EVQP-n,|/ _Unx|>R~|OR뻲ʣ#]1.Cf&VŽ-dƪ3oSQ'd0dַۘ"+VOR_(yP!app5}rH4~u}U2t-mfBhUBV6u!n}BHf}.2k' o@|uD>GP$k8fifn#2e4}[̄>Ъ4um6*C ב6y3]dNDP#K0&z{{ۋŋK/U/,M(}]mĶtU oKZμMFe[:Y&oc[Mtt/.!E>GV6։1H> ,ʺ3BqV)N۽CFUy؞>ՠw cr\qeW_ _In éT#cr|dnc4mbY>ugr;R&}c usN?fu?subc2ҴOtBDl2˧LnP\U*bvo~kQU:'8 T䳟,.\pq7{|s^dtp]bddDMe#+vƘei{脈dOeݙNԵI1i7uhc5}ߨ*]"JL%׿y׾_tMCCRDcYSGV6։1H>+6SYw&S(.umRLqMoM7CuBqMf3T*½ދ͛7WƎ;055Q333CP@ooP#<\lVdȊ:1fi':!"6SYw&S(.u*1i7uhc5}ߨ*Ug2 uW*}vMoG?Ѫ_~p=tB""""(xNⓟ$7[oi/~Ǹ5GGDDDDDIEeyހn ˖-M7݄n ˖-ߎ.Hjޞa||IGdcVQ6!"mlRUcimd~}~֮((iun~]T3NE2]˜ 333׿u? OѢ2pbn*R6_^w~>KAT_68 ?וUԁMyHGuTvinmZ2yߠ+J2iZ۸_LSQiL״<=Cc=6g||_4FGGn]ȈFSt*i ˍp"9t|A~gQq@і-[O{yyL_g&]u '+F]oBdsytoU}6Ս鱚ijӃzJjR cccd2F&ؘW]8L(:OV߄v$&lc5->!iZsM7w^鶲SO  eWC'ɊQW"\ۄuUMuczgZb$l6[3q~uaBUI~bU&H6'}@w6a]gSݘi"|'4O N1:: u~.FFF?;qPv1tuտ .IjНMXWT7jZ|CP;e1'|裏7tk׮memX,ONRIb|ۡ;qPv1tuտ .IjНMXWT7jZ|S Q(244n|+pBu]ظq#ytIx饗:J !!j;qPv1tuտ .IjНMXWT7jZ|Sʨ"t{/6nX5 뮻pg_(]n,DDDD7ߏb/| ؿ?~_?(xPO?CXĎ;p-`Ŋկ~O>YJa||iOX"Nf;DC#8+"SDɤNGUQpdo}χ>!G>\g7\tڝ%eYw2!j%yötEcj?}J7*4M:MPN*u|Gp7o~3>O /qNX"Nf;DC#vؖ|Lm'/:UF!:=I!2ҵ#=CSO=;P,o>|_А<4FGGn]鴲;%JQ.dC "yT LNN}uSO=տ[hQuT*U+ruV$(G-ȺQP(36Q2QnT:3-Qit"#]:>UW]+lW\qEGHA__ z{{><\lVv$(G-ȺQP(36Q2QnT:3-Qit"#]:ҋ~s\Bɺ.9twwo}[ύpQqa;SuĦG(MzHV*)&kcZ&:/ӶqlCl[yXF͋w9=6J?QYe&V'i WV>2S`SV6MWrQu8 %\ @'\JgZӗKDZ60;_YȌ?No6.Muhr[N:7u_:Z.X_(jtz'TjxD/#6啕:Im`vߴm\2JRrs""ո""U!p@HRdٺ9s""ո""U!p@HRiuݪ߻> ADF>TLEeH+@0I}8L} XΆ !!,"Rl8Ǎ -Ǎp!QBq@H<C%gR,ͨ19J4q>5 \\'zL&y VEi[;74cZyM3T2|&ҌXMc$+NSs\Q2R. ?Ag:mLӌi5-FPɤK3c5nL8m)?!)q3^yӶvn&niƴO#IgdRLձ\7&VIVတN1:: u~.FFFxTxu-*OڹkZ<$mI3)fTjrݘ[%YqR~HE,nbyRs**Of.CIg:mLӌi5-FPɤK3c5nL8M(qMEe, !!ZqUJg:mLӌi5-FPɤK3c5nL8u? 縦rX(nx%$yyC!"=sl +BT*all Ld2CTQpKe[%NXs=˰w^LMMw^W_+,"X>(`˶`K]R縨Lpy2LՎ#.&&&^&lliqGFI\.W3~NDD>(`˶`K]R4Plg4}.Qm8bh8 $4FGGn]Ȉ1AD[[슕2$ܕW^ t>HDD$D[[슕:Ee,g[fyH2sl sܸrX(nx%Dsxqx;PI(w IDATjSqXD%eKJ95U*066L&ad2T*M$CʨT%zIRNjOV+ >P(zw]\wޭ;4PI(jSqXD%es]Ư&B! eTu*HlwQ"\g7aFmF["7kkT/ڌX< `rr*Q]]]V|!`jjjgbbbؕ lYm>azmFsqd'pYyXt)}J0>>իW#'~T#1188?{ ^ڎ}>l3d9]m\bgSSSXz−Gw$F;&fu?s礗cی(wuW[6EOOOwI>3F Yooot:Q[{u122 &'6#J&ncՖNq饗֬Q,^XSd o"}~>JKbyq**ONr / }_V۱OQaQm?Ջm62 m­yrfVm3QeYZ/l3HHE_߬Vzk#-`s XxPW/*088Ca``@w8|U>}>nBBBzg4a3WSSEr8Mctan"M`SB)!l@6E l:uY -]Ӥ@#GTrrNڵkj˖-u~7ٳBBB,;+;*00P:ujc eY.:ۨHڵkpSNu~ $i֭ug+88X:tP^^***Q/?+77WzRxx,Ҝ9sO?iĈT=nݺSv|g$fo6~~~&''̜9tO?Ӡ_~&66֌=ڼ+f&::8N_{ܹӴn$$$SzʄTSUU530 0[n1L4tΝ;Mpp 1:uzG}dM.]̔)SL~~?CcLVL||8q9s>|q8wޞ~ԋl߾8gza,2gϮӯ1j\~&$$iӦdr֭[3a3a?{|$jk%K(55Um۶5j(MJJJP-X@Cүup,C;fGQxxF`ү?+ j `poѼu^LDDi߾ȳ LXXIIIiӦ˲LM~~2e~tNCHcnvcY8p6m߿,<裞> 1fժUDEE<3m4ӫW/cY뮻<{:u7n0}5ƍ3ƍ3n8Q]]mtb\.;v6mINN6-[4a3TYYiƌcbccM``̒%KzY8MҌeY i&ӳgOt:MXX2ww|ӡC`?:M --\puک>|،;XϖZ1|&##d~iS]]ՏzOٱc_cƾ}̝wi"""4=z0k׮m5˲#˲|||4vXuU?\.W1''G?G裏*''ؓ4gpvzܹUaalTPPPS-O|233k.5<555:묳׿UzjyBZddղeK9EDDxڂrJYdt բE` 0@={ѣG{]y!=jӦNt颕+Wu}W_|Ij֬Yj׮BBB4rHhҤIQTT&L5O}(44T/ַ~nIݺuS%Io4|wQ^a{λzj 2Nm꣏>ҶmԷo_m۶MwVZO?TÇW_K.D5e͟?_;vԮ]~z1/RM8cgÇkƌ$Ovޭ@%%%)==]˗/WwW_UQQ%I/^Yfiܸqγci7h֬Y UXXŋK4qD-_\\rJ>>>jӦ.b1cccUTTt %q( 8z~FqƍVbbBBBk՜9sNjn;VWW.SPP^{5]v>W5\ssP())I7t Զm2dkmٲ;rͭZ̙3խ[7]xZl.\PIRqq֬Yn `zNRNNnOhs=cV6m4mڴߋj\.J}Y󟒤 6(44TXBx@WVNN\.222kڲe ;KFWX;V{tGFF*;;[eiРArݺ;ԪU+Ĩ{Zv -O>$u]^{VX^S=g4;v[oUo$7Խޫ駟Vuuu󧥥ye˔}=xնm[iȐ!Mnn}_ .TXX0UUUI,XnA_xϙTq́022R<6lؠQ={T>}o߾zGEEy Q+((p7PΝP-^X ߾}-Z^xڵK/g۵ku +VT\CW\뢋.܁!ڿЫJUW]>w,Y0effRퟝ: .ڻVVVjРAjݺbbb4{l{j۶q@0G}`Cmf@6E "M`SB)!lo%IDAT@6E "MpY=IENDB`PyNN-0.10.0/doc/images/examples/stochastic_comparison_neuron_20170505-150418.png000066400000000000000000004167361415343567000265120ustar00rootroot00000000000000PNG  IHDRdуsBIT|d pHYs&? IDATxwXG{.M" (V`(ݨ'ƂQco1AcCP`ޢ`o`Aq?ξ9t,9ﺼfgwgggsvvVBޑv`@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD)DDDDDDz!b@HDDDDD惛ʗ/ɓ'6ladd:u*v܉ ,#$$Y|rtm+Ya,hҤI,]<%lFx VT!<<,c޽EP*Mo⮻!!!,_~o(yon_u1Ysg``:B] cƌw}իclٲ+VĆ <ݻsk׮X:u!!!_> _EN`ܰj? :ݻwGٲe 4?kTؾ&**m!_r `ĉoT@7oDF5ύ7 OYQe- ]?v)S@A Ȼ-lmm >666CbbڴTx{{H˗gN:ppp '(KVY;v -:uꄤ$իWѭ[7888 M4Nw||<Ν ooo#Gē'OU7n rꫯܾ};P?~\-_ƍGJWޜۨPW;kǏc񁝝LMMѣG#99Y###ѴiSewwwǎS,[,fϫ*}9kףW^X",,,`aaڵkc…'1pssSKYɓ'Ѻuk8xm}6ҥK X|y<~gFӦMꪴСCZQ`pvv Vddd`ڴi􄉉 ʗ/'M|wZ*LMMQL :?qWmuzsڵhZGA֭agg07n!C*U ۷ѣG5SL6 ժUJ, OOOt 'NP˻qF4mT... _|b~ZjpwwG_aff *\N۶m!˲r~LBBu{{{N:زe2 s<䵭Ippp)ʖ-`ZJ#oq]䤸a^$뙶6N: ([,>SܺuKk ##SNEJ`bb\3k.4lppp@~8qڶm [[[XZZ}HHHкރ۷oڶm WWW W^Tv܉mۢt011AٲeՎDGGcR `ffjժaԩHKKq[׬Yuvvv޽;n޼u=%K AAA:ܽ{GFJ`aaTT }E||<o߾hҤkN׫S9s&WsssXYYaÆXz9r]vE2e`bbggghB}`jj7o"&&;v@ӦMammݻ#""6m$l߾uA͚5զݻ7>|HtwVQ!?me9s&ʗ/] n‘#GftE[\m&'EFrRǰ/5gK| LLLЮ];Xt)6mڄX: ѣGѲeKtjal޼m۶qDDD !!ӧOGӦMѰaC|G8}46mڄk׮i\TAroߎ6m ڵ >|/&Mp׵ɓ'cڴiDꊛ7o4amk\x 4@6m ߿سgvޭnUY{lڴ ڵC`` bccSN!..FFF<@PP222 !000_> 4kvAlܸ;w:vI@6w@ͱw^x{{cȐ!HMMŚ5kеkW}YDŽ,"!!A|BemqjT1x`~ Y~)all,dYSLѹ]Y=~X>^ٮiӦ߱c$InZ-0C^ۊpuu/^XVugIQ]1r%QD %nݺ6-**J@!IQZTQݣi쯠 !Iz!˲ظqb?hFYӧ5#{/uMu|yzzjԯzd5i$!˲XjZzxx$IXYYgϪMѣeY^Z-bŊBei&oVkm7mڴIH$F1-##C<}T[u#O.$ImڴJ{eY꼞ZJH$ƌ=zHX[[ qgϞVZ&I(S@m'>gk@럭>>@|wB8::yQzuᡖ:5j޽B$Q|y`&!!AH$맖j_}^* Dҵ\QD LQT)QnW!$IM|Rcz^,_UK/PW^ +++ѴiSU Yɓ's]ƛu9v옐$I*L@ذaCHԩSGIKOOfffF+ ɰaÄ,?rH$,,,4v!hԨeYdžf͚l#>HuYǕ.>>^cU:uƺk֬6l$cj`!˲ضm7={Ԛ?Zj ѣ\kmB$k^!S(Qs玒)\]]ZS]ݵ0U\9aoo 񐟶bgg'ʗ/dWm&'EFr&a]?BeuV;v(ڷ^*sY>}4-_\H$5ٳGHF/mh՗/_ֺ-Y֦M!˲ذaC/IDUI45n߯dff OO|_|Eys ===tƴKjl!C,bMߗUbEabbqlݺF@;|!˲Xhe1BȲm…Z@m'>gkܲ(ڵkcСe֭Cҥ_b-['OѣGjZCgggfD;eƪWWW(Yy?SNӘ.1Ο?uT|ʣ鬚4i+Vĉs]^A|?"##q9~gjj*Ξ= GGGT^ʔ>ĬYm6\zϞ=SIԂ Qti>>?# kV={ѣQret 5?J*,6hiimbAhh(>ԭ[ ~g}III|XcݻwK.itSN"{wf #?gϞ?燮]*ۨRX$ -Z(rRSS1|_.]“'O;t]%IZ?YM*EYW橬5 fΜcǎUVO>S\reʔ[Ս59%66Ժ78ϋKӧ&L+WO?߹s'j֬U*yUu|zT4]猼^ )H;Q9"y0+///T^@ʕ5߈#`8;;#88...Ja7H3449Muк<]#_;ѣGB޽{9ƐwsT/S.EK.X~=<<<СC8::*y^ WNڵk#!!~~~ӧlmmahhd̟?_K|Vj?n+]?;w)sssȲ{պ9Fmmm8w.i 4W9YˢmZv<Y&2<}:u*֬Y> BXZZO>1c1Xh.\ x}2{l7Gڼ6j*|ߕALLLЩS'̙3GyG uQFaɒ%J@xbH{(P!?me/3g΄!ZjoT^HN1n̙TuUN[|W麯Цcǎؼy3/Xx1Uf̘f͚,$''FyK4nGAjՔ0T?nk]&Ir=~}>xB\vՎz-Ц0>-Kg hƌ8pqY̘1Z{a…Jӝ;wFobuP-KT#T:v֯_͛c֭j !_W5Т|VK,Q~Cao\ܹut]&Nccc;v ^^^jn޼12]AXYYÇ uќeff/YU9ϺnJ I6nܨ.VVVo7իسg~G|wHIIQ?ć~Ǐ?tR… :۠1&MI&ƍػw/"""b $$$`Ϟ=J^aعs'*W۷^zVZ7xO[Q 20l0ܿ111Xr%VZsٳ022*6c82<~Ae˖hٲ%?Xl޼-B۶mq amm"--76l#GЯ_?۷o#<MNm N +/g" ѽ{wbʕGdd$ УGӧO˗EU !`!F !t9r$зo_ђ-)!>>^㝋Xz-*}] 2D<Æ |.7vvvwn'nnnBhĉ9s^ DW`dd]"99Yc8'O_peW_}AuꫯND/^=R*33ƍSk(_߶ou^:/^xصk4~㗮#կޫ+lz{ >} CCCxzziӦشi~X[[k׮ۘl zέNW^a̘1jǴi I86mTa{^>/,Y+V@DD$IRTJ߾}amm)Sȑ#Ӆ:ik.m'UJ]Fsz\ޡCԨQOJJ… n@իW7|!CO>ʯKFn 4o)))صkLMMF8I@Ȏ;pIԩScƌu}DZh"xxxE([,>|k׮a޽ׯ-Z~`رعs'j׮|KvSn:# wmPR%%>#_E о}{͛B8|Nmڴ)=-ZaÆ066F5ЦMg *ؼy3BCCrJ˛={6>#jݩS'fZ^:bccQV-4o=իٳg=644ĺu֭[A񁙙q\v n݂ N<ԩSpvvƽ{a|ƍSݱcGXXX^zMԾ}pԩSkhSTmƍEjPzuؼy3ܹÇk=O 4wݻw1lذBKR!m'jժrŋصk.\ۣbŊͼMonڴ)"##Ѯ];ԬYFFFhذ!>TX?3*U 88^^^ױo>888ܹsڶ\ަaÆƍJ(cǎ!** ֭ ׵ L8_~%ݹs111_>~˨ܹsq)"!![lA6m^ bҥh޼9BCCOOO!** -[S]va̘1_>$lذj+V V\ CCC+W$wpuuѣm6lذ5j@VիW޽{7n4h,-ۣB x9++\?֞߇bbbΝ;cҥXhotgekk5k $$CӦMQJHDDjjj֛k.m'U3!!.\(dY;vԺ,Mϟ=\L0ATPAeˊC@a``hU,Ⱥ-M4QKS -{51w\-LMME2eȑ#) -[mۊҥK ccc$֭+&M$.^umn޼) $܄!!!ѣy޶ىG wwwajj*<==ń s] kkkajj*ʗ//z%N8Sٗ+gĠA022,}*ϟ?/ڷo/J.-,,,Dڵ?c7o\011, ߶k{xo„033_k֬$iie˖ ___aaa!Ehh8s}S; CHgddiӦ abb"ĉEzzzԳ:txqWoݺ%z%J.-LMMEڵʕ+5~TwEjՄ^^^sW%$  puu[;vP[?(BBB077vvvf͚bΜ9x`rr6mhڴ(S011΢q"22R|^Ȳ,Ν;5Onrmu!D 1{lѪU+Q\9ajj*DŋEFFFy(HN{ٳpttZ?rѷo_&LLLVOkd:#D9j?hkW=z^^^RXYYjժ'ji~k*۶m-[vvvD-[Vhԭ.III?eʔfffjժbΜ9˗Z9ݏt>8~hٲ(Y(Yh޼8tP>uy1j(QN\t-8Gf͚ kkka``41c QZ5aff&J,)6lyСCSN"Zl)֮]zh΋KRQbÇ7!!A :Txyy SSSaee%E޽5%~H_ tN z}BI"ַo_,_׮]CٲevqH}9s&o߮sT^ʝ, TS޽6x5xzz>( mtP"M5TVO… aggFR>ymp}d"""z;D]ڵ鉪U/_Ɩ-[ %KtKTTtLLLo˗/#"">>>ԩS G¢(>S_+Wē'O`mm-[bv{$śnW^ann-Zip7mB""""""}wB""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S B""""""=ŀHO1 $"""""S ^7n\\\`ffzay7%% ,,,ФI8qBk prr3|oƀP|yFÇ DDDDDDy! !.ěҽ{w[#F'"""paDGGA:B OرcaggE8~8<<###sذaCHÇ#667n֭[1}tt^^^V"""""Л֭[prrHwrr7o,мyoݺItͺooo,^gϞE6mڠYfjO.sk( 2BW_]yuE֭QlY۷ ,fϞ#"""""* MMM ezA$IW_]ychӦ > ___@v`iiSTR+<{ ,C$ydYA"""""zS233+ӄx!zөЛ0{M[nsW/yUOuͺŋQ Uڵkp8p OgPd\o?~ KK˷]5 }||O ,s!HYCLjժ044ѣGѩS'%_FFеkW%Ν;Xjҗ/_iTܻwfff: aeddϞ=.ο nnn ''7@oN:aΜ9Xx1F HOOGDDի۷OOO%ԩ֮]u!$$YڵSN%KDfͰb L8Q3tE)vڅ{aÆJI4&jff3 122Innx~`Rg^7]bOOODDDѣ? ,, ˗/G||<ʖ- {z8{,FRJaѢEHLLđ#GPBe'N?1`$&&bܹ ֭[|.]BZ 2 r!::+WD-m۶ ˀ_5%zߨ~=?02L)½_amyVԷ]"a|Wx[*!BR !edd:0/W^cJ|,iff@V6v^VVVWZ .]+WZb[УG|LjCѡC;wNSJxxx`ŊV*  AeބTpqqڭ,==GF2e`aacϞ=tӬ 6 FRRG4hҥ(_}:VX 99ݻwW[?u?D\\(,^ΝÈ#ЫW/۷n.\;pER ,͛f\t r &`ر8y$УG|8p F84i_}U N1|,Yw[0z|Yfaҥ8{,QQQuۇyaҤIhӦ lmmqa|OpMeYڶ}„ 7o;CCC_mjLt7ƙ3gp! 0$[n5jTlc׮]534ln͛qi *Ԯ][봙3gbڵC%%%M6EZpqرwE.]tO)S $$gΜA~pMnuũS?`ҥ/[|9,,,pa̚5 SNͱѬY3-Zji~~~8|y:O 轖.B-g(!̊PDDT>}*ӧo8ySall,֮]=|P#F!HHH֭[j6kL|B!""",ȑ# .Ipall,h޼9ЬY3t9.?'OVVVy߹111љ/Dʕ{niEEER-]$\ry.{ZpvUS$%%)Wzu!?1Eennlغu+զqulݺvBf0x`̚5 Ƕm۰{nt͚5իu!`%9u?,ӼΧz?3H5-O>|8oߎHL0wV sk[t$Ix<˗G}> ?ZpSkfGʲ1M[7=Oq;wh>$IA0[t7...033Cz{<͛hҤ N85sss899a@gF IDATxZPĺe]TCDbh:x+IU=z6H/233q/_^ퟃ˗8zŋʕ+\ʕalle(ЫW/,_ŋiܹ3~GDFFbڵHNNֺX`%q슪NK(Leu_kjԨqaZ*~wڷ1ի#..N>+WVaSI&ҥKXrZY&Ξ=riԱ*8ǭ[y?~k׮Z6ooo{ʘXZZj<]͏kck.gΜA2eԞӻG֭È#鉈j hРhժN>h"PšYf\2͛$̞=ZPTPAc]9={:uG%"w{W߿?ƌ[[[c„ 000PTP=z@޽1gݻB5bСX` 0tP4h@\V=z4FL %%zɓ'VZR ^x͛7+Ay___HUV ZחNvCEF0o:t'Nuպ-Z &&Æ ӹ9r$f͚>x`O֭Ǝ [[[\|Xt)$IB&Ml2iVVV裢(s5hO;wL1,,߃_-6L{).^X׿oɓx+-_./0xƍbΝClMe ӧ狁(.FFF[l1]]]k׮뢓x9mcccE\nTsω*.\РJ;u$&$$4o):99Hh넰3͜sIHGOxKFlloHXn8a3!!={6n>}*?*T*TTT/F_~ j@( --0֝MdJaa~ms􋊊PWWg4Uf|'իFi=% N%""+Wѣ_vZlܸQ?vڅŋ[%Ic-#sssڵk, BLSNk׮Fcر;v,,Y/^[W^^ߩ! D57Ο?;u8`ǏѣGpBKnpS""":t/_k׮O>Xv-7^sF)00f͒: 3HYH҄_>hյjjjI7;3ڊoinSʂ 6mZ78BHDD78IFؼy!]FR{]t?OOO߸qC~k oinSO1p@ZY4:NNN;5#DDDDdKjlSmmI~o(Ę1cC;cǬ\.78EGWpִM?5W58A@hhhm9bT///Cr7 56m'''<--=R Je06\bUTJÐ|ӧ1h #F^z%L:`ŊX~=Ν PT@DDBpp~eLL rrrm6<#Jdgg#::Hdee!%%EFff&J%bcc⪫Cvv6>z뭭?WWW}!͇f߭K#yBXWWg K^^̙gϞ::u*-Z . 88()) .\L9s 5kcǎh4Xtu0b5 (--ŪU0a?(/.]“O>i=|w.uDDt|h߾}_Ξ=b5JsLK7nĜ9s$cΝHNN,NNNصk{1]χ?݋~ Cnn.0w\|HHH֭[M'111mr!DQzL|c!??;wƷ~W_w};vHRpssJj=9HO7`6 څ(" AY[}ƥI>e4!!رcF%KyY}"B" ܅&asj5jkk-u(v=3زx ##""""j)ڎ#:gϞ5Y^PP$8"B"""""b7 mf4wX',,8&//&DDDDDnB믿bٲe={DQ9//;ADDDDH&![o<f̘!AT9!l/jkk!pȱnTv,^III24B"""""b7N899TF E ٭ڒ7) |ؿ0+M ۱½{JÓ8eȑMBxKCE~]HV$T&??8vX;FC2P[ I ق 1alݺJdǏc۷/{GXgg """""`$2zq[/Mr ~'\~SL_~Kp;G}}}a6&!OY_~w㧟~\rr]YϞ=n~jnk{e2LRSS[%""B"Ǔjk }MBXVV`rF&X?US'?? 44ɶG1*&hG3]\\PPP`POVCP\cȐ!EhwS:nݺJ%T*ɗsS""B"ǑbHmno1*FXXĠׯחT*ddd ""zTQQ'OL m6}Yee%?2K RX}٤I 6@?Ejpn*CDD "gX y2h4l۶ 'ODff&Xxx8NE… FFFJJJ "33gΜA`` mBfرcCzz:4 .]jp41FBbb"JKKj*L0 ޽;^|E,Y&LɓP(bڴiFGW GHJ|ٖ݌N4 ;v@nn.d2^~e8q;vhh97nĜ9s$cΝ1b pr2cqrr®]caڵ?>w^ϠnXXrss兹s?DBBnjK/kٳHNNݻ6!Ik!`jARY5>k|mIDDԌkg68mnrmg7#w}gTwmBC %""DDd[vΚ5KfCeee5k9pI !mMBxqG?D䘸 IA$ADDe7 ;***b7vxTgmMB`ѢEҗ]z/n]FyBn5DDDR`BHDd[v3b 5 AAA w7JcK hȖٖ$8z(6mڄ~O?<УG  ""5Q.[pv~)Ʉ6&!|W H2GHY*]J cc'\SJjxT ""-IN/R0pQ#bBHDd[v3e488)))]wetٳ%.uDDHt;z{s;-wի5kִ5T*,Xxyy!""Bbb"퍱cǢd݃bȑdHJJӭNNNF/ggglٲ{B"" ױٖ݌>}ݯm۶!99@TTۇÇm'"PTTk׮HOOѣqW_WP 22 իq9,_ viiePvMnSN8e)DDe7 a{;t6oތ+W"990}t 8ǁ̶ݺu+򐓓)SЮy %Kxbbd $&&"77}<ӦMkmeeBDDۛ_Jْ]%ΝgBRj*Ά e9s&^|E! dۜC bcci&jڵk /O`ƌHNNƖ-[BѺI'NH9BnlFDd[vpϞ=߿?֭[+Wb޽ذaAPXݿB@HH SXX"aȐ!\]]jr+oooxxx <<_}Uﭭq !I ""۲pѢE?"xxx ''|rrr\QqVo[^^Amx '''L0˗/ǎ;f\x'NĮ]Z|mkH*TUI㰛)'N~ pqqAMM l2L4 ?UݨC~kڊoink({ꩧ0` 8qwնtY""RGAD8fP& r*++F7nпߚ ~5K.ɓ'4EV}7۾2LȖ!317lnˆNQQQxgyV/Q^^nT+ٳmuOm:z\|ٺ d2|/>H*|9T_nHnUVaذa77om݆>CCCQ\\7χ  m#G児₂zj kFFul݆J%T*WJJJB"" B"ǑbR:}0h Ѯ{GENN?&&uuuX~LR!###'***pIi111pmۦ/Dvv6E 22YYY333T*kо2lذw}7wޢ[aܢ||[0ӔMqS"gX TO>8|0vjP~U <NpL:-…  `Æ z .Dff&Μ9@ڄp͚5DZcth4,]:iii1bFDbժU0aƏ7|7nzӧOcomսnxzڤ[l 9 !$"$gΜ1Imm-7nDJJ p 4;wĈ#uAvڅyaڵAxx8233ѯ_?aaał 0w\t x M0qtGƋ/h$NeBHDDĄȶQl}ɓ'?- 2zٳ_}N<)UvMV *ʪ;9svQDDDMz._:l"rLm'ɓhG suum݆+WJC7DD$U Eh:>BFݻ7> ???#"7 IDAT !ٖn auLڟ ӧ5TDD$~)IDd;vsٳ;;[F6Ç1I ""۱0''#G4*>|8%ȱaLDDR3v&!t:udTcwj_|-5\鉈ln`ڵ˨|׮]ӧ96!$""KI""۱MeΝox"Ǝ سgV\5kH]xS!""ńv&!|gP[[4n6[3f̐8:Ç1I ""۱y`墳quDD!aΝI½{̙3Xp!~i{<|x׭VMM ݍ=<<﷦(6Zܹsϣ-ܙSF6e痒DD!iBpmٲeXjx }Ytt4._qqqV]F78hӳUmAз4W5VZK.aҥ-sjd{SaLDDR3㫯F1^Smm9yyy:tQСCq!(//7*ו궺HKKCBBPRR3gE%%%xbO&+55E}pS"" B/55׆4RW^?CCCCQ\\ׯC6ȑ#FBHH`pqqAAAA=Z Bƕ+Wpu[ݻ7z>} ''J{s=עS*PT&_)))-KTu|)))f?*Jsvsի裏b׮]6lف?3rrr?&&+V1w\JBFF"""GNTTT i111m#*++h|||,̄RDll,b|> =Zx>B""SkO."j.kg7 aTT~g㧟~<_&#:u*-Z . 88()) .\L9s 5kcǎh4Fk0b5 (--ŪU0a?vattQÇ~GH*!$" I[oknoܸ)))•+W0h ܹ#FNN3ik.̛7k׮EMM Ñ~ Cnn.,XsSNHHH,=T#DD$.]x-(RsU:tюC3f̐(*VJeՔѸ8`0?JKA -2;"r^`|ai""ig\j݌رO>$_2AJK h'~:"CHD$ /gqU\rE9#ggߥ B""۰ gφԡ.]:""r4ufά&"6b7 #1 [NҵkRGBDԱ¿/7o?hc1!$""[hŤ y-HQGg7 aBB`ٲeF z[D`BHDD=qmRGBDqMB ~~\CHDDDDn}ØҭpQulv߿?0h|7R IsϏ !Q{0++ ٳ1{lxzzbܸqOa1!$""tgQ{5iiix뭷/={6VZTL6MB""Jn@qQulv3Bx)ڗL}0{l( >"##ou|Mm*sNv9?>CQQ̙9s͛sϵ5T*,Xxyy!""Bbb"퍱cǢd݃bȑdHJJR4S^^z ~;|||ХK 6 Vg[\\W 7!"jv3BSL)Sڭ8l۶ FFFo>)("** EEE?>vt=GA߾}u "##1`^Ν/`Νz8$ڂ;݁~: ""TQ"e7XFBPPhqv7nOOO߸qC~k oik zC899մ)f_LmbG" KaW#)<<SNŢEp###%%%ذa… 3g 006!\f q1!==K.5NZZFQF!11Xj&L`9NZZ[< ˗̞=}ɟ%w._jkADDN.痒DDaBظq#RRR+W`РAعs'F#K'''ڵ ڵkQSSpdff_~u `ܹԩk{衇p)lذ/^  L>ZEh,$""\B"$Yt3PpssJjh}4` ! sO/ADDd֯Ç.nn@U` "3.5n>|}Qw} "r,Mm*hr'S!ggߟDDnYfFeee5kQCXiDDnǏXhW^ŋ kB-2jb/Q̭eDDnVXQF!((aaaBݻcƍGTƒPT{]\DDr! "&! ѣGi&D||}>:%IR$2SVVHJJBRR9`avv6\\\/sww̙3&JA= !66۷oZ577ӧO'0c d2lٲE_f />}Bl6!8}ڶqQ֭vK'ŋđg7 ̙3Vq \|/_Ɖ'hZݿB@HH SXX"j"44Æ***lf8qPdaBxTFDD_s!A~)ܴQ]?\@DdIߏu֡cڵOlJyy9rQ\.(8|з-// f6u ozMeo__Mxо&#k9|{p!e&!ի~eCٳݨ\MMMMڊoinS׸x"M}b޼yMߌmzOM:޽O$˗/w +VXݿ'jkkoܸ5mAз4W5J۷o7Z[h ZmU__in Rp0/MEϏ !͠ϰdv>P(6laÆȑ#xg\.GyyQQHKꦟkjSL??wqe7ԈL&Wjjj4Œ)7!"d %#D$T_nHKsuִطo_nL~~>A@hhhmMK/// &&F_OVCP3h/"O{b֭9rdOTݒɩ~{@M x !Y5&BtsHII/h=ZͤF&!kcbbb _sT*ddd ""WQQ*cbbm۶G=0;;DBJJ/qff&J%bcc b[b4iUj-l* 5N{hD߾MeT*tQ&_}e\""Mұ7n@RXgxx8NE… FFFJJJa} "33gΜA`` mBfرcCzz:4 .]jp41FBbb"JKKj*L0Ǐ[f ֭[Ç6m2Gir]Nm*!KFDDWN%%@~눢v77ufn6v*J,X[l%ζ(7nDJJ p 4;wĈ#uA0b]va޼yXv-jjjLkD Cnn.,XsSNHHHkfP~ C^^Qw>!7M#8y6ҥ iM%OdBHDA㴸Yfa޽HMM >x w}x饗kMגM2GDDZm,{DDd I/_>}Ю|2`ȑ믥 aX~ ƋoFmu".] "$}?۱eڑΝ;K5?\fXn*!'FDD7'Kg !QMB~vw}HNNƼy$rw"a΄Z’*ׯ~_Y IDAT6F"nvMNNwdd$~'|ƠA$L0@=7!|EDDK'A a.@3uDDd$!((H0FK%h-w?h:!|qpB"UBgٳ4{׿$qX M?̰qBxyi۷MB$"%_LM{ = Ld}|DDݬ!|W`Ϟ=ĕ+W ^d_,!c""XŤnД">g7#2220}tC! =: Me`>Qk9ݸӪ#iw$57HDDv4eƍX~=rss1h j*"s-TM%TT/HDDWKCCjAoXY4h6q o:I=PV -T& 6nwS v?c6>""긆ƸM$${:j0N7nh 67'4(, gqE=A]BHܸqWFBB֯_X~SRRPPPK>-5Mƽ-_Ѵ>u -ykh B__woLK~jy}"$eeeQYYѣGoǛo6СCؼy3x xggaMݺu+㥗^?{KCYx1|}}~$&&bٲexw_F9seeeظq#fdm59id]w.Y֧F-=@1,>W,/ڳv::I]\\0el߾HHHMhl߾ggg 2www̜9yyy(++36''=z!66۷oZ\v >}:d23 ɰe~[}?b?K]\aÀ<Eߵ̉0Q (uuwY#ݻcȑ{䄢"šo߾طo_T( AyP+8,6*Guu5EEEÐ!C 깺"44ÛYh(PYkjspBFDr(hD~)pFI[$MbJDUBxXwy'Fpi!66qqq껼rܨ\.CE?UmۖCuE;mȍ-[G(ڵ&DD1tm>>Jz |IJ~u_,"$?0zꅌ $$$ ~)"##2 /JKK[MM ݍ=<<﷦(.ְf }5ڵhQ}:6AЎgg 0z4gej4D3/cUQe7 ?ߏśQnݺӭF7؇ӳUmAз4WkXKV}[;Kk7:?E5Gs -+C].o'#Zזn$0n`ISh8r.5ϰdv~G{#Zտ\.GyyQgϞVM?5WkXK&+55ݮ h96T|eFFZVߔ˗ST-dZǎo]lvk8ln] hw%!߯RRoRS =]G''C f6Ckf]|B~%WQ[ "g#I)55׆4R9Sgkm,DDmAjs#۽>u#il]lM@[*-!ڗ=IJoe{4癔 K!yBP(0n8} >s?OZ#<<SNŢE`|3f JJJ[o-\wq1116l㑚ua̘1h4Xtupe5 >^z%DŽ 0~xYYYHKKʕ+GZZZyNkY;]bDtƺ:u\]Ft#AA@@@֭سG m-F;eԚ?[k8Mp34kamLXMM@ H !#$ 1 - P?,4 P, Eʽ"*i"7 ER44ǸldwSyg̙s{f=DTQJ hРիcɒ%z*n߾۷#44q:tٸ|2.\hta"?;vsJڕ]gO5eJv11 '0MXƺ}KӫquqRzx²ꈎ_lKӵkBDT7&܀ngSN-+gAGN*.zUnܰ~][Y6%IRϓOU5eX>"2cC+gnqcF:j֔?L|@V}dX^ֵ}ԓdp֬Ypvvc=KbҥpuuU^OѵkW;,բkeA)%GU*9g:QCYۆm8X|Q$%'N(r" :%?O?-s2%!@ Ӎ4W1} kTf[ueE!9JnU=NNӟ=|Oz~gvBff&233ѻwo׭[ӧ۩u*23_/"Y3]d@@u, ^OEu~}ȵc=cqL\fK/d[G\1B.7!/3j Nm tu<̮fm0Wzȑ…ADTT*9c{wj9j*QQr5kK.Gpj]?T\[WG鶌)D[9]m͚Y̕<@.-ӵ婧䳐}T:W^aF!مJL Vc0z4ۖ_djɒ˔~h{>@>Pz:wYJsG/Dtĥg1˃u""c*j'Fo\ W~HY֒78U*`7֖#kg7/_1o n޴>Sh`Wt^V[ACtc@H3gW*S)_zIN嗲.!Rӧoe(azxCsbJ3 .X:X۷Y7ϷcL ue*]Okvu8;S'P~Eycr~QCN?e.Cr4 IQ1 ?O#%4cwfA=j0iRu̻wj׶.)'޳ =[vĖ.}-YOL|5N*WW !Av~?:m!"qd3LCnn/Ƃi䏥7eL9њ×'* hDN''ٗMnyr1c3l<k_OOyu モ}`@HvշLw^_P2gYk8}ȫT⯿,k_: NYVhZN~=jz4e鶍ҩChѣqkxn?||%1/%K,k~eKԚ9T5ƮSOɵk4V ,Ǎ|X=M L 3f%GJw9?یu~:Z{U%4uw72Rf2Ų cT#K: Sh<-k`XO2Q5]gggfS6.NeK""~__zI~Les3+>P~fX$pJ3ud|CK;pXg{*C l74=W E;%7JMeXsBtc@H{];ye;̍jfĉ˘̑w춙g$Β6hD3th fL\Ӹr%GK\SuxϘaYKDTZU/]dbrfn䈔LS7yDe~Sm][g?_wK5uk >^e%1UJ%`j{[|h^u*q'=aLw1?BNcR=9xs}ԑw ;*iu$We=tɓˮ0^/n̛'5\[˻}%"ɵ}G1Ct>8x3n˺9yBa1t(Zv`jooySqذoh-3gg^G`\ѧec\i{&i߾g'0 $=긻7ʎѲ+^?˺-8>|v)e?sz&c''fj䚫0}]}VvZO=%kSuxz,N|mulӦq\OHDz[\2ׇ2Kurʶm˘76g6lBsuTg<[jzL}vGEY<\iKnn|.4u>: Slȑ擝ՖT{ơ|L8h4Gy&QNxzzsH314w^DFFBFBvvA9!{=[ի:ӔY{P&wG.)KFGf++ BVdelMQQ kڵѹ:ue6yN:֯/CSK˺. D˪C䚜8􊊊xkxm+m)**’%?o+Ls\WhPySo_Ll\{C;v/SjAlzGQQ_ߩSTVs7J:(_4}cђ>OLΕU#@O?mz jYu89ϑ#nZs* 2d,XA>3w^ !իWcȑ3g]N:ze<̟?qqqXd bcc 2e &Mnݺ?DÆ 1`>߲*ܺzQ9uU+$GEvnI@Rg W^\\MHfI-iCt/l<)KYhq%%ɻQQXROTD{ԡ9vL-=}3fǼo%׶cٲP{mWo<ɽ.7'K?6=&g#Y۰"rt"߳30r%Ҿ<gWWefP-wo&1*xvrK^] lsӾӄ駟JS@av5kJ%6lؠlv=^=zl[lPb۶mʶ/ WWW1rH;v(4h -:l@dgg[Tޔ=X\UUrB̝+DQ˖ kپEEB+۷ݞ/g-g|!jb*Ӧ 1~em9vLF))BԪ%B"uK#G wwyBYVB ѳW~玼7n}}4IuغU#x-rW_zI:gQeu 1pAABkz 7;ŒٱCv, ! ԩPy*;.aFׯ_ggg)0|p۷/^4orr2֭޽{+|||͛7離߾})))4hZRvBX IDATjz#6mBaa!Fw#F?>csOȤgu9=~:j\r@BK=Zds ڶɓ][oo/s MVӿlKN$\l]=< %+t"XSZ %:$Y)"IΝ2I߾D|^֭+ :uk>pj9U+<-={$oF5uԩ#?c&>*oZSJ%Ҵ4-0Pi]=Mȥ%Æɛ={ʠݚ:sw풿 ~0aFF AK&[/eߌ T*eK###eIqvVfC-U+zL=ժloǎpvZ={ʅF;eX_^ݻ]={G NAʕY3M37&rȗ_@*sr瞓GַS'7&,srڵ>^X_ϧ6Q#y=mij8;GI:%GtW-=$G ػ Ո7`,s͌WTْ(O{l5jȎVMfKg`ll9fpqkn[2kcF#3R`pi2 Ú52A&2I{;'O ʉ7 L]fjB><jKl[=MʬW ps~~9rGVM<>yRޤc ;u78ח7@ QlGhٛ 11o6R}u*[iOITb90ZS=ҭ[K/$@YZP΋RͭuȻMU޶Сu:$;&38!rV.3֭+V&hyyɿk\\df:ݟ߁J%;`LU*tTO]ۣG˾dݵP%\]>յb g6?ej04þdl-**2E47W(~HpM @z+gn_SSeK;w=%EDc8o{4Ѥd X\ۛ7UРik[YޭbVy,ر*h?;Q& Ν;XfPT 1ݻ ߿AAA`8;;Q ==}իs8~^bKSVŭ[V2q ƒB""""dnPbȨ* @xx8Νcڵkc9/_7o"00P ֮]cݺuxgׯ_GPPz/B9Ntt4>'N(o˗#>>| tx"/>Pر#Ξ=sΙ *зo_lڴ GF`` VXbǎ :8{,4h@UGFF_~qŋ… ѴiSiii@- .`޼yԩkL8sE\\ڵk7b֭Xjh"Qep0??SNERR233ѪU+曈R 6 +Wę3gn޼cӦMEXXΝPݻ'NDjj*зo_̞=ヒO>hڴ)L~$"""""CDDDDDDt<1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP B""""""ŀA1 $"""""rP C8q"hy&QNxzzsHKK3Zv޽VFlr/_F||<h^{ WΓ*!w#Ja3Xb8;wC&B 22G PV-,^ϟGjj*4iMOOGвeK?9sйsglٲE)|x饗P~}:t1R@xx8}3pw6ڵkѯ_?$''wׯ#((HJJRFGG8qZ-`刏Ƿ~(_~b˖-޽BBf͚Tnݺ¯L]~=lsssño>\xɨ[ bccyfn߾ 4H `jXv֭[:unݺr-Q& LOOGPP<==)6ml CNNN< 8r Ѷm[r... [sرcGT*5 ?.^gF޽dYaŒ l.]i_ʾPT&˖?y<hР~G,\jœ9s,?9""""""8L@;wlS^e_JTْسg|I8p^z 3gѼy2ϫPPTF˨j899YQU)**Bqqׄ(..VZ0aS6u2223}uOM-y%KnݺJ0ӫW/$$$`޽٨VZ刈7n݂0aHHv܉,2JBHH}=bh4J`8;;Q ==}U]rEEEu2Zt^;&׮]F1Y#+((VEvv6\\\ݜ^k[yxm+mVXp._7o"00P bbb 6g C~zKVS*HLLDvv6bcca۶mصk:vl_jT*ȡ)id@HJk[yxm+mᵭ\6\3SK8̃o߾شiF@XĎ;:t(qY4h\_~ƍ/^ .FӦMc!""-Z@||<.\ySN믕r'OD۶mV+aÆعs'V^nݺa֭SNNrga*((+!_xm+mᵭ<׷V~ǭ:3B+WԩSLj [lQA@ޅ(=4VuV?-Bnn. @hh(RRR0qD;^^^ٳ!55o \|Ä Pi׀HǡFx_ᵭ<ǖk+@NNN%WPPooodff}[ x}+mh4SC8!v Vٻ%DD999z}hm* x7PTTW5hY}]|Xd 8Vnݺ!??_)3`޽k*B2DD/''1o<27nxxzz}?sx{{c w?C)aZ|9BF@@4 BCCw >>ڵ+rss1c |ؼy3j5k.#GK_Dvv^~)Q*vyhZZqq1^x:7o|;w#<OOOx{{GŅ cƌ8tDk /^DQV-xzz",, ?[;vFAXXN:ڵl2lhٲ%>#'Mf͚AբI&6m^{?&%%qƨQopK*..ѳgO7n`ҥ&͛7m۶@`` fΜKw=>y&j>V7;裏777hIIIzVX|1 .ԩSO"88t6mڤQ"""zju=@}-??_ׄFB,@YYYnUF!5j${qQѳgOQZ51f / "##Ş={ę3g/<<}ƍ"!wNNNb޼yԩS⣏>>>>뙑!\\\… ŹsѣGG}$EVV۷W^W\";;[ԫWOG;vL|" @ 6LwC,ZHo"--M,ZH\4h ֬Y#N>-F%DffB RSSٳgŪUXnBBQF 1qDᅨǏDq'ƍ'z!yyy8++K{LݻW9sFlذA߿*;ٶm8~h߾xEΝž}Dzzhڴx饗شi8{ظqJzK߿_;wN|WO̙3G%bbbıcݻx7Ln҄ZW^ީS'1f`Qn]#bqJ]vի+Wgϊ fΜ\Z-:s R? bΝBR"Μ9#233ņ ũSļy󄳳عsΜ9#T*^{G6i$NJKec@xȀЊ~,`M@X\,D~~n777l믿FQs gggoTTxׅ2 TV^?~PTʶ&O̝;wV5^xs !ի>|9RDEEŴi̞ӕggg J%[+O>B{WJ ^ZBB 5z\5ODō7L$cիW Zȼ;Eʿ!䍅:<ܹsEvSdgg+&L ڷooM6 Ν;QF7BQQQw7))IԫWOqzXի'""B޶XO*=w^V˗ ׯ޶>@>t/bR""),\]+|e\OFAA”mh֬GAAAB8f>|||;;;VݬY3ԨQaÆYR~CNNtWwAAڴi1b}YC׮]O}CK.h֬w'|]t1{=w???!pUĉ _°euԺuk_Bpp0u놮]"&&5j0Ѻuk+"""P\\'N.]Ν;[|nժUիWmgE~~(C A׮]ѥKDEE!66uֵ:PT^}JMw9998}4^xA)STTw׬YEBaaA5jOz 777b̙9r$FaCw^zG^^zKSTh۶޶_/޶ieGlI1 $E^P\ 9;,YYYpvvFjj*ԥ:OOOju_z齦ݽ{w?_5mۆ(xgb֭HIIAll,n:m(E𣸸ت0GVþ}waѢExq4lЦ:u-R:JRm?~<ϟpxyyÁ~)Fok֬oNsNJoӝ=l2v999ۇb֬Yڵ+W/`}errrPXXgfBF^̙3fuwwW9bLK֭ !\pj׮mS8W|L8h4GJJE޼yS<==ѹsg-w^DFFBF2X,Y>}O>5k7n=^Y5kfddcHKK+6n@" ٢E :t%޽NNNh޼9<==ѨQ#l߾ݦ;KDD^|En8}A֭[cĉسgj*.UVHOO7y6 g:uP^=>}= ۇFaҤIhӦ 4igZucBBBǎ3YFRa裏 ٦M8q T>iii]-Z(etc_Y7nunݺ~ :t+{Q%{C26l1c+V ::;w4xa< IDAT$@tt49 &VZXx1:uT4iD)(l9s~ӛZϢiӦǚ|8Ə5kvx7hڴ) cܹ իWcnZy3^}U,\NNNxWѡCm%yzzbܸq3f 7obϞ=^: ӧm۶xJ;|!44* k׮%G^yWcaٳ'oߎoƪؾ};v:u`~Fɓ'QV-T^=0dL>W^ȑ#1x`eZnBBFڵkGu݋W^yŢv5m+Ww}ƍcʕ矕`ٳXd zz8uǡC >gO?ٳgiii#bbbVq!=zf͂;;QFr NjP3~x!!!lܸ\7`x7FaԩxSO[oXTxOBRy)D``05kJ6lP]vMx{{+uz!.[Ljm6ǹpPͩc ab;~2%,<==;wx ܄xgѣG2ظqhҤݺu.\P0DEM=zQ!?5k/2e;rݻqbƌJ]w}BV+>E>}D5 K6TΝ;Z7oT_%}"44TZjN:M6)O8QԮ][TVM_,\PcI`裏 ϔ"j w}'"##V5jbٲeZViFO]?X 777Ѽys_^{Ĕӧ ???!v*N:޽{E͚5̳1̽a qmoP?0oll3/ OOOz-"&MW.??_xyy8m|wZV:;KTt@xx!"瀰"O /;*ޱcYř4%77W4l4Q߾} Ā0kd]8==iiiJV(k*9Bi0... 1PgժU_>"##-:LDDD}>|OƢErJe$lݺSL:&3#11ׯ_wS hժFm戀f aFF RK_t쾏=}DFFT*޽1;ÇcҤINM;gf`"""C9spm`ѢE6l{gѽcǎn=SLw3BzF>f+Pic$%%ARaOq*ʐ!C0d{7BYM "zxxΝ;u4sT*e_ݟʚ;Ɨ_~`Zh죒!URr5~~~zom+[[M?5U1vލsae ZFf͚eq= *y{{|jnpܹYYYzeJ>.\yЭ[7tŠ]| O}Zlzz:#..K,AllAh۶-9q?Āpҥ =oKTwU<`t.[ "j*!^܁cƌܹsݻMvZݻ7 Btt48|0N8V X|9"** 2[jjժᅦMUPPWWWŦ:W`Jπ0"""ZNrcUw\*Ì_ΈSaطo.^hrdԭ[W ؼy3 oFJJ  0x`hZ]V_~\W)ۄ#DDdO새 M6ml CNNN< 8r Ѷm[r... [s}vT*VFAiy=+1 $"Zfdd`foFFT*ɲ%q)!-["99&MBrr2zeU1 "lT\lwwwW^e_!OSeK#GAbb"w)Sct+7!&! B"0#c$uY^^-T*e_ݟʖ<T*WnB?EEEB""'ޔ$rEEEfRpuիW}uOM-y }}}թS^Gjjg֬YB""!$rkҥKRХK䏓DDdO새T5&LXXɓ'ʕ+ Ċ+p9lҤIHLLٳgѠA2 \` _~>>>Xx1wz ر#q̛7ݺu |}}c֭~icٲe0`+*. GȞU- `ʕ:u*VZa˖-PʨT*j[nh"",, hڴ^P`ĉ;v,ٳg7@͚5h"3uԩSY8BHDD5DDBL"??\C/ 4l(VIDDT۷jՀ#ŋ"Kes5d=""Z I=1 $"Z  ݤ2!$""{B""`@Hz8BHDDĀj1 $=!$""{bDDT=""Z I'ػ%DDH>Ie\]ggޡ%""`@HDT#$""{ "" p!كc9d#DDT<\""`@H8BHDD>*1 $w3}۾!""B"ŀ xy3&"RY*새C8q"hy&QNxzzsHKK3Zv޽VFR_Ν;Zm䄵kז\ˣZ5-69!$"Zn@U2d6l؀1c 00+V@tt4v܉:Oh9r&L@Zxbt hҤR6==QQQhٲ%ϟ?so-[=`DGGmk߾}ŝ 0 $"Z8pk֬ᅬ1c `L0w6uo>$''w>} ((ӧOGRRRvʔ)Y&~hZ@Æ DEEݦM 0O\0 $"Z3etpvvF\\ ÇǾ}pE&''nݺJ0>>>͛QPP}6RRR0h %C՚ a/%0 $""{P:v&6#":#((z”MIKKC6m !'''O9rh۶^9]s8c xzzaaaضmVѪUcgLDDUK7%3!.]dӾ}322RL-y ZnݺaΜ9 ڵkѣnjU$//vDDd ì!ͅvwwwu[B(4U1ׯo 8-[k=zXpV1KjnpBܹs`{^^-T*e_ݟʚ;x{{cذa8qKc LYT=T@͛n U"aj8L@燌 m+駦ʚ;NWeKjEU{ae?T,2D45\JE4ͼn ^̽{R]z"^׭(헐Pf.K:30tƙa~Fyý9APPW^^ !uf0PXXDDFFΜ9jiiii8{,ve>v9ܹSNE`` ,, )))(..#.**^GzzU~[N–-[p뭷n}@bcLDD687$$$`ƌX|9Ξ=hĉزe9SO="?~QQQD@n:̟?GExx86nɄ+WZ'??III?~۸M@[nEnn.q1{ERR9$IhN5 ۇe˖aÆ hllDBB0l0(--ENN.]=z 33VJ7e76n܈ .gϞ0a~ߪZ J L@CDD7$YN:ш  6 gf7 ,^,TWvHDDΝ R!guKBr{hz EDD^ƀb@HDDMt:B!u$>7Z eQ""ƀ0 $=c""ADD!Ƙ:2S"$"8 .DDԑ7]lWu@Zn*Ƙ|mQa@HvqQ""h\CHD]l#q !o0 $z.\u-1 $"8 ɮ^׵ ""Y*DD!h G#YC\B"€Ջ!5p QG`0 '' Abb"JKKU孯GVVPL4 v#yw؁2`3f &&< i~i>t:`РABii)RRRZ0L8{iϏ&JܯkBDD]^R m:!ܹs'Z-233ǂ`ԩS󖔔``Ñ={h4.^RdddA;w.t:oު쯿֭Ú5k.>TNDDAA@HG :UUUi5-3!!#9rd hhh@MM ȑ#hnnƨQ"..ŋ;]wg64JDDһ7 "7amm-"""Z,8}GyB$imϱw^b͚5n}5DD+zDD]nu[n=+˲9QZsF,]=n>LٓSFc.]`$QMew˗/:d~ߓ$*?<ǚ5k?bʕn}Guh4]&c""AD]_KK L&Rˆֶ:8p`*OUOGff&q ?~.],8q|:AAAv_yyy.;T1 ///]~š.]:^^^I4Ç[///GHHbbbjJg4QUUe>Dž p%<0 v_nQ""D]_nnW^ ܌1B$&&"22pTWW*ٳgk.saΝ:u*aaaHIIAqqqQQzݻobĉѽ{wٳ˗/w:|y2]`cLDDBn*CԵ87k0c ,_gϞEtt4 q lٲŜBQQ?(" \nϟG"<<7ndj0??III?~]70 $""__ׂBغu+/^b,Z---ػw/i$IFc}Y4 ۇ3gbÆ F~~ 6*m||B""N%">QGB""`@HNq|mU@h0H 11#++ Chh(&MJi+Б͛]vaɒ%Faa!RSSq;a>Y#G ;;}ƍ1a>|C5BJJ n&]?V^o{5;wN>3f ** F~agP+$.\=DDDE uZQI,˾DG8t/bɒ%˗/#66G}01k,`D@TӦ?Guu5t:`[o:u*8zH*Z?ш  LȜ9q~wn≈:~=zD ~  YՈ\);wVEffXpp0,X2:uaޒ 0 @xx8ӱgFŋQZZ s0s΅N]sРAhhh`czp/}p(7M@XUUt1&$$w#Glu{60q"i~O>q'_< on,(˗ #uKBrUZ! zr""oa@Hf6ԭ#d@HDDuDDÀTQL`uQ"""ED[*jG{}DDD#DDÀTQ;+ˀF#G ""w8ZC0 $"&}6L˩=DD^yB2kMe$I=}}FDD!0 $U""\yJ@x?tL鉈!rbuN~h5z]T œ*uqSulW_'"!Pכzu;/C 9BHDDkn`_}:u ٦2͟j7Q3eTIB""j/;%p޼yXn222~zhZNɲTl۶ .իQWW &رcVi&](((@zzUM6a=z4֬YGMM {gWզ2jw =6JD(o_|K,ddd 66裏ݱcPRRӧf̘<3(..6}ѻwotA!++ HII̞=b;| >+WĤIYLc#н{m~]Ս6DD7#;wVEffXpp0,X2:uaޒ 0 @xx8ӱgFŋQZZ s0s΅N㭂Aݻ7ƍ(,r(GDF6+0Ze $!Z~VUU!&&V;RYY#G:9f5*]`` 9tL+zh-\=GH=Wkكם ĀH- kkkxDDdYӧ= $9L frl*~뺌CE:Zˮv(B""& lllDpppݺu3I^Yy:;G]]fϞCbٲe?jc}m ?<,Y*7aqVǛ{W$s^姣Ѐ{z{iP vyDG;-J0dD"""w9k,ҝ8ğR---NacM@Z;Uʱ92QZ{0>}: } :AAAv_yyyiB顮FjDG|㺜0O+//]~š.]:^^^I4Ç[///GHHbbbjJg4QUU,###믿dO?z= WnnRIKDUjKaj7~cHLLDdd$guu4˴4={v2;wv܉S"00[A׷z8o~ر/2M֦2_"#˗a٘+eYEjFȷR'$$`ƌX|9Ξ=hĉزe9SO="?~QQQD@n:̟?GExx86nɄ+WZ'??III?~>ҥKѣGdffbժUVC$U]ǍgH6CFvَ̘r(fȟtvW""!2יF`0ih=- >`gC.=zx\5""_~ }`fy[nmRuuוq5YCHѶ߶] AAQ55S/""\ T |qu#" 7\e񦛀^DDDzO|}Ս3b@Hfj6oS{lH]F~eԏ.11ΧZ3l€6pIsZj:&oIRq4Բs268zGD1 $I?|pQQˀ€Qgu:ʡCo_Ē%KEvv6>#yw؁2`3f &&< i~i>t:`РABii)RRRi?(۷7>[6[n3t^,ri(#G_9f-6 ?_}JIIΝSW:/!fpΝj4 Ƃ PVVSm``Ñ={h4.^RdddA;w.t:onUnTTT{}<7+vdCӗfu-#xX)Y[\ˀAnrUC oM@XUUZOHH0Hee%FxBBPSS8r1j(ts3KNMM&' e0FϟAaskBDtd.6 nuZGL&QСbjFW˗KykB[~"""ȲӧO{9omm-$Ir9:>}zCOZ&:b2d.^DDt!.NzrC}g+F x`*_ע(D>7acc#[֭}OʲlΫt9|͓Ye13KH Wu$~Z(׶+DDW1Q n8ywTSjH𛀰{l75&$ɜW(shtjiiQU'7W DbsW={69ॗgk"/V$nZIDlG b$t]e& ~ ߜۖ8|k>L& (Hl u1& @ʱNRW~(sNCPPW^^ 'uP9y2[(SFĔTOGX] dg.^@ne?:B""G< &LpsTNpf4HYCi/uT:d0sNڪ|Ccƈ[_egOO|]rF. PSSK6Byy9$IB\\ӼΟ?W^')S0ydNj/x|m] =w70HI7uQIDgθW M.mlt4k[@h2Ƣ=(7'oADwrhLb[b{ÙL -ĨTL@L лwv^5DږeXJbJm+n݊ŋ-BKK ݋$sIX_F}a̙ذaѯ_?߿Æ JR`ҥشi233cǎVټy3VX{$XbVX; O7Co\u5g!H{L& L&3ܖLMrWk2 Q2 *;dz$9WA3U "f;M&1-S,M&źذG+uh,&ME=ѶرhHX(!/ sԩShhh@yy9RRRlٲ͈:~5נ?qE{c?^Ǚ3gK/]~}?>xj~-6M4j_ipS%%du~`xdD@TvOG <@Qqʵ$׽DDcwoLY9zr I,hˌ e֌'練~=P\y~.L.BOx%6 xMb2CmT7]uU~wX1p6iib;=ʗYm<20hp뭞Z~t=+d ݻŋ= JD^f^{wfخ!D[ndܛoz)2Z.=6j2yXш໹ٳNA3{6z1αmi-iĎgz^Qgŀͯ] 챝.4w!vR^]ft\_5eX@~ʙ;Wx IDATd;VW{_)C22W_V""@L+f8 i_}q{#|,aP/\=׿g[F DFz{$Жg*E'2Bxm@X`M&஻m *W/QoxVQgƀZ0}_LF=g;om)\@h_{M}^r:o\~7ݻFԝbYx15Mn-ӆk2z4*LDVmm?-3G ֟vL*fɲmV,Yfy^$2Jh@͟?b\sF\϶ωNges}Z-QgĀڲzAͭ߳3;m_yΔ+#br s#`.1(*r!$܇--ر↢-=$^szvMik;4grƎi=4siuQ/|P,zepb'uoiM[qq׺e@8g'뢴o3y¶}NM3x<Ί!zPoܨl/8`0[6~=ض7w!,;}HL=$n,ސؖ1nQtM嵝7Oܜ9DDe4y:I6Q@اTy _<+ORϒwbs2O;''Mwsee1% $` )m?spLeuf gƎsm{MB%e~-_B~u$`Bॗ<+1ԓKQ]Xz2uSN`;~{!xQљfŋE;K˺W;Q>=ke㫯ԗa{mxBg̚h}ۓv^-uxDb@H>#I|9Q7Gk7nA7Ԗm&8ѲhED}ay\π#G<ҥGa(e̙|g,]*FnyV{e>\-ְ%t@r*feRswWL6;,;+r=cy{폣\s ^^=,˘2E@<~uƖ_/qA{euf  0!#uq3 :cO=w϶L/{ N\ Y."6pGT!]R{=X~nnIDDmёk`21v3WoE[jβ $ 'tot϶h /ϽQ50ѹm]ѱcpgeh4z>{"KC/^xxѽ:+d 1n5II♀ 23T>P_?ۺt.z"WPp9 p.TT+.'?_ܐ_%K3>D}~ /Ɠ:hX|!yߗhG)^{_:6^D@{w]""Ďaۆ<۽[}]lQ:]uU@II/ l͛' e:"qޗ|bDn"Wί~;٣ {kĨeK]F'kC.[Y/Dx>r"@\l ׬~FVj۫F#o-S_e`Ϟbr"G,= 𗿨{P2ZѶgg_~`[رĉ{gyywnD B@|21!6leqh-X Χv4WSWgBغa^@`@}4w=Q&]o򈺝, )ӟDS+eػѢM,%_,]'TB2XVt9Zr 5S!׋%+Dz.AA"ҥup> C!I}^V]'r^fdk]H<UCܫh~QDVӟ7tՈgr>8P߈M|y=)Co+gTT:,]ze""(H,[X\#jkmܹ Z1dL1KgmȐ!bZٮ٫ĉasV+ _WEtȟzPG,^,wi\u@!։fe}<Ί!gVq5믋 t~L\}ggb s} #pumEŋl&z0@쳢1W͉V ַms^$.NL9>]:#ҬY<кܹbf̬YΟiĴd1Ypbw:Gx:ml&Mty4/Z$.ٰ3a@HWD .}^0lqr!' ؾ]V+iW$2RaJVhOxf1֯m*p Fo:hlt`0 '' Abb"JKKU孯GVVPL4 X8nuj}E@eK?|n #Q{lZ /.O=ib;#ZZZr.w{^[iiiAAJ?/vT3^(8( \}_μ~f̰Qܠ QαcbHeKK W zKktIb[M9s쏠.FyCJ]|Fgkn~;^[ɄM}z$g/4Tlt :jj}_8Ygjې=(_cx IK ;xo--I;Oϙ#;ѫhΝ6mK5L ~'V)j;lo.vML>T) {$O>D$I^fXSS-'%%9oȒ$ɻv2{%?CVin922RtئMdF#;cN Z?~%L&UKd^*#w-oѦ"|d_kqd|Y,~dP[Zd`07.\^d q췿>˥K裲!۷r],,q,!˟|rܹ?;cm>aqY?^GC1 -k=ޣ\od矗=e9(Hkn|o:.ɲ,,ge?K,?,%1eI\zW狌,Z.,,+˙|,,>(+Vr޲q,}k+)YNNx6TnjQ_Yc&{͕ -uKΝ;ji>  \RR`cHOOǞ={`yŋ(--EFFt:9ܹsFvލfOwoHLK*VGّ\ :_BvEGu[,v>߷Ͻ\3dYqS`[[[ Is"߳vo"WC؈Vǻuf~ߓ,*?۷4> $<^7cP#/J9!JOmKX{ypbttRoWy57_o f,mϞFjMzv-ڛrmu3gc'do.\Y>s믁ċEf0\yvMSEۀ﫶|9+-ps!]S6:ͫsW~(9"""pcIي,7u5][Y:^kRYs{6w5}v5W۵W 0..K6)//$Is>ju!!!BբiF#0sL27oތV˨%N~ FOw5!""""򂖖,0LW]I:txtR`@ll,틏?p#::Dm߾> v؁xp9ƫj>Ojj*>sTWW7oތ,oɓN!C裏bǏq Q{fΜݻwcňFaa!***{!)) ǏGTT1TG'Dxx86n܈'OO?Űa稬DRR,999xѣG7ľ}kY&y_(..ƅ 0bn݊o@}}=-[ݻw x<DNN>=z`̙Xj!{ jkk1l0<Ә5kw.  y0=Yc@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD)DDDDDD~!b@HDDDDD:}@h0H 11#++ Chh(&MJi]vaɒ%Faa!:`رɲd9rӧ6n܈СCi0vXtM?իWcҤIػw9ݲe??/W^w}z &M2ŋOBb͚5 WN:txd˗㣏>rw5kJJJ0}tsTӦ?Guu5t:`[o@ee%n?Ç 7܀>|駟bȑcll,rrr϶U""""""SOݹs'Z-233ǂ`ԩS󖔔``Ñ={h4.^RdddA;w.t:on>o @޽1n8|W?zhs07pN2SUUUAhhTVVZcyPSS8r1j(tsҙ3gn,qm=cZO$"""""jo: EDDDeO(/sZH0s~2̚5||yKxn{Wes^姣QWWٳgcСXlչDDDDDDA EqVǛ{W$s^姣Ѐ{zoBWe%^FIh4PUQGhiid,0Lth:USl4Y^ea41}t|x1|p{`UwE#,,LUZ""""䧟~B=|].Sqqq8p.]dLyy9$IB\\ӼKQ^^bccjQQQ4s:ш*̜9*,c$'':$I[PQQO>C Ǥi8B>N^@_WK^[^[^۶q6BЀ}rtt0-- / tR`@aa! @Y__hs`ڵ <;wbԩb"z[7 v؁L6iݗ/_Ç[=CvvkL qRKKxm{xmֻx}3j%Q~:`̙ؽ{7/^h}h IDAT?0pqDEEkqQ<ƍqI|6lHJJÑ'Ob͚50a׿ӭ[K.رcc<`^x%ŋx'jvZȲJGohh0L1 l_FAAA0 ogk=k]k=8zn݊\… 1bkѳ`;ܬho>,[ 6l@cc#PTTd 󥥥ҥKѣGdffbժUVC$U]ǍgHCCCcɒ%χdĉf Q[tBaz\Ve444mb4ѫW/\p᪺ޙIQ= ð;ApAbx{ & $Ѹ$DIrE$(ĠDAP D6Faf`qRmwO/Uuޞ~gOUyX2I 3nBB!)S^^X-m!4jxcgϞPö=B!Da01k#ْW׋Gy6섐 Z >q9ni!R?RFvE]|ƭ?( !B!BAH!B!M BB!BiPB!:u*ZhرCc=~~;#pkN(֮]>}:&z׿a^z ͛7uw*M4 O<񄖍BUU:uꄭ[6P( ! ;Xhz-H=:̙SNxgmN(2l0 ;;۶=7,wuNwX}o}~ms=.2x^>3ԩr ?ЦM+={|%%%a1cFXB! ž={PPP#??_|zzzMsrrb։5111OTUU9>7%%yyyN%77n}y<FMMMG̛7o`|'UW]J1&L_lD BB!;wq8p^;wZ ^z)rss믿׿qmEĠAyf*eo߾a??B4b߿ӦMEBB0aڶm J׮]gywށvZx^_K/\իp5נgsrsscں{Ñ"^1-܂\h7x#o/VXỦ?0y;v,q .sɓ;7ѣGq7"++ ]t>S-󞙘ŋѩS'nCYYYϟݻ#-- [m}oGaĈ@q5ԩS!}mm-.]믿{vN<=`Ŋ߿?еkW̞='R})x^wd^;# 55}wv튔/l׋? tof>35k:aѢE8|0x 19996lX;ĝPB!yg1{lm>AWVVغu+}$$$2 >%%%;wDmmї˗m۶3g9s0`q]waҤIزe 0|cʔ)(--EII ڵkOO֭['?'x_K/a8qx [$00vXbx1sոꪫЬY3|GذapW?O0~x\}վk:thLcXz5^q饗b0qDL<'NĶmХKL<9z}޽Xbz-\k׮ [e{ݻwcժU>|m5ji&lܸcƌ q۱cN>y/;;?Oc"֭ɓ1m4ڵ /^z%3'O4<vZ5x<7 3l0??^?~qu~x<#)==\-u%BH$1 fS뮻:ui }*d?0/sgAQQ222p4̰JRRRaH 2gϞŀ?lRpM]?53?7 Llݺk֬ヒGy>(lbݿ<:(x'1gΜ@ٳg𚚚k5q\=nݺ5 @iii"DiiiǏG"BB!II??ݻwƈ#УG;v,>}`a쒜\6lÆ 3f n6:uB쒝VZ'%ֳgOB^,][(:u֭[ܣӧO?M; $B+0X.X{ᅬvZjo6lW_}˗?v;vć~Çgn{aƍ/p]w-?DZc|ѿ?8׿bݸ{qI[MeFnݺaҤIرc֭[~8ߎ<3ׯǾ}f{8|v؁ݻwرctXr%{9|g8p^z%|j{{yf7v܉]v a߾}~c=z4^x!gXhfϞصk,YYfPQ!C']vZ{ۛ>}:.\{SO=_ӧO8hw}7|;wĤIжm[3&u᪫,{( ! <,YO?{ƏcW 8&)) qעO>7oo+ٳo>tŷ?~ꫯȑ#QPPPg'HHH@^3gb„ 6l`Hrq嗣W^X`^y u .v؁cذa_6?vk޼y8|{W^y%àAp駟H-_TWWc1w܈d̘1xgOpB\zϋ̘1w}7. <x;fƍ8}4n馈H1"QUUdTVV'Ҹ)++Cff&UMI`z[n=GŇ~K{$ܹs d < nV3g ~ϸB!4ヒ}{8w~`߾}0awyop$Xh=CqUUUӧニ #qWO!ia|s!za0o< 6FHLa=0BH!@m6j3B%l*C!B!M BB!Bi0eBi$5!!!HhժUCBHQB!Bi0BH!18{lCBChPB!qރBÔQB!BiPB!BHB!B(B!D $B!& !!B!4Q( !B!BAH!Gm-ysCB BB!4O=wcʌB!!B z6jkgeC!nB!Ðsg*[:TT(qI!nB!2 *+LZgBb !!B RB@NY2c!XAAH!FnPk !n'aee%fΜBcȐ!XzsO:S"??9r$m 6K.AFF p,;w.ƌ֭[b!=czu~ӭ_$M8q"0c _>칯6n܈e˖a77nwGy/CyXvOuSNի1j(߱Cq1l25tMh޼'BH ᮔQ] puҥK)S^KIIwމ7믿{eкuk<?+V@UU̙3Xz5&Nɛ4i222ꫯm߾kř3glC!𸥩lBH8Zn߾ݻwGfff m۶_~u^4h˱{nΝ;Q]]5V0 ;wFf͐'oqlB!JE+%au BH$aII ^PP0paG[RRH\}X`-[)S`ɒ%>|8r)BqD1'%* R!5HIIzjj}';7ܱ>#sOcǎqc1c#BHSF-gϪx8}Z#?BH"#iii8|ϝ;{ɹwoc#}]n6nTUU#!vʾqjRߎ8D|%C\ ‚y|M6ᎍNh׮?n $''3g !7cL Tӟ=3gΜϯIlkAX\\ݻwש۴i<#u:oڴ ޽;زeKqUUUؾ}{p¾},mWLYY*++C̚5KtBqKgcJ_=u >Ncf͚L7|3`kXp! B#G_N|(--}=zK. 7܀$@vv6Fŋa.Zeee?~=zkǷ~kƶ?잘a2!ĭH6qCSI;nѿP&MBBBgXR?uSAaܸqxQZZ]b…ؿ?^|Eq<-Z4P駟w܁?yyy?>jkk裏|ܹs1l0 >SNSO᪫W\pŋ~x\v-Ν @]خ];jc[n{Fjj*֭[%K_~:ujncVj,"p8QY k> pV ~$4 ع8~|- 1JUBq-_ƬYxb8q}ʕ+1l01ޠ۽^/~mL>=***0h ,Zݺu 8o߾Xz5fΜYYY2e :?k֬5k^zO? |r;w:t<z%&N֭W[:P] |ZֳE!B'%e,/n! gg\Ms 4ũ"\ZNPS@FC!NBDM]Fkk w $斴4 1QߖTP׆Hu<5 wlp BB.& !߆.!#1WF$kafLꩄڟp4u( !6^h%!#5WEI3J4aR\%SF ч(0-MK8 =i69ut ӆ0}TPbwXB!!Qp>uWeM)K!-eT ]Ьpꔞ ۶jy=;3D@˨/J IDAT/;BBqӎ T^7pOĽ ̙3QXXt 2WtS0uT#33#GĶmBa\r%@AA^9nܹ3f Zn ׋ٳgÇcEfp7⫯v$"&ӧP]G.lĜN9k.!Ҹ!Bh={|#% T,R;?#ɓ'駟ĉ"11GƆ "gFW^ys~_oe]{}v5 ΝïkL2 ,؝5klق~c2!(++e]uٳm6\ve8<U8w,YO>$M8q"0c _>칯6n܈e˖aرqơ{xGxb߱=7ok"##СCL:WƨQ|۷۷DZcвe˰{͛ѯ_?W_"<9\xs3SԿ:v$ڠ*v4Тs;ҫyyz6 !#!ԍʙvN:thHO6nLRRVBAH2q!\t)1ek)));qF|a]lZncŊw̙3Xz5&N0i$dddW_ ۾}{Kc_lУG\~ul65^{ =R{%%% ؕK9&H8ձa^SffA9S*-*^% [)!L\ ۷{ Z4hpl۶-@[^^ݻwv܉j?ฤ$9aرc >)|(02RPA!ڜ^B2J9N^[8|D2!N\ ’yaÎ;'챑>#Ǐ-}~S0d!hL9P M;\Q%B) !nciXzFm4Ch,:!$kAXQQ:wras3"}v$$[/2*)JՐ!$B ((qMjqvyMbN׿;qT&-- ϟ \;7ܱ>#gGm졨к"!!0d#:ks%W ut;B Fj+'8wN{n!LKۜ,8Z;w7o|B$")qNMM jٖ‚y|M6ᎍh޼9RRR=HNN3gclh$SFumIudrsÍnYPBbvHDPC(U!5UPH,Nr3gNWf$ĵ ,..ݻq6hM6񠸸8[nMݻ-[WUU۷Gpx<ݻMѹsg2ϬYlqKʨnj#OLTN bysex]Dy-5vt#jQNǎ۠ f͚7XOZ|ͨƂ |UVVb…2d G_~oX|ﵣGbҥƨQx?E,VǾyQ_wd3)))O<) ]C@[sZܩIiiuI$Ν (\D4LBI4lUR$$$D|%C\4ƍÃ>Rt ./{h"ߦdO?4|Q[[G}4sΝaÆa:u*Q:ݓ BAH\]M5CN74m'a !>[T$yju}SQƲ2>xǏNӍ?'!ąHDw#ꝛ**Bљ+Ӂ{'84m"eLJRi$"NJ y3B BJt)eOkILTwtYRNN!j {$DJEg)8Ð0Xd0P8ii@jb"א6Nr?-[щh}mtP6즯PWΤlN:Ni @.'qM\U%Hcsb.Xl8T݃2X$\˖7?,RR=EҜRV7B BJrrDdY p7ť.ÀXl|+lR!!Dlf{HEB9/?_]KM=;U5gAAH\aIiD0VAU΋UʨSglCwX!OR [C",#C5'/!ĥxo/'GE`r\iGyyNyqC]OFnҙB.ᄏXz#5svlijpHt_VxAAHĐN6md#gK"h!*/É)!#B);9QPߑvRx#2j:6m '%jR*ZXowE!!t ! C$cN8m׿JTJّDXDIIJO"*gWȅ{so(,X! BJ-yyNj$v}?۠?#M B"dڠrK0v_6mTJH8?KiH-6EȽ8slpde7sIEΟ׳ķAFD RRsѳ#2LC XET Tʨ3'6 TЎo o1' EJw8e ijPQԤ[m"uJ s8v{JIIQ-YwAD|uEECBET2ߵ 3 @AH0'yBw@99a"Y.p"2jکڍH'\U%8%ҶgzrFU k}Fc3ythe8ii@n.;g!ÜXuWO J]˔Brxm_XM!MP牉ʯYb*@äJuorS'ː!Or+Gدo v~jeW+C 0|#ӭ"C~۶na e4[鞡ogR\$M B"(ڵԳ,;lUmPiPeeҠ$SFCHO5ӯ%)Ah?ERRT抮lvIZmpkqJH@AHDx?N:rlvV0ѮÀ4pH9-7BQ,!؉$8$b'n,czctpJݒzJH<@AH8giI '&!jRK!H̕aRv$O۷RiܒIFDG&e68DŽpL!7’ZώDـh'N8폄lƚ_*;ZDB B")UOz% ȭ2[}ȉTCتP^>7<H} LUOMtDR5uWP6tk~jsꔞ;5< DGMUUj o @$$(;Rμ8~\!!9WևӍkyUVEbӪLHPE׎d}&8IS aee%fΜBcȐ!XzsO:S"??9r$m 6K.AFF p,Da/~Ν;#-- ]t^y:=czu~aN^LIv@i}B9-+J@ǎBW†׫NXǃL?ɛβؙ-gluf},QFoN܎U)eԮXӯ_t[bL%:wֳtO΁@NmHE%2DL܎[!$kA'`ɒ%x'1m4ĉQTT3f`a}װqF,[ cǎ7ݻw#<ŋ}衇мys]':t耩Sb5jxꩧpwgyӧcܸq<7t7o.wS\ %C0:ڑJU{ZgϞ ;n7?J 8i{Ru;+R5pS!cbu΍/$|vZ[h▆0Rvq;q]CXQQ:ѹa5 wg=pرc1p@~?>f̘v@.޽!\j%:Hv:w֮ճȬzkzV tݺHBH"Z aN/N۶j#UXsA]_ 2dUަs;VJ3D6mr>@}?'N3.pbu^?$4Gz<߹ m݆֭[[6ß?5559Qt|Н ƭ)^d `%=&VSF#QP SIizH,E- ʖ-!tQ$7z<hv=II>Y4L܋0vD|%C\ ‚y|M29rxLڵkv@rrrȟ9sضsONVQ+J@MMuڜK볒-eԴcbm#!A=XpU"IIH9EULvkW`=!C}hH;9s}~oHbK\ b޽NݦMxPablݺ6mBzz:w(**Bbb"lp\UUo(//Ǯ]ld߾}hٲe)++CeeeȟYfٶ`е3jnLHzrk`O;5JpSz l!v&"RSVd2< B+)/?[05Y6mcgHEHbìY>5 q-ofTWWc*++pB 2#G/ HQZZ~iGҥKq 7 )) QFa-BYYYfcƌAbb"ϟ0:thg3||klߋ?}E߹ӭUm;ʜ"aN;:9X&¤$֭տᏱ*,u}e׮ъ۵+FamXTS׷J$$$D|%C\74hƍ|ڵ+.\_`ѢEطoڷo@ §~wq>saţ>9sŰa0|pL:SO= W\qBw}կ~J 8:>#ؔC[лwobݺuXd 뇩S:71@ZOس[ϖB|Eml() C_"͙=N_D>Bbe˪{u{{~'-t <1jYf;ݻ+_^+>dѢD/mUU*3DxK6̱H,Ff:B/2>,^{/jjjrJ 6wx^o6nUb7/˘lp6z}`GѹVYgDPҖʡl //rMHcbS'TԩB?=mu"9YI_Tfz^ZD -NJRi+v-NZ-ؽ[O !YYJHm=ѵ47Vu2*SV.Cj&%F !t|5P^8+B\Ϟj4 bw+V'Zc\amhq{[X!$ޡ $b-zLOffCXu4фX.*ivRF):BB4f+ /YEA,.ZsV=uEeVF- f?iPrٓJQuRMiLLTcuݻsv{F];UWn)Jt;X[z /kQVF!+]GF'"w/P]hvX4f(&H+NӚhLi%Vv32TSpVhvwft,BH- 㑱ɆG۶ZώZO+6t=ss{WJVB B@EE"g wo`Nc EQQt[VE4Ahg[ׁ^pLM?HB Ov55zqRDgO XѐnBߏ&PvBEwO0}ز:lD"عXTώĽ[}vB&v$2OTwP툐h=+PGdfuK$?k5ֻ7cGxVI-V6sgᮇ!:m٢g#$ߢJװ&)*Rήʹ )q)iOBgՁ&${ɎgާOdgnFna(!*V#B$1ge,lXӡ gG$G%{ 7v+., .F 1BAHIqv};&V2"t4\8V(.']i:PTH’BТ>f!s7|p98xPώII?# B@M|9g#ւPJ*\?2-Mu s^ ϜOPtjjBH0]$3\xC~a׸Xbݳڜ/BzXEEa[JHKB !$b`6{c䴤$$< @߾1iig,Ugcgb;sK lz,VrOd;V"}?"qmaKso%YfZ %|?\$ BIQ'm4$߿?Se:pv cׁ~Ef.X!c'5^ᢋTnfF$` +={LpZEJ}4'(c4V( p c3]xrPGZK ڊ'" D~%rrs֭;Iv릊V!c>̹ii\w_Qo_5o;&q}z_$r%kNPP߸MŸ飢R] qrű&%T*JأZ͍Vf |USݽV 5NzL~65v%tQIwK _?eCLVm7b/SumءcN߾ @_4N( 5 'o;61`(HHP$Eo%mn c8a2qB!&vav湮]՞y_ݹ;Tڨ]5p4H٨qn#=]4sR>Ī}gBAHI1#CM:6j'-gՁ*Qi5ժ0WPmDVINV\C6 4 <_|1qcםDt=^(80O{O fa]֪J=խ?# B;6thhkF8$#TOt /6liw`2`Ja 7U$kkB?J;snqʎ9}Zo<Pb.={'O}} zjWXNRFwo#!,)IcNꊑp :pX!b)Ď`z:Ь,Pnz]:Zp- !?]|㪪>7#Cչ/9`ݺל={'IB ć;623YsBAH MEs"Maue8MI$aYI߾Ap'9Dv~vy<\U%X<ת,шK's%(rs;*{ͩ8C3xp\pbp$UBٰ5Q\B! IՎ}3;&K/wPk8?ݺUZ$@ݱ?^9]"ZCNxKB!:8P]->+N/ Xn;{嗁{ڽ^Z _c6J+-ճ3do%mP BG,;;6"EK4F,#Gz,vHMLTNFP6L{=oPso AX\:_zvNr'NeKnkUƅ &HrJ( ]S Bv U:!J, nП^E=yyj{U2z C( NU#Ds|ʧI.Qw5PsκڥGWq@pQ!gXl\qږCy<7ꏅxq2Wn~k r:MU[oߝ|SH>wιK<ƎxCƘ1*[6;B!PWfe;UGquu8V/8tuohǮ#xn^}iRoz|XuMUU;?ݱ~B禛DnA00vINVQe˜J+\{3,ܗmB)[3 GF}֪ЯJag4( M7whV&+TODjbYxp*s=Tp-J9C6qĦsjc0U'4^tyG+.]T$K7[W^ѻnP&_F֪n1:[ti]T_9qF1g&( ЂU}{/9YͿEώI@^Pgz R~6mR8UR[É]^:|عD%r}ӹm[UnXp|,*ؽ[ 8"K8/ٸv`R¹>}\nǫGnR7:{=Kꍅ7@AHbb61aÉ?t$`Ѣ8!7WOzv<UX @0yXwgKNZ$N^HB4 xE[nQꈅT˄ jRGvtQ{ 6<5/\|I~:v s?#$ޡ $‹;GJ ;oN4i)mP@,"VtAp@QBE'0RQQ<VdYZEٗ kq!iIyTn=9s#y^IFK{z;V ,E0xqOhN]M1GOu#4FN=ZPkizws@O?/#:Z!jE2guO۫WѦm"{;V|$x`uQ/`@H\w \ -Ù />y9VS,pCP\N kp Q7I`NԀw9Z/)"i}WѿPuѹ=&v=vhB D;JǎPF5+4TMe{F!p#Tsqœm'?x}Dl3 x) #C_bh a/#(HM",<vPW.hT&MٷÒqjWT6WZLdY]$򗿨is!>z)5p;zTƪ_U-G[nQ:P_}ʉB5"LP {V-_&6V r+^> [BB 8Q<޵ x2|RlTPBڸQMa.]?X"ob@H4QS~[:Wy^F`x KbbѣԆ|\ʹ98~X]@DT Wld7rqZ1_\qj(jdw$&ѽ4uF ip:i~ls՞C'td{pX9O?k!G:3fx&DDD.y&D0 $5sZi՗'{uqm>H/jfպZFbkz䈷kBD@# 5ׁK]"1 ${Eu/#m?#geѱ}Z10y׫I{N&DDDugO5饗]"1 $ ##ե}jZ"+{ {ǝh``0g|#VՍW^Qp3oׄBfV7>os>=;<\]+58~\QQQĐ!Cp+)IS.qwtS'5(LMI!FE#Cyy9M&|o*g`Pӆ4PG W?ƿ[r|4( 'os``_}KJXA?&3.̠(={ȑXj&Lc…i&uO>ؿ?&Ơ7'N}Ю];۹999ݻ7tT:u o k:;e̚5 O<z5k7.]ÇγZHLLD^^`2a{ZFDX,Z0:Ç=uv <л:j}jRmqڀz5ж*w9NJϼosWC4mJ)Yq?k.`0(cEEEJj]lb0UVَ?^Rz!shBϷOѨ|׶cOVBBBg}~)ZR***lf͚Fe޽cTL&K/(jU(V8?)?gOEiRQzO)))Q(%%%ޮJSӽP3Q#EE)/ 1zhS'[O߮toKJIE?jGg\_X& ƍ c=;v.]r%5kCڎ5nÇǚ5kPL༼prE`L= V.[2?9=#_aNNp<))sW1k p!QVVo`taaNN, :uTLEQl*~={tv:?ѓD߳^F`^uҴ4cG`,uk"""ѿ?p0r$?gzfD~"..8(3g\ ڿFnn.:I\ŋQ\\\ׯ+2! 5YS=!:HIQJJԽ0hNm\۶574Q7jŢfʿy""kb~{/olܨwiY6b4Z[֤ аڎimZh(3aXX/**\ϵvչQTfMuލt0m F AՖMT摖H'zo u3}+ >~;~SW{+(kKɤ~]ocAAjvΎ =R[I[Pv؞y n] i15Cf۶kY^ږ kFϴgϩ-E)T8YYYp: 4o޼kVK?uukaӦM5Z~M**mkREK \޾W}y3n=s~ɂoע3>U5@'Y<ǯݻcӦMwXXfΝ0 ޽{n۶;wl6#>>Ʉ={`ذaJKK#F8`3|$&&"//iii0LxwlS#""""@aII NL\t ݺu믿9cǎŒ%Kp[@W\Izj")) o6ۑ}!""#F̙3cϚ5 }rssѡCx3g`„ ذa***пdddm۶k~>\8(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P B"""""ŀ(@1 $""""" P %%%HOOG-`6ѫW/dee+W 55ǀ۷O>X,aZ_0yd$&&aÆh޼9ݻigΜH 2GudPEv%D9V„ о}{,\}6mڄ޽{NQɓyĉطoڵkg;7''{F.]SN᭷€vZy&MwHJJ•+WGѣX~= `;j"11yyyHKKdBFFU^`0k׮سgOڵ m۶uXѴ:Z竬pjGWZZ Պ`oW^[[[1ՍI&~6 o6ϟ'JJJpB -Zyo 6 +WĪUp}P!\b^ 6DJJ 2331uT[xbXV >ܡNO?4>s̟?SL}!ܸq#&O\{f! CCxo=sxo=ֳx=V3WSHޘFիWCpBٳ7nDrr2Gŋq1j :O>8pиqc̛7'OݻѡCkdgg#99;wFjj*N< z/m͙3'ND޽OV}gD!-- & EAvv6bbbj l=S *--EHHJJJsxo=sxo=sxo=q_%K0uTdffҥK֭֮]k gphĺu0i$̝;HJJŋA@p>++ 8q""""0n8̜9;v;Եo߾4<<7oƄ 0c TTTȨu0HDDDDD$G{O<~{9#roEAAAjJKKK.x=V\%5qߏȹ(oW^[1^P=DDDDDD#~N%y3xWxo=sxo=Gֽ=w{‰(YV4mp|!""gZm)L""ϸu!7Qb@HDDDDDQRSS ѣcǎ}gw1qDqf իW=R/ټy3Fc@ǚRt;wvU|?뮻ޮ Հ!U뫯ŋ_"77 eN4 |˟>}mڴ{8 , ]'}5lt=oڶm^zَFf,N8шm֯_}"** 7Ɵ'9rӧOcȑAxx8{njhbb׵u߿?? &h4"((pE5 -[bAnݰtRo޼'NTI\hat8p Ν;g+>,^z <6mZn Ex}rJ$$$AhӦ 222,))Azz:Zj  >>~9{M7łd:t#G`Ȑ!h֬"""Tev޼yGXX5kÇu?łh 8W\qzڵkѩS'X, >Xhڴih? 旔 -- -[Dxx8nl޼f㑞W_uK9r 򳧟~駟\^( ׿m۶0HLLʕ+{k֬qZ`ڶm0=ygѴiSo߾سg:wqF.{A_ ,@Ϟ=ѻwo̝;K.ٳgm~x|B"""rk-[ܹsjǾ}qF9GVCnn.c2eHAmSWZ-[b8{,rssEEEٳ'֭['1clv}]r-7nΝ;\\wuN_g?lݺ'N@ZZobѢEضm.]իWb ̙31~WY]v=ݻ#FQ?W_ԩSxb[GƲeOlj/w޽{a2c~~-rrr0p@{8uꔭǏ믿Caׯ[8'')))HHHΝ;c <.WAAΝ˗co1tP|WXn233GaŊkz)ڵ ˗/`8|0f4/Fxx8;xתMq޶m㝎j'''{AzzgΜL̟??&LѣGc֭sU>믿bժU/@M/ddgg};qek+TҺcDEE9tꤤ`0`׮]c޽ _+))Q(%%%ޮ P(՞[Q(%%r**?gM6՞sy`0(PEQ>#%22R|M$&&#(C[oU&L`_5{Q&MEQM6)FQr劢(pBh4*G3o<%..f͚)+[vsM222N:)eeeN=>CʝwplJBB(/(Aٸq2ڎ}嗊hT]5!!AEQUV)5r[{(rqd2)e(/r7ӯ_?s)S,SRRR7 ʚ5k~I1LʶmEQ!C(cǎUEQŢܹ\y衇l#**WVFӦMSBCC .؎YV%$$DYtXiiҢE VE̙3N:U9|>GrnME~ƭ;!$""!ee@Hܯ2_1j(khӦ N8H/ L>ݺuCLL """a[a6qWܹs馛l?7zxhӦ RSSzjGudcZ:(a2l#vh#{`{Viiiҥ ߎ֭[M63f >3Wm5'M6_oKԎiDyy9aڲem3ݺus߃3hР˟wcƌ /Pg+ p;{ɒ%UҰkҺukDGG}aw޶c& IIIoDDXX !1yDDDt/S{mڴ'|͛7pJ~vOy71w\HHHbmupGppÿ 4ZlC!++ _5z)[زeÜAw>ۿ?-PK}7`h׮pcxx8ۇM6aÆ xW0m4ٳ֫{p;qvL{0Lطo_T:4n?cW_Eǎf/͛7wYhh(ߣEcDbof͚U qE4kŋѾ}{"!1`_wx":_~Gǎqsu놜* 2}v <#GD׮]ѦMR\]箆 iӦAy߾}ns~;v`.ڹsg?q8W3 ڵ+***ؾ};y{クcǎ9c41`q1lܸe+֭[sdHLLDyy9Ν;m:|i+oFk7m;g111˗/DzeеkWL6 :Fm+?İaSOsHMMuUBrr2 =zpUpmK.?>.]j ks;t 6~7ߌd_Ir>… 1fSNgj @fh :Ֆt;l:u*x tė_~6mخĺuЭ[7,[ƭ04o~3={đ#GaY> :uBJJ }Qsʳ yS AIII|w"" V6)??cSQ;wƈ#ļM-[R))߿wq> x]ii):t耥KW^U~^sq!"""'aDQQ};v FW_>b-ɛvYfѣB'Nॗ^r oc ;u|A8p !!fͪ%G}GjвeKl۶ "(DDDDDD!QB"":dZ]""w0 $""CM6vl2JDDDDD8BHDDafޮF@x)X@? |3p `0/g@`V+1Q_?dfo&"xivMWfoW!1 $""0Hp0p* r€ $D+(H(sk5PR |wAmL%""zEZ)(z{"C^ekA} Pyh (BB@eeh%Q c@HDDD/?€%rB"""WdZ,V+A{yB"""W|%ef'!$ ސ9$c1,L|![ ~Rc@HDDDa}Mkd@H;QRR!U_!c%، 0 $b@H'n8w۵ "2(Y#2RF["~ΞRzwoŀO< ޮ0,wK 镟/.!aEDT=ip+!ԧt\"=SD20Ys_4Pd}D`@H'E^z Q}+A}eŗ'aII ѢE f YYYʕ+HMMEll,1`dgg;=wӧ, 0~xX>Ǔl?w%;;=zr<)) 8t`(++Í7p^pp0wram(m"22=z4Ǎhi= ȗB.2p 2dX 077qqqUAQ9sF׵l`0<רNTTy̟?+Wĸql2]\+,_i0|#MSCXTۋ}?پʘChmnϔQ"_!,,,Dhhh 4\ϵخվ:רγ>C⦛nC=yaɺ%߳~= |X92PKu!"'(3Ph0ղ6_v}};ː9PQB,l(aXX/**\ϵvչսFf͚z {.E%I4_3?e4#e@gbT23W)//3, dC4XͅO][kquŋn_gXkRHO8|i2Ј4*kR6 , mk>}ϯ9gu@ؽ{w:tʜ;w`0{^o߾*w ٌx@BBL&p^ii)rrr} =;V**Z())q5uTu$+ɗ0_ FQ>ieQ/>l"", ~SNu_Lpذa(++mJJJpB -Z={Cap9Zv~Ê+p"88аaC 33sŰZ>|oU͛7ϟ./887>;BDDGEM;q@j1Bh2!!stݜ^FjArDpۊ u@!,YSNEff&.]nݺaڵHNNc0`hĺu0i$̝;HJJŋѡCstL87nfΜY> ,-[li&lڴ зo_[@?۷oǪUPTT֭[^/h[%Y/aL!"_g 011}~k)2 x䥌m@ ܉!!!5kf͚O?aP;һwolݺ>VSkKc̏f(Qʹidb鑲ޏ/! \ Ve@HDuRudMFdEQ_J-*ˑ5PF2W%G ꈌF ()/G "_'+ؐ! ʨ" ¢2ZJ/B d du'2Ay h (./H6Yhک9 y~x?2UEȫ +DuWJzU&UوwB_Hhdz;eTB t ꈬ9NpU6"E8" 7F2RFe@yȇ:P^2B(: l_"ŀdʘB"Q9ojEs]V A926ld+Du@U]F7%"_'!_VP sNe<4.*JKBI;a2JDd!v' g`Nhl sBj|!pXZɚ7DD&+p1xP+CCW9ɴQ d #vŠ }Jz/9##KQ=͔Q@΢?D!Q \Sf\h"#" `z=rd! QDޓP4Rxʨ'RBj(xУ582bY#sCHDx sWjuyv)ʵvHF2J$B zd\e|mˈBr2*.۲3RO!dF!Q d|8ѣ*s/*Ys2JDh8Y\YsFm9{=I5#UCQl@2""{2;D(ZEtInE`+s`@HTs}!eTVZ SFd>W~ȯFFTm_hTߓdrQ d j+)OF9Kidu5P$RuPx Ec@HT _J*Ӗ(! sQ#=Оhen;$6r!8T/UTǏ)KVoeEW1PDDYY|Y _Iʘ'k! \ʞCB'9eշm'dr!&sPs>PhHPl5eC(sA0L%ǀRrdlLxfno]M""}}TD#ÁrE?2l=2*:jUlψR=%+rDHڇPђʜV"$"dӈ RB緌 W t+sΝsU)UZ ^xhxːQ""gd!/c}93S4 ⵌҾ,ߑD#s&NO9eZFYtXT@Vǀd*zʁB~-v*?z2SYs=2>;'R c@H>G?pJʨs.SdC(#pi@;zD!Q>z?C1E0 $(9?]#2 [5sH6L2()<鞢ii2P[b#.]Sh`)3), 0'e([,ǛaRh92FN0,BDOxrRFeڧ{Mka(2Y2r*1Pl3s-4 c_2:9B92SFe+-MkO!b9+;uzgʸؗf')\-'*J.B T 'JTXKk B"EQE{@QPR"^7e̹bTN-#DbO9ܠGF(G`@HD2ڢQ|wF=wu0{loSHXJQmBHD$JYsE2Dym5iS}j8BHʯ?­X& ƍ c=;v.]r%5kf q>|8֬YR@^^0zh[0cƌbmժUrJtM`:vnJJYA蠭KOVʨ&<\ ("3M_+OΗ%8X]!TtDMV&w^9[EO]m/€_999Gxx$]v-((CGYYnF󂃃ѽ{ws( ~>|@#ڇPQhy^tKFBA""@nFO%3PCʨ}=EEol]B"1~"..8(3g\ V\xŵz@$+ ̹sIB. Hl g+WMY*؎v`e )pu@XXX*_XXZEQlj][kTՕYSYFe-"՛S9eTOYB"IfxwQf5eS,IigOÆ\vwʯ°0W9^aaa5 kέ5{ʬΔ*" իj#!":ZG׾S!|zrf""yʔ/zO0ߑx$˫ Kuï¸8V9k޼Zs{ WX,8>}u&EQeE-'/OYXF9"Gk<}e""mr0'Ճy#BBIO!;/.{ӧOw~1G,wC!0Ν;a0н{jݷo_;wlF||< !!& {q8999վ+]vR&ڵ m۶uՊ_SNu&zր@BʨJ5 H&L(݈ ((pʣaZLdZL"2dMڙ:uϯ\`u@8l0ac%%%Xp!z-ZΞ=_~!urذa8wVZeиy IDAT;oaŊ{ hذ!RRRxbXV׶wvJlܸQW.t[tOY+Vd.s* !kr2\ VFP}f){~3BX 8ԝj?R铒`ʔ)8wڷo Om Xxmx@ ̙chܸ1͛ L6uf̘d8y$222pwow8733Ǐ7oƌ3{^wuA!-- & 0qDO2!#C֖2F4,LeID>TT^F]Zt}z]ތ릧]ݺYw)zw[RFpP^N! ~%K0uTdffҥK֭֮]d9F`hĺu0i$̝;HJJŋѡCstL87nfΜY> ,-[li&lڴ зo_[@͛7c„ 1c***пddd &&F-Kh k刌J_r7 SP!0 SFȓgdE:Y _8XVghu5bbՒ9g6&FG:W0P+%$3aHHf͚YfEEb,!"sE5Bqz1Eڗp!yh@(h/!"2Zi+iϜtm13 )1 $Ը|Ieӗd|~8EE][iH/tDpgSF#"zAp!BAM!4٧ʚC(arg>eT(}=,u+WEDTŪd'kQ#-F=[:dͯ'' g6x2SFeo;!ノh'+eQ`V$2F*'YʘC(RFT#O2SF}eQ{^Pn=AD˚CظQQ)JqOd*RV嬙K2ʨ!"$Mi) B.2N5Q4eT[խ{u!"rF$X9 OQ4xb ,f ,{+3$@z˞VFD:WBt~h$kBdRȘӤ'"AvBw8g鑢Le: BB^ P2 ׂ'=jGb.&ɪ B"Uy[e!*=緬9d`hڏd9,]2U##Lt$$B*29Y=2OMlV^eNFB"MFXM!,Hu/SsxtRFe+N/C#+md,]0 $"D:ᜍΉIֳ+|VNuw3vpz=zDW# )0 $idjcw劺C^d4NFʨmѰ獀/]U;B*hT<3CF,ѩD!(=#s!AP a@HJ50})>yY %ozbQ@=Y,Q%"ydet1;N1Tz2Cݲ<2*kZ`@H^epoT,M)ޚCXYf)TL2;@+CFzkx?28Ӟ9 p-UPƽ%' I:Y#}ȼ)z8)ٽuCoD Ys9qF=.2HFP)>11VL%%h4L1Α(*+qc..+׀SR_$^id!XmHBO5h{?0 $"#^OIꚦMg]>jo=r6ߒQV䬚GDu·,,VԶ͓Xz:'=5B+KdF@NG"ŀQ+zzdz6i.RQ!Vfo""@nʨ32ImDK@yvBhMed11jکHJ/?a@HR CBEZ92>Xȷp 8T+7ZN/f2dC\"xjrDMF E7XHȝ.AAg?{wEۓHH GnH[pqkA\_WEX(.( "  . (w8B GC&r1tzjjjڮIT0 $T2C?U,N[ieh;5kV\ZiTE,V-hfuSoשJGmcN+@"Cp!<2j^VCFU^TUedg˧CDd\xMccՋULOy1[z$.Z.6F~2 $!)9b%V@NES֕tdK`@HDl3zJ.RuV9Npq c@HvmQZ]|dŧjȨl[r2d]뮝9*v˲wj扊R6hIH=rWJ5,LV.C0 $€KLߠ+CT-*2U\X@Ekj"p?sF>-"?';IoA+K}"DDzVvTi:Q '$4rm`* TE@jx%"J+U]y]%Ze^ʪ.RXjsa@HJ2*;ܳlXʒcu^o'GHD*+CAŨ WUIe!^ O2Fe(nf$AUSrՍW49@,b<=k? Uy!"j(*) 8|zzvmu>^FOeb'A'*`@HJiv/BEDu`%(H"詈BwT< wCFUJ{EE\y5l~dc*J#)ދ%igԱ in+J+>^6+xHZ&&y 6}W=vm{f:CTUX!ۧ.==##;3U 58IIQHKڵs!|@XPP,$%%!<<;vĊ+Lٳ?~<޽{c˖-n]nv튈$$$G^^^t]dz>  ,, Z‡~XOxxdWz{s=mb/:ZׯǢE0x`aÐiӦa޼ysL5j`͚5-%%ǏNJ+зo_#G0sLw}? ;УG|8>\}sb =ڥ'o̘1 ǖ,Y+WvyK壨Ν+ƐQzTU=WB|k4T>X|QyNv^{ >-?Sr.U=eKTTp֭HOOGdd;ɖ-[кuR۷o .`۷ʕ+hӦyAAApsuVDDDqƥuD]ѠATV QQQ=z4[}RC.CU=@e+?U^j@Xի<_~pOXbٞ`_eKTU0;; '$$@uRp6;;y<{dgg#>>go&-Zqa޽;Wfi"`)[ UY('هj=-f1pO3f©S8~>yTla>3ydS"5{T0??!!!:n絺;_ktn &'?a9s&}]޽f$9YXYʢaC n]Y 7zSGfwT԰!ws|mnuDTo=᫇l:W:aa@|=SEbvETΣòa!*ҥK_-,,k5Ms!9r$j׮mzی._񧰜&s(Wgg[O="(8}UEkIu3fBo4M]ŧ""RCFe끘Qn߿GT {\ԭ))W! >RaBBDƱDO=[=pM֭`?O>T4J8UA5ZCOZT9G6^Ƥ{ȧCD<6=,*c4=LRcMڅHE"QP B@|&_  PUO>9R٪aFFv]jw}MӐ7o.uCxx8͛7G`` 6mr˗uV sN1۷l?SNnΩ/ȧX|r**45A U?z-}UfL%l f^}.f0|B版j֔滾6'bZjL0狪Fi-;SN'-V¡Cʕ+x7 0gtIIIGb׮].C'cǎac'O… 1h ѷo_̛7œ;w.\6馛Xj믿$tJ5kN8[. ?ٽ Ƭ!DgKK~_c>LW1HEv 7UH 6fz ȏ04t<1[&닡2;sYY{.,Sv>RӷoÆ #O?4tݻc8x fΜ믿]w󼤤$L83f@AAڵk?~->MSRR0bhXv-ϟ֭[ce[peM>- XTM@N뫇p)4`Z߯7ۣAx(vYZҠh|>C`D5>ٱj@4\5lh?7,Sш櫱W]$VӧOxٳ]z ժUÛo2ԓΝ;c YYYzoa*={̷bTx-[OQ#"[%*&Lii+ȥO^gwi|Z!=XyffhعS#_ s%IQ#`Bys]bctڵE]Q |!U*xbYn@ܔʹzbbDf{=rѓ!桄TT|\Xud6VI#9YlGfYzdFpꉙGYQ,NӨϸ ]R%ofh4KŪl ~[ݳ$cEUpa"R襑gP1d4 wCG6kH kCy :o=sf~= IDATK_=fҨ^]j-ʆ!)㩒VS* l\У;;qq@X~I{/c+CFU!"(S^j!2Tl-EE^xTO*ŦMK&jK*#TҬUfz}U*ƍT0Ғ4MSU5i&"n4熆!Vz!60Ҭcea2)'JNΟ7'BUg*# ysϕU=KGՍLEcb@Ys-Ofl4[?._93ȭ/Y T40z v6;}! @n{#FX~DOL)B"Rk'5iia* ilؾ\˶/FZ5^C{7PX(Qj "Ru4魇*'OOϋlТi@l- =C)tш||Y~ҵ!)ƚ= 9#" p%ӱrCh%zDhTX x=v-V3Ȑnh\<ǜ;'QeÀvs4];ռB3T hظQ>xV-96+bXvةm[u;]u~[f ]g'KW2hR1:tN>?ނ]POUE \$$yb%hذAjб#~:urM.# iDk}fIJU[Q虐OKz+ڵSwU  p bEپi+Qx|V;#m[l:('Pv*sO0zev&_t-a@H`eT|=zk֘ˋ/:8UV*aUa?]ZV4@LU@حhlP* ]{<FU Mbufc$ݠC2J%6(>NbH%S=-WK!7FD%T8+E>W_44 s:fi 8;qZbx̢/I`~{D_o03\Q={eD:F}"3߭=NВ\:j:45 ! v!W+9ZJÿيgOz+{lV ]|eV[2= SD%ܧvNs_6Vu.zem k\p%x_F.:Fa}59Sݩkաvz +5U,RmrjFʀ!zᆫf*n7QFE V+}}N4Ғ. ~vl|u?Bpl,кuwIDO2\߾WQv9c~]f? AnuL\s;QnekEʀ!# :{^fqq'd[mz4ŰVIIby(.vտȏ=&?o.Z"y8eJ70ڽG'zdƍPAA"?˖ODϜqS.gϧ}Hzj<7ؽVڷ%QUǀLMCfڴ?U^ѫ){'>n4ٝ@.Z.@+Ȯ]k?r7(nd+"6YW6n,Fsg5M,5v:u k-(_ʼn2={VƀFE.,U  90dpaپߠAoV+-ETN5t(xgSt UF` *!@mBBBJZJDdWޢEr "ҐYQqYkP࣏쿾~} 9G)2 (Sٽqe˖`@H\+CE/΅ S'1'M^ӀÁ 2*J>lJ 1681phٕ]zQiٝϭ4dJDml&e^vH7I&8qΖK|-3ltPʛn 2 #Mk+u늺ZvbhrU4Q@([p]D@FFMgQ;+ ̛g?/xߑlku˖bX̰ar ||O,]{a: 6ȥc[޻~Kg&L&MĞr9?_] +|ek,vfǟ|ܵ)D!(ݻ7|Qʤ EEsg&?b|]MG3x]44MT~'NOgp /k۶垄D!ᆱ& "Bl c[򡇀].^߬P|Cmsg$~5l2jgF2DUB:nEL"Kb]ʍ \22D 1nEVt-9iiXPӀQѵA&p6шb@ %4ƨ(Ⱥ6[&=Zz-ȑbA=áCrp;1hg\DUBPw/.N,g? _ 7̿ƛ?Yy܃Ÿq[o]T@ HMeZCK|_&ȥCDFĐď?K# @YҀSPVFekyisɧ* DU B:p]o\q|*[b)ٳſeJ['رb]{9G~ ̒X_?53{/ +{^jԐ{}||׿ʧʃy7N j)QEa@HW=z)iUKm{Uu{W^ ; 6bv` f#aU"#ZCC= W|JDٞŐS*q^\;< 7n t^Bs >(S+j;'HcNCAbޜ9sZN$&ʧ u-ΜOD*ɭIjZ3ee* ByͰa"(SsiѬ{٫d:UlX+,>:u-tP$&|:<cѵA6e"{wAkk/U  9h | `dsv+'f}d]wm&fT;8X.~:\pX:lHb0j"6M~+ ƀx?|k n<-M5iC/l\:EXUK'0PT3g=ѵ -M!Ȏ]v#OCi9rÇGLL U?x,cmaqjZ}xi\K cI-lSliѧwoeu[vXeote[xOe|d BT=oWKtȑXx1&MT̙37nիѹsgu]v1ydb֬Y8p6oތ :ݺu+:wMb8t{9J,e#`뮻жm[|'Xt)>C >y^^^233q9< ̙3c_pCxxsq͢gnhs з/0d0}rFreAAAk ^7&7xݖm1ʶQɲUnUyukEHz%*lÆ i̙3.^]t뚦/v;q5III~mp_}Ç`}„ .޽9M>]w8?-Bڵ1x`籚5kbOp4ϝ;+V`ш(֬3fDDD`cK,+WpwwߍCa.߮];nyQFӧK΂0 bOM͚a7U{<jĈ*:WDWUp֭HOOGdڷo'[lq Ɗ… ؽ{7`r ڴir^PP222\nݺhܸq4u]w:mۆmۺ}{X!,)0xQ`VRdZ_XFlK1y2д)S3"""4M̑gռ9pmU\*^PxBBt]Ǒ#Gl4#;;>KLy2~}`jOe.vf>[[JnpRE璈T*t]5 ص h?^]x<0T+:2fPVukߞ-f+M_y/ f!$H

KI?u늕cbOppET0,, t \xw;4Z㷧sJWދoFz o5;B8^f('[arrDkΝʕ#ѣbE@%6GEB Kw` 5pj㿋{2}+k)7vʔ+[e_f=TXe۲y@omgk7)VC~êWW5o+zB׋˴U逰MCvv6 11k~իWLF 1>j:ߛQe<˶8{V3yݖ3Xee|!ge8ɲU0##W]i222o)uCxx8͛7G`` 6mڄC:ϻ|2n݊#F;`Ν. ˔̏ihѢ6mT7l؀ DDD 77tÁ6gQ%RXX1uEEEl(rxۇ/t;tw}.֭^n]N2NqYz;b,Y'NDjj*̙M6aʕҥ `ر;w.ۇdk׮رc|AԬYf# [lA.]ФI?̙3ѳgO|.Œ30n8k1/_>7Ĺs"00/s{r(="""""gU> ,((ԩS1o<>}-[SO=}:Ϲ{_upYDDDDDD~!b@X ++ IIIGǎbŊVf8R?ظq˹;w 7܀(b̘18ydôiпp`ܹnϵR6m0W^)ˏQ)-oܴiSlM6{E1KsyZclyZ?chذ!"""PV-K.-u.[k̖-[5~i8lٲx햿*1۰xbL4ɹƀzjYn IDATtܹWL8m۶u9Ç[n?;w=~'lܸ_ɓx' ^yV7wߍaÆڵk1a㡇*OV̖-wAuªUV<0}t[Æ C˖-qQhݺ56l|ukٲxZ~?cǎEbb".\EaРAx7qwukٲx:|0nkTdφ tM3g:]xQOMMջtR9ZV^k/Zyw}:tylŊi[oU٬ cǎ麮6m5M}R-|f͚A\^뭷QQQ3gT>fvرzTTXW_^|˱_~E G<:eV"=##CoҤ[5ܕ-[y#FSoѢxV b.\@7y,$$wq֯_ÇW`ϣ/^oIIIc}Azz:,XP^YԂX^ܹ{/t1cuxʘ4i #G`(,,ĥKl^v`ڴi5j=n*&,,dxdOÆ qM7aժUuY,oyV2,, nӹx"ܤI&A4-iX;v DLL >#h׭ ^椧w޸[駟ܹs4h^'n}{Gk=[qV1 .c坥kJݺuQPP<O]F d,PXXXjϡ˗/ԩSM Ell,rrrXn@nn. Ԯ]7^r'n:t({/n+^nϞ=x뭷0a>|Ǿ}pE\|ӧyV ULFFvލϻi222*(g׆{"44HLLDZiӦRmܸemȀQTTr78y$jժ<Ʋuu%xسg-[F׭}^Ξ=Ve [> ]1aԯ_G aڵ 4O>k"U~d?Oz~O?kh5#F%K0qDbΜ9شiV\.]Tt>} ,, ;wF\\v؁z !!!Xn5СChݺ5UΝÌ37r^}U9s믿o & **RYk{1d\1o<<3ʪY!|mNN2331rH4n_`0`.]V8q"^z% 4Æ +QFv` fvnm曑ݻ#)) Gᅬ]va̙LUW^8umf===]-ZӧOׯ\6Mc:syZcly3|_~zBBӗ.]Z\^֘)[^jSoٲev{!"""""S B""""""?ŀO1 $"""""S B""""""?ŀO1 $"""""S B""""""?ŀO1 $"""""S B""֬YV?AӦMzGNYDDQp8Q' O<tlDGGWH{oCVVVODDI˲9Hǝbڴiؽ{7.22=|74h=2{"$%%п2{""/!$"J-..SZ5hZj9c͚5p8!.bbbl24n>|8~Q~axQNDFFSNXf͟?]wK0#33gFJJ p>,gy%{ ))) E:u0qD ?PE+:DDD*yX`rss1x` <111X|9~W|ڵ+ {sN,X ѿl߾ 6tk׮ŨQJ߻w/ ޽{1dݻ5_oquס]vXp!^|E,XM6ѣG?پ}{L>]Q1 $"kԕ+W믣^zCb޼y8~8иqc V°ap̙3Dڵ_|r̞=O=ٿ?KugFxxvލ˗0}tZ ڵ>} uA۶m]LLL;%"kRxx3xԫWaaa.nj9? (_{z||:^^=hڴ9ذapׯcɒ%(,,t9?,, EEEt ""=DDtM rinΟ?@l޼k{iddY&N>-uݻb |W{0c xDDD1 $""B;v ]tYIBBB0p@ 8_иqcl߾D/fff"""5Bv4r-3f f̘L?~+WDV Btt4h^p8m۶RiT^-[|P5+V@ 8[lg"""""*L:tOƢEРA̛-"M'x? 778wrssѦMKeq<;v,~cر8z(ۇnݺ{ؼy<Q2:u {MФI 2yNiӦVZHLLĂ n:h111;u7$433su^x鈎FZZ^}U:tHJJBtt46oތ޽{#66xuSNEڵQN̚5cY"::w|gp8/۷oGLL t]իxvZdee!::tk.t ի>`ɘ={6~7tM7z&N)S3"""PZ5<1bx 3yg* Eƍ1vX|G. =駟cǎᩧK/aڵ矱tR,Yyޢzaozׯlܸpbȑ8v{N’%KcaժUٳgcܹXv-كM6n; ݻwwvժUHMMuwϞ=if̘nݺaŲe˜i?>Nw}HKKChh(\ӦMsw޽-}{ƨQs6m>3.]5jFA5peK2 ŭފW_}-Z,[%@zqQ71%%峆xLW^Xz5N<'O[n}p|זes|Mڵ1adee7ވAPP ((TpxJqY3+Wtf͚DٕtFLDDDDD2իWc=<%s"##qcFV1vX̘1'Oӧѿg+֭o%@:{,>3Gk?2ܱL\t zMХKFfܾNYYY\53RRRJ}o{쁦i.!,, /g#%%9G>]tqT3g*B9s N]vxs eedd_ŷ~B<8us^Zj>s.qÁ{:=+Wŋq\|?#6m9r$^}U޽2e {w]qE|A2A`A$h,h0kP˘K4d0 vq}vA{ᾴ/{C>>>򒭭4:/;<<^O${? ž<==eaa!CCCjy||mw96j|ll,Y^^NZ{&''?=wbb"۩e㽽|*JSVswwE(]f||33GBőB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"JaÇ2damm Fcǎɲ'MYk׮"Y^Qyw!2EQj<QDDDycBHddYh,N$C02rH|WƸq0qDxyyɲ%IR6).YѢE E֭} ,,Xpjj3""?+ "eII&Aar߫ [+Wի|كFQ|"_6h9ׯ_[[[xxx୷Ç 5o5c}޽;aggk6l233=[͛7ѻwoxyyA",, {#GPF X([aaajpuuEΝ@o7lmmQ|y 0׮]2222_^+7|۶m3[$;FsZ|94iҥKW\!C~:u}]B١RJM֍Gtt4*V8;;q駟LOLLD~PR%},xq]b޽s^n+=3gϞE޽ǣ'4iy晌)7obĈR Z-\\\PJ )) yfȲ>}\Fzz:၌ Ѽys899oΜ9c^KHH@VPti899!222ѣG2e j֬ ggg899!00ݺuÑ#GO{Dd$I,ʕ+$Iq"33S_޳gO!˲2eA>L(#Gv YzA;vI믿.E۶mȑ#Edd$ITRE9sFp1bѹsg!˲?6X޻+dY;vbb̘1vB$Q^=SyѶID{'F!7n,$I-Z0yILL>>>BeѪU+1n8;;;;akk+֭[`'$I"..Nʼn 乎cǎ {{{(w.ƍ' $"##~4ieYܹ`$ڵk@QN1l0ѫW/acc#ѣGEEʕŠADh4bˊ,:kkkѵkW1~x I8w;u$dY:uɓ' !KpppVVV7ƍ",,L|=zx o{eaBHdt IL~>3ybcc$IbܸqB *IDV^zUoٲEh4kPpeY#$IO?56e!˲/ }]!I(SQ[o YԩS M%$h2..iӦeh'QbK,kBevZi3;Pe6Zh:vh?v-$IQQQY }&S s焍 ׮]3m6hm,"44;w߷o$I7o333E:==] YҥKwuux }͛7Eҥx</^4ZnFFhٲ1HX̙#dYs1'55`[n '''akk+v5eڵB$1|p#ӧO,%MBѴiS!˲86I$ ;VȲ, ^;w5kIDPPB$[opAω7Y&DLwbj4ϓ'ODHHh4⫯ZVxyyׯhbŊe Mߵk$I,i.]$޽{뒊O>hy/^ƨTB"lll=!9eff wwwQ~ömې44I sСƍXlܸ Cj_^_ͭiӦg}C]v CHH[WWWt-BBB4ho I `rs_pk&˛5k]vȑ#GjIIIر#7nu`ނ/xbBHD`ݻժUC֭M6lfϞ DFFW=|;;;YYYKt/\dܿlBx]!H$v}{=wYyWكO>+Wŋ!@ʕ1qDt-_1kڳgLN˫9oL>lIѣ<dfڴiXd~;;;DGGcx2rƜW޽{(Umu?bGҥMΉHbb"ի#<<pvvFARR,XO/_'&M7_ʕÈ#0x|iJa7RJa8q"֬Y͛7Cwww⣏> … 7ߠAHOO… 7|h$Ǻ?{-_?dY1ydXcƌJB^駟)9ۛ'&DT`~)݋2eɓO1vX:7̙O /Y{ TTɨ'>9ծ]|8tҭ77'ׯ5k ##ƍ1gPd7n,O;˂ń 0a\rvB||</^K.aΝZN65ՋMuQ3fݻ;c0mҥ7r矑Gb֭3g VBǓs{W^w}ӧOc۶m1yd!ڵkc5k֭[۷ocر&{ E:%I281c ̘1/^Ν;7૯`}=`lo"*8޽{1qDTZ'N@PP&N{Իx"кuk˗/ŋ,fSHIIApttDqƸgdeeM߶m$IB:u^h=9Y[[A4ifϞ !K>?LCVV~n԰/ݻwǦM={&(˲۪u1رhڅ pe돱 @ 66\'DEEM۱cG=ٲ,v9r$,Y!~7tF!Dn1[jU 87oubcc,X}dYF߾}_x/;s۾};@@bbbchZEΉ7Y&DoCaeeKL2Xl4 za00N~=z} !0{l۸1bӧ1y[{L /Zn$ߏԩS>̼}ɓ'F庞ʶm۰n:9s… hѢEA`eeaÆF322^tt-8q¨Çx`cc/75N޽!ԩSq-}yVV!{=}y5k`ҥF{ўCy;Aݴi~ƃuJ^'7770{K)zcF󳽧N͛7N=?;wD6mE_^vBJ&%%!11h;wӧoAω7Y2JD[oVZ\|sA͚51c ^zDnݰl2M6>l{{{ѣ/}$Iҿk׮pvvƦMpQԫW#G|2bbbpa̝;+VDDDʗ/;w 11vB޽1w.k޼yhܸ1F͛7nݺHNNƊ+h0(ϱm6Z'OĆ ~ }ԩ:u@9r7nх)+WƏ?>}zꈌDPP222ݻwNs9W\AڵQfM\rx~wܸq|Al˖-CPNX[[I&GÆ 1j(|Q 6ɓLj#˲/Cۓ'Op)l߾`#::'i&t( ]h4n+V .\kCmذ!0k,ܺuK!CPT)񸹹aɒ%ܹ37om"88WƯ2(!˲5k,--M|GRJ^/_^  [nɓ~aڴiJ 9@Djg,!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""PL,B"""""" ńB1!$"""""Pc4h[k_~VE-p:iii899N:7o ^t ,}4 /_^dLDDDDDd$JtV°ax8p;v@F'@ƍqq5 nnn;w.qaTXpIUVhӦ i&Z z˼tѣGk`}(W DDDDDDc1 РA̘1Æ <}5jԀ'cv˗[nXr%:uuЮ];,^pmܼyUV5O>p_&bn]bзo_}-}ʕ+f]r% K.Xz5222nnnF |O6T2^I/A꧛sԩSǨ<448w\v$28hZ!44[lyI]ooorooo!pB y3220kWD IDAT,^zrY/k׮ŬYm۶ذaCA6PUIKKQ~zaB9q_O]\9_UÇm۶&"""""a1 =>}jT+Iy |O]\\iӦիyIeYƆ 0rH̙3iii … $&&ÇAĉ0""ܹsq].]͚5}XTaI_O8TB 55U222₻w%Urpp0z[>q_!$"""zRSS ^f$C 2ѣGpttT: Y(sJ^*NdYĉyR^ԍ7 B??}u a1gZ[Fy[?wnaMDDDDDdY(&DDDDDD !,XfM\?"޽;fΜtL@bbbtڵk_~m۶-r>} &`ҤIeu= Y\/Uj†  GOC"bBHDDDDC$2z/ggg4h?.\ڳg^ڲ݋=zo߾믿бcG8uꔾNQbE,^AE !Ybwwwiiii zjȲ F]v?-[߿??~l5jԀ|}}1d7舠 ]V?-++ *U/4ǎ_>Z-\\\,Xqqq:F 0eʕ+޽;ܠj?ZlڷooT^J4oƍs_8qڵCR兞={FYvmL=nnnF\\\~h۶->CT\'OF:uW_k߾=.]HyLѣG{8s v܉(!0btq]F!55pssáCb lݺ/7 ɓXn =ydt ǏGvo޽{raʕ8}4&NcŊLt ͛7lj'~A$t ÇGqwh?~&Mڵkqq$*={nݺ&}gXr%>lrѲeKk8|06mڄ7oK.y7 qqq‰'лwo\z:ׯcǎa޼y0uT.\V?ɓzۇVZEDD`߾}e8p222 - *t(DDDˣG@,dY&SNe~pssiii F#n޼)WL0z%I'Nc!Iشiy $:w,Ν;Bek.u'M$j׮mrWB7gggq=޽{B$g޽hժBKȲ,.]$bԩ"22`ޔ!I8B???1{l:!!!"..`nPgܸqjժesNNN5k&4ibP'44T;6؈K-ˠرcf}uLF<핎̹{pqQ: 3Ʀ藛ѸZje˖Q"""ЦMDGGtf9s jժ;;;}YXXpYիWѢE<]fM䄛7o˾k̟?HKKCzzE1...իڴi֭[UVҥ GEڵ윯iii`ݹM:ժU֭[QLm۶ J2($ .\@```c >s 6lhPG([, 88ؠ>/,{{{!ˢR zR3)SR>k9"zIXYyflܸիWǜ9sPre\th_rE$K.ȑ#ѷo_lٲGELL G$$$ ,, ˖-CPP8Pq긹A$ܽ{l{3f B=z:tcpQhҤ 9a:::(v)^^^qAٍ7;w@$$ԅ !h׮'g@:))JGAya|ݓWԟ}eÆ 1qD9r666_6664[jU=zTcd?[hPJhZlݻaaa߿?jժ7x=G5j`ɒ%f-88籶Fj ްiʄ p9,]-uɓ'QB|ti2ep5<<@bbscZs}{ARѰaCvܲeQo'PlYz]*!LNNݻi&>|O>U:$*>cccٳXz5&MÇ?w޼|ظq#fΜgbҤI8t dPohӦ ^>¤$=*T?6mmۢnݺpvvF֭/mMW/fՏmnl""Ipڴi !Z*N<`TbV؋~!$""*O-[5j^bvڅݻw֭[[.3]WuyyD>C~zDGG#,, GsJG% b ddw[?~tDA=O={[[[СC֭[JF%⃽wBtDDFebŊصk 99ΝC&M/_lrX fBfBKLFLډJ{AaHHH@Æ QZ5m۶v FHř{X<=ۈp# R5Ch'}FڵkѤIL8`իWPtTXYnnJGC dCFD'Ik$ !=p+gϞO∨.fJGAybBHDDT"!4#} k IJGDR2:~~~7tܹʕѣGhРnݚy߿~Z-Z#G ꤥ믿FDD|||:u`޼y2Z9`ooZjaҥE-w!Kw*B""I5=L\p'OƐ!CdzªU0l0">>ڵÎ;ШQ# !Ю];?~FΝf͚èX"ŋ2dZjÇ 6mBll,ߏ,wܸq6mubѣdYF.]d)BeÃ{J.IHmo>DEEڵk/A1c x)jԨOOOٳ˗/GnݰrJt p-]vXx1۸y&Vj0>}#]z0`fϞ۴iS$%%!)) R>Ȁ amm]bNڷRRV:"mT)`` Y3#݁ $!""KkWG5Z$/Ybзo_}-}ʕ+f]r% K.Xz5222nnnF |O֗ox}>._}nC$noU= J5 >ϟǸq_ hZPts9:u"55ΝsݺMwwwxQJe !naaڞ=JGaÃ4fLvZS#""*YT.]...*U_E\\\\\u ڵk6*W^-Լ7##fB@@իgLS/2:{X^(zF8|X( h4ٷJռTf/uiii5*O/̼B<8p Μ9Cɿ_$?&)]:t$!߅tmt:t4DDDTTT6m.䳈OE)Me@+i<<4RK!mS6QKҞ_W6""*9233M}mKEK[F]755'O,MTWSc̘1رcM.+c#lllL~LRe,|y 5$] Pz.&Dvґd$G=ImDDDEiʔ)f_b(;/fLO:qơbŊ8tPsQ7"$$$@$9a$$$AAAWF߾}2SSSq̙cǏnhY/J!vYdØ`~5PS4!> 4/8{,233 qVZ/uVX:k.t͚5ӏOhJǎaeesϛ7hԨQU.xTÅlN6RK!I ͛gJGj!T1CDDşF^ E!Ɛ!C0d4OOC PKqjIRՂmDDDD/j¬,L2jx"+% ұz6]`+ADDD%L0z•+WUVٳXp!~wãBPkJG<Lud9?׮K+Q6QɢŽ;bڵغu+1a>}k׮E֭ @w6Rc5nǏjz <<[lQ: *"j!T[LJ%jLFF bo"""*fTCbF K=}$e/+4mDDDD/j⪉++W` DD/JwѨ7$zxii[2]+||n#5aIO:ڵkԩS DDuumch_2egπw#wzBBۈRMBhkk&vT#$e0D Y(_HNV&%ʲ}q(5)AFjkmDDDT&!lӦ Ǝ9ܺwƍǷ39{唾p4u+1.QVz}ʾW@C񚻍 tM>M4A _ĢE Jw޸=5Ld? Ԭt4ѵ/p 88(NrʟDDDT4TCcǎ?Gjka8~8ʕ+txT!,r*$IbBX̨B6g,:dT"b)r?C!ʕS([MĄxSMB8uT|'=zҡP*_>٧업jz˔ɎǤ!T*A6 U>헲e y\yݻwѹsgà"nP.j{U˙(g{2dQ񦚄sؼyaPSz5S=/feIR6A6뵴dj=DDD%jn ԬYӇ PdTP/uկL}f͚B@@]t ,}4 /_B۩90{{ rExr$z\"g9;NN6j.iPA^Kȹ_I!deQܠ7.\իnݺU$իVZaÆ!00h׮v؁FOv5j0w\4k FŊu,Y˗N:}nL=z@v 6lXTS<.rgR1pA%+Z&.ed^z5gZr--mD-5E0LEiFfl|ޯ׼32PwbLNJRUGSXuc҃Șp4iv4Uk8^TF}UUs&ZGADDDMB-[DBB&OLlڴ 2ۇua4i`ذahݺ5N6l@ZZRSS1p@AٳgcժUs_}UpwwGqquCuӛ&a+>^pdU mTUr ~~b~"#ՍI/ʷQ&[R"PBDDD͐х s^x={ĺuФI|ƍQLǼ1rH!xT4lД @`` ͛7taÆpq pvOZ$_U6'K-*ZFwhlUmCDDTS&! G۶m.:T4ngdd ** Ę7'==ʕNcbbPXX,z૯^zRUImb1GCdB*Pl۴h#KӤXHpHfJJǯ\bva[ 88`Hu-ך޽{c޼yزe >}`۶m6OOBɆ3UYŽMomLDDDN7sO>Jo޼iq8]>>>V$ךV){'ѪU+L<}zbn4m*:O@V-CYjvL͛LO?5ԩcwii)v؁&2,{XV-ܬb#7nZ`Z[`ĈxpyX}9,RU2.c1W} |}vTFU=//ua2s´4cFDD$RU\MZ_C4O~a`@RRR<==ѤI,X rhgNNXLMz.]~~~f={6̙hh6k8E UCؙuFb~""ԋsͫBf1TW!t6"""͝;/a< 5nwYYn޼cǎ_~Ntt4p hf͚%[|0ׁԢhBng\5i5smԨ@A聹J=ϓ3k,WF4ON:@[naҥcHIIAlli\;v|D\p6m2x"6n܈ӎM.^XXvv6/_v!((Ȧyzz}9\Ԩcx/WeK(// 4mdԤ[vŰBHDDJqww؇%uh>dh񈊊¸q*뭷pq$'';t 43f 9s˗/77}tXOF?&&&"99#F#G%KPLӼǏ#??/2]vjԩSq !!!8u.]BZT!T{ k*^8Khw.TF@bXodk#W߾&!LMM֭[+ҥ 8ʕ+1k,Z /_F۶muVř1 psX8ussöm0e,^EEE+^W8fwRR)!ݻ7}],Y/_Fݺu: sէ>S?K6֯: mkS7V̳TU+!}€+Wk׀;P'&"""n?wTћ]v+^{ s/_^bhTN,]Ӫ$%%UZ*C !C 9BWnXXj(8ϳ*RKQ۶DDDDˆ*7d߶m,ӓ2?:ZҋHqgEjU>kMøq㐗=zv؁ 2\cy]bxYݺbj3'KKO.VX!\5k|l#""J7 O?7o_ܹsM4;#Çk٪ΣF)}{c2Ǹ؍+%@m#愝8?k*'OVDUoB""""yf(3ΝÅ pUٱtp࡛YJ՜SimiFU4n3!$""r.CX^>}ud#s٦MWՍǒ:uGR'iS 7Wl=X!+cLDDDT='7nDzl`#%ShVؙ5zB !T{eMaq&l ׾} H\aɒ%FFfՋ:Ƙ:uTX"Np37k*ƭ'7cd$kQt>#>>-Z(*GuɆ[?ee)T9G`JB_o=JlIgϞu$#KȨ(`^}b |5I[OXØwu$Fjo=aM[hZ!zrZ!tꆥDsAmdmAA%""r.V֭kq3JKKdB=bP\,Iz-ZGgm_Z:""" 7|cӧ1}tm۶s}_Ejjё-M6>Xj[\,zcMpf㰥eL8I/iw>MW/DDDDM0!!+K.ҥK߿?*[ [HgehL6D1)C$z,M.+ZA#{$p㆘e,Fw%{[qZ!ܰAXƚ `KXMH>J\}GYYYQTd:"ڶ6КDN˯w߭|L(_ŵ%IqF^RXlyDDDE7 -[O]v B'bmUqT2!uqaW&=&2&J&$waa@i)(^XS!ed[ݲ%P`DDDSC8yd<Ӹ~:\˗/.]:<Ɇ{t&zf0jִQHxOΝS>kQM0;;Ǐ֡l!#WZ(;GTd%.\󕋉䣛w8gFB3&%[xy͚qz"""g9}Ŕ)SpQi0`FlI6WvK[ƍ" /O,RY򋘷LTmS&Cמ$-eHt5 /Vz`0Tt DRت͛lMNMӧEGVS-O?U6[J2ZVVfdйRcDZU+P5i:\V6]UܚΚM 3SaΎTHtRbF+=Yc&%yh'bC$UmF77e㱅_"|tڵ GDD"""0`|wݿӦMChh(|}}۷[um~~>F =z@zzz+9mڴS$GZЮ];]m X(+FTFQi-c&!\jz___?ǏGZгgOYFHJJBrr2 E ؽ{$IBBB֮]c޼yC||}:zz 7СC~zV-ҁ22 avBXVLLz =mTB[ڨm[e[STkzNhBZpa ,Zh+ q )""Bxu$ mڴt,//O x Hnݒ$I'5mڴ{fggK^^^+޽ԨQ#̪X [u23%IνqC<=%eb#I϶k$)0PI&M_BI𐤫W$iFۮIjNpt# @~ͺs-De8^m.)IxCpE豏[SBxI߿ԩSƍ0f 9r$ҐmT4l4 yf7lV'֭[3fLcƌs琖fˏ+T62%Z0j~ʖ6UKl6J{*?,,B!""B7 aXXvQ322 cbbLϛ:T:BdeeZhQ鞒$U9?љҁcQ16eIM6j8u ((?{W/u!""@7N<ǏGFFt7| 88`H[{Z8B8f]8q"&NÇcݺuxgQQ+1=oϵ$YVx+BmWJϝwcCDDDMXajժ7oV:~ \k0,^DGҁ__T옊//mD9F=a}J{3 U\""ܹs_e&!;vl河1vX,\~={`0 ::t|Ϟ=ETT]◿aM5KDDsww؇%uf7rssѴi srr!IJ 43f 9s˗/77}tXOFF099#F#G%K s̩:OǏG~~>^~e@vЯ_?@hh(&N磸:u~k֬ޯu_ǎ_rcb XLxž6I6k?j{x?Z7{'"BT,Ov""" $<f̘͛7N:+W`̙ey+Wb֬YXj._mb֭3c0U3ܰm6L2/FQQbbbb DFFV8x+3;))ɔkzÇ~H^C ՒNDIEKѾ=p8ppZG#?{+r#գNիEom)2!$""'$c0Ovv6w?۷ 6o W_}%5QII P\\c6tM``NE bYؼHMmExb҃q〻OI4~cڬY=z&~mVU,_H4lh5̝ o/Gþg{Λ'_LDDT鱏[S#P:t:Zj;7Dff&A'dk`=OObb\F|%! IDAT3gGOlm".rWz&""t3d['=Z0Av cb}aőQNţqHOmv{Hb&9{((HlrXS.KVDDDT TfJc^ڈ(Fqc䉇ńdH)#(.7Gmڈ}Im n:Zŕ#DDD$0!$EӁ ՄL㱗NfOu"_rTQ~ZԄ !HϘF`>ycqtIƚđ6/6.׷ ItBx0PX(_,1!$""/$޽{+߻w/L goRWL PGFqqb 8Z2XT="#K?9~/"""n±cgggcرDDry=ŨkWSM"wB8^^XBOmhԫ4kV3^9;$GEttt۷ѣG5aocGYѸֱ5n,VIw}hڴ)rss`#8lt|>b1i !X,gB y/ƪm$W4m Ԫp&! šC믣UVر#|Mdff",,LFt 8t~]Xڴ._kG.]7G5-!iU\QMy~~~=za֮ j%zSX<N#@s.u~zhwc} ?!q&~)OOO|0`JQ@:3OKr$ݺ~xB( *Jcu4sڷrrl 4T,\uDDDT ?\4h? JKKU!ァXuƍ~Zs4Qx}`L#g{w`Κ/^^37O9ۧ];1iIyIDDDtaYY4h`Ah2>HK{S*vGq:r$'V<ž=;Br񻭧\nYbn޼Yxqq1VXADd/9: n# g[T.kJgD9(hL$u}-H59_$uvGDDDMB8bĈ {]v #F "r^83[\*PbEo:FkK.z9|tJC=sΡN:DD'GB(wW/Մά+P6 }_EԑCiRu|$"""iDa0`0гgOxxNƒ>ad9:ݺK@zb *:+{^ۅAϞZG8Ș??RJ}Tl߳m)>UjnU52$IIIZ@2Q3.z= >pƅpGDM=:%+P=ΙQb"[oad L!ݸqW^Cqq1MP"66۷o|= 4?z*ݽ{7v ???c„ (((pΙ3gV;jMu]ԂFάQ?h}Rmw_)JO۶@:wvC<"?DDDd#T 0m4_Td ;L'%%aӦM4i"""ܹ]t1{$IHHH@ff&N;K,A||<ʕ] 6V|nH]Iq)@-Lմ-[nݺ?''C$?ޮkɁ`0{npssC޽1o}m6>=Q2 kA_ǺuʧF;ڨY3];+rjUӁ'-9)FÇy՟FSO))ݦpĈ駟ӧOo L4 SLqEEEbs4?W')**ZIL4wn öm0zh>,+V@AA\Ѯ];yzz}[٘L@v= EFTG꼞#hsլ@} K${nTZc0<,QH[lL"%%Æ S1114hf̘ . "")))8s |XbN>F arr2F#G 00K,AYY̙Su^~eš{=z4Ξ= w޸MM:'N@Ϟ=SNaҥ(,,ěo{&;0nxd[/}x%xՉm4v,[7W ,[̜K6IAUBI#Gs`"""Rn*ҥrJL8V„ PZZ["..t`[ŷ ۶mÐ!CxbL: 47| o۷o/{95j6lP޽{ K,رc#>>iii֭ro @ǎYE졇?~A״Zm$6@s$YjѳܺkJaƤR7w{M"""Wf$},?m4k2͙ ź)w)Z&U??HF\X>ϭ4ui`Q1B;UKEU[l#ҹ"QTyM[U7ʔOƊ01oM/|ge(!GM53Q >nM!7nҥK}vm۶R/\PVj~0vܿPNձ=ts(iSљsXQƍ+|>XȤ*j o߄PU ߁dPUjV|QfBJDDD͐C!::nnn8|0M #ՁwU  ,Xu$F-[ݺo#-(<}hZm4aaC[Ph5_^`U""" 7\gP9s&пVE]MqUF3f} }!j0y꾶5n_b$'W~^ߡ}OjLDDDMp(r ܨZjv ;wײefu3UuB6Z{GVgb%M*$8ZW&!>}:0rH޽[pZt7h ڕ+`ˌ+?E_ |_:ZOx8Я~* غU'""rIŋG-k!77Wjw w5JUgU+W/%T}  FZ&Ɖ5ӵ->f-(.\}LqwF^JDDDMB၁b8{,FիWQF0`6oތ2VW_^|zUX^z X6}1^_^9-ӧkhmBTέx\'Uu봋&MBX^PPv{nnnDRR5k;wjYAd\е+0o5o.3GѪbb7޸}LgG۵㯴Ι\)Vaoww?¿ '"""*!pϟz*>3:u B^~~~0Ho$"""&!lРvڅÇcĉW^sׯSNiBK/_~) :)Sӧ뵎D}y%/Dh#VB6@SG 7NӺ}S')ZGBDDT&!p=X<`0q*EDв cƈ9Gzo LTqWU6v,PRu4=g?O<4hPq^y][lad;H^>/RSECKzHĜ=Ch;ISOš{}1327DDDd?Œ kעsXl{9,Z2񊬢ldH cb K#˗ErbˇVD{w`mC"JM: իY%"" ˗d]ЧOӿ;uĕE^:Æ}䡇QK@ӦZ'6׮>ަn7l(JJ"@DDDLnXPPib' >}:;̘1֭CYfFH`g2`&嗵Y1I{t^xxWeXgB5絎yiΝ;{l2,[ ^^^0B;i\,2h3i޸qS{{ O XBhkoE~~>^ 6_^Zg@ƒii@鴊bn&>{I 9Y$i s`BH`gvbqmn݀3g{-& n-4Oo%*u4DDDDŽdά݁eʣV9'4ؽo_}ca"9$""""ؙ ͟/-[&ͤ1w |X2&'yq\^w߉ ~((:""""}bBHcgVO<|5С@ʤ1bĉ+gVKD~BR;h^,}7e0!$E3+#GQ-1rž{1i>}=uW򣏀2QFTH{N,bq """WƄdάrj#p$ЬX&/{1iWF7߈-_zIT֬nݲ>le bEe76V_(@DDD¥bL6 Ell,onյ=z44hU{nt~~~Ƅ PPw$_Gxx8jժvaڵ6L vf[!:͚ᤙ]u^ZZ9ss+AlIq0sX2"x ׺}\?0g=z n5-\3k&r0)) 6l-Z$$$`$ Xv-Əy!//8qDs322ЫW/ܸqoFKb;sLL>{[oƍcСX~?Sٟvf ^-NĊaZ-2 *]ܿo4Ƕm TQG/gΈ$~4qcTKH}Kr{ pBӱ7nHR\\k׭[' iӦMcyyyR@@OT8O>Rhhtuӱ_rss+ӱlK?~|w.5jH*++*((HV5k$pM7oJƍԷ$ժ%I'IIҙ3K}UW\\, B#4o$i#IuJғOJRj$]vs$vmbteeew$-IIR˖4s$/I7n疜+75T7n5t#GDZZ^ bcn˜1c*֘1cp993kBxy> |*u@d$3bin.+PZ /Qݿh? YĢ"#uMس \ЫWG9 =j߂ADDDz2 aFF_xLLysѡCJcbbPXX,@ff&nݺ;V8fdd-ZtOIOtL6W~1ʕb(ҥbu"")SݻE>mpH =<<<ŋŐwE]+޸iDDD6s.GNN_ Ipy{U^ ϟwߍ 7=,YB?b5Xȉ$1iכu#9Gzc0*; 8 *˗? ?/扶h!3 {DD/.ۻq\+IZ- 7`ģl\;&ì,E=+ ׿hJDD5$j͛7+Zju`0]kܹ_Ñxʓ,#dd%nnnpww~r8{V̭sj%Q%y%s-慶m[21/1'Gl?'w14/OWvMi|ܼ)ڵZG>>{{*5wvsU\s#5\5?SX+--5hQQ!}]R$UXxkO͝[5sN)KWߪo:_"rZ|nÒȒ\r>vBy.FGGcΝ~zehזWZ___DEEZn 8pJJJ!CT|_~2S^ 777|v:*$_ Hv߾}sѺukԯ_? 770%Qׯ? 6G\xQQQӧV^mz:tǎ3}?=_|~@vv63`ѢEwӧO̙3f<""""""9LBC '|'""")))8pkz)XOFFRu׮]q_B`` ,Ygb4.=EE\\ZlѣGٳXp!Wgڴi?>FN:?ƶm۰f͚ D""""""%TBX\\YfaժU|2ڶm^z z23b\'O4%)SO>AQQbbb0|o߾޽ӦMqw`Ȑ!xW,ykxH̜9=2oQ9.m&""""" !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD䢘(&DDDDDD. !bBHDDDDD\*!,..ƴi ___bV]ѣGAG=^鼯 #GD6m*w1L:۷Gڵ~tg$"""""K%IIIHNNưaðh"xxx !!wx$IHHHڵk1~x̛7yyylj'*f]uEhh{ЩS',\'OFVVbcc_Yb$I:5۷X`&My&Zn |f]~={1b/"** Xj\ԯ_߿?9'OVgzz:7o___ӱK.e˖h޼9[~t"""""*LpƍQLǼ1rH!;;쵩hذ)@ <7oFIIxÆ ^m<۷ @zЭ[7϶hDDDDDDvq0##QQQp<&&9СC111(,,DVVlq"00P2 aNN+$I8]x-;~DDDDDDLBXTTooJ}||Ls$IV^^f͚aʔ)ߏ:ZZj͛߸q= Z}EAAJs -)++CAA`0<ͪyDDDDDj)--EYYYI2ejXp088ʡ999ϳꔔ`8|0KlҦ Pvm_H^;C0j4IsN\~2{`@ttkږbϞ=ETT]1IaÆo еkWa$//leBқZCd~nYKzeBXXX:I1|,]=)))5m"|DDDDbӦMxG}7n܈qaÆ Xt)z!a&kPS"=d焜?%hnJe˜ 43f 9s˗/77}tXOFF099#F#G%K s̩:OǏG~~>^~e@vЯ_?@rr2yt>>>Xzu<#M$"""""eBXr%f͚UVh۶-n݊89Ri ۶mÔ)Sxb!&&+V@ddds<1㿓L O?4U[nH I$ p(9xyy×isKH$# /_gt׷PqRB""""WRXX0I7ׯ_a,.CD8Ib,UKb!"" X!tr\W\v\PVV[_},Z뻹apssÂ@a!0k6@L 0|֑Sݻk Ю@&QVNZUsK.\* FAA>>z:9gP~;pvnZ9b)/?_$z`E 3'\ >?DϏ !9 qÔHEyyZG ?{wdJ7.R(؂AeQd,tQYԲ3Ȁ_: ]vbAjj*fΜ߮y1g{.$$N=|0.j 111_|2+0lذw _=x 믿СCѬY3$&&{:gԱcG,ypFF~if1bbbbs>\}ՈD6m0c \_ɓ'cڴihժ0gy-ZoSLA׮]OO>x饗7l0,_s|,)(N`V,6*6d9^:P+ 飬 ƍ}݇| nvXV/1c0d"??~--Z%K_;=ziرcvee%:k׮ET}7oƕW^w/V^۷;Sp7o߾ؾ};>/3f sߎqr-W_W_kgyq֭[׿O?4>#STTl"233uVTWW{^8ܘ8, #; JQZ. 2;9r$ڵk@LABUUZn𸺝;w%K %%?:w1a?HIIw0gL>3gδ߯w⥗^G}o딕!!!ᶄ9֦MTUU9zv"0TiQS'5;$&aJk%8DAy_.ͻ+p7W^ ¾}:w}~96`TTTF۶m$FEEjٳ~?SF)4aL&5SY\Q)KUR*niٌ?xѳgO,^]vŁ\>q뭷b())O>*^;**ʫj & 'OtyN:aZasxEE ;w|awQiū61Ln&&&8Hwĉ0Ln;$ B *>V+N-[@VfQ7m,B@c2N?vYfaǎ;/cetӧOG>};ݻ7JJJ믿z1,, =zpXaә3gb޽X|*}ݻѾ}{tVnǜ>}?Sٺw຾͛7Yf/?A7믿F۶mѲeK_ǂ*DŽuǫEP0KC*e!"}lݺ?>޾%Bݎ]XX>C[nA޽1o<;qD{nc!&&gŋ rrrO}\c뇷z yyyHOOǚ5kn:{.\ڵk1i$e֟LMJuu5QUU׆^hiY{7ؾػHJ嗁?Vũ̕#N> gy9`1a:vSd\=Х PorC= X,s4>yYJTqEEטQƎ>}`ڴi(W_ڵk;}cMq!4Μ~[Lj@n*dE);#*0fqP# Q}Ϸ8$\xÁ-@Lbڨ Y5S@,pS[+ sLfqE,DD6)))ӟ$;2?K.cXR@UR,N ũ ͢q~U,.;JyE.;Q`AHAAjũJYuFF͚ɟJ \*eOE*e!""jXR@S>.*u}TҢ)YT9&YĵWUU\ɓݻ7ydgg7SNڵkqYdffo222bغu+[tVǫRaƌ;5yt Ⱥi#{–EvqjˢB\7 8f @D*}>DMMeeDv># &`͚5xGѹsg,[ C|jСCk.L:Z/b[xѧO$''O`޼yqWbݺu7nf3ƌfvegi;ut\dƊӦkqUZ" Z #f֭[b xp}ᣏ>B1uT]r%㩧<7"$$fr?ӧOcӦMݻs:sRC*]f;9#;T ņMR8,DDD'h s!xXz!ԝ2*uBBr2,qq@u5p,* mYl2"2)p…?{_k2>Vy!Q` q;%cͶ=z4f̘rt˖-ÁtRO|߿)))DApB{ؽ{7/ŋ={ڵ .~S zd<#TUU᪫;#> oLQOBAXp:i:K!KA qs˖rn TT*DDD"h Bx7\,*م,^nwy02mY*/ ɱcb\D&""]P7o͛>K.u4oyyySN0a&LqiӦaڴi߿)Q +) T)6R-˷N!֭T-h 6V\ی"""A\CHQC(TjJU:a*U/>,DDD!Fjņm T)],1 Q`aAHSCҴ;ڮŒEgT?:,DDD!FZB¥ep6Mfcb\~DE5>,s( $= 9#?m*WPU"s`]7K@Hp,n U](P $ݨT|.TnŶ-K\pܓ*,Z.*CDD-9Y6mT^PN YBBx5$$'O.Ϣ#; Q`AHqFvRqbP,ö,@j$qgm,CDD԰ $ͩ!K§.JYTTҬ!VvLd]IDDXnT(2Mn,M'$/Kc'K9},DDD!Fa}TUEgT8JY\_B""!i SaX +W-nΎˑ#`f',DDD!FŁJ n-<6mۊJ6òS9;ǎUUre6i?,'KҢfQYڶ|:,20K9df!"" ,I7u$DG͛˻^NɤN81?qBv}g5VeQ99<hJb%*uΎ*YL&u$$YRJYTLDD(X.l6km(#8ʽV)cAH`!v=Ϣqa!3 Q `AH !;i>QQQ~[x^Si`-k=gTEkqmW']ȵ)KJJ333weǎӧO333qYݻk.Ԡo߾ Czz5]wL&&O?{g{#GDZZU6զ sBu e:Pd}o]+#cҪq)KKK$XVqs?&}Fݑݻw_~h׮nVdgg;tmCeqB!tN,|_Tb+NUBDD5ΝCDDD###VX۟[5qW[nAJJ 6mڄhժϟS ;8c[(5ojUlwYvT',r͒n\""@4aTT.\%=w5Ltuߺg[o֭[>|85k~'NDn<~n41jՇvޖS^Y3sb\gcǀsٗYNIBHI)Վ*Y3x߹ےfhRRJlgM6~?6}F^^ŠqF¨b <<ܹsz.9T݂q9v];QQbRwBU2ΝrZwFWػw/***n/..dByFؾ}{ۋm_W^ ŗ_~pj8Fyy9jkkߏ.5j.\{wF\\^~e\xgvxg} u]I&СCX`ACaҥ6lz!o1/_㪫2~feeLƽ^c=e*R#wq57>^tNegIIE.Y9DDD !xGPPPɓ'ׯǀ1L0lƆ 0vX,^SNE||<6n܈.]87##Ɣ)Sd`ʕKKK1dxQ\\Sw Y|/! N ŨrDE[h,))b^'kzA!p̛7syK:t m7o<)6mj~]t+_ST')Q\رøS8u该,EEdQ3R-KrsDGqq8MM5& Q !B:>: ZjJ0fdf!"" ,Iή!Q5[?5.Bac Qhg7!X\u0.Mr2pd@i5E,FtTʢRWN,DDD!Yx13*MhJݍY3QUҾ>SYT9ggM&96Qum3* f![I.uKEP+ QSt,U(4Mn%xysb18}ou28U!"" ,0*ulT4tebˇRI./NBDDt $͹1JO:FfQ7KcBqe@d)QStljO0s*YL&u5,Is:2QkCئ PQ:eLƎlg, sdq,DDD!ݢ2FENv@e1n0*Em d':v| ')fqN,DDDM BҜ/!ADx옱Ya3w1Y '?td\wQ˖@D1{6%5U*iwE,uBDD(X. f5su`\GAJY1reb/Cd'Zvu>@,DDDM BҜS?uT2Y5S۞ND\U%?J WEX ւuŨeqbםqkcǤcGy9wg;w~, *'faor%* HL2DDDbAHpU8+5՘\֔r"7Kc95 Q `AHSCؘΝE7#c.Lw<9.))ɓ[WN,MChT""@t,~N@i)p,6-[k~Ytu:-aabUXm+F!tœbDҵ{I,-['/ Q`AHSi?:z_)U:a, Q*0m4$''#::YYY(,,豧N¤Ip رcnٲ\s , 0ydTVV:?q!!!HKKCnnQMC "sYY<:$Q%r*e4Ge!"" AUN0 .h"bСزeYV :˗/?ѣ8p q ;;ϟNj/a̘1 }Ů]/ƍ#Ghehl iJsغ*tQ5׆$bQ,qE.78R"""`[bŊG}0~x SN͛]>vʕ(**ի1rHѣYf~'x-['|h߾=&MBdggEѣGlܸzu5 ]m_O{^_O;-],z}VfRRBnǁ_.LngG[W^>Y֮/ QUm8q"pWFbb83֭Cuu5̙3(,, s=X,x}ؽ{7f͚p;w^@ ڌX[;bCOfu|V&N ];mI.}>*Sֵ+wS/h ’!&&L]ٱcL={{ڵ 555۷pG}Ʉ0\y啰X,]w݅'O>UI'sgæ{U0o:z>M9|o}k6_(_ÁkWqҩF,DDD"h R$%%5=)) Vu{ RL&=V+ƌ=z`՘>}:V^Ç{Tؠ[7ɛT,FtZT)ڽɢJP'ɤN-ď ׿,h s!""푑XjOW9r$fϞsb˖-ŻS']ݍ)R^{ J`ʻ(PMA N<UӦ& n}7^z!44_~QRR}jm:֭[{*++QUUGFasڨ=՛,z_i5j*VD'w~Yv9SJY?.ǯ)Go²eːd@YY0rԨQ(//ǚ5k;v VEvv6 t؜~ĈҥKrd Azaaa.BBBz.yz߈ivTP-*]=Ĉ3n{{L:wJKzޤdQe""r.$$4gffbј1cѹsg,[ p(ʦO|߿)))DApB{ؽ{7/ŋ={<0`:L4 ‚ 0x`"/!!O>$f͚nCII ,Yq5غ,Q xQضMt/' }:CfQ[iwСdCn}{qz ҡbjz˖g!"" A!7x< 0ydb0`>& fa1ذaƎŋcԩƍѥKfddј2e ,Y\Az /裏>@nn.-[77B Bo; F>z,Ҕ;*eQ[W_dQuj:JC7o͛>K.m07oTk IDAT<)6m(Ӄ>|У6%7jQ_ o))sѣbQy}  _N':ÖE,铇 !ÛO?7YlŪeef0k6Un\vXDe'Cge'QSfqN,DDD*bAHĈm^kJ/SwF,vZzP#KT6Lț,Sأ,G)Mg""@tMkl`W/-\~9k8.={*)ܫ"""U $yӡM߳tzv'*||9.*eѫ8mYL&}rLZ׽+?Kn?gh(P $]x:hr5+u}cQo!T)*JY{#q( $yS\~9WfW\孎JZ/rk_Jng!"" ,Iz;ooo\(i, 1:5LK~E,"1 Q48&F`z]GKGdҧkR> ׆%%EdK,b.Ue*X.)TIndqq~Uj%~~An@z PY)? ;DDD $y{wo##mJU:eQ3:9>Z.kTi1͘ $]x!Tkkwcn1-QK*tlYح4&/9T"6g!9_:zMˢ vq8|Xt[dgc1@VjşY B҅7gSSŦشh! ={kPk|tFE~,zjjgѣsk0г9o⇄6OGA{%%=?+;g1qQ!Kb1O gOqEf@|WJJj $]x{_it,}@>e˲};%*JLղ.h%Y22Ŀ1*L'""R BҜ/g&#C"WZղlۦNVYa6G*d󔈈bAH,~߾bcˤt݁cǴ]6ߟN_UUҕ ,,DDD!iΗ'=ػ&5U,rvy|̆kT̶l $'bcb1cǴy>r;vh=YTr""@3m"lvJJYgT)KXS˩fiVºo,b۔g!$""R B҅/6=JL2eL&u4kt6 ]'OeeϧH,Isv(iȢ]Q%?9o$ 6VA: _mW^ |Y2 f{22Zo W*BY\Q)  X|-Ӂ~va; b5CpQjqYF~M>/ ,bUP\f!"" ,I ,K} h1 ,rgdNbe']ݻe'QHAUVUUaڴiHNNFtt4PXXcO:I&!>>111t˖-k`Xɓ'0͈}ƟF(huN s5dfb2is\8&-[E,zG\}7FDDD*p„ Xp!ƏE!44CŖ-[>jbСX|9~a̟?G%%%/"''yyy3f篬ĴiT?vUW[j\R_VaadEYڴӝUGHASnݺ+V /^xw>#oSNuؕ+W:z)<ظq#BBB0k,>hٲ%>L4 O?4^z%.sEll,FɟZmY(|>\ {NFw4 үPTݵ{ZdYBDD( \jBCCc-""'NDQQ>WFbb"Fi-..cƌuP]] 8s 1~xX,}X, … `jVφڇmk|hQ)*t}bcn݀/kW4M- af!"" AS --L]ٱcL={{ڵ 555۷Gy7x# {R?g2kQ0FfFNk-͢S!K˖@ǎ-rsc#de?B""K ,--ERRRۓ`Zq͈c[ZZ _c(,,Ă z/M?g.(&-:Xk1긨Q帨%4TL3$%]ddJ)ϝ;GFFcV?]ݷkTWWcʔ)xеkWތ=eAUGu: YYtϡek!"V9ŹQj QS4aTT.8AB(k2쏵u_c8~8fϞpOmm& 'V&#8p@LVVvq=-df;wnE)d"G f!""ֺÒ1 LJJBMliڦ~OƳ>:u QQQՊѣ^?łp?somFZvUc{ZZeiHMogO_϶m_uZd!""͝;)ӱw^TTT8^\\ ɄtdY\\hzP|Yo#j_ɓ_Wt;vDNzjTVVcǎzrs@iZvɤ``fC, En>L~x?VOzXবL~EA ꉈu9~w_+XFBMM UUUaٲeBrr2 {qf9j(c͚5ێ;UVa ";;_|TVV7ڵk;`ڵ믿QQQXnf̘ s`ږE+Zdj0~''x-EU,ʧҶ-_LDD q;%cwѣ1c sXl8K7}tcHII … {ݻ_~/^lp >äIp!,XƠAk  ;#/Æ @4\̜MqUvuwk\r꒖ٮ]toE㒙 ;2>޷вP~u[O-x5˿L*7-S"ooGybrIDy#Uje!""% p̛7ٳgQ\\l,]555M͑_~gΜG} ӿlڴ (++w.]t)N: &pƍ=0LZOu7|qg`uj%"鑶VYRR֭}[IV(,*DDDUAHbЦEA53QpJSkM,bOJJk1QSł4Urb{Y(|%WZnݺ\}5on),ZF6Qsu,Zd,DDD!BA[.b].h!ou -^Ge#lh ()DLGߦGjML%4Uv_; Ncjje ,bzEǤ}{eK#WBw|)P]s5,IZa^Gkuq |o] ?=V,7,Zf:Gd$pҾ=|KDDT $iyEwJ~ع8qBVb_x8>Gk eg43B~!CZ(ȖBDDX.:߾=жiŦY3=Lf* >%.r߮:W>/nض 8yһ=!QzeaAHDD!iNCァFo6l繽;-lS5ɢ1i*1XvnĢQ\ӨWN*,CDDd4 -_GGVzУCk 8v̻e`q} 959Tkw[,F_$""jX>5)6vF={@^ǥm[cGq,?b+\kS*e!"" ,IZi%ԫQ*tZ%#FKFoVIO4ysM^YnK_gF""!iNiza\G`vemgr 2t(7,z0wW,͛i={gOQaAHM70Νza@Ik92ju\V,w lڤFjH`:ϋvrز,&pme!"" ,Isz,ҶXPE5S֮1zfo]lh(<͢gܪ_/?Kbb97?,?m؊S_H%,ɸ`jʞ6wYy=)x:zU>*e1ˢ1zzz2C,ii@a,Ng$QX*6C\TUs6%Ӎff@b!L7ȑSeˢUÇ(,hNs);aZ' t]͞_qwvtcO:Pzg8sk9ϖ- nWEDDDFaAHk˴Q~FzEoeQ{F-+;ɥE6mD\bS""`4g';Dש,Fa~>x = |,Ç[2v46mԈ,w __˨),dR'Kd7(P $]U $$xT@Oazaab` ,_.?KjgΓbCv9qefL~V.\P#KAz!iNAY@zɨwo\F12KcYBBrŨcҼ]nT]NoRoTov4dbAHгnwcS4t7if  <(VeX1ETߋ"g#rز4V([Ƌv""@4Ymf,aTGY3q͜ HΜQ%<\b*tZX\)?Wj?Hn@t쑟;{b7)QSǂtY{|ڨQ ۴QWVb0jp)(p?62K~dj>111[l5\ł$L<ٳgN ƢM6[m6ߧlFſv/_Ȏ A/b.]DU7ү`,|{,&Ǐ@uY,&,DDD" &`…?~<-ZP :[lq8ՊCbx1|=zľ}[RRl?/"rrr1c8oɒ%xpUWaxǰw^deepJZ11b*?!?Mh(0q"odu{LIjdei#N"%&,#FNoeMc5i [M&uΟ?oܹunb dY~ѣG-Z}曭֊ mK,f~hmJ?~ok=~_UUUV֪* F&mZ]g|bsc8w>hΛg\V֝;曭+rjX?wݻ[n\oZ/jjZQQVkyqY6lZvZ/^t\d1+X7gdiӜ]B|w4vƾ&9 TA!\jBCCc-""'NDQQ={HLLȑ#a̘1XngubX,s=X,x΅1vx-[kŷ~͈}:4_eԻp 3l  ,ݻ{NkdnΟw"9`8`য়gß9#?q!""jʂ ,))AZZbbbn̴ޕ;vO> nٳgw^]PSS}:/,, .9 qqqOeF>>Ylx@LuFf'M+|VT8.Ze$͢E1Ӱ@%5UlRuZY{G III nOJJjő#G|z,cKKKa2\kMPTT;1c!_}>Fa.4R@`6-Ml6eêU%Ssvn"âhAFɓ vb2ƾ.Q vM4@^/L^]]aZ1~xlܸ׿p5PYY*?>?oSc:|$u(=8zu2wPTy)G7lpdfyqqR99l'{N䐙%2Rxy2$$w.!rss]_+++e AS5 555˳VUUe˖!++ *{qf9j(cM ;UVa ";;_|TVV6؜ʕ++`Ĉ~0?!!!~=/{* IDATu,9Riֺ:3n5 z ͽT Ҭ"9kׯC;/2/sjQƎa%|!`&1['S{RDDD^ q;%c`L=3f@yy9:we˖XZgӧ#??GJJ Q.\{/vލ8˸x"fϞ:>, 뮻&M¡C` < o…xWпDFFz~nkTӜ9bɓ|[Y̘!N&a,? t$ꐝ'E!<40|80l!!٢h]ƊfL15\DH+ASo\ɓݻ7֯_c2`6;6Nf36l؀/ƹs琙|t(,,Ĵi0e4k 999xsNL&Ak^6E2]Ţ%=bL bY3&8 3s24kviҾ=0~Β%V|%91g+gӟtTYx:U|w:*8ᨪRƍƿ@^]b#g9yR dt}T^q| #eIȐaGi Q0˲mp5@x8pꔼ2z@nӁ!Cufmۖx^zu^˞={سgR4y/˻¢EVG"J'RY5 ز\~cGT) ͕qϼ},]5jpժU#FBFFF~4oܕ 4nܘ+W'O=JJPPkߊ+o۵qRRR.ED;lе+L ۷àA ^ ɰjݫ DDt|&!Сuq߳gOlڴ.={riv ֭[#22ҭ\͚5֭ۘ iF!\Z Cj*w٭gn] 矇KaFR(""auU%33BCBB0 x^[`vKf{sfffҬY9 ڶ5!C}a?5!)l9ܳ kZf͠iSs;ͤ^=];3 avv6j*r:0 ׹gWk\H<ՁZEDcEۀ03lags\ߚx8>mN`S[ݺh&[}5k[ejB$"R$9s~u;~p?B^_>7\N`\3?v-0VbkA\%Q y,?eǽLBRd7LZhQg˕tŕ=!!!|W\/MJU,^ln"%9w%B[VӛEv9+gnݺW_q)e֭[f[n%[Բ֭#00:йsgjԨƍ;\rssH~~>K.-[""""""eիWƪU %88F1i$jLݹs'AAA¤I;u6mڄf#22mHH]t6m{)Çgt҅e˖dԩ۷_~DDDp8Xt) {xLL {c""""""xB0)UZjPF `7u<::d>LPPPoBϞ=-ɓ9|07v;vrIRSSKHyxu arr2}=o9С>}!C>cӦMݻغƎoARRR a@@gΜ)t~s?|vVdݎ_ N"A~~>AAAnmH0,,zTevlѢ4kxӦM1 cǎXOhh(G-233iٲ۱LzUu-UYĉ[WꄰYf :LDFFNFFmڴqfѤIߵkyu 0ظq#=zp<'&YYY[[۶?*Tp8Yሔ^R][  ;~ܲsNN[nn[^8|WNrs)Hh~&j^Yhsg&ӰaC׬'OVZfc}yyyڵzѼys:uDXXs!..s̙vnR{bB+jХՑ'YNV+vcfRp:e~=}dܤ% ̭vmZ̯jtNSy^͚g.> AA;$J* DILL$++]|r֮]˜9s\HSSS!&&ۓͲeHII!..nݺ <Vsg)yCCa>9 hv;'O׭HI uapZ\41-apu4nlu4"""p8.'nx37R-fPDDDDD<B4ZzBDDDDij)!JeDDDDD<B42*"""eaЂu*snf̘auR JҨ˨w۬޽{o,wgΜaҤIL2Zv|Э͛柚/Ypڵ+|zSrI"RB(U+gVӦM/h&%KP^= kfܹڵ1TJ{ڵ}|w 4[n~UK//wީ8b(!JӦ QHy,].]Hƍ0`dz`V\nϏdnJ]p87o;wvڴlْ~xVVvAAAtЁ>u,??XڵkG`` aaanWՋ:uРAς wofddCFS={dÆ [-^o0˄ J޶mӼysʑ#G\۶m[>wO?n3h ԩó> _M^]6-Z' ??u^߾}=z4ǏQF_b/27x#cƌcǎ<\vennf-ZTb]b=%RiZÇ!;HDDD,:wMll,?#_5va0n8.n>Lff&W]uOnQF|,]$5kFgժUtO?4C a֭DGG_0P>oɓy'Yt)N[o}m6֭[ȑ#l 2cr饗 55ǏDFFg}?]wURضmÇtMՋ-[3w\{Sׯ駟f^'%%뮻mIIIq׳gO֯_OnnnE!ZNN999VR- BDDN20N:Ul|ɩ-?|qvطo_LJ fzn̙c4jv ???6 0ZliL4l6cɮaٌ>sFey睆aѣG n$''Yvʔ)F݋ʕ+ 0ٳg3~by_հlƚ5kVLLqua}aۍ{a 7v famڴ1^z%2ݺu3aرne&L`9sQn]}1x'gcѢEm޼۾-[::?zV&ڵݻ!;vׯkGDD nݺϮ}g߾}dgg(A{ 0믿뮻͛7oLݩW^g.(!!N:D&M ]/$88mfc׮]o߾ԱGFF?rW9uࢋ.K.neBBB+ 08}%G]FRMEDD|ѣ{׿Bf0rdΫQL*zQΏv;9~)^z) ;vd޽39(l6WwEcq_yf>rrr\͛ǺuYx1:t`Y5fqرb˴k׎X`a:u?lٲ͛74s"a)J7/J9|۾ÇJ=f+gQB(m[M,#""-={eKx ?زtulfK^Eo:W^ɓٴi,_N[p6oj1slaaaԩS6mڔ86|֮]W_M\\]v]vfssμ]P.]\ϧf͚tm͢L4;wh"YF/2{ZnMvܶi&Mts ~*Vxxxq}k֬!88:XW^ye/Pkm۸袋hذa%O T*q IDAT7ھyu=`V!K;:v*ׯ'11o/ЩS'ڴiÖ-[عs'G!///Ԯ]{w< :ƍ0e^|E^yIMM-43eI.6nNZZ&MrsϞ=L0uֱo>Ws駟ؼy3GqkY<+&&f͚q-vZ~'-[YfM7mڔ1c-ѣ 27{n>3jׯYf [neذa(E>Ύ;Xr%SLaر=$GO?eƌر)S2j(r|  kIt\0OpfaDDXȅ7͛ cD7F cc8sJ;پ}q 7͚530c̙YYY`n_am6F``Ѹqc0[s1ÍZj-[4F:f]ՠAcaƙ3gÇ 406lh|ظ[-[k6ڶm6ʙ3g;hРa]};Ө_QNgϞƆ  ĉ}EM`s I&kR0to76lh:u2ƌv^LLQ~}uomtފ3 HNN6zeԮ]hѢ1atxGι[5 XtѱcGvFDD駟ߌׯ/|M*9lQCT+sA VOHOڿ}y~n tu̱b%o\ve?P<믿Ί+O<~ O&2Z$O[.=z`ɒ%nen/EGG>s:tnw֭yss_~:W;N?0k_Z{=G񚯼aH)YϟOll, 11???vl6BCC6mO-Z(uf͚Ejj*_~9G{v9ݠ DDSc|>zj"Ejժ=axÇ[˨Q=z43f8ozSk;lp_sۜiW/#q oe&v;? !!VG&"R50k,󉏏t:<.l2ڵ"fD~>|̙_|7l.ѧ+`Մj*BCC QFL4Ђ;w$((`BBB4iyyyDڷ4_wtcƘc.$߯AMj!, -- ???ҥ ˖-#!!ԩS]e۷oO~ptRHKK޳.}:ƍs&99Ç54..7|zYX#""hҤ _~eρ1%O?9bu$""+ ^|aPs6si kyudλsN2d[]111dggiӦ9vX ))rnnNJc)U+p8􄈈T|X\:b>A)ajxuѰ0zR }:-ZNf܎7m08vXT2%ZN<)ST]0wƍ- CDDXn.,Zӧpom\:u D<ԩSILLAVӼ:!l֬C-9A6m\322l49bz~N|*(9;v;ફDDDIn.̟Ш-FzVYE|3<ٿ\T33x` `ܹ}a0|6lHdd$'O,r|bBB6c:Tiq׬Y&"""鄷߆03!=6lodPDJW3T n!,Aѿʢk׮,_k2g׋355bbbh߾=,[֭Ό 6lsoHNN0 8}kӨ(暪Jԡ9CDDBҥ0y2/Ctt*"-aʕ'0[N5zjͫeJޣG!$NVW)͛a8ũS!&F%"ޮQ2("RP\׮''N@ݺVG#""啟'ܹЯHY)!*`6 }X׿¡Cf.:")u-FRR~ԭ[=zd2}nڢ[ѣGykiڴ) 4+߯[(6*"R} W\뛳*BXˀHLLϏ;v~r6PMa-Z85RRR8q"L85j0doɓ+0f "|AoիWƪU %88F1i$jLݹs'AAA¤I+333hܸqjԀ+HDD$|AxHEc?.]l2p:L:U}׏K.%!!4{2_رc̝;(5kVz5k``#de MW_APHE1aSjF7nxtt4>|x7IIIgϞe5:: 6йsRWW^ MVG"""mv HD|Iu~ƭnhrr2ٹs'k"!CCvv6Γ;0HJJ*SF?gܹNϕ[t:\_U ~\JDD<Ƕmp50bANgX^e4,,zTeCBBsɈB]66ma;vzNsR믿ӹK}޹Jjlv7F:DJcԭ 3VG""R9?A0vшRB(`|-R/"r!`h^|hDDē)!үdgCJՑTOa.:®wz)&ģ¼yVG""R=M_~ ~VG#""fWONN5kִ: w/DDишT#ڵЮш7>z*i\'kl#>֯ VT2(""P<Ҙ1pՑx Vf^VG#""ՉBHzAǎVG""ٲ[`P_FDD!漹7]w]hu4""05WЌ"=C<5@׮KVG""a6XPɠZ9odV31ܲZ:2=C۶VG#"Rדb$%%ѿׯOݺuѣK,q+ӧOv{-::TxGQFѩS'q8qKRDƚӨ O?c.T2(""xˀHLLϏ;v~r6PMƹ -Z(u[>|8kfӦML6իW\TMlv}Vv6~; XTw2Z޽{ԩqqq̘1IJ}ȑ#lٲ®?c {1RRRٳyJsum~hDDa0b3| g\Of͚E~~>t:+g֭1 _BW\'M7/X5|o%""R1vRի cժULF4iE5ܹ aҤIzN#GĉW^Z}#ppшTAo1?~<]taٲe$$$t::ulׯ8.]JBBiii{ƍ+]?R~ f35 V/:*w8  /ݺYxCh999*[V-jԨaL>qƹGGGÇ *8|R_|ƵW?F'''p-00;w!Cꊉ!;;M6xͱcbIII188~q7Ș1c4h[n-ӽ92l60l!|%x.y/;fR2("t +Uë[olHH`.Nf܎7m08vXpѲ vms=,ZRWRɓ2eJd{Cj*,_n&? ]P8kVG+"Rv=IIlu4""gqM(ꄰYf :LDFFNFFmڴqfѤIߵky̙3s2p8mN{qJ??sΆ~ 'U+sͩSPъ^v9np:7qD|"!{3r0?> 6$w'O9>1!!]رcr;~xl6.2]fb7??2U]MwKTDD@˖wщͣB۶foW3T n!,Aѿʢk׮,_k2g׋355bbbh߾=,[v4p3l0͛W_}?w%\BNN,_//ܿ7jK/:Y>6oָA\JrJz)/^̂ ر# .thuDEEb :n'<<ٳg͆wLJ~Hff&ap3eƍG(mO?YH> #G36nlu4""z _)yods^:U+xUq;eTD͛;vsY K."R g$TS x:F=FDD|Bz]ǏÉVG""R^~b4Jըj%h*yDD)!n"≜N1l :EJ'hbD/d&>ju$""4˨IOo1XA-ڶ]BDĔ#au4""˔OҬBD4g9&X:%.l!˳:0dQyhDD 0B/77rrrKԤ [_lu$R]FķOsY3nQ `N Ç3"Ru>o)SDDDĤP|¿eNfs`VG$;NѣaL :&-ii3f9}ǎG˖VG&&M޽DDDj! a~>A`kZ;%3|9!9NاPj8-ZX;%3ڷ|of͟~)ϝlQ\g>hu$""")!,FRR~ԭ[=zd2}nڢ|ݻwSvmv;ursܮ]VGRu FݏP:O=n+""i4LϟOll, 11???vl6BCC6m.آ}yͽx\b&Agys!3| -Cسrr8He;WHDDD{2j(F͌3[^z\5?3 q..)Yx/8p>j*|<865[Lë>>o7xl2ZY'>>qsNg%//GyGyvڕ)Νa6|qq ={_Η'LN'[HzjXjӨQ#&M-;wDpp0!!!L4R_'+O>dEކ#"n:;溃QpᇪIė9ӰaVG"""R2u- -- ???ҥ ˖-#!!ԩS]e۷oO~ptRHKK;:DBB3f̠N:y[K/5[Μ)78t}V0')- IDATIDPTG͙}?UDD<(KANNN=;Qa0}tƍ:Mrr2&((zx7IIIgI}{w[f]`ÇgÆ \ve;77rrryR֭.]~;K/7ߘSBHUy!8u ,:KϸUǫ?LNN& [`` ;H{ʐ!Cꊉ!;;M6xͱcbIII%[n ._rssݜNg\[xs%KgKWsgs‹R~n""y3.Lfu$""t +Uë[olHH`.Nf܎7m08vXp=\s5nݚ{iҤ(rL2uy;oX&+˜w ysGl1J? &o)""Rgy5Xǫf͚1t2Izz:iƵ?##F&MJ<﫞ٷom۶uoLϛTpۜn 7P!-@e;lB ѣDDz8qb+!ǫ޽aР aÆ G*ԩL$UGeK#)!ԥ S}'K// s昉VG#%+X.:S ڵnVGRvNAl, 4n\1us<<9IBϕc3ϘcpEDD+L:yIs|ٝwV\; jgeA&W͆ z5Cn9>-y.4'd>#0j!unQ͚5̙;}͚fVB<L뛉yPkudRo̼uL+Oe̘͛W|_nk|=m͚\b)""R)!ա YIL;=TNW]kVNR2sZد8> Z^DDBY6޽\|c 3CvFΧ\ɩb*Ij]+Ra%J9FD%P$r;}/cv~}om?3y}'n 7J!/M -dܽ|h(51I/ڶ:⡄PK-'$כU^aV|z_~Ma.ShDDJ?=:zţ-k!S#P9 Q_~i)yS@^9'`6k۷;7.nxQ%""R(!׭k&Р< 6\*)yܹ0yr5ӼNW /XHSB([7s^yNg.mo>ymzMSzyk\ Ǝ#DDD)! >>mՑ<3f?{wIv5k/ۼzqَgDDDd(!gwXp0x5qy>ޚ=0b#?MΝ+$/hzDDDd(!+{ _;Tjժ?̚Ko5C czw(ٸ$>xq#)9JE.]"Xoυ ]#A.ƍ1cR_Ӽ9lZr1IvLhz˕:0qqqtڕ*UPR%ZnҥKԩv=ѳgQ^'J$f Đ!&9ڀj٧Q矛+|p׵iczy> ~yHDDDJVpE >0bbbbϞ=>|8K9F`` /#|Z ԎfE5* ^MHo9m2?ns!!P&|S(0sƏ7@ ];xKSxLF8x 5"""iӦYsΜ%""V3h5f͚EFFQQQ$%%{Mzzz&55 .z)àAf8뮕 YIFdd'>4oap ϡ֭#$$5kժU#22:S݋/~~~IZZZ[~=*TbŊԯ_W_}8oG iPxMHO/22M}|{ոIR[grHN6KUv7lz8xCx}Űa3f M6eDGGΔ&yѥK4iBRR˖-#::};VfСW6[Qq̐̕+K1l̟ku$+= <^Ov_\c;(""T!t8l_LSNeљٓx;oDDD0w\6mD۶m w9p@7>ƚ}%ahؼnڹ^gBfe̺u=-\ӧw߁:d 4kN]_b)3W""R|>>*T`޽oAe+<".~#)))tvMx?1l$ީSyB--d1߇Cҗ&'yo 6[ٹ3,]jKzEDk;6RSSLp0o޼sXVJ}4ܹ'FGGc޽{湴4ѣG3ϝ>},צK/Q\9:w\˖- G QQ8Fܞ9ݻC _MWƍ&1; ,m^{ n>Pv3 K|)$&wPDټ|(=EѧOvJLL 'NYfX72{7֭[ '<< Y|96m"""͛g֙Hhh(Ce^hׯϩSxFܿ\0w.0fL9q$Zș76%s\};w6΅6mLp&'Sš5[ozf֭fE\~O0Vbܸq,Y ̢E,4Sn]:vʕ+9z(vPx N͆&Mиqc-Zĉ'y,];>%oeʘ"o lѢu>l,Σw$]-[ЩSaŋf "v97v[$m=zڵJ'#CVG"""bR'В%o|6[0T|pܻ{3}{7;YM?1s ݩSР&M>0_q86[M9XHϸ"1l>ڥَ"?.O3rґ Ӭ\k5͟5h{e3*dw>csTx2%"0ivT{ٳK)SJ~[8a?^CEEDD.SB(".]{Ǐ#jU0gL֙9;ž=%ӆ3] OwPDDjJE%cBf٧֭͊KEkχn,ǻE"""%A F`~xH n83h1c :+.wDOv5k~#q-JE%Ax||Ikʕ%KtxAc\H{wPDD$wJEe4i۷[E;X&d'9r8v G¼y`AAhtG|gVG"""zhn:}I:woӋ7yrp8+*\yUܷcu6M wH38ax{[af7 5 V2bjp{}}3'R"""9SB(".U+ض$(j 8.[^{ ~+u0d|9|%Ԭs9wGA)!QwՑ,-<5Mr׭[={[?6n ?Pl:fcxՑ.%"2l6KFccjcu$͖[eN4^۶5=VuF&cG\22̰HLEDD\\ѵkWTBJhݺ5KSNlGϞ= y)_Ês!!!!,X@eeDBBB5jpp< ZNʳ)rZF|۹V^&L`ĉOJ6t qqlM K ܟ|Қ]U6&!#ꭷreHDD&OXT' 2P״jՊW^Dl6իW[UV^~#44Pq'%%ڝndqCݺ[]`6l34J%6m׬"̙q'Ǐgر9ge 8p yes8RjDܹs9Ofѽ{siiiٳGfkذ!͚5cժUYz?>LXXX.[lvf7ԭ|X1b*=z8mWצau$W̞m*N#VTE>}ڵ+1118qf͚b 6nٳ3ߜ[n%<t,74h{;MN ޽MTqlA+OZUz}%֡CW^ߙHqRB("nnʒ))EgZ2{|}/>OT4n |Rmocڄ^DD)!qӦ-[w!$xcT;lVk&L(vDDDM̝a31x2S=:B7_|[n7n eXطڴk\5ȶmPJ'""EϸΣED\CA͛!,lѢbg\QB""h ~Ѭ>ZX fȨx.=:E\\]vJ*TT֭[t,e:unv3|(,_EAzz:'N_ʺOA9=z@x&zߊ{WܑqG=9eᄅqwŞ={馛9rdfΝ;?K/qVZt)6?N\\\o&~)7oe˖z|}}IJJB I S+) 4  ǩSfkP3f+J[qWz;3(!iԨL6-ϲ;wɓرoذ!v~@"H'ykBy=| v"ĕVܕ޻<2zYfATTIII^^roIHHDDҦ ̙zG\ဥKV! oɠHi֭#$$5kժU#22:S݋/~~~IZZZ^h6_mX{Ͱx8yEL1d LDEDDJ;}{}Űa3f M6eDGGΔ)S2ѥK4iBRR˖-#::};݌ }]ڶm7\ܷ%"t=z]0}:fB+#f^VCp8HII)PrPLSNeљٓx;oDDD0w\6mD۶m '|B=1cO>djqG"H[qWz;3x:wo9ݻiذ!>>>\pAe)̶mСCu59sWpѢE)SX/\kݎW)IYo]+*==_l[\FNCBBX`Afˈ^F Oγ@N:U8/^ʕ+֭իW/u@/Qaqy*o]+(QONR[jEBBԫW/|bb"6-k@UVq9B ?ݎ-UC(""""&BAFF>pRYx1cɀyCRjUZjs(WYfѽ{siii߿ʕ+SflmҷoBk+u"""""VҜVנ}k׮p 5kƊ+ظq#g|nݺp "99˗i&"""h޼yf2tPϟӧOGqj¬8ZqƱd.\Hpp0-ʲLݺuر#+WѣvBCCy7>|x:m6[C:.]JZZ+NHV """"""PB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%""""""J RB("""""⡔x(%k׮TRJ*Ѻuk.]kgRF v;˗//PIII<3R|y5j믿^\ """""2Vbcc>|8aaaŞ={8|p׌?/b FFFaaalݺz >cx Μ9?_\#"""""#pX+9x 5"""iӦ]vѲeK&L@dd$K.y^tRHll,=P{wkrAnh5f͚EFFQQQ֙~СC _oذ4hɬZk[֬YC`` ~~~TVK_/K.具w*T`˖-E PBx}q! yٳ'ь7.Kً/2rH Npp0|Yx}7""""""R:.]Ts&MĄ ׯѣӧO24&&4^xB6x`*U?L\\d̚5 Frrr DDDDDDrSWsl޽ Å 4hP2|l۶:/Yfe, }|Awr̜9!CPbՓARRv=Nv;^^^QDDDDk |}}KuJuB‚ T6 ZjkԨDFFRvm:v8r'NԩS'm(:t?Ν;IJJYfCE6lXTRʊ????(JuBϐ!C uMVHHH 11zeOLLfQzu>LBB7|sm6_l>}:dfѴi?l6uVx/brĉ\{*C(&55___([޷WW ^z@^cժU׏c2ydtYwؑ~~e˲qF~,ڵsѾ}{zꅗiii߿ʕ+Sf\>qmڴjժlݺ@^p! ]Bjj*ޤD܆޷wg\)=EѧOvJLL 'NYfX72{H?gr8ڴiwߝy>11P3wԉđ#G3gIII]oTDDDDD<ZqƱd.\Hpp0-ʶLNr3hٲֺuk-[Fbb"*U",,I&e*"""""RR4dͩ;]ܑ/;Vܑٳp >}Z]qI*Tqg\QH)un̯"ZҲ=""""""J=nRZW܉ng„ zߊ[Vݱc #.#)))۾W3hӜMRR+V4,O\K~M=:RnPDDDDDC)!PJEDDDģvV^}]ư7=zSD/<O>}۷/?cfƍӠAzCBq9˖-iӦTPo0b…Z ݎ<K $== Cśo d2Hxx8ժUbŊmۖ͛7%Kݻw!!!tܙ_|1{ݵk={Ϗ5k2dN}X"/h׮˗VZ dddd^׹sg~iƌCj ***x_}UNFIpp0&Me˖̜93K޽{x<)!pQݮ=>|8?_|p0zh>zc8rTo&wW_eܹ+ 8QFѸq$:vȑ#Gعsg$Z6lu9K/{u_?{,]vUVlݺ?Ǐs}FTTg׮] 6~^zѮ];v믿μy󈎎rݛoIŊoy嗙4i֭˵M6qwd9׽{w6mڔ\۶moIMM-:VZx{))PG!==~!Bճ\wuO\:u5jK,aє/_+RLl]mѢE>>*_Z5l6Oε7yq88K{ynv3}ѱcG<׌iobul6[Cdk֬ɱcDz;v5krԩSl<{dzJEDDD%oߞ &m6Ybޙ\qFի?O˖-iРK29]wMs̙XlY5je͜DFFw^/^eі-[?Pn]n,z9r$?hhy}6l/w(ڷom᧟~7r׮]Ԯ]U-)yJEDDDĥ|İe>{NFW;v`޽bڴiٳ'ez,K®-)yJEDDDĥTTxzEpp0L6-3xG uԨQ7һwoF7Zh_eУG:wL52DǮlٲ|ԨQ^zѴiSNW>#]6MGEŊW_}EFFݻwiӦ9n! /N޽ݻ7AYioZjvZ6oLy'xG;vl}̞=͛|rVZ\t+Wc~q.VRSS&%%6T'))+fZQIH˖-3fա_+WGz~M=:zEDDDD?G|3fXe=wf|0bbbbϞ=>|8K9F`` /Ww֪U@|׏.]0sLvItt4'N^+{ou",Ye499KK||}:ǏϏ\_Nmŋ 5.4nܘYfyȑ#tMDGGh1Hn.\: ITTT7޽{%%%}Q"##)S&m۰lj*j׮ͶmۊDDDijl6-$#"y իW/v2dHiժ $&&R^󉉉l|c޿?@7oᆪu֙9¯?^r]n}[[4HJ  599k"Srӟ/ۡ|+Gr([|V`8p|x:m6[ի˗/'**z7Ǘ=᧟̧c\GDDDXɓpD_I]h*UUBYU|%XO) pXxŊٲeY)55oooRRR\j(@p0Y`FDDD8edsەG$yO^oճמV$wUlb W~-m,!lӦ VBXJli*!¸x~Ç _2fG˖P&Ԩq%PqM&s'11[n%[ߴiS"p9!0={dM>7\: 43;tZ$~+kN4!76muk[.W-M,2*fnڤPDD$\hz.'7DC6xqcu"⊜>oq!&>|g6C("""%s?$}UcABs֮ure;C@,wx--,]TgÆ u |,_|b6}ߌiPG\.ϸhRS;HVG#""M nFYhFDDed«2rՑ\?="..]RJ*UD֭Yti2:ung;zY6}]|A6lnK.%q+.I:= =_ӕ Ha}."%%%k˗/$XNXX111xyyg>f#00^zUMf͚֭[iӦ N*{pU6Yv{9x/Xnf2j6F&/fȐ!tޝO>0˱cׯ%1>cǎk=̝;M6ѶmڤIW | @jj*ޤPlB]k""DDD [ĝqݍCFgΜɠA;v,#Gرc 0y]w{TP{q]Nrr2۶m˳QFp8 #555#==ݩ0gQHi{7}7 w ? A +aѪUf~oy狵,XP2"!!,רQӧ'00Hk9a&N` {L8R!"PU,ED,HeU)kaWA*+"$+6H!v?%HI~Kׯ_oO&<<0 CB+Щ ""5Ax8|1 Q[Ns ̙3+W.VO JI0 HDDX̟Cނ JDrKAuV,+ͨKR-BeXz[7OG""E$/2`QQX璒oSQ#׿̔%%IDrz - e?qdիСCMl4vOU~BCMRiԫgwD""k{{w~YA_=`i$vG"""ARY8~>W ƌ;Zm5SE\Dn0: aVhذ!?￧Zjl۶~~ͮ\>,f-p?&| =.]aÆj\f,19x*T;맟m[F0kEDCckBGѵkW<==SmgѢEӦ\>,COH};<<ш=:ewޜ?>/һwo"JeDDW_AvJED\ eY8ҙO০q6mBDS 1hDD$lI4h@Æ q8ѰaÔGzxl_GvZ(Y$%Jq,Y$>=nnni?&&z|rQT)5kƧ~WoP{3)>HDD /MUQq=4'K۶m)^xsTRֶs!446m0n8ٿ?{*Ub\B&*DFFFpp0aaa)R>nݺ/0r\_vAvG#"΅W^1E5;.[̛7]eWi=z:uЯ_?}[sݛ󸹹QLZb۶m9soo ׷/uݑ3`pkWhD =غgˋ3|cgHL2dF @lllIJJ~׫\rdիW9rH'{Aؼ(DD\+)lMO:E˖-0` QFqi[bZnjbժUTT ___ʔ)Cxx8 8p|}}'</0n5} ED$>""`x^DD\ K/ŋ駟!&& .0`[b:x ǎO>gLDD#FHo@@ÇgѢE|4mڔz葭s={YfHsuJ￷;3{6cFk׶;-!cڵw}ر6mpܹ߲,3YV"E`Y&L`С)yfN~Z;wnLˈCY\9,ٳ<εB1111sL: &Ogu׻ըȑ#5jT[дi}ŋkw4""s yDDrl̘1)>&|?8UTII~wϟ/_={f55СCDEEQJQQQ8ʖ-{>| =z4/r)Y{t77[2e~}30ED$[@H,Yow4"R1|tKHH倇[)+@ڵiժm\:0|pƌc`` -Z/鉇Gw֍%KwQ~}9|0~~~~).^ݻӽ{w͛x5:x`4#ɟ~~fԏg"b: a~Ժuk6l@hh(c[ӧsi&BBB ! 8-[Fdd$c);z(UVW^̞=;wrST)ƏBo޼9UV0V}XnNhDDcǠyshD=:eL߿'/fpРA1ŋ3od^oΜ9J%5W/* "3gL} N<ҩS'5kYt)'N_+4_OLO-˗!(Ճ)S4BD{\5!;x߿~!cǎ%**ʦ\>,ٳaonzDDC(R.ww#Ncks#ަMΟ?oCDRX¹swݑ˲`@O>Q2("Rؚ>,_<+WҾ}{":uO?;{Md XvG#""fш~mZhj 7|Ð!C(QDʾ +|Mٷy3 iڨNV֭PшEckB*#Gq4IKNa hhDDk^x!3S""=v_Rȹӧ+!%:ڷ  vjLIΜ< wG@rvG#"oHt<[Ю̝kw$""yϲL˝e蠈B)7UDD$oM۷Âj/!""B); HAq#˗шH~P =^LJJ;wt gC:vG#"" -[xgh֬QQQ|lݺȤ0i||`<#]qqСO>iw4""ؚ~gmoooիW8~;M[[qXc :tK.QN/nwH.#!!S摜7Ӵ:X^ O? Arr("ʎж-l wmw4""٧{\5<< |AЫ,_Ŋu@vl׻h.g#Wc’%Kj*ZhaW.O˲/07_'Nb1eʘ)Mիu`9sof wш:#Jtv %߲M_Ԓ%s76)|M^S2(""c1c˗ #]k׮%((%KRD 7n̒%KRCs >аZ*"Saabw$""l!|w8|0˗J*iwmK\s!446m0n8ٿ?{*Ub\?B&}wҧOسgǏgݺul޼9Wߓq>*"]f [.""ք':z(gGHHHΕ^WZ5^yvA&Mu\Z_޲;qUi*O+2[‘#GytM2dF >>>|MRRW\p̨\2eqܹK7It#'"YgYY::M2uUVRJRLI ĮQu8s 'N`͚5 [HD-\wC&&d¤$&Nȧ~ʱcLjO|LLc:x ӇaÆQn]-[FDDIII)вeK^bccYt)+Ɓ[nB\\{9 eY]6S1ҲeK{1ƍ/O<?pGRRR%y/ ܰHDU̜ GBDݑ䎤[Ês:e4::{ŋsyڷoOXXX_V-Ν}2СC/_>ʕò,Ξ={TT tקz=zhѢ̸ըȑ#5jT⑼ӱ#,[mw _m!cƌI)(5!;8qwy'wuk֬aÆܹ3e gN/_={f55СCDEEQJQQQ8ʖ-{>| nի$''$Ǚ{t75ʗ:v4-(>FDdˬlhDDrOXXÇO\/5SС֭ॗ^",,իӳgOcKL]vŲ,f͚Ͳ,̙CҥiԨ`>1""A۶mS%&&~S?>j3fpp}e)El#_^(Qn;{.^#DD$wV֢27ھ};۶mz R!"KN@ӀFDĹt<7%D =z'S”)kӈH{\u O>Ɋ+ Av}hʨAA""l2Zzux 5jPNӋ.]`Pس4;^z }X_LDD$l2z#G81פ!,4##-_Ï?BvG#"b:O2*YKpԨ? *ySLmhDD{\7-BVfzXDswL)%"",'f{ ///f†BÇâEpPޞ':_UME[aR׿DDD [pc=ƒ%KXd =&<e_Nsbb"s;iӦ|;v={2i$g4^0C˖P&̘n&:h=jU˗L,cd߱.":‡~8S9֯_Ѹ>}X g(^0e xzfu|AHŊop0t {^"9uuH{\QczKc?oFֽf hX\`F2eT+3("cYp]w;H{\QBa)waX3bصM(SLܿ$K}瞃"E2>@ZED>34ŤDD$5: +W4i6lԩS$''z~6E:a)xy#xuh@ \з/ժHj)VԫWϮ\Jnٵ˴8v <<|7SOTC=6sAEDDdZjgg"?ы \y 05k"fdp %""?ٚ?!CqFΜ9Å R=DĹ "G#̶Gի"v,xE FDD$}&<Q\9J*ER(Y$J34֮]KPP%KD4nܘ%K硇-#888;r^^^{z"Y#pmys'U+x9x]ڬ)(.ѣDDDla;OSs!446m0n8ٿ?{*Ub\B Y>A !/fD2p^̔%r [Cf$z  -Z9Er, ` '+fw4"""7gkQѣԩS~r߇~3gΰw󫯾'W_%"";w0VBPdȴ(h~&MJ'9EԷ/\jB::SFl3&)))S'11A1h 'ܹeKΏu >3gDGw~)6m+sdkBgѶm[ٽ{7W^;֖֭[GZXj*Uח2eNz___ '1118q"Νc6Dr;` ӧ `@3VFo8xzz˞KgF}׬lM#"":u*3fH5ܢE c6mJDD=zԹ ""ŋɶ]ͨ]p0?&'oϘg~ڞ)v/CǎV9Rp@vG"""97矩R |TV#GPN\[E|||H"Xń :thl޼'Os׏3gI&Mnyg}~!%7o}B7M}`23rL `bY3;p4ǝWò{jժܹs3?`ZF:t˧z\rXٳgoyJ*s^}Ux*WѣG8ZǏlٲ)ʌ[Z9QFeX"swqL+eDpI1غլ{Y3Sԩə9z5&l%Km>j0翕ym[v{y:DDu3&)e1vXƍ˗3usС3Ɩ~i/^ÇRJٳgӷo_6mzԭ[q1lذWjU;P,J,aR  t8 ww %2 ~fo=G^UͼI?4o|bkΞ5Sc(_cdϞ$4#㑑Pш$}.!!Mu|ч0>>Cq%ԩck+WҡCZE`` -Z/鉇Gw֍%KwQ~}8|0~~~׮]_n:>yj֬Iv2W ?ͨ݊ysӧM/}Ǡ}{/o+ǏC&ɐ+9dFED$wyl2zuԱ; x 7nO^z,_m۶1} r݄B@@qqq,[H뗒 DEEQvmzٳhժUs={6%lQ_?3M^=KfZh۶7~ڭ̜01TZFe)S.WyܸM.f΄3gBDDD5[>}djkɓ\#Fxb͛G͚5Y`AB3+W&00+Vkfڴi9q&l'Rk!׷_SP͏&sܙ &*ZE\ zv^ 7zu D 05զ +eʨ+WA6{fN5i8] +WV-4L̩W4̫Wug1翡hsSPgЩS/aJpիM_Q;mhFv,ЪYao,""qǖ_d…T\޽{3PtigQ "ʕfTj^Wĕ+fdвL?ٺLWSM5/[$ל:e *1)3]w h$`'O7:wiIfTj&YcFEDqǖ>~!'NW_?RJt҅#"R<icְe)ShQS&P[0SW7.]` O׹69Bؿ? K:7k--[LA-[y{bIL4h kRDDĕٖ^-^RRH>RYG7{HaRihF ޽M-[x58q|?$e>:7ߘ KMp9q\s옙6fh_:7kOϡC9Hn-!z* .uԨQ~>cǎڇPDJLRf-cl7h ^9ij6uZX}}(ԩÍN.]`ʔ +Tf͜;}5>v5#muHxmfȖ2/-RJӇݻsm9;A n0xь^Mnliz]{E]O?}Q|j0Uڵr=d޽{@fUb;[LMϼy~}p!CLյkSO\ٴ}p:Ξ5?4+""yKc[ۉ;3 ܲ޲e˜k҇E aع(ڽBp׷%KL?49l k@F݃=DzLі{aݺJxLa^=ފ&!O㏛hYfT󉈈qɖW^j>G͐>,"yϲGL?GA`Y{U0`g*7,6FsÑ#f%fd4=K|;w]Lj] ""Ρ{\%!ܣs? ´iFVXI$y$vY͚Y|e.֪@&foqͅ }>x3mt SI6ln oh2syEQBSy4+UG/^9$]IL3Ƭ̭<7Bf-3 -Z8%% L[0f{/qShd%""Rp)!ɤ-'0ITF$nnmR8`*f>)pY{Q0aB,SD&&$ϙYگ~5WBǎ&a ㋈24.|6J-Z W_L[nZ/ͳw5kL_ӡS㩧L;rd^cMo3^0Fr-Gri>?+sy8}XDqY8w.$$ꛗ/Vj<7m"-2iv_V7a.4U:_Z<$[B*Y{fknV>:T[ݸr""5u%.N{Y) Ŋ]wAyW_5#}k@rIt/7X/1 6ZPN&A]<#oeFn=g=(!tqN0x0^m3֭{}׬MUΏ?es'xh5*kMN0X:g{fХK[0q"lޜQO:O pd ]ͺzӬ5\f2kzGɭ`*}XaPu)ΙXL㎜c tf;-ˬ#SAr+QQp/?B&*Q$j KB߾p0 6ul2"""HJJ7L7 -[rҥK,\0sիW￟5krΝˠA8qƍ˷)"""""RXE|||H"Xń :thl޼'Os׏3gI&MsvX~=+*Tp-Wt݊ҵ+HSG7o?~_~5j˗֭[}BBBꫯسgM5df̘ڵk<7f̵|Vkpwwr,"y%!!!?E\[qUv%JJJ"999౫|@'jbܹ0-#:DS=_\9,ٳ;j]$u+J׮%{ tBX|yz왥4jԈCE*URGEEp82L> d?A}||pnnn8t7!,d:!̎]h"f͚Ř1csAΙ3ҥKӨQ#.^'^m۶)9|0~~~~={???R7~x<==35L뛣,""""lZӚ?(!Oy|}})S ={?)r),bcc9r$ڵL2GoVYfQNQ|A^ )2{;:u{\]WvE{(^8+Wk׮>:yeYk.pXK_fѸ8nTg,___ܹsyN۫W/7ڕi%$$vAѣG6}GSF]ҥK)R}Ms=Gdd$QQQ6F'֥KHJJJe˖Ѿ}{*V-((5j駟:+D)-Jr2/ ^H_|K.jժ ^ ^$''sś>kWZӦM _~I٦[(!t1QŋOI&)ϋ{DxyyѲeK?ΩSL)s=ٳǙkڟ5jjFYlqeJ(eʔƦG׮ɓvmo:.ĉeY?~܆DRSNsm?oȶmۨW'NCBBEuv"YFO8{5EL2nP* 6$99իW3ydƍqs3cv'**@߷vSBbL+y5k֌f͚{ر#u_?OʵP쒕k4..tfq7|3տw҅ի3b.]J.]t۷ӢE z n2bzjW\Iy^$?뮻x'ذae\%5M|||ǹr劮eHժJ׮8ɓ'yG)UK,pB2~k*TD2RJ2-fsҥ5:(5ORRR~Y 9sF͒/xyyQLbbbRgp<.\`~)^J]L9p.]J}8ׯoSd";|0^^^/^ *PlYvڕf;vZe_>ewΝ$''z|ҥK矔-[6e]qWҾ}{:ĪUYf}k/%.SN$&&2}m̝;M*+bi۶mʶ;_jn:8ENF[lIҥ2eJO2}Q,r4?k.e]kt҅oKTƿo,˲;ɚ]b  D@@se׮]_-ZAAAx{{Ӽysʕ+O?Č3d۶m) 4l???ŋy;ٱcJ9wQQQL:z 0`|}}tN2ӱcGڶm͛?>cǎeذavM)2vcbbhР!!!ԪU իW__|xv%/ 4Ν;y{@ tu4#\yo~ gͨQ{l޼͜g2o޼  CO<'I/Eݛ92YfMnּy's?IrfʕYhQ~Ӯcǎ־k{kkk-ZZYred7o^֬Y+2;wLMMM&N:d?fܹKKFP,`/NEEEm'?uִdĈ޽{gݺuijjjwGK.>x6mׯ_Ff̩z9rH 3gfٲeiiii3"'Nȱc !J;vl󸬬N8$y뭷|$6mJyyv֭uz Hccc^z饬Z* ?~+5ڵk:w^/Θ $uuuiiiɞ={rW~۶m йsL4)&MW92[lɘ1cs C|Ei2}̟??uuuٻwoV^/ݯzxZ;yΨ---ꪫRYY%K2 *Y~}^ NB>.ė?䓙>}zフ92rK6nܘԩS׿5Yw1#?3v\~YzuVX$ɮ]aÆqgӬYܜmo~9tPx≋ !ٳgbׯ_\5(g B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP!@A B% J ((APP,G^xBMSO=2w{nݺcc {8g馛ҧO#Fʣ>Zz~֭pQ2Ih܎;R^^v,gjرyKcלNom޼9;*UUU޽{+$IvޝS&\rI I u);w.qeױ[8'M6/汝3TTT䡇zSxinnNsss>\qIo=ill̛oUVe̘1I M:5 9lyԔ3gs)++˥^ɓ'ƜzK<Ν>}6>l?.KuuuҾ$./>?Ȉ#RUUÇ/=wUW%I>я*/$6mʄ ҫW1" .,ښ9s0`@~򓟴^*I˗<$[lIuuuZ[[vTWW'Iᆲ_>fJUUU&MTsg?g׮]$-Z9=sw{IM6>({ꕑ#GfƌYti ۳u7{?3gm۶X"˖-+?zڵkܜ F6lؐ$_$׿L2%{u]߿?˖-˃>5k$I-Zŋg7歷::tȵ^[w͚56lXǏOYYY'Iϟk&KsssҜO?t~_g߾}̜9s1"ӦMhlْ7x#7pCn:tW4I힅{?O?t+Yre^{-?|@я~ݻw'ylAڼΝ;;_}}}֮]}e߾}kСCYnYU׮]sݧ׾Yf/~9p@8رc[nַ73}l߾`;ѣG|>ϱ[n9rHm'c?ܹ33fo߾---Y e^zVDG1f˖-hݺ5\\\`iiʕ+?Drr^'N{򂥥%ʔ)ubȑѦ{{ȲePNX[[lٲ0`n޼i,ΝC>}P|yXXX}Źs>_~3w7nmsE <UT5QvmHJJKrJ VVVI6::m۶,--",\ׇlmmQ~},\P?T͸H<==*LLL > jՂQret ǎL0P:ݑ5c岲0w\􄥥%c#>+V%TSYsf͚B1l0LiӦYfʔ)cty&Tg\ѹsg*U  ƍfI-$%%aر񁵵5Ѽysl۶-}<.n.]œ9sꫯZ;\a맆޹s,--QfMDFF;iii9r$*TkkkaݺuL4 UV*Wy̯VBݺumY>}oҾٳgnݺ(Ulll:`ǎn>N7V(mo~~w4kL{ hV͘1b̙eOvDFF",, *U5аaCOf'NDVV6 Vwi#5m~~߿/Ճ=Ç֭[j!77'OFժUaii *`̘1:Ǡǣw([,+W/hhԌE2BO133[6aL0hӦ ʔ)XL>6mBLL lmm< *ڵk///ܹsX`&Mkkk2sLl۶ ]vEVw^,]QQQ8pe9t7o4k>>>￱b [;v@ݺu57o֯_v!88O?X?~\{/֭[#,, <ŋb 6 NNNHxxx ,, ؿ?>Sܹ۶mN$yfih׮ݑ8,Xƍ+5իV\ *`РAPk֬СCo>,_燈Y>|8( Ghh(bbbРA 4vvڅƍ!!!HNN_ ___tA/ 11#F@PPZhchݺ5/^w#( xnXv-ƌ Çǜ9s!C ֭Á qqqOФIiNNNr ~wlڴ Zhw^k:u([,ٳ DRRZnW_}ΝCǎѲe"u*j+WI&r 5jVZ!-- Zlo GQhyww^x7tn [?u];wFFFV^{.t#)) :t@ff&V\0lٲáCЪU+XXX`xwQLtY'W_}NNNlق %(k^UVVZ۷/p ݻ[lAfP;@(jۛoo\]]Ѯ];.] Edd$~mСCq| ciip'i;5kI&puuŝ;wqFgΜ tk3]tÇmo"""p68"":e <ݽ{Ej0`󈌌DNR6Ds ݻw޽{ѪU+cƍ:u*nݺ%Km  ݻw?gϞq})#GbڵBxx8<==Q((111KKKuܹSE JJJκe˖({i_^o?w!_:F)uWVMTU+W,EQ^}888ȩStCTUիWk͙3GTU9s?==]N8!HN :^tIE~L!ׯ_["5kgggs*")J6mt%$$899Ivvv)HժUuʖ!(xyy9~:'<<\rrrֿvٲezRz+"ҤI111Ygyrr$$$CUU^5<<\EOOR?5f]~i1555juhN%33S<::ZERJIuڲ .ԩS穎ҥKbnn.r:ud-j9Gsrr*7xN .\ADQ޼ԭ[W,--zN>x@J.-nnnz߷]v(һwoEm;D^eeeIf\nܸ.88XEW^yE5퍪b mE#ZEUUywIKKӫ/޽[E &<63==]*W,rMcPUUuWRJ\xQ<22269ɯM"z1 A㏥K.bnn.&&&2o9s戢(X~___177׹)ɑҥKK@@vYNDUU9{lyy梪l߾]oݎ;DQi֬' {Y`ڂ̜9SzЍZ߾}EUU iyxhnrU劢ܺuKok׮(RR%F#88ۿKE.]oݺu,X@gOȓOEQVݻM&MDUUIKK3iΣ7ޢ޽[o]HH\.|< :LLL¢c|>.V~ēy[JRRRi?QUU~7ݺuUUe޽:ˋv~UUe:5~AoM{ӴiSE Du~(BUUeΝzی?^TU 6\tIoM[2qD2dle^:'PK,A߾}߿fff 教[n!)) NNNڵ+f͚#,, ͛7GPPPR7[noo___ٳqqq]6=  1WӦMo>;v 6هL3.]v裏0tPl޼ ζGll,\\\W_-"@\\vYϞ=fԯ_]vEHHnx 9vTUE&M5i&&&1~OXr%.]ۣaÆ7إ OԩSx̚5 GBBΘOMʗ/4ehذ.NEJ(6&&  ":iԫW'UT1-ct5/٣p%ԩSGo;j'_*W7!vvvh۶-SNhԨ`eeepzҶא={bԨQAnФI^ۘ1c-Z;ܹkAAAzW/Ν;qܿ_. ȿy6СCEƍREB ' Ƅ ^f 饣A1dTXQo\Ý;w' 55NNNW݋I&_Ŋ+ "xW0~xtMo{Ǖ+W+$''CQ(U211Aҥ 5@~TUŮ]0qD/3f Dvvv۷/LB~4k 999h֬ڷo{{{Ǐcݺu.1~Gs5k֠sΰ믿J*F{{1X.M|\~eo޹sm6ÓϢ\5VO]LR:x@X5/?3K YZZ",, ӧOG2eܶ0}ȑ#cΜ95kAit///b֭x"L 2`EK/^DzF!44011Kl2{m<'aoo,\a&2F eeeMbS4ܾ}GVV9͛7cΜ9ٳ'ʔ)޼fex,友(O>W^y+WDnn./l߾s#`kk~iÇ:VZUV>8?G۶mq1TV-'_999}3f3pDEEaѢE;w.lٲBؽ{75j/Ne֣ǟ iΉWoO?8r޴7ni^^+E=:uٞ]xy~5/޽;lقʕ+c޽=.j{ѲeK,ZHLL;mڴA tRl۶ gϞE׮]Eϟzvޝ 7UU~W<څ]rk@4훦O־ ы!'|'077u~9r$D 2.t8p@9&&FgDC〖/_Ǐ,?~<ѣG +ػw/~W ݋W^y1,9zwihW{ׯ_<޽3"$sCD0vXc̘1PEOҥKxDddd 0OOO$&&ɓ:˗,Y[>U95!"4i΍0v'y票ҳgOaΜ9|v`ԨQGQWCn߾{!55077/x5O }+<}zT͛7ѿ(gwѢ(xL[l1 g}c`FFƎ EQЯ_?.]ݺuÍ70j(|곔ѣGŋ={6ЫW/rQѣGl_!"'E.憷z f—_~ɓ'x8Y_~cǢJ*hݺ5ԨQ#˷N;wQF򂭭-N:M6ٯ(hժХK"::탷7L~ٲehѢvۣZj￱n:888Z|9-Z RJprr~zXZZbĈڴѣG1|TT PqEٳ._ xzzGΝ;ep|wuaըQ:tEQvZ\t ݺu+T>믿o^z^:p-[=z6 ^zjժ011AQfM1[l^_>|CΝz*+4hÆ Ӿ*Uว#GNaff}!..ڵ\y!00P{̚]ڵǚ͚51{l׮]Cll,N:ׯƍ#::GӦMqM_-[O?T2<X*V/F)֖-[ڵkĉ:(jjZjv@JJ ܼyÇ/Ԙf͚aڴi8p :u;;;8::wP|moǎakk^{ ڧBz 8p &N7nVZ(Ծ 2tP,]aaa N<-[K.XjEAQF … hӦ z쩓Yf駟Ю];ԩSfffhܸsũSp͉} IDATBڵ 077Dž uV_6P/]68uEHJJիiӦLS\9+V/x `ƍhҤ hBBB*ƌ'Nh2~83Ŕ("&&&yy؈DN;޵kWqww )SɨQȑ#t۶mK5QlmmZj2bws=:eOL22`w"j9sF#nnnbnn.nnnҧO9s^ڂWUU]vA:tX[[K*UdyNWai۶-[V,,,Udܸq?hӭ^Zz!UV;;;qppZjɧ~j\Y`ԫWOlllFu^)p111)k']&4lP\]]R<<<7ސ-[?kNJ.Zѩ7l boo/NNNҲeKsZrOOO6XyG,--] &)))y槩s":u'OUU7t.:w,NNNbkk+ 4M68{QPHMM)Sىx{{K6mdzS'''lٲbii)jՒŋ繏֥kQn~u'888/XDDVZ%u+++)SGaWCx]ϤYfR|y777 ~)rW_iC꽮(mo^-Z$oTTIlllYԩ#ӧO<رޫS$mGLL4kLJ*%ҨQ#ezR+233O?oooJ*g}Kٳ+WNLMMu^O9==]&O,؈ԨQC{=y\~ǐW]̫Ҽ&">>^z-e˖+++UV+33S>C R|嗒~ = ыD"ׄ 0qDڵIDD/7Aʕx(y^BBBg禪*EH=#Wŋѷo 1DDDD/@RRb̘1%]$"">QM޿$3E1&*,!$"""""2RCHDDDDDd)DDDDDDF!b@HDDDDDd)DDDDDDF!b@HDDDDDd)DDDDDDF!b@HDDDDDd)DDDDDDF!b@HDDDDDde˖AUU?$$$?ѰaCÇGZZZ iIHQ|gYh޼9|||W_ڵk6mΝ; 6<1b@lu7G}RJ!** 666+bؾ};7oŒׅ \fM .ѵkB֭[3 RVVlll33.X_XW(X_Gbޡyw7ThP)P~O§bvm.]Zgƍq1B͛7NJ+駟j_=#-- ]t)4D effX_XW(X__JF Zg[g0ekHŬA󃿿?p,]+VرcuN4 AAAhܸ1Wb̙ 믿^BG@DDDD/+THDa@X̺u 6`۶mHOO+ qt???l߾G{;;; 4'O.uoŻ/7n"3'ٹ;s;8X:uix:znvnPv"zш^ϛ~Ǘtq%ÀM8'N,t  ::^t"gTD]Ž;M< {x;ym9.KSKX޻˷.vm\Nɗej¯|5 F\Abr"݂tt ښ]CK""R҅'1,#33 SX_4ږt1JDjjv> >?ADDDDDde%SRATUYWPX_J͛7~)'"IKKCٲe\{]F_rED{˨SDD5{}~r)DDDDDDF!Q1ٳ ~ٲeprrz%zDEEAUUtQt\|"666t\]]_=n}p F\~<G'"L<~+|KUɄlg<2*|UWwƴ}pA!...(S'%:O Nr֭[ 6 Æ #\\\0n8}ܽ{}AR`cc֭[ܹs'w[nЪU+ܼyS'ŋVVV 4׬YMؿXdЉ'2W_vڰE ;>^d֬Yre9y,X@{ RLݻ>}Z6l *UUU寿o^w~B@hjj*?;;;2e6MffԩSGv*5jԐzK'`Qβ1ch9sFEk߹sG_~rEm狫sʕeժU:ϥA"pҥOUUP x@_~E\\\޻wO,-- *'N6<<\\]]u+Wm޼|:w 󃁦իZgڴi#|=zTTU+WL Edddh/\W>s\En@-zr+WoVD ֭[W'֭[L_]viQE>,"B[[[m,8 13,{ **ק҈KX~)`I.fDBZK%aV'ŋ>}9p瘘TRzIs?QFPLXO >͑$OG`_>*WׯyL$c]\\ ".;v999y*SL1t\]]yyxxhvHJJ™3g?Ѿ}{tjՂΜ9@?ǎ9֬YS_zujLa=,̹:}tiPF L0AթS/LLLʥyjs]zzN;}4޽[!سguݺu+^ydRV322ga1[lL͛Pu… $tt;"Gzo4]0UEE㊍";_o21b=~g7rҗ<|WO[322pM;wxdW^hժ'Nur F3g`ʕ;w.F\2ڵkAa߾}믿ЫW/xxx&L)S0g={'ODdd$kmnyzz"55;wĝ;wtfԨ\20{l\x˗/ǢE ]NC,,,0zh|X|9.\8w6l`knaUR=z@>}f\t _|M6:OOO̙3s }|7ҩS'QE ?%*^7Rn7~e{qSlRd3Ƽ>]>O<_Ţ0zիW'$&&jRbccE'CqvvO?Tg?wޕ}H֭]eڵβ+WXZZڵkE2O r]QUUgġCJҥEUU0axyyL*_HVdŊ:<^^̈́6fd|ɓK,,,S ?S^}U&Mȯ7l%;;["""[,,,]:u$'O3[$44T4lذAj֬)3NbbtQ\r2n8I˖-lٲbee%ժU'/"r:uȚ5k T&;;[ʗ//[n;G' UUufMMMÇKB*V({k׮iDDD899/Τ2!!!2rH]zU:t vvv ݺu|t+9q4nXCM&44TNjp{N*PD dٲe߿?::uΝ;HHH@u08{, _VV͑=+WtYSt_%T׉BDpA,9? u]bPAx0oDe^f'ODek.… :0|_HOON>f͚̙3[_P{wIMMͳ^0;vӈٻt6FWbZi`x86*|i nw}^9+>.t֦MG1|I 27$>>`^,e#55 Ō3Pr ~t:g"znܻe!?.<766 Go8/,8~ުިLU7A2NY:ujI Ǝ[xa4k֬@.lؼy3BBB`oo#G`ƌѣGb;++ ?7s^q:ѓK~ƑѺrkLi>Hi IDATSR2RCXpx$aPAXg ޘa|δ{;}иqc 23dlܸ/,DO.3'-W"G #D1b[ʡxm4jN[a@xiaժU˗!44:u7H+={ $$t ..UV5>((ؾ}{yL6 /ƤI  >I54 "]@bQ <Gx돷**pp8[;t1? ++K^6{[*^/£G?޽{ VVVHLLɓ'|1b7oĖ-[ cǎڵ+v؁۷o뭋ĀocܹE*ד?EʗnE 1GA¿`٘J.v\؁`X#ުx4`@ͣ^~{B///5 ={cbbb0k,Ԯ]}s,aCjj֭CXX|MOEWeB"]N҆s3tq^J7KoaoaBڽhwLyc@HDv/))`QKfeeR5Ӻ} 7nD6m0b+ٳ-[DPP6nDdj@ck#J8/l: /D(t邷߂?GQ׳.Dd80w\+'%jժ󃿿?p,]8x \\\W\AڵiӦ^'ڵkVZ_},8rrbve,>-+ kvAUz`eq`@x)>cGޝ՜ݛvJRdI)"df R1 ؗ_dɒ"R$Q!B _W񸏙>s}{9}ٳ|#c̘1HJJ1k,:u ^^^2 q0 ZmoOcz0H-066ƯZ;v쀖VF$,aaax5ߡHIIX,͛7mw]aJӧOyf?1whXd wX~L2}Eaaa7!&ISMEs Z;O<éu =ۑw W|Q#bX|V'J"##_W0Æ TۆzT&~ ґSNxB${xzz 򥾿&MBtt4._C**++Νý{0~xlݺ0`8@j !2\9<:{JN~g<{a3@tztiScvg8p >}*cϞ=|U'c&͚5U_QQQCUߪT`„ %%''{XXXx{{?/<ƎuYv2! ,Yĉh֬&NC#4^=ϗ/66o?uM1r O>>غu̒`_flݺUz~„ ƿ0Bi<bݸx"RSS1o<ݻw˗/ǝ;wl2`Ν21/\=bccajjcǢz/uuu<{ 2HNNׯG||VȈ[LWWWsY͛7LIIm۶ϟgbeggKݸqb"}LXnݺ%s߉'2}}}/=ʚ4ie:99 Wk>qD6t H$bd***2>6h w1fbڶޞ,LWW^z~L,2cҥ [tiS~Yf׌1455َ;c]xijjk׮?cgfҟǺv*ԩSL^^=~Xz,>>D"cח5mڔJ|ή_mmm\xE43.DÇc۶m8{,1aL4 &&&|Gi X;AU7dUc\9O"t#=9FYn c 믭w}bIT_:"QL[[[صk`dd$/o={lgg5kր1ˣ{2w c***022/cɘ2eMaaa>I__1dddԴ/ BCCb ܹs_FAA޿wUkaBB$ _~HAA%w M֭[(,,*Hd\.5/+.^022­[p#^hiiɼEEEcH$H$i!Ν;pqq3FMM2i;w vvv>xmɓK.9wMyFpVF׮]e~s ѲeK1ssshjj"!!A***6y+KYYyyTL!dBآE aРA8v\\\ 2_ !ȕړPH%D"t3ncǰn|=WCG<@zN:W TU$Y(\RUUqmfff"33J+~_YR ֐}Xβ)~좢JǛcXlqEL2Z r狯>LJ[BSV?&M ::uVq@yYn*]W=V\cݺu*!HpSp:u ׯǂ p5_U:::xUOOOL>]\NNZl[bĹaZ{XdffOA& .闋R&I? &bAk1bFY˼8#7cڿ0lLnӺNVDZ_cܹزe 9sF댮\C$zt˗{.,,,*CѲeKܿU|*((Ȭ[,MTTcXzO̙3g0iҤuuuCzzOLLLxOiWiW.#^Bbb":vրcȐ!/K-B6mp̞=vD"^rhmmrی1W >} 999n]z)]]]:p ̲OZZ?~,&>>YYYDrr2޿kk:,R}V;wnd0''_B`a C!J3xz,t-`N¥K(bmv]{7Lq)D"kV ߿dzgdn/_7Ą m6ܺuK&Y -F={`Æ =ے]vpssԩSqebܸq044~~~X|9֯_{!..۷o/"mS薑rrrpY|Rev!??+}W\ABBd^v ˗/GTTp!xBP]0vXD"L2 8~8+|...rJߝ^|9m&S vvvpwwӧp,\)ّعs'닸 rrr%+ڵk0aj%$$ƍDvv6bcc+ŋ011pY}/ZMՕ0X,D"&^Ђ[BJ/g&L|BXX6|f΄gCsαC+o~c_BاPTo1Ɩ,Y Xff>fJJJ͛1 ƌ3ط~444XfآEd'++M0iii1UUUʒK+r{aLII5k֌׏=z1HVV,,,Lzoe:::L,3???c2Ee~f``TUUٮ]d|oqA4˖-cLQQ+VHυΝ;3ַo_vСEeJ+dSVaLLL"300`ÇgqqqeksJ*.,--)ɆY-L lLOO)++3333qF)gWJذ#GTXTjՊ:uc0X,-*7ۛjՊ)**6mڰdzGI2}}}Yfqpp`s)[ZZswwgjjjLCC3eddkmm-s_~|(UtV\YL!bLۃ1ooovo߾If`nntMVNNN YEMFܹs1bS>plܸǎ,>>HLLZݣkpXDEEC|Bi p1>_E`#"`o k}k,2n`A81scP 1GcG(˗_.|v))4ĉ駟((///dgg#77Q~t E anݐF !!DP!(7Q`|Bx"`g+=+CxJ8sp L0t0^Sz-(&:\W :IKNN?#a4|@*ISF߿iӦaܸq,1leeSd eӻM8q|BǯO?8: uH %0Xc3g`'M%464eq}xzooD"Q% !Gx,F i e/[/Jrq:4UU h;.\߸?kCCafP"$B$pҤIƞ={J-*Ci\]]ݦtzʨ*bccѮ];Cڱc̈e1Ht4o޼egg}xOȕå FM{Wz-i(!) GBKadd$s\SS,Z޽QNBkahJIT1p0Ɛ2'ġC7`~FϨZk.NLǬdcnXM!*[p:!|8z %7Ckְַ;ȉ`-m1< :=Ǟ=VFֽaohۖI ƻ:!K aQQ!1~!'' pqqA```S[8ܹskOZvCOު;l[ڢB:R%|-br8p>߸?ߡT !4jN"xzzꈊB``  ]{}Ν CCCJ Yq92w(Dk}kX[cf`" ɐA/_%#<-iXpqò%z)AMĵwy1#-DBJ'(eeB*Mp ޽{1fLa!55z,$I***F#GJ}A@@6n(=|rV|`[68#D"@!V-j߿Ƶ(/=G]U+l[ڢ~WW+I,,*V: pӦM󃧧' sssٸ|2vڅӧOc֭~ .v" 055-=zP鱇bشiS yN,CNNƏAHCSXTA?F !Op2qXFnD!I$9g"m&.-J wB'N~w1"agUOԣ2wmKj°0?X~=~GBOOJJJx>} L8qqq5fff۷W~ (ZB>}HOO<3th IDAT)))hݺuȗ7-`T?IXJ(g|B!#Uc`~Ӝzn",% 믭GDRoN;JJzkN:~Ek+AH3w7.]}:::u=[nݐ.\PH$c "^zߘoڙ[榝4BH>UbC!RM #En>[nfM|v޾:v'qn9!^[{>o9ߡTBUUUژnC:::pww; /^̱Ǐ#** gϖ /daѢE?>쪴X^^>,QyW|B!(J~ys$H@xtiw)BRWhТtB ְ6mիWjhh1nݺͭ>&Dp;fh߬=ߡBUE6}O%l̘1 ӧ}}}xyyޟR9 _u0!BMkjҘdF5-q}hڂp!CHkC׸aU^!J8z(zdB6#UE !!Dp9Bih MM/[XC_MP!BOp s£ 1|aB!|h !!D0GȽ 5w(B!~,22Gjj*$̹Ç!.}p&Z&02;B!AMtBw^xxxN33 ڸFjjjXn}UpO>hDo'!o,(!$B- ߿/\D"̙3֟)6S //wB!C---y```8@VV B!B|SB[[Fjj*B!򁔬L8:{A6| zpܹ8q"ݻ%%%qWWW\p!B!+,*ĸ#7|C *qO>!"B!BHY_Z|;=eTQQ_.q<11cFN6Sacw(!twwܸq...hڴxvB$FFF2555e~7o֮] L>YYYؼy3pcԄB!ԎbEܝqPH.^`ddѣGCIIJ0`lll<_XX͛7cԨQؾ}#`bbݻwSBH!BNN?C]QpH0$** X[[TNNTTT oߢy2uuu!R_aB!Rk~jjg5PH%:!1cpyt̬,888`޽%.뇜(((h׮z۷gϞ+Yf:u*/B!R]9,GFB2!wi̙x n߾Ldff".._ƬYxIEEظq#=̙3Ǐe޽7nڴi.]ƍtRB!4tkAV=˰ߡJR444nݺvUcH$jX˗/O>ƍ322wACCx)VXeee\t >n~~> H //_X !Bȧ1=ysn0%KhnQzhQQQ (**q.\CD"`jjZy{{{cprr֭['= Z ˗/te+B!p1fN ˼f/ږ.A'7ك-[?~9sѱT-~ (… ڵkeڵk|rbUUU-ŋ[!B­onU>èHe:!ܰa`ddCCC@ZZ,--k׮2En={HmQPPPsssN/)!B!eť@k֕ϢE`R;AjBCCCDGG#44wɉ^xcǏGTTfϞ-=fjj  ggghܽ{ӦMjB!R?でO/Wv7FH`htSb ''G˚AiLMMamm [[[hhh ** AAA000kdF ]\\ www8;;ɓ'ذa WxB!2^;>DhF*w'\Rأ|vzΜ9kJ7777ٳy%3f BBBpiA__^^^)/?իWc޽8y$ЧO,YR !B!Au `~W/ ڷW$=y!vZ,wlMe``$=9K&I 7#F ""ڵk1}t#{ !B)섙gbِj,\~ xyq@K3/o> o_f5ntBتU+1c~ ˖-+>,B!XM¢"`f`ӓofez6)AC';ص ظKON5k#YYY0`@!"B!B+/`͚A3O89T) .  ##̬s # G)qƠAxB!awZ_ll*O~a>~+ͭD@~\g8LLYYTt8˗/?~WiYf&!B! Rd$1K*`A֟J;s bPAGR^Chll\v"u ?h~5!B)1tW6<<X;C-1T;Vիٓq냠GB!H}ˍ NmPUO<6k?8 J !Bi$f⪉.YRow3wn`7^CH!B;ӧh@\a¢BWGxC !!B!D(mٳ'm}"aoh_ I")/^ĸq`ggǏv܉K.!B!+*&OO6͑1vDݬCʈXlB!¿?22EGJV R0j|FA'K.͛lXiooh##B!O?AAb G@CI# »wO>%khh ++!Bi8~ 9~E;bwgF A'-Z@RRR.] Ohh( uuu%څwPUU>CĄB!S 9,]Z~.\"nݻv# N:ضmD"dffCJp̟?PSSѣlٲJCAAI!R)ϟ7n7ogDpm @G02LLmN%|$?_`j3;v/.ǝw}G-FY5t[(!8BHQp?Sй3ЮJJ@&>f_\!8޻ڷll{{坟!Up@d$0p@LPe*B׸GSF vZ߿% )2B!/^lȑ@^@)VJn.]x= ,Zտ?0x00p 7H?7|S;}=zhX;O lق?Xp!,Xѣ;;wr3>IwƐCjL|RNDDD <<۷^PI^BiQW#G F}ݹۗK-,ֿ}ѢQqk綁3;}Ru+}mt[]eGŜ9s%$p^fa%5p.wQU[bdt:''.Q++(*u6o)i҄{~Fq4e\R8w.!EnEBNN`D .H cɒ%|2v Տ>!!qVⒷ?Qb@nǺwz8wX% sƸ"ophZ2!+ao}V< |}HÇ-Rm*y< ߡzhySEE"1~p:!1 =~Ũϵ[q{ͅ'Ngrݹ ԿU%.]֟66ii5܅+WQK"Bv(?޶ } '`ŨJguj|vC0k#Q**<<ÆMҴ)Wh&9ᦐ!4 샳:*:|Bx" & ((RE n ]aa:ٳS~2bb[ :v.GܻU=^x_J1nOe˸/w媳4#G>JQcnн<ƭ?N555quo^xbb"wz)%%;v֬YSf/_"##2'O۷޽{0D@R{N@۱S-wAu+7:f wlmwdXQ37pԥK\!Y}bb--[V1rtm'ೠl3ȉG׸GSFǏM68+lڴ EEEڮYf%A$$$]BBdp߾O;een?3gf̀=y|Lϝ$$$ !!!j"ŷk R֒ ʷQklA&B,Ad;?0MHr3W;{Ms9AP0֮籎t$Q#yIC乆ժ]S:2*J/76l(`~-,v -KGyH+WN:ׯwwwX*2om߾}puuŮ] KKKO*{{N%**n{d IժкAA@ (aJLz.^ xpwFFr%޽9dfK-"mC*-wCF}||tj'I_demm ###<}ƍn݊ua„ 1cFǦ^zHMMELLNI-ӉΙ3м+I^^矁5kaɟ ayN)Iu?N //C%' oCF323`7_GMӔK:-]4B̞=cƌo׮BCCql1`bhݺN_7w\9t-9YBlp\ay) * Iє|%…@ݺrbS +Ww0L&n>ǕW Ëq -J033ann .ݺu:/>}7rJL>]dUiii9>222}>.=]3ةPS%,njڷ\OK{yam-Ws{n.5$6|׻"ٴi4I9z{AΝѠA={///͛pqqly&$IB]رGΝx75999t W;v gNIA}󍜼,_\XB wVVJGcLLRN(͓8P^7>Cjϙ#t?kGӵ1i$|KKK˵à ^''v oooCڵ+6l؀@L6 <qժUmhh(|}}ѬY3뚘p|5ܑ{啎FI}Wk`9kW/{ R:ɏ(M#9[DzQŦ [(:!P,--CKǎѢE ̚5 Cݺum6;v &iv:tJN::ݕx D%@~򗄏>R:35J.ް|90u<'믁&MX#/3g=_ufɉF˖Qm#^ڷގt$<%KÇ=yu(z^]\(d?f6)5 vspgXZqtwܢCF}}}qM̜9r "*Vdhڶ_~PQ*"y.7=6!CO?eѪ6{6' [O؍0ԲUA*ZzChnnǏnݺJ=!ۡClm^__U+yd۶3 ӧƍr͛rdQZU^jZ5QV[|`2& ]@G?[tz+>}tDTedCǙ r@\v=7s.ֱb@ JGBAϵkre*UךU:%9YN97o8q>ҡP1 a6mpqhʕ lll`mm #"=O?ŝ$+WluK.}<\( Nz:l7JGBV9GE;v #@(WyREe8tDT-X ּґP~et r}izrs}\LQ##"I{ɏ~gY\޵+x&V)~8wVb i넰iӦJ@Dԃr`hґptF&'ii@rrtd˿aR幆zup|-TYS(퀝J/ d_3";=*;|0 4j7o]GQ82"R҂@6,P\]yZ<<畎P7/r|^,9{WC۠hrt)o.e2E&\;‚2^'[lA֭aff< &=̙3(8QH0H9 @.?gp*-7e p P\U êJGBʼnJ%gy@*,*?Ξfv~uz͙g`jl6UNO˗cŊZx{{#""BȈHIr'.LL+۷4͚nnrQfd a>JGBř)Щ,׬drҥJG_;+c".NcbbФIn2e 11QHiE;hxLL>~EaYD^U+y-1c9Jo-з/_Y6m (e{X8PHǑkGXP ab>GAժ'2D+VȕX~ܰe-{ rEMEi*w7MٽLoܐo 8;EU.]R:ʢn|cwDqYژ넰>|8N8Ip-[cƌAXz<\th#h;p8vL.D 'ZsN 7D`N9%z[ͅWSk&ǏÔKUtw+QVz㑙-Z %%M4Aҥ1f|~ Ύ5i(75k~+?ߗPw@ @iӂ}{Xp${ݺɏ4aC@R'@vj}{/+I$!7NS}ds:!$ =pi޽{6mҡa޽hѢaeecӦM9ʕ+J[aD%ǂG9|£Z-$Ν DG}a >y*-oF4jL߽WoJG?n჊LJ*777XjVZa֬Y022BLL _1&M³gX <MCK4fس'FCV\f̀ʕu?rA~+$y! bHrҖ-VJ8o5mJG_n=G˄B$8 :ÇǼyt:X|9&O ??BdZH6gjt$TR- t.?{ [.1l\Y3#ɖ-p=[ۢ(+++cGrrȑtfr%f>}ґty=#LPrexzzf4e˖!33SN$''CÇg}ƍC>늉Q:*$I.D!-JO"#ޖCƍ×7l+'իˏ!C乇rA ũqؚIP˄pРAX~=\޽{/m1o>b׮]oqM`Ȑ!:uj!6mBXXΟ?˗/+5~ {h×2 @oŗo"z{mN{a+Ǹqr._kUL˗ xԮ-W-*))?k+ og J/,Y;v,v gggt!!!]x׮]C>}Я_?lٲڵ1񵕲={oFsnc(GBk8)h;ؽ[_rqO.vditD31?^>{}WoNXGrin=ل,a7аbC!}=Ptiqqq 3g_CT$!0{l3駟Xp!;Yf!==&LxX թSruv픎%J.m%w !o~]VzDEժ=Փ{ ¦M@.s.Cv# _{}tTmB*JI @FP O$I¹sPF !%%^+u닐hܸ1^sbٲe0/rܧR`Tc:Њ@r gg"IKIN˕/ Ԩ2A_[7k|&'vIw Op% <=%[v_nm`Wϟc֭_q|'Xx1ڴi`Fº"((HҰbcc࠵\rBÇ???TXM4A\\ >>p=šRJ:/C[ɓ'cʔ):H$'="#Jܖ$8,]*X\eJr.{X+g'oD`j\X~= uгg|Xܼy...7oބ$I([,눍EժU$  $Ix!tzddd?;ͯQ1aa|xiy~mt'  oڀ5.&N<N #G&M<+S˄pTVCСCٶۺukGt6l@`` M@j* 0c ܿ_ӧOcҤI7n6l_B5∈HXZ ʏ$FGah(x1p"`o/jZ@͋"IhrfddiMŀ^&={y(eQر#ZhYf޽{[.mۆcǎ_~$m5rl2e {g:uDz}`~`*#!"%IJ?~=-Mbȥt%w[|ѐ>˄Py}Jٱc&N7bըY&֭[LvkKTl,/DDTLJGn'FeBXܙc޼y7o^kڴiVI%*֭V: "")V8<=a}މ+ IDATrd5QB";6bDDD%Mx|8;W: LHl|QO!$0!$"/mFHI$YPt„ʎGq(QNYF*GycBHDzeVS]cHq.JÇ#'( Que!1!$"矀7`kt$DDDś{I7\\$ *\ *Q%Դp$/$!RJ̙T(QRUy󃈈('O?AJZ ,孼︅ / 4[t8@1!$"""""2PL B""""""ńݻ-Z5P~}lڴ)K4̜9jՂʗ/O>nu222K L2 V-VB~ЪU+tFFFA 0j(Mtmaaa߿?<<|GWEHDDDDD$l2dffbԩl !h"t ^^^ӧO2T"""""2pL ؾ}]v^{Yܺu 0`j5j5֭*`phx"ЧO7غu+O ̘1CaŊB`̙h۶-N::u(VcB !RSSuj[ti@RR={6ƌO,\}j54A1gY a.BCCg;Ip9ԨQfffHIIAnݴ"$$/7n 333&ggg4nǎ)PSRRrlR`ddJKK/QnxxP~zWedd 333}/rADŽ0 ҩ# ppp_\9!CM;Yڽh%*[Nj!BBtSH a.гg|Xܼy...7oބ$I&&&yfsܺuKOVPT$)6!$"""&B!233y0!,`]vņ iӦ/UVVޠڵk]v… Qܹs8v T*XZZΛ!""""*$&&&J@$>Я_?ԭ[۶mþ}/o߾vΝKKK 6 B 33aDDDDDD a!HIIĉqF$$$f͚?~|B3qPThѢ̙jժ)9&DDDDDDJtDDDDDD &DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBHDDDDDd(&DDDDDD !bBXV^ Jaddwfi14nj>|8 D$aڴipqqnmm<22-[ϟ7nDll,vU!bBXHڴiw}76}lmmq!j@ʕ1`ݻ-[,P@qh!JJJBfff:UDDDDDD a.BCCg;Ip9ԨQ#xw^6333ϳf^233 JImb """""###zBdffBVC,Ą0 ҩmvC?_ .hB >>>Kx899ɰҩ->y1,--DcB ٳ@ue-[VN:066?Ν;k!22]v/ܻw9a!AV#99&&&JC^/~}8^Un=)))([,{v}km۽{71b6+++l4if5k 99]t^ 5771!$z &zQӴXtbۂ;wl6۠W^hUQB\LMgK|9MÄ5j_>ʔ)pZ +WƄ Θ1hҤ  ׯc޼yhݺ5>#)Nnrrl*8rn ܖg_L Ҝ2@DbbnD nݺ!66f°aðg|8y֐Q޽{annQFaʕ߿?6mڤPDDԌTtM*7A!hN[)WjvFhJ:K $6Dp-!GG_f *̫N;aP:BsEC =# !AЛKIIьQMZZJ*b?E9qr/T9vnkiͧXU4~HII|kTTKFf׏cRIxHNMFPHHŬкzBA)=8G'Y;naBB|^/ʸt]/ yn  W.ɰ(!"Ñ%")2qir1c{aT-:Td*ZU]wފ~+#$"Ee܋R,KyQT.~~-:28(wI&aEh_=$IBĩ"쾸=?&F"l: sʼn~'Gg¯TRzuhvüt~-:L:v=C *I+uWv˻pdȶ:ouJ8ߎn!%-%Ƞ1!$)=3}mv9[.>^4)LYEw_܍Uٺ29441=VQIńJ Xė_V$ i0& %+(dG5jpv)Y.[IO`Ev@KDD%B"*qst}kZekXciW?{赣%K_m-s$}b_+WKCAR:3uTxzzbTJ*XhadQFٮiӦذaCDT=xuҡPQsd *ǖ6.vOh=~:BwA4siV(:GfbȮ!HH+7aaa066FݟIos!x[{.uJ'Oj9r$|||:s 2DΝq]~;;;|WS0JB"*q ĀwUŮuxX ~g!KqY4rnThQ|] gE˵-9$@`` Pܾ}[p9$kݻ6I`ffqei_7233 31bvڅ-[ 44ng}զW^XnohqƄJ?ѥv>WM8J[/DG@D',JY؛c{Qx[W%''cƍ4h>c~հ;PF M6qFR d;|ᇰ=ڷo˗/;Gjh۶-=z !6m OOOZ G ussmۖgHaBHD%Qź T)K1O@5-عq `RIh9VF4yƍUyy3g"88ǎCbb"|} Ŏ;{nڵ ?ٟѣG#"">|-[N: ѱc|M::uBttV6a̞=Ν> #G?c՘9sѭ[7DGG]vѣ-[.^x,\0Ν OOODFFb>|8ۧodd={k֬zT9rOٳ5=z-Z"""wK7njÇcΝسg jmW:u$IbZm<($I;w޲eK?hm NNNZ ih;W௑*U,&&'O022B.]%466F5k֬ kkk;ssqq湣VXtժUC2ePJHH^7$)(R߿?ܹgϞiڹkVVVZu|VVVаaC 6݋-[bŊ—_~h6lMƍcʔ)z?z?jՂ$It.]44h@s j֬{O43f LIIKзo_f̘+W+Uԩyܺu iQLퟡ# ueff>c "*(7r,/Rj'AAh~M0.Ho$Q223Pͦ"_ƴ 6N,B_!mol 4|ȀҥKcŰ|sh=$Ik'|*U`ʕprrBff&j׮||HfffWTYe,V=IIIGN}5뽿cǎ4+TcW]z۷ǐ!C0sLׯRSSajj}M6صkكYfa޼y2dСC̙3'舋/\ی5 ˖-Ò%K'%% @Њ9k^Gn^7-a呚ǏJΝ;(_Vۄ-[zjT,###,O>Œ%Kкuk899 ./_P"١Ch&F&y7~K>Go :n舻o Op7$I='⋭_XU]ILL 6UFF֮]y!**Jxlzz:$&&MNHH 0qDf͚ZLt5ue˖E||Ǐw}111ZjJ*@jZ:Wҥ5^/f!Ν zyf׬P ͛7cXb9s+W~PZ5ĉs=|.\=zzzۚ>Q՘8q"f̘'Oh+WNNNtR*W @ !~wa(KKK899iraW 򂱱ֵk׮e=}4,I0}tkk׮˗1l0ѣO?jժ޽{kzr {{, 2e~>uT̚5 x"N> ̟?_󾝝1eb׮]:U?og .`񈊊5m }ŨQpAO>|)Ѻu|BsKzsmw}q,#T*tN D//uTw_-:~;+;5k#l;1a<־)nڷo/ڷo'Oj rdWTFl۶MTVM֭[ׯk2e: D*U4'j׮-DzDhhPTbǎ6M6{}ƍ 333akk+ڶmWX[[ʕ+5kOOO2~ٳghܸPZ|bʕF^Z|QT"M&v*jprr/j`QBVE۶mEppV|xwpppz cccg}&lmmZnnnbԨQIII!D IDATgϞB8::s \!ĸqㄯoz J녧055vvvYfbǎuiӦb˖-Yd+33S gggQti)٣URDTTf[bbPTZŖ^31tPagg',,,DΝŝ;woVZϢ2Ň$֛.DWF>}p)ԨQqߺ?ѱcGlRJ!555}"C4)PއUi(R`஁w%oE Q#~7ww"vuO?AP\"99YS?)))ǹh%ի1rHM/\arqqi_kQsԩ8;;+NаaC1]vͲ/%[t8d!ЬY3XYY;vDllNǾnoo_!8?TX2vvl*گo1{ %({ҟpwU:l9q/{w41E,={LU5${>lA*^0sssK.1n8۷Nz~UZZ,XU+Jx8$I_ j`b};_ٯtXT ]xp*ҡHeQ G`ߕ}xo{tX%"#:ۛCcƌQ: Bs)WcѣhҤ k,]4v @`` vޭ[vӨ#31h_=~Gؘ(OoDPTJLG~r%EC2JDCF"44fffy>s-G{ͱ͏?+WboT)---ǫeJC*IЭN7|ii66ݜjnT2wk)T }+ 09}sꊠ ھ뜝sL0~x <&Lor^`&O)SyAzf:=z)J?cE ,j5k*)h]MJWtDTp􌒘={,s]|e˖Ͳ}ǎ߿?:wŋsNOS"}t)ll`kft(j]5 >c`dbCFܽs "RÇ2!EB~mwFxx8ڶm=44h֬uMLLr|䵘*;w 356&50 \bWl8H LĥPîҡ).T4CX5jOOOԯ_eʔAxx8VZʕ+k v:tJN::݋:|"t$/T]"$6\4tnthTXe {s.1DDDbBXu]vFJJ _Ok+WСCgLttb.ZWoӃN_qCG4Z;v(/M5Ht(DDd8d#<< x\,6m~~~ "OCFcbd! vX,jD1p/ҡQ!2TU: CRc:u*<== 1*PJ,ZH0Ql״iSlذ"*>>PհE۶m|(!}?~ݻ+VҥKϟ?@J0dE>[[[XXXfӧѮ];XZZ|ٳ'|_}Fի1sL[nFvУG$&&[l\xXpa͝;1|p۷O8{,֬Yh <8rN>ٳgB^ѣGhѢܽ{]t?f>|;wĞ={pADDD#GVQVUT1~_nLYf9s&vZMn|-%%sA`` Μ9ra?>͛hn:tK8q"Ǝ(ԨQݻwGfff[ 4Ç$=O*<ҡ$@III|-BrʉCi]iFkׅ$Iŋ"))I.]ZlٲE?!!A#G Dճ}? .VVV"88X!Ĉ#]ņ ڨQ#!DaddPT"..N::O  4h *իB$j*g J%bbbr|/5jh޼yy-wܢe'H]N*Fp@p`\Myzh,<Y尸x*Q)C)t(5`gT1118y$o@mҥ ѤI366F/Y&q9vk8::ݻ籱É'p}dffB$\v nnƊzS^^^YIe{TT;ӧkedd 55Ϟ=){wU;3,"( .-,-,ŅLs}mJS3Ӳ,3uIs,wAVpaeY`3s]B9s=p}繟bSeɩ{7| &h믿"44бcX;vXUq߾}Xhqmx&MW_}wFϞ=1dm|OX'''ݻPh߾v[ڵѤI2 ,JSNL6 6lXmwErr2ƌcjk48;;{ؠEܹ+WSN ř3g{w IƍW(ݻU:B"2i<]#_׽ѴNS <Lnp,隵RZ!VޏYk׮FgK$ am]3P(u{ѠAY^^^(,,D͑ήJD!Z]b?R_\̟?. Г_スQ.]B1qDpqqÇ1vXF3f 郝;wbϞ=Xp!/_'"77 ŋK<==SSrrʯ=eZ |IGQ@<@K>(ϣâ2zƍӯqB"2its]:u0\A(zl +'$4tkVPZeץ*4 6n܈˗///l޼ĉ/^7oܲw $$$`֬YG&M2UV{233߾}>O`\x 6,F?:رlmm/fr1ؼXHK}@FFFsz{{c1uT|syx?vvvOrrrP{ ?-S̚5 ,; %b ~$ݞ|x7P7>+z {Kh'ƍ>|8N8޽G$Ippp1c0}t8pΝèQ-ue B:uJ$^?~DH3gZJݻDDDTT}JIT4 |:aHx#fXzt)[ ecq՗_~^z-tȐ!sl1qpp3ϣsprr–-[t>B~lSNҥKK׭[rehԨك3gΠC ?MfΜ]߿? [YIhi{_~{EѱcG?_{t͛~uoQS(:u*N8 Xbz @._/F˖-yb\믣Yfׯ4===BDDDUV2e j׮yɒ%ܹ3 ޽{sOLJ%^y|L6fY֭CVЭ[7lذAkee-[ >>[ƒ%K`rc*2i$L2ӦMCVgرuKǭ^AAA0a v`#O?׷F2. MZ K'2w[oړk˟ۉMRaɵؕ 4LYOb3޻w6ByyyeE3'6l[om3$???DEEᥗ^2H^-Z 66>>>rc:v7|D!Kx[}BHD&]tR~ɩ SvOA̕v)R!RsRyR.\ggg&&k׮No_!C qaBHD& a9aJ)89 (s4)Irh2dFeW;WC!#ԬY3:uJ0  >ҹbڴirA:`BHD&+&ªP( eH+>).ߺv_C52Y)zaȊ~/rt%"B"2Yl!R c̀5ȜCF?,A!`gg*bA""26LlÆ P*%J+ݺu P*ضm[5FLd v3 ԰AMaMȚ&7@SyKLnP( DEE+ efٸ>(V:jՆܡ5[+[m}ŪV!:=_7'CD@ב;\P_䈈¿ƃ :{9^sAdd##2)9)h@0,JB.+"V F"v%†0vXh~o@_{s:RVSACPnn.T3w2d8>HG7SѠ6B( 4vmƮDn~.ǯb֡uCwޠ;5c1LDDƄ Hnݺ!77666ePb߭[رcGJ -ƥMM h2 $IH?u?^o~ -.Aŵ+w(Uboo\è6$!F\:p0 jרn~Z?u]rYeŨuZ {-FX0Cщ!X4&zfooQF!<<NNNeXx{{k>O)SLJ !QLE7nrAP(hX!nc!I.d_ؕ 3̀|:!'C ܡTjN*||L}* 888FjS oM؟S {k{CV? -[˴}o4lnq`EYհN&$ :kkk :t(]?`]t 駟j-\9s~'9fa 7Gsx( -ї1ΫB}Bɧ{GMiR̃JB;vh30R!gǑGpa|n?N>y},䤠rAdVСC~ qqqhܸqCCCѡC۷OҥKXt)VZfrZ]涢i/ITv5QJܚ[3 {/GcQ"< W{WjmλBX1/n+ޖ a9~z,wGFF^zҥ l~:I(K9s0w\Cd b,=Q>g 晼}'+'}vz^^h!!h qP*x{damK<@D\Ⱦ8uI7 Z>Œ/lb.\0!Գczm׮]hI!M +6B#Fߤx= !dԱZ5j Q15mj*$J&NP.v. *sdeʝ+$137,dFV^߽[+ےZFm8pFm]˶TJ~K wƟ`UF80!$"DDJim|M]Gv^vD1;/n^'qM_ ^ԅj8: weX* !YvĨ*;\H{pMp"aBHD&h<7B{k{[ Y3zDD$ 'y{DDDDDl!4 `m]W 0ߐ6&fPt@\ T5<ϡsH0?|MLDDdl*@,B3qK:y]l|NDDDDDdY(&DDDDDD !bBHDDDDdf*: Y.&za(JBVVVj5}4mvvv[.~i\rE艈Uf(\v Wls Я_?;v ƍCV?n݂W5FMDDDDD |r>| Ȉv5\M$|2gÇΝ;w^ܽ{0a"##Ktmڴ):3f`P*ѣ/^S2HDDDD8UFIwl!4q|zBDDDD긶#£BH:B"""""" ńB1!$"""""PL ˄XetDŽB1!$"""""PL,B"""""" ńH`Q B"""""3P( !bBHDDDDDdY(&za(JBVVV}%IիGGGԭ[!SDDDDDdI) DEEϯzggbO6 +Vȑ#1qDܼyWF׮]qQm۶&""""s!I2JaBh }Appp5 V^{ׯ׮gѰaClڴ !U2JcQEaaaj5ݻwwbܠT*aoo_!cBh$[nprr=bԨQ:t7 ==gΜ+WWW7N艈R˨cԨQbbbl2"66}7mڄ{/v?9Rb!)$8-$Ii_[[2EGGK.0a>S,L>jB=?`ѢEÑ#Gj ??:JDDDD+02rRiǭ>l!,ǡC иqRCطovFAϞ=+Wj͛7ǒ%KpBcUen+,oh4۠*ӳ>>>HHH~!;w+V(_@@6m P9s`ܹ:&4FEEa޼yra#G˱RRRիP(h4%U(((lNW*Y;ٳMVALAϮ]Vbݮ]}j5n$a˖-ŋ˝ð4e..JDDDDFR{KՃ-z֩S'm۶Ubbbn:b̙ѫW/lذnB޽q|pppɓe|DDDDDd ñsNݻwޅ'&Lb]FҥKe޽666ҥ ϟFRp ǒDDDD:㝰wзQ'lx[}8Ȍ(`UF80!$"""""PL,Y8~}h;"B""""" pˀR g]ZɅ]F̌ ;= ЭH .8ebBHDDDDdFUFss#?_6׬ @L y30|8p ?FSb0!$""""2c+W͚/Tv :Z$O=ܹS=迹?~KM , B"""""3 ,[~9:۶@DpV踶#qbBHDDDDdZ~M@F")5||EFǵ1(5jT-B"""""3f 0nT_S1`|gwK$bBHDDDDdf$IBZpH*B6nlma܅P(b澙~5dQD ێgJyx@o|ENލ?``tƄ {ER)idW[?JaBHDDDDdf}V8NNcnBpڻb;hXR0!4}Gpvvڶm[ѣ <==1yd1 ps/^\ Pæ`zpTeVr`֭[cǢwXp!T*.^b:u ={DfͰb Xd sN'""""Sw<Х t 8;WΡ^xÛ$jdYZZ^uL<˗//wwy...8x ?~<ۇ={VGDDDDdfΟ0Eؽ]t%>\ם<#^0>$٪UPXXy@?ܹ}ᥗ^&0rH888ᆱx('ZeK_^vze^{aI%LB=;wpuuEdd$GJ3={ )zkkki'OЉ ܻ+W0غxQl,їwS_|#eCUƄPqe=cǎ?~ìYeffBPӳ1<==qʕ ĝ\ѽӐz֭ bbJn迹?Fa`J8$!??_}mmm$ |M4h_+WwށݻWuQv;QEZ44yy}[Lmq(| Ta"Dl!,ǡC`gg ;;;Fَ1v-%{}v]2FSNDDDD:Z ̞ 1 ė|b2hʽrb:[ III($!''G$I,qLxyyU(G hԭ :&wt$ꐛy 6S~t`d^X=P)GgK'Vacc|6^C?Ҁ_޲6|lDu1O^&@Q>֯s/y.O=\e)5nl*eɦ׏>{W 8=na !8}عz򾾾;VNFGd $ xeؼa2͛~;\l DG!$&$W|$I_.6Xj2K))2X8TEL" ̙#'%QI˗(WW>@'2Xڰoc?De+M@sdŚ \v(q<8~jX_xH`:`@_ UB"2k,{]w!p:m0~|_;pxb|R%K\XE^xO駁7'NCIK㍭ ?ÿ>Ď;\Y   ldبrْ$`1ndf`RQ?3S?^RȐlwX-ΜU7>"SO@b5}EwRS(ޏ<;~@ *p`Q=y4&Dd֮dר'kjF_A|m1܀}??C1!%S`DW5jS])^LQbyȼ[`E 1ڶux5ŕDŽ͛o7>,_{{Qm~Qh +$D7G+֪Q7[%t  WE+㫯160)0-jCŰJWt"{YZD֡n ??bjhTvsծ)on/,^o2?(Tu.]D_~y}[?UGsf̈́ z.\]EoGGrE/JbBHDf}s֬NT"]R,^lzO4 ;&\{E=Xl&&ׇÇEbJݯ**aMgZ}L9!I^:&u&u dBXn"""`cc bҥڵ+)%%&ML:˖-CÆ ka̘1rNd4\nUH.:N߯;yҰMT K,NAG) Dm"&Z>/](*_~Ej*JC!IOiiih֬&L˗בM[?f_hذϧVacc|X[[W9~"S֠;#=Z|kjH$z!rGR\R⋀['wDDn׫<C V|ʮ]!N6vL3:^SAgA:/zՇ-zj*b޼y2cH`РA8Idn\Zb֫u d%. IDAT> t.| (qe%_w,ß | R-G922L~C`` v 8:::}0333u1tDf%6V$66ŠP'nC۶sC-qyZ+EkfjQwt$*׫'ÞIнYRQcs"(L ,z˗/c;v,~f͚Ukj5aÆh׮]5ELdbcCRIQ`PQэQqqN·h^ }$O g+;88˦M)kxFaxBXߞ[A`BXI"y&ϟ9s`РAظq#郕+Wم&Nx|P*!)VVLLQ11_/rdS)Z2}}Ek᧟Je$ D38̈́cQC=vuŎ5bܻw'O,\K,5k{!""±2 20G{ӧn%uk%駁39o!GLsT&|9g!iik!ܕ |F)@_^}===^^^HJJc C$xo^̙+P9s`ܹ:.){Wmj\HJW4}߾iKE/ `.\^0b~be60|80y_8z)zU$Cڶ|?")05KgXYɀG_RU **J[Ä9rd^$ddd:P(pss+1n8<+k^^^%yŋ^HW!Rg&1}҅}8ihC*NT6Sͱ}C_{xQq/fG*~R=={6}RrZSPX/O~!+B2KL `4iv؁)S ++ 6m˜1cΝ;w^u9?L)&E<771<&FK -/}8}hӦbw| LL=!,ҫx-`8kW1s}B &Z<r+ohDds[v/, ;!U _{5jl߆?ߠd1!$",bծ-w$R))#Dpa##}IIj5j*: `#˔88Ҵm '~fر+UKet1ٟގhf&ORʘIKH Sak '3xhmՋ]́%>{o:TT%(K )Aʱcq~Ql+t8ȀaBHD&ZZj7ѝ;brGG%I?ξB"2Yd е+q# DEꉱYkXVR)+~Hƍy:2 UΑG&wdMAԮ- **8 ^~HyV0cY# Vׯ! W$I8r[TLdYU,-+Ǐ.ݻ#4O/..@͚rGb>JQTiZ17s`BXq7Q(kC!#ĄLo زE$FrֱEٰ~HKK?P& | pQRUonGJ\A;v!bBHD&7h]IM+[ =zEv+^էNE 1Q\_|ԭ+vlpS+O@/&3ueBٷozggg899m۶غukߺu P*ضm[5FJd^|}<cv1cD X8}sUByxy1EMb}n%gWX(X5h w$%63!^!rAF [… tRtefٸ>tt)&a'êU xyQ1+a}EqmOlِLgxkbz.]f$`n}#\"$ d !JMZZ^uL<˗/5Νի1gDFF8B"y<#Iv˗/t(=6mDz׭qqvc{hvKDسXZ䜧ƠKIJo-vj=[j 1o<@^^_3yd 2aaaQeH~ HyG&LΟ܀!CCv RN.^ߟ:#><嗢Z,Ƀ+jd/4* B=;wpuuEddd֭[q1,^XhLB _/nv^xALka ܑ.#;g hh=Z$w=˅ $?ƣѐ1cQ=KLLJѣ1c j ۶m{F h>O)S)))2FNdZRSA䎂ʢPb?^8ؿ_t{ES' 4T,mۊA*9Y<`7ZӣP͚e$;~++ ,L,;[u_))K{5µ x32FCƎ!Iu I`ڴiAXr%y888.\̜90oȌд(b V˖ɢ5%) W_ $o/Ds+R A,Ӧ/`J ; rR\rII^~Y(L˅ x' #ƄBxxS(Cƍaggwb1bvލ'O",, .]ҥKj*W9VZ]6R JUs IbBh Q#Wĺ7?^{MCBvDضo+hT,NjuP矋j66Đ1֖t.RrRX'PPJhPXXXmI#00ׯi_OOOQl;$IBfDzХK233HKKCu\X9s`ܹ:p,9rqzJ, |ggZ=\47,.],X_OBP௿D8w(X*>4n,Pqktp=^^pu;REEEi 1|#GkBBB igdd@Pӑ=R(xWP('''ΛkkR)9`LjyS(DK@ÆbZMul;nf`LDLV[jW//iMpATa%ˤTM/$ bSDAӧ[D֭ŵܬ==zCJkqhL04{lnS6x0!ԳaÆa˖-Xv-֭ BBB ,k׊ܹs={6f̘;VC`mm]fBHdnRR]R=zS=b99"AKh(*&%yZ]q%rv6nqq@rGaZarrAF cX|y^׵kWh4EEd>yS@U$*kWrۭ[[rJRR{ſӁ5čO_<cyodY ],%߸!ݢd1&ض HK׳䰴u|.o!wCp ;2rF'"Ǒ!ժ%C *MlӁ˗tɇ벳EPq^uJT\\RÏe 9p]nߏ~nخyyO˖;ɼ B wwC!#DŽLJn.pDTT ٻ*sȢ jX OR>i۷ĺ\q-2$r%740ePQS@eclu]393 s{3} HLLN+((N8ѣG}y&0{lU\h4̬&78V뢪`!]PUx$,##&LIbŊ ۮX+=;>qh5[v-0w\@NNNDWѱcGݻ3T"""""jVwF-`nnkkk̚5 =<._~cƌFF3<oj [ Bc(,,Ă v@Qkkk|,\~~~8uz)}n sL+ "ө1 ;;"?>`ªUGA ;;[i {{{/dVcBXÇ׷v* h۶-LMMCj6l~g>}ݻw)[IE޽;;S%PsssmVa``/QE_HW+T/B9ؖDyL+ꊐ푒'xBk~f "y@v%mw[%թ=FwTW*_HW%T}V'@```>ӱcGҥKpttT_t *JIܞ~iҥKqeS!&&FJDDDDDTր\̘1۶mÍ7Ю];L>TǏCVW^Xd ڴiȉ!aBHDDDDD@B""""" !QńbBHDDDDD@1!$"""""j5PL(&DDDDDD B""""" !QńbBHDDDDD@1!$"""""j5PL(&DDDDDD B""""" !QńbBHDDDDD@1!$"""""j5PL(&DDDDDD B""""" !QńbBHDDDDD@1!$"""""j5PL(&DDDDDD B""""" !QńbBHDDDDD@1!$"""""j5PL(&DDDDDD B""""" !QńbBXBCCVK pR;ݻC&MBNN"'""""T*͛GGGVVVZwObҥHIIݻcDDDDD1!!Wm>#4mFj cƌAdd$z8B%""""CFkPvv6ʜwDFF7PA F7|$"""" a fffx嗑ٳ(((@ǎC8}  ffff9r$|}}aaah,_ވʕ+PT+ ;;;9rqNDDDDD  tjkll } /`Xf ݻZ{20!Ç[i;Jxm۶3 IDATҥ "##ij=e~eZ JUf^A@]n5 a\]]S۲~>EHJJj/"rJW\NɁNm۷o\akL+O 00Z[[[SO=F~àA1d[rڵk033+ R~~>4 rrr`hhpc!]PUԼī߼aO CP}S֖W&백њgDGGc4 9s艰0^i}%DMdhh _HW+T/5CD"tiFFF~_yDQaBXͺuOOOt ƍѪU+|Zm,Xooo 3f X}ŋ/- """_I]&_| a5:t(vލ}!77vvv;v,f͚5d<== L:=z4.\艈.;<ӻOGAQá:D%" Us(U$??FFF˫CHHk;z%??M4͛7U_RsqoxƝChl(N]>/tbffVjh(q&u,zO54nXaz%xlQ{븒R,KQ՘={6 !RgO>2Za]DD5!e Q""ǣc>>L(&DDDDDD B"""3zjۇI&5Q7:u(eȑ8p`xQ헟''';B"":`ȑP000ZVjDyo1cTiY?߬5G{bĉ4j|7ZmWZ''b5kaff_|)))3 ۷GƍѲeK?999EK, LLLE)ЫW/cǎZȑ#1`,_ Я] mڴ͛b(GgΜQÇ+Ο?ѣ1w\bΝ>,Ѯ];h4ifҊmܹDxx8`eeaÆimce?Đ!CФIX[[W^AFFFΝ;vvvxו _͚5ڵkP***ԩSѤI"((/**ѿR /p;wDǎajj ggg+]¡CVO?SN011Q~ ֮] ggg ZVꫯ0p@h4m?cZ 3gK/z aaa|2"""6VVV֭[+\BuZ^^<}BԠ?OJо yjSY{{pX2neRTTTwߑ9qDmy߽.?(M'-e7dѥr$;^l}S%#FT&((H#Ʋk.eXXXȔ)S$))I6o,F֯_ 9z9sF'...RPP ""!!!bdd$}9}ÕeDDDȅ dǎbcc#aaa""rQT.{d>>bnn^aܸqO.QQQ-NjHFF߿_,! 2j(wiQrR3eYr4o\rssEDdʕ䤴;|XZZʦM… )[`ERI$22R͛ߋ[NeŊҨQ#9x,J%-[m۶IjjL4I͛enoZZT*xDDz!'O֚6}t-s9aBXBxݹGVX%OyJ体%25R߫u+eݩuV[ӶYeWlI&U'5ƍ+/sssYh&//OdȐ!!2|||CkӕiIIIRĉ,133۷HqV%==]ifS;;;֭[3|֭/!ܸq2V%11QYOY öo.&w%y_|!r]eڞ={@IF!NNNZ^{M &""R$::Z *J+!|$?6m(t9 "l2ܹ~Μ9Ҹqc% )Nv> 777i333ٷo_qT?r)QJoeiѢE1""B ܞ)SQϟ/"޽{ŋKjoo/"Zw߁^{M^z%Jٳg+srrDR?\;vLj_;tPiW֭[&G#\$"[ܿQ2j%:w¢^Ϲgw@0X?}?[FFhW\_A0208:olx[zui akڴoCCC}pttĊ+J-zߵkWX"xgZ~v233W^"55o6F),,ֺ~ie^mKŋ#!!oFAA߿{{ 㑗={9?!!<ֲQTTD<<<C\\ CCCxyy)۵kWjT&66?< m۶O?Ejj*QPP =.u?܃Qll,ann5HMM Kcذa** ј;w.bccqM.^WWWxW>}W^A׮]ugŕ+W:uT{.˝odd`L8nرc?2yyyw^~JBǎcرZӼKU}hff (LMMˡń"g7}CgOh߱V-00"+ؙS?~.~xU_054}GQ5yy?F:a}C7n7u-taFT*%IP|J%..d%@1~x,\M6ůQF!//%)kUe2&ZmI'N1o<زeK_zѩS'l޼w022Bll2')\~~~ؼy3lmm~)L뇋/bϞ=طoz &`ɒ%4] 77hԨCÇc7oxYDPBmJm޼9DZLxzzjqrj/!"*ޛzc6 ;K20JN0<},bƠ}g,?͗7G@Mދ²H7'p*XڤbԩX~=tRm?~...PTpssCAAV,$&&C5k{{{uZVZ)*K>* e.])Iٿw*ӎ9kNuʴDܺuKy_r}eӧ[믿ڇǎ#O///i.\)#///$''ֶT077ִgBBnܸEm۶-U(Do ,, +W_|S\%jPT:(.SJ bڵKԺuk򸹹iOW899yZ۷o#** ݺujW*Iڇ a5 U@ݻw}=,,,uU "Ռo} =}'[wHjݤ5vocEN {=G.g2j^uP|emÛo 6ٳXl2.^IIIزe >3L<쌀=GEll,-Z @8ΝEO?Err2+W*m*듎_deei%`%իW#==6mse166FPPMM6!-- QQQذa_ |M;wĉ󕐶mۢo߾3f N`ݺuHII+c|Zɓ'cqY'/_E߾}i]<빒,X _J8QҧOYl|ꫯJ#F>pKT $h_/+@-v6|vQե^%E2Anܸ|9s挈0a7N,--ZfΜ[nɛo)M4F#/HKDDji[lOOO111kkk񑈈)jZVd7NlllDVܹsEDIʕ+A4Ixxjˌ@ǃ)… IQG\\KFy,es$baa!Æ UVi9s戧VBleffʈ#Yfbjj*2vXsN׷uViݺڵK/͟?_<<ڷo ڍUFk022B߾}|r8;;Wْmllj:L"n޽~_sSgZ#-L0X4篝ZX؎c1076|AT'ZE>[dCz^w!>#}A:j⧟~/,,,˗C &&С򐘘/=wb@]yyy!|9°aL1jukCFj =V@DT=z/Ǝ5k֔n̘1ꫯg+2B`7P$E~ʠRob}zl}ZXc0a!B"ځ aoT*Ѷmrt ׯ_GRRR.] ,X~1|YrrrٳgcΜ9hDUXlv&oc2X##*^ui7Ӱ>f=_pp؎cUC""ү&M;WΝ;#;;TН;wbРA8p mV !Q՝r=zbɾé3 C" 8>u3Jxvڃfe޳g56l|||H544,dܿA²1"CC*~3~wؘ٠9ܺwK!QT1,=BXڶm OOOt ƍ'OpEoXt),,,Ӿ}{<ӕ7UͨFNl}ukUcO|%e5 Ϸ|V_^!$"XTj6tP`ѢE8q"~;V+tܹswń ڱc~1GIރ5kjeReח߻?>.M]#qңKq5窾Cz Wֹ}hhh?Vbԩ#Gb ŋCD_~~>PHLYpp0q ܻwOAѣ }͚5KO[@T?efaUW6w8ғObfHO>ũ˧zUk f~NEN9rrOZVjDyo1cTiY  =Xݻ'NT@Vojj*899=Վ;зo_@V̙3Z `ڴiz !5ůH*5oIidWLy2ZnC?b燿Ky]r[lwX5BDL䬭abbga03_jJSSS̘1'IYrrrcɒ%n˿o9rTAE}Qw.;,}qCipiΏ;~ip_-nwu1lmmѬY3eii ٹ8z~ɒ%h޼9]xH{g{VVV-5"֭[ DӦMhe~ɕ_~077233~zXv2/##j;v@Ϟ=hСC8qBٖz r%488@a| ڷoƍe˖?~L;<KKKXXXGHOOܹs;w*sӧOGvhЦM̚5K+sprr ?C A&M`mmW^y gxx8:w _W7P_u4k fffh׮BCCuSNE&M`kk TVn۷oGK6lnݺ/eܹ;v)Mt ZO?N:D-Xv-all 77R j5+ 8m۶ŏ?XaÇnj3ЫWru EP'$//OߡJa]YwjCݛZ麾X.?#Gם-dggӈ#d GGG}ĈڵK#2eIJJ͛7F+mC=*gΜ~HHHI>}$&&FN>-2|pe rٱcHXX\pAT*޽{%99Y,NNNRXX(yyyj*WJff䈈ZJYתU!777?~2?$$D4iR}=m4M6IZZ8qB6l ""999boo/ˁu2rH7Neݥxzzɓ'%&&FE(vQ|֭[RСC""r%KLLȦM$))IrrrdȐ!|Y`8qB222d׮]bgg'K.U3g177Aȑ#bgg'3fi狻=ZΝ;' 2|pquuUb(W?7n(?KTTx{{2%111!WDDDHBB5J,,,*>}Zj\zL2EV\)͛7\Yr899)>,i&pDFFJ֭%88XpAQTҡC4y|bdd$֭dYb4jH<,KRI˖-e۶m*&Msssyf\`l>}9c>>L8~Y*ZEg),*w(TI2c iIKi=8WnzBبQ#iܸ277E)mK ";h-G<<]$*JN8333پ}'XjZӕ6k֬;;;彳lݺUk=ϗnݺ"7nܨ?jILLTSV"pB۷ ;wDIn_ܽ{Wg100P#FI^{M &""R$::Z *J+!|@$?6m(t9 "l2ܹ~Μ9Ҹqc% )Nv> 777i333ٷo_qT?r)QJoeiѢE1""B ܞ)SQϟ/"޽{ŋKjoo/"Zw߁^{M^z%Jٳg+srrDR?\6,!\znݺyLk%z+;/s>ˡV6rv>)#V:Y zpm&"/̯֗<ⓞz3g 66;( NJ+J-zߵkW$''CDCCC$,,,o ++ CϞ=˜gyF>Fooo!11QuoՃ(!!RkVVVU566?|7m6tvvv077nj3pE6Z=e(66Zkkkܿ8r2B>؊'DGG# Z|||@wŖ-[鉠 ?~\n߾+WhtT3k޽ ccr!88˖-Í7_Zq=U* ;vԚnݺiM.u_߃E333XXXTXSSS>rf5wDD5exY ;v ,6 NĿ C=&_17eȃ?F:a}C7n7u-t3T*r J\\Nɺt7##pB4m+FT|Դʟ)KY*ۦVz$B~~V'0|p̛7}􁥥%lRAEWlt 7o.uFFFU=:Ş~7o-222Я_?뇋/bϞ=طoz &`ɒ%OO 77hԨCÇc7oxYDP>[QkynܸQ>>L^s+ѷVޘjq%|s,>#vPrH.!oF^/&bԩX~=mۆ@߿_MTTǏ* nnn(((@TTr&++ )f͚:th*K>* e˔iZH[oPܽ{WI9kNuDܺuKiSr}<3ӧOk#,, e^%,k;v >}2… :]}oʃֺu2W?p ,ZH9qR˰o7xݻwǴiӰdⲳCTTwRt@qۗFRa…8p }]y^^^HLL,w憣G7P=z~VWOOj[ "zi 1m |8Zh;w.-ZO?ɈCHHV\j#YYY{n6իM6?9β#((ӦMæM(lذo&Ν;`ĉ JH۶mѷo_3'ODtt4F5D=/^:t3gZ΄ pm 2HIIAxx83g )) YYY((( .^m۶!-- WFDDD#8r.\bҤI|rˮlFFF~>?{lHMMŹsk.%%I&aعs'1n8D,666đ#G*l.]ꣳfBXXqy$$$`۶m[u!%%+V;Tken޼X;w"Ɩ6믿o߾.z p"U#pKTZfv/4$_qq23y/{&RPXvquZ.*),r 3ș3gDń dܸqbii)2sLܺuK|MiҤh4e~YEZ"""DVkM۲exzzX[[DDDHUjR$CDdܸqbcc#jZΝ+""NNNZEeV\)hOEV]f%E0\OY.\(NNNbll,ZE?W^bff&666;h`)ɓ-fffJT%<<Զ+G$22>:{O7n,ңGϵkפo߾bnn  6lZJk͙3G<==Je(33SF!͚5SSSqvvcʝ;w羾ϭ[J֭Te׮]Z}i!Fllldr*(()S4mT}]V)L2EkEV*/HEшeǸ!$zݛXZx卩STd ;aa8@iW{:4+DT; a㗅COZBG+QW_ؕ ;Į4̄`BX{([ƕ ҼqsDDD j Z]e``W͚5Z#&DVc Q25@Ra޼ypttԚneeUgfΜ{=3ŵkx}BDDDTg0!!Nmn:̞=fͪȈQ18>MNNC "j\{0!A033Z]I&W_E?DUwIރ5ZPy' "񁅅/#%%̶~-N8%K<(ꏰ0o6f6Njfff#G퍘888(mݻ>SNE-ȉ&73 fffή8Q(N^:ĬD87qƝ;ĩN{8鸫qpqEgl;M;UB$ I=aaLD؀0}333}Р1! //Oc/,X5k7mѢE(((~X5 G8{Ыu/}BuJgӴC;vx[o#(46j>m۶-s7tHeڅ l2]ZΊ;Dц0v*,-bQ076Gֽp"_Dԥ(D%G J l5gs◝' Ţ^cS=hB9c[^L+ꊐU8EHJJRϚ5 O>$^xddd\v222вeK<+:4{l̙3G%wqݳQThe Z5+я F&Z gsOX|2/9+gp4H=3o<̝;Wa4x*aYǢsF||<.UUTRADRpMXXXT|!''eBb Hz#UIOUˆ+W~zQLI~A$oҥ={DϞ=sҥ ڶm-Z`̙XlQN?@&885kļyvZC~"""""p4!X &EEpp3S(HJJOۃФIDGG>goon^^^h֬F)te/LV-FDtXA""22R}=<lmmo+WWjnS*;Z(etɘ8qbϗ2/edKym )r ݼy3s;wDBB:tolٲر#&%%!..m۶-`(LM&:|_A  SHlAdP>>>G`` T\W$4i9r$$I’%Kpgbrrr!""""??$E/=DumPϭv\ BkĒQݻ7{ndggC )S֩S ̙3T*y 3I0!W2rWKB3ǻ'DDDD$s!{`._qp)* =%DDDDDwnlKچCOd|֌#DDDDDd45aԇW(w8&DDDDDFt|\}гnOCd"FGe!$I5}rrC`BHDDDDD͸uc;*KF,f>xbaI%;*B"""""˵k@Rܹh P?./ IDATԩxx_ 5ސ;dzؚ5kT*=lllp H-[ ڵ+RRRSL4i'N߇+ $SDDDDdΞg={  cǀV)S5i+5W2e~6o~ fb(7&>˖-Ï?0 ((yݰa|||jժhذ!q|E ʕ…s1$`˺B!Pwm~U['JLd)$C @$!''G}K.]趃E2d-[駟!!!z*̙{{{8p˗j4!'',&"""zOmĆsԩ??{y~*RFCb~nѽ^6nkܒ¦2EEpp3S(HJJOۃФIDGG>jѺukcŹχ__|ٳgF)te/[}c=8~XdPhRoZ~mKńy:ã^^^8w\ϱ8u-Zg5kN:8x`bUTn ԩSu<""""\QQbdE^[<"X>}:MV͛_,&"_~9Qbܟ]BVo_FGjtSEH8sF4wټ(b4}%Je+u'Oĉ ܦh a``7oΝ;:>Iq<=zgϞ-r Â`(@f(ɜ< y}pc-kmllBkڴ)ggg$$$ ""UVskԨڴi5k 33m۶ŕ+W_CRaԨQ2~ """"2W bTP/Gw|\ A QQQؽ{7!C`ʔ)yJFয়~qFڵ vvvhѢ>sԪUKO@DDDD殠lN(by!ZwH6\vq """"zҦS:/edE7ohuag>׸%s,ر@hAXzx)F4a,X2JDDDDdbbDwQC9~^[u2AI!$""""P 0l0ob.=p%YpȂ(j kP[qvYdÄHVӧF2*cѡVTvllX2JDDDDd, wLê04pJ!h/ {X@j- {` G,Lj*в!4>h #DDDDD$'xXff>3qӆ=0Ɋ=ٷOeƍ {MoBSr2IVL,V -;?o%y1!$""""? +oƹ[շaLcBHDDDDd!/ڵ3q7؀ЗCQTdń@=$Ixۆ=0&DDDDDo Y#Geˀ~^~ ~Ǹw8sF+.Xu hIͨq>lGxL BKNN 04h۶mÌ3j1sL@FF <<<(ЉȂѣGǎQx *x6m5n УP>ِ$ 'G߿/ʬ˖ۺuq?}t􄏫N& a$IBNN^YYY$ sرcݺuí[xbL0* ')S&w;Qq\dm`6 /Z5`0 -6l>XT  FY8x8tHHGǽw(UJ;;ॗ|jDaCc9wN?=a;%mC7n9.-Bll,ppps޽{9V>}pR=|0>x w4MV[NDDg^=12q 0zq-+Fw.\ERZ0kHR݃b$sgQ"=a8|XB4i4>|(~rEl%ǎ#F+ѣ_~ڲE$XQÏg~D:_8EjE^Ra|}}׾K?===ww< Iܹ$Iw xzz+ւ<SxDDd>4O[ڴ/77`Hq?|H vƎ##DM`|o:uD'UJZ[[1U>}1'?3^_w}n|y?׳|v6vF.:}F$&EpwwGb @JJ QZӡP(PbE@zPT)9r=zO 11z_Z Bڽ)&"T@Ϟb$q1`  E vM$mۊO?M ɔeg_~ t"x֯o!!󧟀h⫯V#.rQc4~f;v3jwɓ'cĉnh4Ex0S0PHիW>'I"""P|ܮNNNhݺ5֯_ZڵkVѫWbmN "H׮͛7diԩbUnMÆbGrGGߞ=b_k6,H @?{@lh\AC*IHwMװT2X׮]ٳg?Ѯ];aΜ9yN3ghѢL4 #F@vFz""2yׯ7fMt2|Q ͹sI@ݺb^C2 O͙^~pt =bzuQZ%QbjhBLydrҋ8|1i$lڴ oFڵ1nܸ|f ..aaa8z(Yf=Dh`ggI!2$FAt HK<%}eʈ''reJyfM.MsGbo$ѐiddEFv'IbdΔh"A]D ڧtzmI۰2a%~{z3pE|[D|x[r9~YwG_E G]ܪU//V^$|v噙"axQ[W(5n,._~`zZNЮXSo7|$.ӦƤIk)C*)))b^k._ $`RΝm&zws,3 Fx[r9~Y䑝- Z:%.;v5MnNA '.[ڷ:u#d$I1JK7 ,Ͼ$+bΡ%$dٱcHBx3&yp,hkܒ{DDŠVիb>Եk"9|}QZx`1-sgv-w(T@DfbsSʖtR-Ds Dc2o2ѥK\Y wH_~3m##s2yr~PyFFBǚ_  !SrrD"e ph8`kҥ~|P(/qCydg_~ T(w4]v8k7m厌L]tmiFL#Y\(FܪT;+Sx]&mbY ooQbwQq̞-:#w$[{FLV@v{2>]W_Gҍ$RP1!$" лhβsetr|Ui1Բ%"pZӖ*Fyr 7\oz~9Ny "5ySMZ4j {[{CƄ;wĒmJK]Gr6lh3``1E wΜ)9$ھ],[pܑ 1hZ0HLL*J+ pp;qp Ϟk-Z$_-YrGG/ֹ4IH̏B;`;bD## lRi$ R=DPHL=.3QhJ)<|XZ|jDg՛7κI0qX,YhWRb5%EJj%厌Jĉ@PXwZ( Hпf|F*&DdNwweFs w~], )Qɋ288Ɖİre1pu#pX:/ĴEF`;B"ZYY@^Y@`{q@TT2$ 2E.-w4|y`#Jݓ;22>Ǝ<<ĴE_`5cBHDVk0|ܑՁ#*ժ;wpq\*q(OqF C##C۽[T#w$M'Dž?PƊ1!4hNNN Ė-[r߿K.Ev '''4j+VN1r"ҿ`nn|eK 4hL7)=[tǴ;WXVV޽?֭֭[cŅap|P*Ȑw/]ccYXHN*U6#+rGf6oMz|}厄 L(ƍ6m]G厌btT)#1h^a̘u!66|888ܹs{{{@nMDZBn+~o1c k׮رj4BZ#4.M TI4xQ4xMto{Ӿ2D(HNNQ@&@۶b#rGF*}I;B(V[5, ?)/"##鉔IpΝ|ĸqGaJ*t[xx8N\%7oSH3&دY -+ٓwOݻ pCr81w+QHP( onN| ӧOmH~ׂ7 )))HOOGjrOOOB@Ŋc 4=z_ܱB[ݼy@b!jG2bl@9")>4(WNr(]ZHy8:Nj7.î]Y^n֯;~4\'Oĉ ܦh a``$ W}N$DDD|}>66}A˖-~z_[[B6ի$/<\HȽz3׋U}$ʽɃ "!& nn#GSJW"#E7|A"adpv튐̞=7n܀oߎ8\2ttRݻcyӠAԯ__@dQ,zK4;!ӡP-ZDž |Ï?s,nNV-vm#!C}xmwl#Fǝ2IHϗ;~j2J00!4;v`ҤIشi֬YڵkcÆ y\p ><1ÙL`*v4uի}ڊ}KڰA$dyJS'8p;WbarGhY7H?ii}~LBgt2Yvvv:Sbc~;*N,^,JK{CIsx%ѭddEӓkE㙏?-}$$ xz.w$㧳?aňSoƪL-9CHD)'GGrGBxten1ՊRҐ`V:o*e ZZ%KTys1Zب@@pwB"։$w ak+n['7.uT 4Ctpws3:{XCTIxyG>YpAj%w$T&DdZ}厄̅J-edZĈ…@joNXMΞ絍(QQCC?us#mnne+^4PJY U*&Ddwޑ;4e;t&|yE bo=pNq$'e'NĪ~}`$n-JϜ1yQQNNrGb~2SItt4BBB'''b˖- 777(Jl۶#%2*.jV;dJ%и1碁Ǚ3"۱C;Dӣ 2=j4/G5jt}#Ϗ?SJ@R~L ""ڵfϞ_GZZZ%D3g Y3{{qܶ9#C,3{70oд)"oD^}U<>\,^L7 BBǧdʡW7cx7 `BH1!4˗CaڴiZ JUkFz ͚57{({^]H!u+b=z$wDDϯR%CDs N"ڶj4o.M}} %FީlP ĒQ/GGGbʔ)&{[lA||<͛'CDd V( Cj[N?,ht& h0DzLH@P8Bh`ɰA `۶m1cZ-f>Q|3f py#'2?h/w$DDIɠXS2-˗W_M=݋>W))bȕ#,B1!,$IsҥK IΝcu[naŘ0aBn ٳ#?8xz+DD$?][< ]"x`Mt}k߁-Ey*_BFF4!wd!66OP )) >>>Gvv6zg>}`׮]8v5k/bX|9^8VMJ666/D&*JGDDK<Qq('/ 2R*֩#F8$\=n(izKNhZtumKńk_'RRRT-$I;wSLK/-Z 55qRSSQJkj\Su"srGADDak_7dx= !8|XtkQ"ZDS*)ܡ3}F$&EpwwG~ ==ժU}>== +V!%%y^P(0tP( ܹsNNNzZm!] J"˓,~{U#!"(JB[98q8v xeQ/j"+dCɓ'cĉnh4OBCCqF^ӧO "FDD|zgΜ7oyS0yd^+ֶ֗Є)ʇJER^*O\; d`666dx)e`]vEHHfϞ7n۷oG\\V\5m4k!I^ytҥC'2+QQ@1v12u%4;v`ȑ1f\~6l`D&+K3g""z~LG .… _ZHQYhaCQn߿YW[WPȄqi;y$T;;C!ƄNL кQ׎ϝT4&DdVҀ+Wƍ厄tIA& LȬ-Z(M _;nKN`BHDf%& ; """HF陘ِ$&DDDH 2.pS 8&Dd6Μ=ח;"""v-$0!$"j(8-Dժ&XwKi+S #@H($IF;lHNWll䎄tITTqZ6Fbԩ|8F … ~!$""""SS$ItyPXhh(6n܈իWc 7XlYtQQQ8w|||IIIСCz?R GGG|"""""#;Xkpmڴ={0p@a툉ʕ+1`ФI8::bȑ$ K,NѣGsP dggcҤIشin߾ڵkcܸq@bb"BT"$$C5d B""""""+;""""""B""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!$"""""RLB""""""+ńJ1!45k@T{Cf͠RQFAV9YRr` Ojժy%ωhݺ5֭E/B#i߾=5jT>&L@o>T*@ժU1x`DGGu%*Y)QVVt:]ݻh77~ARa%&Y)&F IZl '''888k׮HIIɳɓ'#y 6ıcJ2d"""""B,50|`ѣ\2 ## tDDDDDdeA$oҥ={DϞ=sҥ ڶm-Z`̙Xly^2en'"""""2&EEpp3S(HJJOۃФIDGG>gooxa Ν˳$IȷoFF<==z_Z '''%""""2'/â1!,;gc?+V^z(U9=z>hPɍ7P>!$FJZ [[[!sd>D/*^襨lTX% ݼy*TΝ;?89'''nׯɓsXv-j5z=.upp(4!$z-&|)9.@db$mۢG1=*/wU/mo6[},lJBkڴ)ggg$$$ ""UV;sLEKF(vvv1Rϗ/_u*${ko(_+;DY2EY8FrΝ;оf{(RF?/_2HDDTRǭxْET"<<ϕѻF#*9 ~F1`Moиrc3rs?Gڵk3Z wwBḏ??~ I:LX 1cbQr]N$IxiK_AOTNEDdΞ7׸%)7YÄ XvAn2J~ /h=)ϝs'w(DDdŘE8pTuA4jDD$++DDauׯȋҥJcI%27ʄ`<ĵ/wDDdٓ$ QQ>b{{1\ i qaʔ)YJ;v̳_ff&J%bcckIٻw/PL`͚5y;k֬ŋ Ƅ^ުsٗ/o5;"pe G)B![kQ^#GDll,^*w8VG$( X=oJR}xLhǾx":uꄐ?~F{}\]]Ѯ];,_hqa0!$"wakZBiӰjn syDFwYvm ej56mڄC7@ddd?.ܱc|||`oo]~=Wj:wۮ]мys+W*T@ΝqbACR|С2339999r$aoo͛ȑ#}<2W^JBPPǜ9sPR%8;;cx ozpp0ƌnݺ? ,, UT@jj*Zj(Wlllr_Qjj*J%oߎVZARaÆw$IfϞ ooo888?^΁7DJƍ#&&&-[J*W^kΝ]6צMЦMy^R +/_Fhh(ʕ+WWWHMMݮϹPzu̘11dɓ'TPC Э[7,XPV[h˗/7͛ڵkcذaѣ-ZgΝ;cƍE~vKDDVc/ܜsΈ'ף疞𞁢#;aզMPNԪU V^dggc֬YX~=p]'>)))رcv܉(۷sݮV'ѣ?`ccnݺ+DnC||3?~>>>x=rJ>}GF߾}"?Z.o={ 11:t@.]rƄ5 3fsk.h"ϊ+-- owqQ_˵r(rzⅨyW)U*f_5;4A ʥ , |~llr3a潟ϼ~~~üyrϟG޽k8q?^纕.\haѨlpO۶m5k֠G1bbbp1aѢEr;wKK.aĈ IDATr|}}qEy}Ž{pݖѢ1ҪUTT0BݡLMVH%+)/aSMeλYB^JIȳ>:[rmJJJۮ O/v^^^l׮]1*++-g<I?AAA+W$&&2d.Ų6˗/g 7 {9`DZ@YxXRRcL9*//g"]tIny،3C@]uƾkcǏgu~fkժU[nrW\y444du2={6gO}L¦MV:::-[ͫy?yD6nj8Yk kC=VfMB_[_%ipd5 񄺿%ERȎ 9III|2N 2eJr,NNN044DBBlB쵥%rsseSSS1}ttqꡈOPYYݷo_8{rqŚ}ʵ0qC⠡! eѳ3d+:G(--ȑ#':$ڭ[7^zInCXe˖FFFCbb,#G9s&92JLLD~GKYYttt]b ᆱHIIHKKkpOիDS.6///TWW#))I6Mpγ%H{I˥!Eʻfn{ W`*iM7U- B%uTUUɒؽ{7{ۚr9 c_~ؿ?P]] 777T4$\ľc~:ֆxZ4?](yT)_ѾKJJHų[ ?dҘXt)Μ9m۶K.4i,v]]]\z믿n:_W\Q*ebbGջV`@zz#GyhPC$=W }eaay999ח;>qzUϗ _5|}}aee}}}xzzb޽cAH{R6ɶ ?op ?8 DMMO[ —_~8 ?CVVVgIJJBaa!\]]Ç?prrBAAAG ҥ 455q\777₿[n޳E[LMMq}jܼyS{eM({ẑs mmmdffsr5F6/zDGGc?~<`ffV ñe8{Rqr{xxx >>^aEaǎr)))055W͗' ₸8Y/) 0`@_U7͛jg4?Jqظq#>,:CCCYt:.]m۶sxw曪֘ Q*7MB;"rv$w }Tb>)D'NI|ȄB!KNNu?KC_|:]TD݉{!vAvb=AEpp0,Y")aÆ x7|_Xb?~Lbןe˖u^ 7;q m1 6  CjjR֌711i iJ JϪ*@@lݎiLQOZ\q.Df݊!%DV^ [[[ub"00Q#D='bBs={XbΜ9///dee)\W"૯BΝѧOf֫0k_e[BqE1ߛ=:\\a[U;K000P!ĉzN(SLuͲ'OɓeǏQFaȐ!شiS6.\Dҷ)(㻰1QME82N&Ă`"*M+΅z5k,̚5KaBHCY NBn CXXXm ߿7nocH$NO&-zzs"ֻ =njF!uS!!Ʀޤ1((+WĻヒUV56LDn:B&*jM4&_ ޽|8vB@TUmBUF !Y]ÒA=*6p@xxxw000@LL akk+7Ν;?~RXZZbXvܐ۷o{κu(!$DjVlX܄7bގ0f_4rTHh ! 4dT>Çx n߾]vպpСwZv!48ttPw(J>%u? <0 ~_luFQ8:Vw(jǏ+фQW[?"ʾWϜ9WWW0ƚ)mժUXx J*e=V8Nݡ4HKC"aacp팥"WH3ho^t 7n\˟>{[ZWVXkaرr튊$6`ƌprr|PͲe4 %V)W ] u,,qCX뮮X}f5=Twh sU`ňăNqc '@XX"""=|_yy9̰fx%cccoi8jPBHirŹm>FJ.ϻ tQ9vC(O?wK/s}k 8q=z4ݻWÇaooCCCL6 bXԩSDBJJ>l `޼yxrooZ=/&Lܹse+**b t :::pttD`` 2331|pg|z Lx<>|8D"q%c 7oFΝ! _~Es=W^yC߾}q#[SL-S&P899A(G~ #G|HsbŊ ׿w^{5 233e˕y/cƍ5k 0|7P̟?_33gL0۶mLLL{ضؾ};^unܸq;Q?J !R^i^Hk88Ȥ#8?<`KO-E,uFT=OpqqA׮]1c 8puJKK駟ÈFaa!M&&55'N@hh(BBB-[Ȗb,]W^ٳg1a„F#F[nt.^???~_µk___m? ׯǖ-[?{iToয়~ݻCWW:u%:)))>vѨsc刋#OjQq߾}ǒ%Ko G,+|ॗ^¹s1c`1&&ظq#q) 2D~Cqݽ{&M0o<\ѻwZ9C@@nܸǏ׹nee%|}}a`` . ::zzz=z4*++Ӷmwwwb͚5(--ѣall;v aaaXhzΝCzz:qA5%2{Ν;/-҄i***VQQPiV+OdV;&-wbKOVwHD>p.@%%% JJJmW]]**+T>UWWбzyy]v1dfff,""B<<GL$K.͟7o1cFq(Kn_cаό2qZuMnʕ+<õH]'''VUU qáCKXB!;}4ccٱI&ɵٷo366feeey\cgfr)SiӦ{O+?fDZZG׸͇z !R8C]o>$,LzNFh-U3++QKSeuz&˗/cԩ>)S4K!CCC$$$A(^[ZZ"7L>}:tۃFPǧeiii۷\eii Xзo_ P:F\2=Gů#Gl:tlhjnd_z%ٺ b1-[WWWAOOG [[[c̙8rʔ+11;e~.eeeѩw+ֲׯ#%%E.&ccc#--}?W^rѳgOؼP]]$<7779~@ '-=*!ַQ[zjg/& +=+,Sܦ ZC@*fvׁPUU%Kjhkkc{mkjjʽ8N6^~ecBuu5PQ9}QOZst xzPƩ9R}ދgee%L[[;?Kccҥ8s mۆ.]@ `ҤIuuuqU㯿ºu~z\rE =zTrZ ׯKݻ79R]ز 5D"sy=|(ǯz !R^i^!|VAX:`)~e<(ꍭES=o*:t_~%&+++[YY)W%)) puuUj>Drr2>cx{{ >=z*TRK.ą r ܔއ oym155eqMݻ 5N.9jB[[ܹdmm {ј={6Ə777*0|plٲqqq}6Ξ=T\...|.^\Yhx<v!w~===SSZq|y{>...Ҋ|>NNN n޼ --F}6H󣄐*MƧi51TDN"Qv:`)8vſ%M'Gʜ>CYY\јv?=}u֭[r")) \[[[̜9sʼn'=zT8s ssscH؉'dm*W.lРAL :1cb} gfffL ٺuse<effm޼1}}}6grJH$l…ĄYXX>M0A.rtRfmmttt# -߸q#d|>_^XXsx<mx<@cܹ0mmmfnnƌΟ?_9W=|||H$blϞ=[V$** 63HٱcP\!!!ё 6tPYEEe>|ȄB!KNEejTUU1777eNN={=ϟϊcʽ B( IDATَ;jvMÄB!311a ,+4{l6au}]2qMOcٙuOEeZ1NКI$hiioBڪrl=tOa!"3^'OŘcjxIQQR=Vb=AEpp0,Y")aÆ x7|_Xb?~Lbןe˖jJlw]62JiuJ󠯭O380a~ fwCޥ jVsޛGT#>> [z5lmmFQZZ:AҲPQBH^lϋ׿Ǵ_Ag`Fp2i?9%9*irss]fffx8~x3FLH'΃zZ^6v~`y<nG,k>IU#VC !E=M8lذvvvr ]g͚5x }+OP4yFtcw 8t C"8 K]_1t *Bԋ~6ѣGS7o޽{n:]##MaI|Cl`|1 ~t!)!Ox7]t3놱]bDg|*q}u)'4(!lB%%% ǤI0h * O㽾὾A\!ggꟘ<{|C>>nbbbm6xyyի} >C|%剩%ga,0Ɛ3 (,W'/ AUohkh;lilQ 0"RC&N!N3±zʽOKO `*Յ6υ|m>"!#(*CEERm9'OɓeǏQFaȐ!شi#[yfTVVbժU 6.4&BuAq\L]b#ND!(.cmU}vQLK\!FBcuB`$0 /|R;Q̌Ķ/Bl`@߱?z{jXni.ZUw9* ooq8::ֹ CXXl^FFn݊oF%Ik{AH[ I! m8C]ХCr@ڳ}7wω uio%`*2F:xe28\{]ėămzp2vGU%OG=mLUU\ږeRAAAJTɲk׮Eǎ1ddff߿Cff&:u7犆[Jm"MuBTd ?g?9Y5p% d]A@DJ iS:Y􄭁mI_rNC)Kais??O/GQy-i!M{Y $>\q..lذׯWwǨeӧJJJFdddǁ1#+ܮD"b145C=-s~3 iUs\ˉC܃8@yA:9z(\;?_Sݡft>޿*H)Ɂ LͬldЉ05N?jPZ_vYgh\BHzqjPD~Shh(bbbmڴ rn޼5k`Ŋ0`@nԤ iK6L^V˪lc wȒ?Sg>GQE'S88aJRzK=K^r|I67soʦȹ*V%$ 664\2ӢPA`` lmm 8ֺ`O>?~|sMHAe`kh [C[wwc HO@B^N;\ }m}Yh.:uP˛ӐQRXhvC1nlc ٲ$1A,~# P&)C8;NNMZP((ZEk/ZԩSӧO?>֮] Sӆ{5Lby<*(CMzZH>ݖ%=w=LCVq,t-HZAZF:FM{88*.i85+C~i> e)#bRB)CK'5mSyDhBj i'|O>:t(T!m =4D]ո\"<| >LE4=JÅ( ݆6:tB'N5fֳ~ i0ur y q0Td N^r˪p]>LGq6"2#ۅ_K]DC{ڢ~G٤NAK8 ;; B$J !J^iit4tl gZ˪Y5rJrp22q˲ׅO ag%K`k +=+76iL)HAWy3]K%(̐%{!%OOzְԳl1rwA'uAHD !!U+̓ mRzױ_mJ*Jp.22]ldeR%%Ӕ%:((Fg|D=j jZoEJ{!8 >% Td ] Օm$0jNtҧ@ !!UzE=DmttUMM]mSͪ_l/lғE!o1发#)ɑ?>/g3"eTj a,4D^&4RUTYwU6T%-u"*YY֧\<3DfԛMZ5`ckCB 4((+@AuT]ܲ|H$0P}kmH:{LGzqℤ(!$5٩BZ`$0Z1D>)DQy'E(*/B%0txx5QBHQBHi5KPBHqtt [ت;B-"%=J==B!DEH.Hf㟍UY71S"7S!Buh8E76ĸZbL@ێ ;TA4BHۤ/qIB a~ B!֍n σ!$B!vBB!Bi(!$B!vBB!Bi(!$B!PcHB ՚|>rsskH$O@ VCB!RHAhaÆ ohh(cǎťK[oGxoʪ&B!76ѣGSa/ϟDž ЫWfB!Bhh*))Auuucعs'&N^z eee!!B!= 00l0C(rm㑝ݻ߆H$H$BϞ= !B! U1P9sm八W@:l~-cO1f\rݺuSB!V12JBcP6`ɘsD"QAT2ϯ5/441113fl.ƎhÄDGGcԨQگf %B!- Wx KǨJ9::{bbbkkk\|Y/!!/^ vڅj\za---TTTB!aNYTO׸͇ԩSӧO?>֮][k "##b lڴ <>>>ϕJ !BƣV=!B!yGg67!$BB!BH;E !!B!SB!BH;E !!B!m!E !!B!mQQxB!BH;E !!B!SB!BH;E !!B!SB!F0PQ8B!p*(!$B!vBB!Bi(!$B!vB ǫ5|ʵea޽,,,0vX\xQMB!B uq 6Nne˖a9s&.\BݻCEtt4z݌QB!֎12J&2zhxzzֻ {Ŕ)S$ꫯs)!$B!J82J6TWW׹L" fffrMMM  #DB!BH;F a`aذaׇP(RSS_~ ‘#Gp]\~gφ1z-5EO!Bڂr`Nw_h4) U1P9sm八WZǔ)SuQQQ?$B!DYc+WÀ#- z1PQQT[mmz]pC g\|0004dTH'Pz~!,,L6 #F!v ???̟?OFZZF*HꝪB!փA׳y3a4> H$j _UUU aI! 8;;UURr1227o988.\hT"e֭C@@@G!BZ*7ow~~| 89x}S6l؀׫7B "9sJSSS뜜pWgD"Aeee/N#B!&Nj/~ػX㫱f|Gu.H$ ;]vUB!*1n;{6p<0kt)e>c'Z9*K!Bq#&s?!~Iuʀ3˛6ƠkC=B!!%Cu1_?ib8dHGZ&&B!HIzl:]Ӧ99Mi(!$B!HMukz*-0k@#~FZ9J !Bi'@NϷ_Hrj"-%B! `mB55>QFZ.J !Bi$@ǎ/KKiR@|#-%B! kΠA@@0q"oyg%B! бjx1 ̙4S˯\Gw%B! `emq@r2yj 1,hV6ͅBB!BDzDW_օ17㝐wpb ״;$2B!b>?DYZqW;;CI w/٨3$wDD=B!rҤH7  l |9qXu' >}qFǩ.Hh60ݻ7=Z]tt4 HKKKC,!bB!/ҋwoĉi **cego-줤;=|CC0q?~?zU}&yaԨQؼy3|>p]v1b\]]}vܻw_|RSS !4o_̯q'Ҋ{v"!-ާJ{/]4568tx%uG WǎHXZJ+T?O9o{ (B{| ۮ^:t@DDD"o60bĈB.?\ŋDD!ţG$l*o_`oo8 >ٳG{4l}@Sy ˖-L0رcV^ H2#[NQK+=}'*4dC#!5$Oƭn`B3VVM!Bt4*QL8wN٩&1mcґ0FjcSGn_&|{[D̎# PHx{7\8$$$؅iӦԩSv#kأ IDAT @(//'OȖ+K"Իは14b鳑j/866JhseyptƭqҡopܼIB$Żpb&Osv6vKƛ8زE|Shۏ93w*TWW׹Lѵ-Q-JpvvFPPRmk~ZYY!55u633c =gu}X5+ߧ :bXu1\uIt;K>攟/s>д^ OqH$aX=ۯY}Q$:)k8 C`` :t *#Fr8x b1LҨjjj;pQDDH[PF>ҋ5!E8رC:dnHi Bڋ,i(]߶ h-^A:z(>JڇxgE~ww*M &lu @] b:#=-z45Ϟ Jo00PSD!!l'NŋqI|7|S  `x뭷pQ5ENHt wpΟYT:lPZ H <^^*٪.!-YMBI_A=Qw0 r ;[K1F1TS睧I$hiiIbx[?'Ge>xsuԐm#w m4lܮ0) s3܋mfnM72 j:nO9\t^<9׹}}>X\}ɲNj!7nwj5M@l,TvRgH1S_zWB3Cӧר0a:VlJh+\ ]vp5{ٹXNN_r#!%%5k20ko\c˗EB/u[cNJ$\5s-t;¼ Mڵ" !x˨Xط{OlDM |uC$$\]Tc gC2*q5?}76cktz\.8HB"2kq~Y7G^~D1DC6q9sD {,"c_7LO{b{b92ƠR-oý;fw]d@hޱ# !˗bZ\9x}jbXx&! */7oxZig ڊ8b2j~U QQJJ-#)YtI?/Zn8sF'2+ o̞jT:Jٲ@oˆ=0VB]6m([K8XZOR0!$"?@߯L̜ ̘t$Dr6-8`Z,1U,//1XTPv^މÑGT(WA/Eu>]T 7B> B"2kiy]N5;Y}pv6qI@ޜ#K͘20~}7Expxz0!$"e E^J>Sn#͛QD{ݾ T"]aH W71ޱ_!媌jU[`\qź }QQjVVܹb*??1 SAL5!8> t\*uDDL3dc,_|k!22{+{4d *-[Ė-[4gdd`ҥ֭\\\`oo-Z`Ȗb:2FAb.֮]lHSpժ : DGKs?E7oܺeݿ=dNAL.;$Э[7X[[#22Cpu67>ĉ1|ԩScƌA``TS!|QZC@.!7x1MgSaʔEO? =:*"OCH:۶XD*[1 4unl:&)) cǎEpp0,XPv8w4hynĈ ĺu:u#d"e++ѐM4 XL z//`Y3q];"Kv玘Chʕ.|6LnZxGNL;*A B=[l1}t@ZZZ988h%97\DۦCmӧ1,Jy%hFG[oRGEC 4Yo7?Qu1t'|{DFqޠbBgpwwΝ; ;;;888 ,,Lh^ٜ~I!|f tXpPј!˔*z lldDѰ=tH=|h~7U*V7UfƍgL*LRKŨFaUUpTf2&zk׮wEPPn݊={"""SN-* _|ԩVZ)b"RC"__y@5ړ1džln..bpO 8(`0 Ah91¥Q9R\?ax@Ȭ1!,ZӧOuzx RSS1c L6 }7|ݻcѢE!/^ė_~ )-CdmmDw+k'o-d9̹!"L;$%M?,uTd)s l,^ %]e*I܃]E/J*5u)akk˗/lЀ :u*c}Xz5"""Э[bǪR |dqA4*7/jUaC`.n)̽!3}XwmHq Ү|zKQ*߂닿naI <_lafeeچ%`Bcݺu:m[jU k[*UV֭äI0fLqqW ŧ~k*f zZ5khSՈBJyGǎ߾q |(7)*M k\ -답=:2ҷtiSaCؿFRGEEjkz ce%tH݁Ufܼ)䐡/p i: !ꗣP( mÒq0!3___ 22-[nݺ!..g\׮]C޽!ѷo_l޼њ_%ذvt=wyY j\RLXin#eVźO>=:22wrvض X\ ݵ+@Li?EcBh;v|&Lݻw@6W^ǏcbȐ!Z۷KxD/%6 Z ϛL$Q>$QARe }#EJ !%Km8:/FDKn#.AAbT_,?CBjb>m+{tk^3BtdnYFX9QY3` =,Q2-L#˗ctƍcHyY9;H !\e8j/\;޽bdZ=ɏԑ&++1'y ,L1Y-#CT0!,2~@FLB"2;*XQ1Qo_Qcf 3Sxސ )ɓE Gh(pёԘ߾+PϡT#u(dIIllD5fo/z9=S3G4H:lL&[9pػWׯW2D!Q=zH0&Ddv (Zrb݉bcĐQ::7֯ kv:t >::2&~vѽE׺]%B"2;lN&ڷN$tm ] Ix˪Z1C.V|}6*p2hIOJM­ǷX] ń%S(ԑ CQ +xꏍ 0l( Z>>b,{ Kl+_juJPȄ1!$")[V+ \-D;wtbBhҥF'q0r#L@:ѦZ DŽ& \.8<();rH5, UD!3E%2o[|UpQ*B"2;r }nn^B #cBhD!;9G$<²墈RTHnj~E{{K7oJ%銿uqҧnBuJW$LB"2;lO͚)"1w1., Ro=hnTmbꉞE83T4 i>%Bٿ?|||PBۣe˖زeK?zUT\.Ƕmی)aQiԫL, ќ>-ֆ;W$z˗?nƍ57#@Vԑҋ%큖-[[6o!, SDZU!sjϞ=i۲e_ =*Oq^ 4Tԩc9CNSS(VՔ]􇌆O?5JUhB=CLL ֬Y3gsPR%xzz>3ܿ_ΝChh(BBBжmb} LJʕF+Ѱ7 IDAT~yy2+"1IsժG-Qad2Qٳ/؞?/֯/z_L7OJo2Tv*Ԓ:") Nk2L>>>Ľ{дiSl߾qqqXr&i~WVѪU+ء%]dbi LE:>^,9AD:kݺ@ժ/uK샨$*T7rTwO\.ǯwe]Đ֮ X[Kr/,(el2 'ʕ)'u8d&;0uTlڴ ׯGL~t3Hd>KD2@z\/W?b:uZ'~E$>9:GDcq&$YhBH"kx~-ת%ǚ5ſL͛GIf;HCot2* xE<Dw"cPEDNr5)I,-pQC4sĜW"ߏ8ړ'MHII?IIpSbX"zkժVhO=w:6񰇐 dxxy}]׮=O/_sݶ-A2 ?/̔Z-i$9'{Wuqy ">/eʼutu-w/udFYaQ25VV'Z8̅L&G\YHӋT*mqMHNK<,:8r''񵰇]txuÜwis-tQ0Ȍ0!$"rLd.85J+P &z{?;b^H +U*V|5s'9GswMر;tDŽ̆Z-B "2mJhZ[twׇ"9۷Á- APՎ(IwLl'"*=CUI?CHaw.!xDDDy!&-W-BB"2W\=xdk;-%V⽧^bYz)E?4jTfq( R!1$^w˼_c_>/wl""jAxL0jdT*3XYYIKcx8B1!$"""""PL,B"""""" ń@TPhٲ%lْg;JYfA3|MܼySdeei}%*HVVyNxxPqz!]k<2jQQQ B׮]ѻwo( \t ժUÄ 4effG1bxxx %%M yt(J֐Efպ8xxPqz!]k<\Pϒ0vXcn`:tG"$"""""8dTϖ-[lL>vj/F߾}鉬,ddd3T"""""pL,66عs'\]]aggѹ.\͛7ѤI9JJM6ů* Q=KHHBヒxxx`۶m@VV>3v6UVAVc֬Yѣ;ƍKy*DDDDDT1!,ZƳgtڶlٲ'O@VcΜ9裏}h"L2JOqs>D:mOT**^/F&prr!COOO$&&"99j< LIܚ4i+++$''͛7uNJ%_rd|a!zj5yꙟbbbf̜9PR%z˗GϞ=sN\|#..Gxrvvv9""""": Ss`޽8pдiSl߾Xr%5^>j,Y8yf*!0!4tL:6mÇQ~}L4)O8}4BBBpQr`ܹ[%aBHDDDDDdR@DDDDDD`BHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD !bBHDDDDDdY(&DDDDDD ߿>>>PѲeKlٲEk;B.ySȒ:(** AAAڵ+"##P(p%\~]k;LWWW̞=jZ󼋋C&""""" $SKKJJBÆ 1j(,Xm;uٳF9ճe˖!;;ӧO,#"""""'&z wwwܹ_g˗T*aggU",, DNDDDDDs,!! .BBBm۶!""YYY4ۺshҤ w!"" ظqgADDDDDs V3-[,L2P՘3g>#={q(3j(^GE֭_ B|8SN0aA]6/^ӶCABCC{轥Q۶m}v 0!$"""|N{q/^uֽdTsq9R:ܹs/ԩS5 6 rsvǎrҥKQvmؠM68vSNEHHDQqHDDDDfF=ʕ+gc^^^^^9L̙3=ھ$Igqefflߛ6mĉ1}t:u M6Enp}6=zǏk.A\B\ytYk{aÆC=pkE8ܹ*Tƍ5ϭ^ 6 6le˖i^ɓq}X[[}Fggg?琟+]t3f͚U> oooڢf͚Fzzu\~A=+V_ ͛ѱcGbÆ [q(Wj׮ hvڈD`` QfMZx.\QFaȐ!pwwakkkjܳgO/B""""*+n߾[n8uСC6iii?>q!\v }N߰aqFH\xfBXX@@@@#&&ժUCvCV{8z(6l؀x|P(%>\p-[BYdܼy3_r=z;#saӦM8rƎ[-ɓ'?D||Rd2hLyuVDDD`yZTݺu1|t K,TR...r  P}}}1j(ڵ 7nСC 7n@bb".\P>###ѬY<=~-Z Pv[niOHH*:_A8rsFzJ\ E޽X|Z۞;w͛7/qxQΝ;C"$$ 4Y[[x1nnn8p:u2e`…ӧ#88޽;>}Ǐ#55~!1xb۷G~0|ŋڵkb)raMƍ# @) mƍCPPJ%Ο?kΝ;/D6mI&[_/ĉѺukDDDqqqXt)/_^s1a 6 hݺ5.\<*ϕUFH'N@FF"""yׯ|^zELL >c@`` V^(xxxcǎX~}޴={Zk>l۶ ZѨQ# ++AAA:-0c dggkԤIoHHH7ZhpTVM ooo 41lmm =Oh޼96oތM6I&GDD\=߿?͛04ogϞŞ={&99GËdvISTƳg&"""饥||"֦M?~~~Rb&M{#\k>>PѲeKlٲ%v* fB `ccggg書yQ% tP(t_]ff&z#FRRRѣGpqq 0!Գ$;X`A.X‘#Giճe˖!;;ӧO JGVc۷/<== cJDDDDD ;w+0 pM4i#GRRDӦM믿JwDDDD%~zTXm}jԩ&L`иj׮ŋ봭.PP{%zoiԶm[l߾]0HL,!!׮]ûヒ lݺ={DDDN oн{w,ZH3ɓ' !C`߾}˃ިtpp@r zիW իW<'`cc9sѣGZۗ$,L;-- ͚5W_}U?Ʈ] Blmmqe `ZGFFN:V5QWWWkqqqŊUR*πr9 rѹsg݋ =z;w4sNTP7nSlٲGFǎꫯns)r$`BX'''.<==dԪUK|rr2d2M4͛t++|_3qDDDBV#3[Ce\.G*UO>E޽iӦiĊ+4mcb̙EԩSO?i%r?>|}}5k|r <qxyy6n|o>?~/^Dݺu@Vsv~/5kiӦ6{l 4ƍԩS_|:v˗ںc6~x['N.]`ʔ)777?V7 … qN]\\Ý<*&z燘YFCV#** *UҬ7X IDAT|y;w˗Q^=@||MqXYYy΄u .M}+~ÇGZZJVw-[޽{yeNOOǕ+W YYYPrx ///\zGʕ+=֙3gPzuM2CβaS3g|||G_oV\0̫W~7Zk%fh"j͍&Mhm\.lllOlٲnv꙯/|||{iӦؾ};rJ}֬YEN@Vcɒ%\rDN 'Wm.u(D:{ иJcC!3PF^Ϧ>>)""ñcndhѢN<5kh%49V^֭[kGPh`,Y6l5jrj8Pre!mڷonݺaҤI6lkOiР<|gܹs5s U齁Cd(9=pc T&u8dd2{i֭ݻ ,ubС ,Y@Tpqq+W^QFa׮]ظq#Zq榷p…Bf͚kѢ.\ڵk^GGGܺuK}BBV ʥ 4#G;|0իgJΝC漉kآ2[[[,X O4k {1BTDW^Xq|o餬Bd']Dܹs:t(BBBРAMMkk. SN(S .\>}:aooݻӧ8~8RSS.___ŋoooo憋/B.k׮%]&K.8|0z]v7F@@VS@k۶-ƍ (J?$ǝ;wƗ_~6m 33&M30^̉'uֈtR,_D#-- c8s *UWWWv*ϕFl ðfkݓ:"F=zwe_;,u8D%v ddd ""...G~J{իX?bՈ:vM ٳg`cmЪU+ 85BHHK/ 3f@vvVLM4ox{{EGjG̟?ƠAֶ͛cشi4ipDDD`ǏyL&ĉѢE C8z(^υ%S5LJ5={ɸT >s%B^ 2%\Sb y :8\QHKKCbX:iǏԡI&!55Ȣ>l{l!_Ev'qDDE˹nw)"ADhʕ!I%GHzLȬ Ce{}IQd2W;@(IGꐈH<<< u&c^WƋ#܇5fHQrzEr F8RDDDJDwLoIJp "* 4vōo t Ll5+DO$otUz"VreaZ|c$&IuȬPW klBTLZQr}v\a0!$RK.C.|'u8dhM;$)@TZ+{nKwSꇏꪭk[w :2Ll.q:{wE}q$ $$ !.DBUn*VHa"""J ^x"AN(h [ HB&& l6˼<6ddΌ ].4<6ݳI33[@iu}I}WUJFK*We5:!j;#> IB=-:ެY[g顏3YΕEڝ6IjR>?(e:3˹:+Ee.wpu|rH7׾Y놮Ӹi.nF 1ixSSے+sʩ2uuQݾvK<^;@.RP>^%Mh"J-@\mOKmgVubW9Uj!\Up؇Vc[˩:lzcz-9x0#k}1r@r"=)XC(*=o^4Y[7#+8[я$)<0\YѡjvPQ1QfMgk&iL1 u;߽w 7|]ج?d=~|^.]'r6oy&(3W u.G{@5ÿ*Xr pچ$SN0|Z~LҸ雳TX MsN]/*d4a9۸qbbbn:ժUK Oa<;!ɼqCn]|nEwϯfwto}1 e}r՚[K?gVh-(_rvA;vL#FШQ[oW^z5eʔ_l2EFFK.T-y*(Fբ~`6W朚F_UzQ,w ٴ٨޿W CꡏR?qǺ}-eaύimu㺿깬ww[m\l6z4ݣwas|^s=9%ORR-+Ia5k&M|^3g(00<ƍϗFWJ见5yE?sm}6٨:7Q=tK[ԱVGUfk+_׻<׊+:%u;gÞ\bnx1*s>;\YW%'ḱ5zwOU/z5쥮UŷkL[Ypx[w{GaC'D)Vמ K[gtEGG_iii7U-Q9.qU T6AvƴLg>?Z9h[CTXZGVj\7Qٳ]fӿZ.GC-(=;]n젎:]vjQ]2,mCzF m9EPn|TSjMԱVGuA-"Z(Zj*VF=ճQO]ʾ~L}4RZVZVh!/u:)JYY@X-^TFFFJ,<<\aܹsE7PƍժUumӊ}n5ɩ}Cmx\Ae^{IRFv<%mӎ;_?}UjyDsEFahC5m UJ~Ҋ Խսa5i۪[WUjLiӦu խ[7o'dTzc;wTbb~髮555U֡CJ9A*%KP%]tUZ7R*TJMZ@S/' \r44||աVu!o^Fv[;Oɉ|t^5%&'bE Uj**8JQAQy_GF(?T Sw`-lROBO$Ir8u0٫O?Tbr! T/k R v- %s3{^}Zxt>I~H}XƼmfEGz@uUZfqj\F%I:rB{NўS{9Ikxqպ6RFV:n =r6h XB-%qqq U6m fl2dUۜf,Ye2:yᤎW$O9)u)SZVUUOVQo}}|oyk>z:9_/_yٽ9;9awX6sn?~aȐ3uYp3,5%x^jJ" HNO:yN 'u(lY%'+9=Y. !! Rw`G1'H^G>:}]mna3Νf;#+}9.k?qS~C1bS-F̛o~LQɉtIpRSJJSOP^{k~! km'o7=.9vQۖ۾J\ţ!bڹngu۹Lg~>w8osG5ikvSOtf_о3Ԭb'o^jfs~NОh7+XpQ޽6mڤQFEZv6nܨ hȑQ͚5U~}mݺr iYi9g]ȼ t!R2R}LJץKpf|.p*;'[g9_*}<([kaM6czޯ.w /\9t.Ft!B^em95+@FsyٽݫAk|#.o3. uQV]QK3f m#aJN/b:D2@iii2eV\d5nXPFJ>paK`233K$Kah֬Y4iRzR||Μ9@IٳgnP׮]ui=+q+CFK/+NJHH$K\ཆ tٳGyQnݺjժzԯ_?3FׯסCϗ֬b'YP~Ng^.2ŋKldd$)**J(|xx йs$I7o ]IaÆjҤnZZs{2uT=ez?Ow!F>|x^ӦM%&&ĉ[n'Nfz6޻ IDAT,eggivtryc\VVV(?$r6h E3 Cqqq U6m$I2 =%/֭[iN09aQ1!,gS׮]5sLjѢ֮]m۶iyu޽,Ynɓ'K/)00P&LpOzUF] --MSLʕ+ƍG)t ͞=[+VБ#G[oUO=7o^uq&\oǭ8B s`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E]dÆ ڵV*Um۶Zzue5m45h@~~~jРf̘!馪X iԨQ4sL9}JJJ*ܰa[oiȑjӦvء\III?`60 wq=9zbcc5f͙3vޭviԩ:uj~XsW_}f͚]q}YYYQffg܉}܊Ðr6onah ) /}h…9rח۝Zlwn(T6RZsx5kΞWww51 nzKNwWSy^- (K~~8N\VV2Z2Z $0hѢya(..NjӦMMOO?BCNۻ؉0E(Uի[r._:*j>popr֯_?uU3gԏ?-Zhڵڶm,XPq4HQQQUJJ^{59rD|AC@=c/ƎuwzseyA79ŵy?^z!={V˖-+47>M8Q3gTƍs2?XaHw!-[ưQxÐnIڻWwWx-swKfaJ+'B%ya%w-o/=R˖FXwwEgq:%//b7=oKP{]QbCcS?IN*=lKл \t挻+iR)5ݕWw22 Un^\,.νTRb{"a m.r+Jf3/}L6H Jܩrl6sС\ !4fCDWz}J'6Թy n+cffx.!C(W/ZP*F W/[l6s>W]MҲTf+'"H_*=+JvEd&N^x<] !m$%$"ARI7N3 W-ǻx!qQPH1;.?/Iٻͅ1+۟;8QZ @Vuoos_MLBeˤc] Pz{KWV-鮻s]I2j}w+'!EԮm^'RpEE[]6M2EvO](P2'+ʠ-fO&x&!cfO˷ߺy[ݕaC{Ӧm̞B4Z) RR*&x!QɼϞ?@u%).bc͋ dao@IJ U*-]*'%$&O2lԾ4v,AK/(t2$ļ-E˖hJ5Hnm^t?CngIsW6zPQzr֥K"'__Bo۶M7| &(55 _y7o\uA+_0aä?YGwWTk9%2uy?KinuKҗ_JԢCH@XΦLK^}UIR= ,W_[ntΝѣGk8p;J<µ5`w9&KSH@~ѯn}| &͚%]X~TF o//tc>`iRilihyoxa9ڵZ` $ 6>BCCyfw}zꩧK/飏>҆ Q>Pev)1Qj@0SyȐP^|b^:~}駥ӧo@eSP@y;#G̿=&%%:*Jy}.6t]ҁRÆR۶ҽ/G ˖-SPP7… ڰan>|j*w XR&潙o7AIzeIsZ6o4ҷߚ;qJ#f;jjؼKN'Y#fKpfaXԮy[oI.P9]짟~҆ tw?o}6mX[-[Ԟ={*T#QĘi?n^FQ#鷿5c qemܩ=|Xo3p3.\h]ЀrjԼ7߱cR.sϙЀҢEҡCwrn!=~\4. *&W]lŊr::uJ6M^tKT?@k֘y );ۼahyyPsa:s GNQjKvI|#ryo4=6mكxtVL]m($D8Qڱ~f/|۶R:f09ܮNtH%+i#y-k2oNvkx0 effj٢ *I˗/Wխ[7= W%.JPyC޽/^퓾JڳwyVdTQxZSʯ_O))RjdR'pn/zg"wQߗ\Ywk^G7P7/89CᦧN"sp@4ϯp;{vyu$wWZt1rCjʜrty.Mm(=~rېP^^nGO>gϚ=ɦMӧ";[^ !!gTPPNW~@! &&F/.ղE \ll6ZaԩS;uꔢTÁ>7fT/Of3͋4iRNE)#ҥgdHGN<^!Fw}Iߗeٲ~_ޫiSsUebN[am2%Ern;&%-w_\{TuN?Jn7GDFJ;a…_Pzz&3<`l~mn؟$TT_a?wnP> /+U|(f}jڴP5{SRRTZ5=Czggee),,L … ,(55UE.c`*ө^ Tfff(uJ(t1\UTQnݴtRוZ{{{;P8aQ12"K.-vh3fcǎ[u})))IsQ=Խ{ 1d Pڵ]vm4yd} ֠A3Y}Irҝ@Xpap!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(a9ҥv{oeׯ_#Gꦛnׯ_9@qN|I JҢ,h/(-q+0 E\O6nܨ3g1cƨO>zw{ZjZncǎpeZ_ZZrp}ʒ233rP^PZ>nrwכ]l2IҰa ̟9sp $aXlԷokԨᦊs]~҆ tw@XBNpQp70 effj˯ k^uV5# >>^]tr6MPtttGю;4~xͽHlZZZv9#++#P J ?ө"ݷG ,ALL/^\e### [ll6ZΕ*w#^ցK``Kh+( J}]iӦRBBͽDYCTvl"MI=a(''G.i=.W_:uKcu%@BYt۷O$)11Qϟ׌3$I-ZP>}*VĐQ0 CkVddvUrK,ш#|{k@V`QB(!XeffjɪYԾ}{mذevޭqƩYf R:u4h 6lŋk׮]M m۶ij޼N>_|Q/^Ν;+I:qZlM0A.\?:uh׮]n4Vs 5nXv]u޽{ >^k֬?"`ٲe:{f̘!IJKKQ" .\ I /0FD["ooB(m6mRrrƎ[<.^u֕_@ 0_}T`~v;s挪U&I:yΞ=mZ]vڳgOE75zh5mڴHƍUJ%%%)&&FAAARƎ I:w,04b}:~V\k„ Xei_iӦrmڴnÜ:uJGFF0 i/*++KSϞ=f9R׈#$V`ѣO׫UV]kْh+(^YƩSp8xVXXeY&==]=HUƍ;杴>Ԇ+PErrN'xBE.C{$]xQ5w\IGeddhzꩧh+Sn]uIWhh֭[3fF;v,m*KHOO\Բ ҥKygΜQ޽իWfI}Іc),,Lƍ+v _ǹ# r :To߮&MHX݊+t})111N&O!Cw*KWfffs%P1dDFFu;/**KB%o])))裏TFrA׆BCC9*kZpƏ'Nѣt%eeeѣ:w~Q`~"h+5o}(11Q֭Sƍ <իk݅^k.ڏE8qBahWիkΝU~}M>I^X瞣N[$st,4?++Ka(;;bmlRaZ/PNNւ effjj߾}Zrrr4p@ܹSofޕg/w]w/sqF%$$hU.ܨYfZv֮]~;ojڴԩ~[#GD{4p@EpBy{{SNh+g%&&|r95o\m+mP͛7͛@ݻj،n&JmРAz5qD5lP/ݻ駟cǎ.n2qD ۷ PaÆI?֭[nЄ t͞=[k֮]ca]t?{ͣ@F8 0@:uҦM[oGͻJ-m[lQ׮]q),,L>c=ZϗD[_~YN8;TV$IǏWpppƼy4n8u]ѣtR=35hРnٲEÆ +4СC裏СC뮻t!5nXںuFݻ7|MԪUӧO믿.ڵӬY@Neggk[$ZtΞ=+ĨK.ڴi cǎiJJJR5$I=>C駟.r=GUTTTa(..NyJHHЇ~(IjԨf͚M67]poT۶m gTTSXCFץ0(I[ =Gqr:VpppCt_n6FDD(662?`^zotX_999(@ !\ |oي#Ix񢼼_n/x4((TVMΝxJHHІ ~z=={v-5$)99Y-GHjժNΜ9;u~m޽{w;vbbbo>lRً٪UrYpֻ(5jHC5{ljJgϞէ~-Z{=z_uKQN~* @:u-evm׼.rq!P7_xI&)&&FwyvޭڵkaÆo˼5WZU .7߬-ZO?￯I҉'}v{e(7MݥX=`QE`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`QB(!X,@E "E`Q5k>ޮ%KUV.y℄(>>?WڵK\^zzwi=p5JԷo_U^]UVUll߯^zHI?^#N^Ի,u9׌a*G9F9 eJb6M'hs9-26鰠rK8_\l۽|}>\2~---XV?Vp={CEEm9at؈W_%6600 d ĉCaZ駟a٘>}:>|,?fy^^矴w^x% ;<Y,شiӀz) `. 3gѹ^d/ry$++'Of8{,Wf̘1X,L’%K>>m,++cܸq8NvϴiӰZiKhFF۷oX{g0 &OLeeyls=]8y$vٶm9& QZZJZZ{/~ע08s XV:SB!|>vu@qq1agyi{1 pܹA/SUUo6dѴh"l6f[QQ%%%\$n^yy֮],"""""CV LIIRSS r0?3\p_իWSQQACC uuu۷?բ &l۶^zcǎ?J0 .OPXXH[[cƍ9r*oNCCMMM?~+cbb29rI&b[n%33͛7 ٿ9Ν;$&&RZZ:wݬXݎ^jjjn▖ _t)GڵkZ"""""rk21c6mbĉL:Ç?55BV+AVZf#==m# ;;'|' wAvvCz{⪫y77wD￟8-[f72jkkVpp88GTW,_JM0N'===,"""""wְajj*o6NҥK̛7|:::"?~:!!Obb⠫psNfΜIJJ v%f}Ο?XMt\au̘1x|~~?K.*))[IKKc͚5G<`@vׯ_7,汱c7Z[[pfcm67nˣrO]wŕ+W^/[nHOxRgg'ŋö;,##k׮baΜ9?qh?U'Np\&,KX0@EE}yn7.˼Gj̙1TݚEDDDDd +tttPZZӧի;潅kƌ=zlB[[[ľ}ƍ૯2nZ9{ٶb /~:466rq ̙3\zbbbjEyy9\t]aPRRo1q˖-o^.^Hqq1/&111☄^|E6l`TUUqe:;;ټy3wIѨYDDDDDF߰a\\Ν#//͆رc8pz u|ĉlٲ%Kt:fԩN2x;v,555,Z<OYYpgt:9x 1餧ϛ[3W\dҤI<#$''y<]]]йsܹsSTT_`….ܿuuu~)999|G¬YHOOO>=ٳŋw^>Sp\|>|>>k;{Sg(D)B(@("""""EDDDDDHR R """"""QJPDDDDD$J)D)B(@("""""EDDDDDHR R """"""QJPDDDDD$J)D)B(?.lCVIENDB`PyNN-0.10.0/doc/images/examples/tsodyksmarkram_nest_20170505-150340.png000066400000000000000000010310611415343567000246050ustar00rootroot00000000000000PNG  IHDR(^sBIT|d pHYs&? IDATxwXW6{^%*Xɣ&X>-5(=F`IXc7V{CAcܥ*..3gΜ=[;HB""""""9rIWJB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀHG1 $"""""Q tB""""""ŀ_+W.j(<_}\\\```===;wU(e˗//cϟ?jՂ)dY{,ˈWAe'ϼ%AehJBeL46HDޒe9?===:tY$I**h9r$.\:u`ر8q"ʗ/N ,y닳$I%rW^C!!! Eƍ9_my"xGzT}_ z$!44B۝Ba۶mpss͛yūChҤ k(ImۆrqO1c}y=ߗ/_iց(7lDa@Ho@w>");< XXXcǽ{XA{Lmy~L}.bccaeeܾ}[-555jԀ_aÆ1w8F'OD@@ʖ- kkkt wĠk׮)|}}SɊٳQF bŊ6l?^kj* &&&Y&L 8...022=:vSN)@_8p@ݕ7?ckΟ?nݺưGLkJAà :kzlٲ޽{_}\]]ajj 4j'O.U ,ؿ?ZqӦM/sssAX`Aً/0c 4l@͚51d<~X ʕ+C$K՟edd`… ccc;wTz}#66VQ^.XׯGF`fftMpĉhժ,--QL#**]ٳaee333x{{ѣZ#|055Gǻ&''cذaX"LLLPF ̙3YYY9 L6 0776mիWzQQQCٲeaiiaff|'믿e6#1իW9Pzu!66{nȲ`e|Oܿ?|||u+Wh6d|]6ʔ)KKKTZ]vӧ5E!z!ggg,]:uB0п\v aaahѢz 6য়~`hh/_~h]?ӧa\x6mBfPF qqq@Vf9tP>|]tAٲek.̝;aߧObŊԩʖ-(|7طo٣>> 3z~.rsy4j,]vpqqgp ㏘2e LMMM6ݻ] ~rY[hnhѢ:vT\t aaa?~|VH~ 0fqprrBRRۇ!C ::˖-Sooo;wիWGpp0 qMcǎSka )) s΅;>6www@BB ///j vvv>n݊@/`3g$IBHH:UW~lݺڵ7;5kܹs8s :t[FVV:vʕ+Qp_}/9qf̘Mo߾3gΠZjO]cIرcB IF{t={===1`EuZjb[pp$IX[[iӦ)}Be1|Ez޽$INܾ}[cǎBe1ydEpqqQoB$ѩS'9iժeY=*HIIQl$I|!>|eYlݺUc[bbqhheYwZVAx{{ YnњޫW/!˲8~"[nBe1p@}RRRijgԏ{-dYqqqX!I R[*iiiZϳgć~(lll˗/۴촵P!I(Sxb[݅,bݺu괬,QjU!˲صk"%KԯݛdZ׷o_!˲>|"ɓ@Ȳc~Ӕ)S$IsΊXamm-dYxNUS'Ξ=-Zg˖-B$HW=抲T]& b۾}С:mBe`RSSm/))IȲ,<==5 !DBBqik[n$i !}eYy?#!˲~:Mj`` ߯?f!˲5k"m^i+ϟ$;jϑj3DR}@gmm˗/ .|:v:uD*Ui>H#C$Ir"++K-..NH$HW}2eFy111BOOOT\Y˲044IIIdff [[[ѨQ\W!ܹ#$IիWۿ !˲Xb"={̛W@ܞÂB$駟[@'O Iķ~N{NNN"5552: ٳ,Ê 'LB$1rHuZddd?eee 77B7[h?##C *D2eo@XjUյ>yD0={VH$Fqo}*6!ck lbСBe}v?Sa``,X $I?*Ϟ=$f͚OAU@8nܸ<>yD:u(ү^>gϞeݺuK ߶ {oe{1eadd5k֠AYUc ߔ *VX<{ Z}Ν;;;;̙3GcFFF|Sh޼b /V\ӧO?ϳ|g7oڷoN:^^^^_0aADEEA$uY3gbǎAJJz$I19'N@VVZhK.a̙8|0߿/_Y!IfŊ@1GֳwA^NӦMq]\r^rhѢI5$997oDJ⢵0Eډ' I4PS>ӼysơCpiEIаaCDEEx=f=zL\v h׮Ǝ`Νhݺ5PfM~h۶-Ocǎh޼95jTQI>NNN>}:N<@xyy]c5t+V **J͒%K Ii=F~K05kVBll,ڷof͚A@ѵ B9S95k_k̛7_5K2ehM\)`|򈏏GRRRӧO!Ǐs]:?cQTtrZrAgYiذ!"##1eDDD`ʕB 'ND׮]]VaÂPoQ/MRR4h8xzzW^>1w\j-["33-[Daii Yql޼YQUlY4k3Ҥ$Hkjo7zd”'NhO$ŏ*G[Ӻ]Ud@Jp bΝظq#X"F[v-f̘?C=:uw}{{]IA ;v 'NĖ-[{n!`kk`:|r,Y7Fzz:/_{{{X`Im zo[QM5i$__5@^0m4(6C>a@H:gڴi8rpEL6 cƌQy1,X\S?(>|P1yʃh2UT<<<VP:TVjԨlق !!!~&H$Ty={Ÿ777Z YYY8{,݋ `С077GPPw&L &ݻ8tñrJ8Na6?˗o>4i;PO.m6S9'Fp\]]1qD9rD/&&YYYsbbbھhܺu o߆sw jŋΝjVHS۷$^zou иqcb޼yB(~ V^N7n !vؑ}ޕ7oB$tAcہ4<==Sտxy&vVUy~ #{[B"7^Rzu̙3Zٿg477GժUq]ܺuKkYoRÇڮqd 2/]!;Yၑ#G?6mҚ ݺuî]PjUDFFe|S50p@޽K,[ ?3dYF߾} }Ly=T\AAA8p!fJ;3ѭ[7cհÚ5kݻ+&2o?۷/^zU,uB`޼y.4B1Bk96lUbbujG eʔ]pY4l#G̳ :u -B*UкukTT u:>}`ѢEyxb4k FݻѠAhzzzq̙طo7oŋرclllЯ_?u^ȲϟW4nܸB=nnnprrի>$gϞ  n:nݻwWy%.]'}Naf!Co>TV ׯ_ǟ;j P.\/bؿ?Zn CCC`غu։ 5Çzzzh߾=>C :v킗t2e ::7:wuiٲeK-Z֭?/6mڠ]vر#Tsa޽ Ď;vgqL:Ν'NYfw֮]?@oM6!""C֭S[}ֲ.\7n`ĉXb5kr޽{|2j*" #F;Pn]\~7n ~]777FZWWWddd >>=.]Xb,Yf͚J*͛7uVcС^ڵkN:X"={?>Đ!Cy .={0rH4iǝ;wyfi111A^0|H9{bn߶rYt 6D5ǏcxF[TmS<{-9S-Z;(:t dYsUxB?^TVMJ*-e8p@Ȳ,&MQ~lleYciHSM-1{lQF abb"*T &?QR*۶mm۶ʕFFFA4jHL0A\zU>ܻwO 0@8;; ###agg':t }nٳgӧU([077իWCw!LMM,ˊ祠ϡBDGG ???QlY]۲*o+WFFFV4nXc=̐!˲طo_INuB˗/ۋr sssѠA믿RSSԩSEݺujÆ ?VݻXJB[ !͛7Ev턭zf~۶mM4JÇxSRRĀDŊe_A[{myܮEVcq< IDATZQQQbРABeki.KuC,텩˗/ϳ@+˾KTTeʔ" @5#GЬY3C AJJ"O\\dYڵk\_(Nz† U"<<8p6m~B5jlllh"x{{ԩSR:3g燚5kbΜ9sf͚7n`۶mew&M&"""""ʁ$%]pq4n=BBBiiiQ\9DFFڵkѵkWDDDO??puuE`` V\sի033,]î]B|w6lXQ6Qt롯}ӌGݻ9˫AE.]yfddd?{/PгgO455U]Qqљ̙3puu"S='OFz4===k׮Ο?W^~|pww:0,, 066'Ss#"""""* ߿t!p޽B @!IRyCenf֭[1w\<~mڴ; |~DDDDDD3ʼxFFFWWoNybŊ5kbhӦM>Ίt& 411AZZF˗/ $I}U7c0c ܻw,@eH5j9 """""33YYYZ !333Ȳtj,:eSkΗ۾9OWbE@BBB򧤤2|DDDDDgϞ¢3;8d2QQQ$ mY(B__ԩ:_FFΜ9>,z޼y`ggRbc昧8L9Gcvv\zddd )))000( -vajj*xwL@ةS'|w駟#<<7HJJBժUATN 6C^C~zkNjii ???\|z˗#%%]tQwou\r:/U7QSS▙ re``6B[z_RiӐ(zwt& DΝ1f<|UVExx8o}X|9bccQR%¹s"((/^--Z,*3exyyEׯn߾ٳguW5jn޼-[nO?T̛7XIQ{ @DDDDDЙVXo+WӧOQNl۶ ^^^<$iܚe;vȑ#`xX|9Uၽ{b6l,,,зo_L:UuXx1-ZOlٲƸqr>yNIWr! !DIW /55U=.tM} SK3Գ1-;ZաR(##HOOg%zoRnHMM-jh+++<}mJ%SSS;N!qIWX*&+J DZ%''ޠi{Si ׄr;9x8%%]*dYĉKe% -Çy()))Ϊ_p {4iINJyY lr=aBCDDRRR]F(oyfJw+ee2jfpm(' ؾ J+Ω&zyƵ7>>>6lXIWɲ-[˱.]b9[n={vIW!W2JDDD%hӦM'-- &L@hh:-,, ,cgϞ,ˈ/zu֡F011Aݺuccʔ)xIK=Td3^ᓸBE~ {{bdݺu(S 7nH766ҥKqMEW^YGAѷo_9s۷'|K.ԪU UTʕ+n0 wN5L!=`@HDDTgϞn~1b*Tsss4iTo_lyfsK.EʕabbB`ڴi\2LMM၈~ѣajj 777,[ rРAptt \\\0c ٻAelܸ033;?RJ077G.]0w\XYYz ׬YmjW^>>>;vl_p@ѳgOw>c4jΝŋtRLX[[߸qWb UVGV܅ݻ7N> 777 2{k׮xիk@^xxL<޽{5={S5 Iüԯ__ʕ+hҤ" ɊuQqppP_abb!RSSߺ,*:%]J7E #иwp}>Ϟ=í[r=ԨQCc\_dd$,,,PBoҤ={h܍p*T+LB*26i$ 1rH߿.\@PPyUݻgϞظq#bccqqL>]>Ǐɓ BӦM51fgnn#F $$˗/GLL N> bŊ'b˖-y&.^?SdΙ3WիWq5]([yM1gܸqK,Ν; $[n\cذa8 ڵ+]vO>bŊѻwoWݐwJYfyh׮Zj͛kqٳ'FիCFJy0zht͛7eo7`Y&ڴi۷`hhcǢnݺ>VZ̙3ѰaC4jHݵT@N[`=iӦXx1̙www޽!!!`l߾=E؇sss1#++ [F:u0l0XYY3}ڶmmO?E*U<7GGGl߾'N; }bܸq&M?O?wwwlذ7oV NKKæMЯ_OKyTBZFF ^, (6Y}t f,jxBz_-[ !!!HHH(骼s}ŵk.jg^z=zt1լt[x16mڄ;wjݞk4~*U#..CZl`$ >)BDDD/=aff۷cŊo֬Yغuk1`hh t5(J< ŏ?իWΝ;݆h޼9뇎;jlEjVְH~ŀǏǬYsT\ ,@PPPUT ,SU|*뫯Bݺuq-L<.]BRRl߾͚5Ä PNYd` g/_]t͚5xRRRpy۷DTJbbb`ccĉsNܾ} 6,Ra-P!  ;(Q)T̘1o^0M6ؽ{7]G/ԪU$I5`ǀ*;k֬~~~z:t?  ,,kH =- +DDDDDPnB:uԏoߎN: ?~<=Z5`k s%ŕtu %zJѣGѴiScGGG?%Q5*$z-IUQ c!$""z`ذa%]&2lR,Zt)'A̦[n={vIW*UСCx\v -ZPose)t!,Bf@HDDDhӦM'-- &L@hh:-,, ,cgϞ,ˈ]t :u dY5?SLKT%8 Bpp0ڴi&Mf͚탇G ֐ K5*% IBt(cȏn:)S7Vcҥy"]"ӫWTTR3f̀[b8pN:3p@;v k׮ѹsgiFq+55SNʕ+q$&&[nrnܸ 6`ƍ8s 駟p%/Çzxڵ W\?[[['X~=]ι1j(={޽:@ѿ̙3Ŕ)S h6}tDDDh\S$lǩSk.\gرFE KKKcoooѢE EOOO1f̘9;muS9weYkl5S[*9 6l@HHVpM$@`` Ο?QF-7N:*U9s~~~Y&̙;w`֬Yqmۦ=4iлwo \Ϲv;88@GW^E===s/^z`N&O5kb޽Sl;{, E$Iy&VdW~}+WqTPKˣG}ܜ@Է.N O>x7obҤIꫯ޺Ǐc͚5/bԨQquѣ~ ܹ3\]]1qD`ٱco~޽{Q~ KKK`o}Ez:PL%DD^e\Vt)ǩS F1[({uT˒ >>۷oǞ={燁b̙@ll,v؁{K.úurCIfTAt1ueccIis%T\_~%k/799ڵ̙35&SM"˲6m0߼;$5QIHH$IA0.& [FZzPBt={|ׯ_}}}Wfdd`7wޅ}#""P|yu0ҥ ~wdddϟ?޽{1|p gϞ ڵk5ׯcܹشi֬YVXZ@T ) :b%[1""/#}|z[UT>;[S\v ^ÇcY^BttnիW=M;j}&J"BBAl Ej_BT+VkyEUjoūDEѢ4j'!$DDK$$!ۜLfdb&\W.r<~Rzs!)) ;wگFGxx8:wiӦa…I1p@۷/233QzuEmҤ NwʨF:g'IllLe1NNN9r$N>|8}7ncȐ!矑'N`ؽ{5Ə'NԩS>|8:v4gggL2&MBDD]3goAdd$`Μ9رc^ ._U&˖-ƍl޼^^^Az?~3L2)))@ppQ NNN>}:[nk׮Xv͛OOO,X׮]CѦMQ 9s&.]`…r Ю];ڵK첉Įt[ǎzj̝;fBhh(&M?:aȑh׮>|ɓ'cʕ2KÇc E^^|}}ѷo_el3f@bb"jժa޼yHLLnصkN@cԨQ>}nݺ 彋/ŋѭ[7DmHJBSO=""""C,Y{vڅHZJ}-Ν;BU-VXaP߂ϲ<鱘PӒLSҸ,dnVe-W۽%O-̟?cǎEVV @vv6HJJc*2*5g%P)*( ܻn q5&DDDdN8EÇhذ!VXÇ뽯^z;vSj1b}*1Ǭ clP\\~͛77j@_V),s1H@j(|18::ТE X[[#:: P+((@LL x ]Xp!K6hZU(]rrrJV2 EE\<.]lػw/ZliX۷1c ׯGRR֭[G!""WEB|r >.\V\ \sԩvѣG#99K.Ehh(z @0,,L-Ɵ'OTUɋ ;OO%4p Ď ̚`„ 0aq!$%%ѣGhݺ5&M=z0H̚5 6lѪU+o*JR&aݘ:u*VXG}@ƍU!** ӧOLJ~5 A1{%.!TqȒHB9,ZAAlmmo1SOG-i#pPah2g_""el}"CKOe=%Pzxqcd<0wxDDDDD&T쀼<jw |z&+Y&drB(!T&.. h֐GOY=z?4w&ɰc)hwߡo߾Oe୷ҥK !U !)|\6ڠz!"""-- /BٳgT;w.d2}gϞL&Í7*=ʲvZtpwwG޽qI>1ϟ)J2E&?6wd3JQ.%""J$駟PZ5իWUڟFqJ{0x`_8v|||ЧO*4o5† *-2 Ir9͛:u׮)ff͚QyrZ2 ~ƍu>b> ׯ… akkloѢ֮]kX%g,Ԕ)SpAܹ{_ӧO;v,?͛7#66 /2󕛛?6l#GzK9W\m?#&&Yqqq4iqA.]ž={p%Z  +lق_1m4={L很8;;ĉXp!>SN޾}{8qS ѨQ#%Y\zU!ŋEED~~CQ:wȲM;Mb,!l>E Ȥ.lsclagg'nݪlbҤIB!HMMU7$$DB~z!ɓ'/]$$IR}'NܻwO'//O899cǎD !x뭷qL&B|go߾*&'' I˗Bԯ__|W*}ܹsUu*}fΜ)5kҶrJ{k׮*}ڷo/f̘3fyyy*Ν2LܸqC}g,3?O]._2e9 )SYK¹z* о}{e4i(**?\\\_ ,Y&MPzu\xQ2tw*ώTn3f ~Ga8zaÆ̙3hҤ &N?C;lR{///!pm@||e=zb6}<(kgϞş͚5$Ij{i۶.]?֩S'dgg,mժJ///D `_TV-kSfmJUi߲e UڒÇ@Yi^z  UL-?)ٰӧ!%8;;YNNNj]v[嚝o߾qvڅ?!!!;v,.\ $&&b݈ A~Ik ̔,-jԨIp}}6lw}}֮] Q*FXX.\(3@?5M&e*[xG$~&/… o>4o\zFF$IB͚5IgϞC"%%r۶mC||<"""믿;<*RT&=Э!f\E+="yE!/"""ںԿFhԨqqԭ[p}$$${KN:i}Vaa!D@@{`gg$tYk5j <<ܹ3M P$믣o߾D՞o/`&MԎO8q{lll8h7{laƍ*qi۶mZ]f͚*<Lƭ[еkW*ϛ7X`]իM6ʣlmm1sL$&&]t?pqq… qXYY]vصke9M]鶎;b՘;w.f͚PL4 tȑ#Ѯ];<|...ZM<+WD~Yb///>|ӧOGhh(닾}*c1cѿTV CbbwƮ]0uTF¿ozj`*sٳyyy_wr?.IɓСJaeeﳮ*"Ivr4jL;;#{ƜX.߻ oGi}QE(egg1i$ddd;5jT]7@6m0}e[z5~;cq,fرcq-; QE,܀;wKF2Nſw\w"""oɒ%8w^+V 22Æ {ߢE]d+V0wdY2@ ř!"2(R$z*KFQϺm`-ƭP׵b p/ᩌCDDDUۉ'h"<| 6Ċ+0|pիW1!,&!CZZ4hҞjЦY,%RU HK$Ih\1.߻\ aɊ!$"",CСCIlڴ!=UdO>1cm9s&VQj3k%k?Y%!$""""Rg1So/F׮]<$&&jRA.WC4'Ƭ..yBX,<Y a:up9,\h۶-+Ԗzy)gVB Oe<""""¢K0zhsA$2^^CX@@$U?wJme&"u{b9,*!|2ߏ۷oC.W=Keh=W_EV2ʋCHxR+m("""@Z,&!o1fxxxvڐ$IyM$&UڒREelY7@IQwV%""""*b>  U+0KFSS!IR#̄+u""zv9::";;aUIf1 1p@sA&֨Hݕ84Si1(>"6DD$ NNN,w^sA& ?mm,$ճ2Β2~~HJF^a3CYfرchٲ%lllTO0L1axlys4G8glP۹6޿:&QUa1 5k*$IbBXL4/Rc()*S2^½&DDDDD,&!~C 'KFRKFz4ERfaÆtZhD!ЦMsG rIһP$qw*/dapEŬaqHDDDD,%3f)S {{{lݺ֭'Ԏ€dɨdVH~\U^xC X[[ѣGpvvƧ~/$ccS(ѣ ggggΜȑ#ܹ3光'"''GOjj*y4mpssC3 V IDATQ4 bVΠYJԒQI h%GDDDDTXLB7八W*ݽ{$c :˗/Gxx8kX[[_~8rׯ6n܈ &`ѢEsw' $$?Ʋe0j(Y Rw]ܺu Ē%K0|x{{cذaMTRTK]U >X2p/ʉTQ*s*QUb1{q!4k ɓm۶!88?q6mڄ%K`ҤIphӦMáCO?ѣغu+^}U9s`Æ ʾ3g΄;8'''/F(Zl?Seaaa1o޼*VkA"9X[+HI6q5*x41y !U""""bf.]:Ν^zaӦM_> ?˖-ƨQmvvv9r$=nݺkV&Aa(((<|QQQW&0d899azEnnZժDO Z[2eu7o*$tbB""""',faÆ;99a&}~LL ޾}{:uh̙3+o~-мysƢm۶Ugcc@{?~dgg㯿ѱcG)Ԫy9$%I+M_1}e @ܝ8V%""""6lSkTI ///v///!p-755$ii 5kD 0|p< ~/K$/iPešw`|2[ vJ*D\iGiuW^7^!ޒ_4Ѯ];ܹs+ӑkKY0QX.pfͰ>f}!e?)X#dh^D-Z2&QUapǎٳժUS~_TT}~yjkK6Ks$I{K~W>>>׿$$$kh>FMd2 ~)kI[mBC&v&5]>ikVSD{& WTT4]mɴ̞bߘ$I:t5ԯ_K,8ږlRgid(1`]7zElʚ3g>UQB<M֩dsml &E lxh"$"""2yaܹg=rr۷r!>>/R DBBUڏ;IӧO;v hֈVWPPcx2䵔rrrk֬Yz)јCXr`;LGɿ:%'+ۚl4JDDDdNf5''=3̞~:<<<* @aa!֬Yl0x @zz:mۦl{.lق0\]] 6!@NNwڵk!4V5F뗹Zz{\hUbcM>R a 8"""""YYY KOٗ0a1n8oW\+c1c#)) ֭S裏DԫW"!\|9 .+W\.W[9|t ]vѣK"44Te qaCFFn݊hL0$UEDqeO3pF(ɇM\=!l"=;ef13[nEΝ;v-[dH|ذa&N"oԩ$IT,2 wo+V`ڴiѸqcAAA#>C]FO?綾^֭[q?֯_e˖}-B@mhZp. -<[TxDDDDDU޻w...jZW-K|Z[NeưDjհf%t٧W^ի QZB@-!lw_[+["Oލ /CDDDDOc13~~~ؽ{Zݻg΢2ZBXݾ:\p%Qɍ*j¹ۜ!$"""g~7nܹ={ۇ%KTx Ƣ2ZBL'v&KPVjٳ8=ji!"""",rai/nj0kF4#lUΦ5]r9 ɒRh9HӚV"""">OlwwwsAFZTP$*McEƲA3f8f*%AAA*EeHKKÝ;wrJ3FF%r2:ۧT;\@^a* .jөMg갘W^Q^&f͚޽;6mjʻd u]"v,~4tr[8x`!"""",&!3gC SC<)*SF[8uiBB{{V-Ea[nq&<0k%FBV!WHOWjSL7__lTBتV+$f&"qW3ɘDDDDDUYի# 6 ?<ѣ_+D2\$ 9CH^j޽j}~n8DDDDDUŜCxQ>@F ѳbf˗/@FFQ9)Izj!lc_r IBn.n]e%3Cun0tQed5PǥΥB!!da˖*zšd!"""gE%8qn߾rt 2LQQE!R$iW$\7n`B8>7aC3vK*pΝx WWW%IbBXȋTyGGUF,j amڨ\ cƫcUdT.kb2X.*7!ve ɔ(JBgd.%"""g$!ћ$-!Ak;FǢr0}XZB#$"""gE%@???P:%4(!lxB?j(b0!$"""g$6l@HH1aL0ի$ccS(ѣ ggggΜȑ#ܹ3光'"LFiӦ!((K/SNU=- K/uɇ_m’.ҳӍpXp!6mڤL7mڄ `޼y&cСX|9_Ñ#Gt'@~qFL0-;wн{w\-S3&&!!!x1-[QFa͚54hJkCvtRL< Ɵi5r&MvԖD.W_2$'uE:pg ,|Z˗]q!IXtOtI罛6m$m۶)ܹ#o^uʶk L&eӧENNʽK.W~~ fk 1h^]\RaQpU\ܱ0o8Vx2ys۸rOzvXfɟKDDDOcq,fSkOeX[[+;;;9GEJJ{n݊ڵkW_Uyxx`РAؾ}; >DTT;d899aʶ 8::.]ŋ~_Kb ܾvJf:@r-L_iS@Ϲ[n8T9~"9epɘ0aƌHDFF|)ST111J{׵9s ڴQ?}EBquXm۶*llluaiiiiϒ aĒQ ׺lI$8 F½˽Wt"gM\"""""cYLB8flܸشiURkn2^{SSS!I־ѣx7u ua evI?m3͚i!tqsϙ|8Q}IKDDDDd,IW_šCp=ܻw/lg?zvvvj+P[򫶾ƸsFaԩ_ : ˴j4<оWs0B(3!g$X38Jˤ%""""2$'Oڏ? ?yyyj폋+M:88u$I{K~Wx۷-4DAA֯g:gmlѧc h8P,t 򳩌}r!NWTT3,=;Vʔ;BjjZ{Iw-Y~1 ꫯرc5kf [[[_:XO# iX2Z"Du w. qfAn 3i͛7OrYLB@ i)QHHH@vvJc IƱK{icǎ-ZZmF111jc!DΝ}= ??_׬Y~1**($%Z5k' i(*\6l댠A8tPA.I2t%eUEf5Ǡd !--M=55~PXX5k(~zN:U>UY0ضmݻزe `ccpuuEHH6lؠ8""999jӏ7?VZUὒ666Z*lcIU''EWzuNN$w;Ⱥ[uQ D1CPXX暎JbFҔb7{0!$""gϰtT<2>}`ƌؾ};U̙3ѻw ?}8p f̘taHJJºu>#DDD 11H/_Dž ʕ+!'3|t ]vѣK"44T=/_UVcǎ?^{MFV /,sꇺ-X@1vvQע0BB2 VV@v6Pz͚gѻQoL[#OJ!k 兰Y_A""""zY̧ŋk׮EPPـjBddIƈĬYaܿZoN:)HLuT&aݘ:u*VXG}@ƍU!** ӧOLJ~5 JgB$=zGUK.ʄ**JX67!l8^> `սKI۹^g\θ4vmxb$ CM8u:PK%-@NN~={hժz-NPPP[[[[E/?@;O֪= 4lMux  &߂Lҿ9bhesw͛(d4?x;x;zgkV9{ك~}u\`V*ϸT3C(*e=aP-*€Y!6=k\sU1CUTՒi4EBX#YYLQg);ç?p6p˒$wF",-0U+9wn{)&K@=q*I """"*/&ThFS>Y;C(8IsB[,,³$IBXb!ڹ}wm_KDDDDd,&drB>تgJZ4 @y*r}0c dF̙3ѻwo3FF5eʗzB@1k#~@t!4p-aom)'u\UyޠDDDDDf1 ŋ ___=z@ %K;<*!`Q$O>8zwsF9C5!|9ŞłO$ aMH" !elnse1 a:up9,\h۶-+F IDATQ9 T IBh v=&7`P6pΐ?#\Uʔx}y7 ~61,jsFm0 2p:аn%Fb=b0:PبDݐDԯ^߰(.# _^WmF?q?-seքpǎx`cc;tϼ=*ZTP-,#! kfQ#88hEB@< .V/mK1d+&K0!$"""ʬ +4xzzW^O$8,O@۶$m]U5+fjĀ="!ܼYoH7{_ڨBapR^n2+_\q!Qe0'O\OOO}1Ji7]p0pn8-\s߶pǏubnK-yc ԺVkYhr?X3<0CDd,MUF$3: _~EnA`{[Vطs,'['"m1bƄP$ l%""""2$ÇW9Ç1|p3DDQʨ$l[QyU-n 9:Ԯa3jȋ uZ6Hq""""z:,&!Bh,MTV "G ;۠FEBpsӓv9w۱uCo_r[gj oUp @KDDDDd$'AAAhӦ $IB^ЦMW֭ѥK;L*'M) Q o !'?Gk$%/. .vY$McKDDDDd$CXR]4&&pvvV^EkjՀr ^O5&7ZByf[227uu7(`8&wl["W]AlC>oxZl"""""c=!3g~x7`ooo戨4ww= &AA@b"pPή|"TRMIڑ#A:z c.ݽM2I1y0aZM[\/hsd%% {{{DGG#228uꔹ"#=P$A X6f7q ҲJG,p7!N Z6jom7cWI^| }׮?:Z[-† ?HK.h߾=&N']vܹ3n,odFZ5+2˭kWŌnn׸~8CP2958!4 sWBX+ U8vGN%""""*IG\xŋ!;<2ww#n J`hXvJ&:9eӨk_=\\>bxLt题% wǏY\*$UФIe[&Mb 3Y25F';+jz~sg(! U77hP$ m=ߟ5lh~.%F4;?KDDDDd,I}||PPP^TTooo3DDB{QBWWEPYˬNw{$ۃ3г'*/mG,}30,!|%$e%lY """"2$-lĉxb3FF0YQX6:6(""""29%/_nT#$m >B<7ˎ-HQK*?w UjL/Gh@Bh-{Ͻ'WLw'} **J~<]„*,-xUڊʸ(1j cPW[+[j3 V&..ˠ?N=z 7 ,&!q '''|B~~>O:u2ެ,=pvvFϞ=q}9Ν; ^^^8q"r4k7|˨]6d2> %ѴhT*XX !n;Z,_9~͚Z.zu88>%Šj9BX0=VkI.W**Y6 {=8 8 ^mIIDDDDU$ӦMßUVk׮ܹs퍈1tP,_amw\S`$L"jbq..UVouOYGEq ZX۪-{ -ZDDEG"$$*zE9pcHE9CPPlق#GbHIIA6mpmqqq Dvv6-Z`O>;eĠQFL)}Z<  { fM<? wR현B!)3޽{b -[ɓ1{llڴ?s n݊9s`Μ9>|8/ԨQǏ7qIlذ'Og}B,cڴiZm[TT FHHf̘e˖aCbb"6nXd)*0C(&e 9ρ6ߍ (wj^9jлE !_ϱc@Vڸ:bF(*"B!?e& |jժóWi-ZcH$kYZZbذa8y$ L ܹs'\]]ѽ{wͶ*UO>سgJ%˗ƠA 5m \m۶iãįbz2U@; 2UN}F7 Vh~7) ܙOT$q%cj ^"W|]tE7LVGKlO3B!jݻwuO{C///hmƍ7pvj,,g G pHǢ _X}(޽[QRzBnn.4 ֯_MZjGZ,{dڵKɓ'رcuT /H8"":7T!K_ C3t%:`m[F C̗0V\1iC‹DyADR л7E}PaŠlO^Q<ͫ7GX~fyI!b Xl;L@P(мyR;?zI&a„ Xz5ڶm̛"ĉQ^=u {&M`С9s&V\mBVcZ̚5 Ϟ=CVj*L<_}:vkĬY`Ç1k,̚5 <(ߠ/$J!+E$ߔQK$,,,0w\̝;Wouaݺu#,,Lkʩ>͛7ѣGlw"۔W#,qA23Lo#qwhȄ7bQukwXa!G D+`77SF^_?l֬cvE5MiOaa‹SFCgVY>^L:&!B)L0>>Dtbcc58JUO@d޽zBn`0߯>p?< sZB!?'N 'N0TTf֖z% 8XXַkV-M~>m)-M+0RA%?^Gv3?&!B)wL@ 6ɓ'hӦ ֭sYV1'eYB1XѱP9@>͛RՁ ̬A5FV$j;>ן\7z,wx輡Q, +cVKB!;e& H$޽;ك 886mu={@mb,_^@XĭkؐOܽ[@)V|JeHWW@*b-ĐAuw8_(j*ݻ~tPZO'Q@* !B)L@ Zhf͚A$ŋOoСCozx(EDZ..fj3`ɒ}:9%o7qu{L A n&OO`&?8+ұTH |9bsTQB!Qe* LNNFhh(ׯ6m -- +޽D|ɛ&1ӟ H+uh|fu"R޻W̾݁N.f`YƍBC#3Rzl#3R-*&o9]E B!Bʾ2:;;øtFJ*j䄻E^E7N]yEN,`^3>^Iq!`I YBLD<ȝoBP#FN:o6Zl1%(H={;Mܛ`/v'w%>B!!e& Gf 5jFDJQ^^f ,{30e 0omTsA 2!Co=,A-wJJ1OpDXOb%F?_#;_!B!O<_Uk[DDj֬ ggg Jl(jBSB=ؿpXt,D3-[VO΂xĀ :[ʢVU1q8B jT~tWj};z3p$c)RD%JhGHI3q !BHx@8c \|Yŋ1l0bĉػw/~78BR< ^ Jx1I^ % f^,Vzz6 cVsS^t^e綞xR{AAbܼjD̚\ǬTs[O|zSo;S(UJ|)0x` B!f¸8߷lق&M`;v,.]m۶|=R8Q@\p_Oǔњ5Ǐ~ylM_oJw-@37u.PA: =;;`z^;&L@]4IEVB*"k|G>EB!SSSܹ{*7F\[['n'ɤM&OƍV鼇Ҍϛ,]ʗ0@Pzp劑}+}O!ߟ SHI_ /M+2uѮf;ٹCRT$L*o~þ[0x6!B)7h** ?M6&!B10qD=z&ML&C˖-5~#$&3b WτT r%[AM홒6 o^FƊ!M݋ ;A!J5X c招uLB!b7Μ9[իzjXXXh_v-:tGHJ/[4m-?CN>>ZK#l0bM~}BOU>,pr/ am ܹS;p"YX!oR2R0h (Ύ IDATUJU)9$hg=5c侑4B!8o< R 9T{Zo߾ӦM{C#UTQhԨBGz/.:3k@,~~R.޸J?|—0a!8A=/rr67$LBm=2iLo"S56Ɓ`́1XkB!4o< cooX\h{J23fB[g-qEi/vms^ml/l~s2G15XCtr@]Հ%`&yL[K[x?Ă";ys#(saɳ Uß'cT}B!L@HkNJixSD߉.r\\l 37:ߐ5pol"[QעR96!BHYD!)O -šѿB)?= @QBGsʣOM\ ^3kLkz ܽ ̜iԡA軣/in޵_.[_ORRv֎sПh~a~8Q1ŖK[s[O7DKiB!R i8v -ފE a@p44,Dر,pHH(}!!@ǎ@n@fѻ BAZK.+}F'>=͗6cdG<|Pg;J)_˗JR,Bes>:]蒣q92ZkeWB!Bb~FC*U}Ennk)hf _ dxP]J~^l qj҈,m#G{=:U԰Sh?b]HT*dZԩW~߿حbi?~8=4؋VZӛFɐlx{Хvaٙe3B!k8xt]T@?~Oo(~i99詎Z N۴ˈ[7vYJ,!="1|#yZ ,_b1/# 7;[qkWC'BK\p&7; VbKEbLi=~f : !B I0eqNx"M*5 $*xX/_~XQ^41Q}Zmso7i˟_g%O],>  n_$Ez(Imoqd칾|qn]l54f/EmxucLtEW !B a Y  G!|}%#ٟ3_zLJVr>ʊ(YSF#iߞG,YbR$kca{qeWxCPgVm ܼ ܻ5HVm mtw)6\^ +BE"o_BYZb6-[ァi^Q<ݺخY3` 梞{iGŜ9Va'sBPT DD,{> ͯnk0h (_b{xO5.lh~ RLzuu΅GO<B!TP@H̯s1cbu| _b^t\^tV _];s5Zz<|/qٴ Q\߃ 6nqD|+>Eg 6qk-]pUC1cRc1`sKTZ8=4u\E`U*d*j e"G`s|i3l[cѹJ2zn׫W_Y8r :,+Q}QuL &~}݃חں. 988j,QFjբb&vƵ'sB!?Rajښ<\c1&Cha|)pc(C(1 7| !Θm{ ]#Ѷ2]jj2e VX$sOoE`:ved@RaPTY 9_E~P&HnΫ֬|az;{cAH% 9#7U 1Ԯ۳|-`l~e>;…]@㪍6gWӛ`^ c \*@Z37 @X }p p_cE|`?́Oma sp[UVu+:Ev¢p%J'-xzgϠzhSL# GJ![B:wIyTVqXү_?faa&LV^T*eǏ7Zf͛7gl̙lŊۛٱ[nieϏZM2YYYBǝ8q"ck֬a]ve [222a>mݰ/Z.751wwv2X~?7˗yx0my﫯=p\5bl|_^]ikۖ^/歷  %aG쒞XZEFשS\X7c*ѻrKw.eg3VcE׫c66 ؟||bXϭ=YXِ!,".|zjM,e{,[0^؞=T2cG3X}e>cq]3PW2߅ 99]~|ƿ'1$=)D`RY~1تU=}|""؀XyUBw6$j[}nuճL!s*cNN0ؘcX0XlX16Kߌ1]Rk >} .\ٖ<==Y@@}nA`E))))ё 8PmΝYjXzzfۚ5kH$bf[bb"`#GڿUVCАaY;b1Zʕ 9x1GG.]*y}cs58JKmؠl{ءJ؁ɘ5Sj/NawOk5?z bcÇ<~m|cьkč]~1ؙ3pL;ݿجY}077BGlll41T&NNP0[[[ٶ|r&իWL$S?,k~r-1-cZb̞ͯMOknss5/(:|׭ \b"ϔN|2ϙ""_dL +F]˘ cO81j~M;aǎp4cYYvI94oX*FA 'OfA9-DZn>"/D~5]݄1%|ɲkт޽Աsn.N~%c5koSclr  ضKcYuY|gF}X~:aqGXX`n͓Zoݓ'}QQ%%1Zn?""ب}XmGf;ۖ5oFX~ r`kv֒8WO$%47QQK뿲g[,fZ0ߟ|Y}ԿMXLb KN3'jR^ѹKʣx_%yT߈8xyyFkjժ7665*Wƍ7P~}\xj'Jua\\r9֭[蘌1Ƣyzo^ ^y];~VHĄ{1Ъ?P ]514Z h6Eg15{zj6t(;ӧ_~i@__9xoXjih_,C f;}H珙3U֯||NNa+vny~<[Hkk̚B,9,Xg]6}=M7N;}ux{ɄB)*L@77BÇ ۺukÇQ~}$%%AW>2)) ...Y})1b/8j_JQÇ лi)i.gdI@ݺ'_l,-oRKOc#~gzWժ<?+9Z4CZVk~ 7}y"<<^i«76J2ߟg~_+~~bzu^ / ֮'y/IW]_UԲԮ 'OxLYZ|]unn汶/Aвe|9>F[oޞ?fP* 5,~Xk?gg~hqp^x7 7W'j͚bJ;;>Bcpr:t~ F,F3-<4D<8c|mڔUURe_ M>c>Ȟ~ל'6Aܹ /-jpiP)qHUg"YxKYYB K,,aa% 2XZae)2 2 bKX- Ka!0^$ bO *M,z]6PBHVaBkkkX7 5g_A46%O~U duGX ͖)Z@W]TGZp8!x'_y[?,,yaϜg<ɦ4AD3%PlD?${%!|TXڷtw8}'wAj*KN'M4T*{e9&Lncn埅cZ.ଆ:V/S7~-0[[>y8Ǟ>{8+)#++?dJ@"iTo XLG8>A'O`PƒLOٳy3?7>QAl w@A%u * X&8˄LUdt2)*s`TK!aT[Â`ˆ:b@2!aAI)j*P T"T\'3,LM-2Bi %LV ӵ]w[Ó?Lήo7fT+FW gB"f!bLj .VJK*L@sfRT Rnnnv͛~m>pСb'?K3Q5/h ^|B%);??adN99l%93,('3&cQ#V/*̡$c58p XṮ0fÐiǖEb|X][y≑8RKeqf_ }}}q!k9uAV/2 ^^^oooH$ĠWAŋ IDAT^vJqqq۷1q52ƌ'?\4D"z"VD!B!ČT*ހ1Z \/XÞ9sM6Ehh(Ǝ* x{{ Ǐ>ׯ_ל Ѿ}{@bb"jժ#F`ҥ[j{!!!AoG!B!PaB۷/0zhxzzb!C{ÃRPhѢ._oUT+={kԫW!!!x.\6mτ `{ؽ{7ۇ͛7ke !B!4TPP`ʔ)Djj*||| Դ:t(6n܈;whBxƍ(deehذa~N8 &󰵵E߾}1{l)sbժUHJJBڵߢ_~B!BH>* $B!-4D!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAQ@H!B!B!RAPP`„ Vd26mh}BBB k:۞8q-Z\.FBf͚?D1cѮ];899M4Addq/B!BJ| /^AaҥH$ ‰' CPPlق#GbHIIA6mpmqqq Dvv6-Z`O>;eĠQFAo :vRgφL&dɒB!b1 ̙3hڴ),X1crrr ;vL۶mC~sNtxyy!((H+Sx\~r8p߇>} '''L>SN-ǎqܽ{RPn]TB!B ; HfiiaÆɓHLLԻΝ; J*ӧR x%1h M0\.Ƕm۴a @,J*6B!R: lllk'665*qŋͅV;T __bgڴi˗/cԩ}6ܹ3gܹs0aBI!B!(aRR mwssc >,־4&%%Am aԩSѻwo̚5 k׆'͛;w?,1 !B!: ʂeVVV狳/cLoO}m aлwolٲ6mBƍ1p@9sX$B!SHnRvY[[#''lW;y?-~_|Μ9kǨQpIVHH$X,.8 !B) * jZs1jrD:UPߔͤ$@ժU оyO5ԇ>Jk׮-tD"AΝ|rj'##vvv&B!.-- ozi: šCUXԩSյ,ũS D"ALL ziT*}>>?~<>#_fffeIyT*aaaBAVr[R^ѹK# )℔Gtc0ƐETt2I&J׸r=eB!,*tW8::!SzzzYwCe{!B! a9WJD$aڴitޒr[bdzGFF\\\>O׸{Z!###C3e_QPM!B!B!RAQ@H!B!!!\2b1K|5>tPC{۶m1vÇ!V*/h3gοWYT*QfM?MBB!#"";]c7wލ3gj~Y&.]jr?ˀ$%%… طoF֦MD"l۶M%KPfRSi:u*V L֭[R)ƍǿcQ@H!Bp-I&pvv6KuGLfpCR+#H\*.hٲeݻ 'OJj_pPOs;w.-[09sr;vBд0`;W8yP@H!R :#G!PV-вeK8::J*ڵ+ܹobb"ʕ+8{,>eaÆz͟k۶-0fD"bg0`C.[lÇd~Ç!4SF7lGGGxw`kkΝ;#99Ys,J#G0dt]kPرcvZXzϞ=󃵵5<==1c M@H5ŋD8rh^ѸqcXYY+WW"##DG= 兽{%K0e|FDD>|(MhHD!!C.]3fɚ.##_5Ο?bX+8@V_/^ĤIV5m͆ڵ 9s&=z$@vv67n}1b<8i֬$T^]gߙX`6mڄGo4ϙ3?36l؀cǎ!55QQQ_C||<иqB,=z| ƌk׮aժUذafϞm{8i$̝;W^vލѣGcܸq|2BBB4t~3f@~pEax>޽GzM4ɓ'ѣF9> T(0aULM"::ڨ}_x8;;ڵCllζ'N@- QF!##PYf?+D"f̘ap [nEaccGGGwQf7NB$\0 N< _CΟ'jr RNx* A@)"-RPB lv~lvfwS I|<Φkd@ hZ&mDdd*+uϴ<<<|}}xyG #**իWs1N8~JNN[n宻",,{>}4ooopww' @ fԩٓ;/2d%7GGG\]]' Xf|rfϞ#>>@Ї ѦM-Zij>ĉҥ SL67n8FANX`EEE8pW\A$򁁁\rXpp0 ]^;%K0zh-[VeСݻy,3tP>3&M?]=ٳg9rAQVV;Yr%#FXwܹ>~\G%>>^M$ [O6mڤ> >p^uٳgCJJܡC&LΝ;4h26--Prrrl>)ؿ? ;fzȲLFAo0A`@ZQlJ?ի `0УGxKnhzw}K'O6+`Pt:ג$r%%%z6>gyE`&H7kaY)SJlkץfoMڶm,deey ,Esss ZpƍhZƏsrrc߾}uz6mD۶m1b[nU~Ю_Ν;=z٘1cpss(% ۗ,YBPP" s&ȿỴ6C 4#:]ӾRnn.̙3{wnݺc6&**#G7k,1˰a9r$={$,,z5OOOI0z' ZC$,XOZZٹXN>MN,^"L9nݻ+eLDDD;aaam,Զ~믿YTH2hՂȑ#nv<..N9oÇ5J9DEs1z ^m3>vŝwҥKÃ`z֑i99}Zm3ooo|}}Yr%gϞe׮]L6L9@}Qټy3?MٱcG~._Ϯ]cɓ'8qYePӼ~trrroZc/ ,/ 55ɓ'_#&&4CҧOVXav|޼y$''ω'8u֭cܹKطo_.\ȩSHIIQ{>}:III|9sŋeOސa_~?رc3 f6n 24?Zfffdq<((Y|MHdsl]{"??lüy={6ׯ'&&^zTx*Cnj(-)|U >$u?ӳgOMfQDӱcx衇"11Qiѐ=jOZZ;wV̙3X?q}dW^pc̙5c*EsyvNLL/ٱcqqqu],Y,>BӻwoN-ֶ߰aXt)-"22UVĀWu1c/'NO>m61'xε-ӹsg8~9Y$yҥ6:88/]vɒ$[neY?Y,Ǝ3F~vv,IoZp,Ih 6( ܣG944Ԧݵsg>'m,˲>>8;;k7G5jM777m@`/Zi ed2*vGѐD\\ |t֭޹䡇-NٳlQoooכ*h|8f"++.]Dzz:2W_%99YiFAdƍ`0o3|0a.\` 2xlڵkIOOWcJJR j̘1o'zj^|EN>Mhh(\p/.@"de22^d !W{WՆwgmU@ >Z O;w.k׮%//(+﯌$"lRѰm6OλKii)qqq$''ӵkW111ܹ3g2uT<<<?~< ,?~P0`"1c1DGG_3hРDAf!!@W('T dYYLFAB32sL:騺@ V/ILL$11昏?chˋ+Wڢ_~޽q}]cLG5x@p;ePP{濪ag(r|@ 4=:P ~dzh՞lJ+KUC _m!% ]m@ 4B Uit-B898m:xuh1%=_B@ nG ޫ}@IDWeYC!Ax &L~[^7$66Vzܸq<{/SN}Ͳ~mƌ… WK0:) @ PY['LZL^K e4INN믿&33[^s|6oٲ배0-[} gk,Ge۶mL4I9v=hX~إK65'[laȐ!h,t:OΌ3TP Ӓ}`Qfpuu6m4[qVK@@@]˗3|p\]]c$œ9sza57lJ0`o2j(I`1= IDATWԍ@Udd݃)(@u[$!l !:SrJs6C 4ƍcҤIdddhԩ0`s9.]bȑN\\chLL}kz^ә2e rss5j!!!g}ff{JJ K.UeddFQBF׬Y7| xxxUUUŤI& ^{/Voݺ^zB.]W]zzF:3۷ӻwoxҥ NNNtޝkךhyqss#<+ADB@:Zϐ%l)-%|Ue(,#,[xBBBR]qq1ӦMСCڵ 3qT\\/رc̚5 i7l̈́+W޽{m6?ĉ3f !wuǏ'++LsKJJXh~)w&##W^yE9pBfC^^y_~޽{[^7ߤzݻw3vXL©SXbk֬1wk8k,9y$QQQlٲ_~ӧsq&LOs1ߠ="..Am ֍,J! zT${kK!!+ڦ ,7tMF{SxxxrfիW'O?%''Cpq888nl/}v֯_O޽WWW3ۭYb;voY˙={6<_]׭&/K,ażk5k< :t >>3f0w\'$$p+_/Zg}'0eow߭7n#F`,[0xk`ĖEz%Kئ3g<[j"#BYI:_"4zǿ;6s*9l3g0o<~'1 HDFF=zE 6a.]DEE7{ꪈA ^ @aa!YYYyyFC^d899"P aJJU$m:r>JH=-3'L~.AS!I:]{Kjj*s{[nȑ#MoFoZ{eذa9={Fjjj'J$ҋĉ6HĂ xIKK3;ӧԩ P) w1?Q܆RkE-!!Nl`qjGLMa?jaFP[ eʕ={]v1m430rHyGٻw/ϟgO7gǎ|">vʎ;طoY2iO?Dzz:999z^z%,X_|Ajj*'O&??NGLL {sCҧOVXav|޼y$''ω'8u֭S۷/ .ԩS(jbN>$>Μ9ŋٲe ӧOoI^^GȲ̩S8z{n rK{ V/+**9s&ڵՕ}s-((`„ }Çݻw/qss#((ɓ'S\\l1n 6mۢho-<Ƭ@\\/6ÌЋ$ Z!$¼øXx*AC-S C( IXn?3={dڴifct:;v z( ٣&񤥥ѹsg̜9sOwAAAm ^y .X]>fΜɨQ;v,ݝ\ޢ)//7;7x`KvA\\wuK,1 -裏ݛS2|7l0.]ʢEdժU$%%1`:wݾ bbbxᇑ$#Gk&vGaa!O|Xvqq{%XB;w,:b]I`5o6m$F~饗+**d@hd@䢢""A֭O=pBYU9p֭cѢEL2ѣGɌ3toذ}i&) _7{3{l|||HIIQu;t عs' RƦJNNNhyWxW@e+[u[u% .>]T/cǹVY5{t<ڑ^Nd@jvGFF| w}7eee,_4FU|M! jzL rzeC⃳֙ogV+ƇZa*WjADffqӱ[k ?5=lfRSS0a餧ؤ7憣WBBBmޘQq3 BSu~dY! %4"Ul1y.cT@ MkbU hRSS)*2Iڿ?$]CY߿?Vf*++9rH{… TVVү_? SNHĚ5kԩ;v4}qq1V_6,ɻiiM[g83/W #'Pe utpG;UEDgfT B!ۆ^gʕʱ ۷/ڵʕ+>},t'$++͛7+Dzٸq#<:OOO ڵk;1)..fĈ{ȑlٲ?%2=9}iԚ:6 n]qtp$ۮ2yE h5ZBBU>ի1dT5&.R3qPճQ@ u+1}\\Çg֬YdeeѥKHOO?Vƽ$''+M(,Y¸q8~8~~~{ x }ϟO8p &L… ,^!C]v-xLIIawa W< nK$XEF6MγjHBEEᡄ%Ũ`K"jQ1a,+=;tlYz2As3a6mD~~>&**{7ٺu5n8 {/111,^mMJJ wyyyxzz63f ;WKp6md2G|'ˬ]ɓ'SUUW_}e3F$4hضmO=.3f ]aΝ2uTV^ٰa=~!#11I7oo`$IRn{B>YՕFۨf^=jdjzS&FODsO;xuZ5J*KTC ؾ};|dffykN>o-[: cٲe{eԩfOff]ѣGٶm&MRs=h4֯_o6vҥ5Mͅ^g̙DEENv;vY Nә1c J8::Hbb1Є+W4 9E~ؽ{wzBT S_6-bE{T!tw'{[ɉ6am0 =@t̉k'T2u"=4"#TE 9s AAAN[ WWW\]]moӦMUVK@@@_˗3|pjZ?gx {8%m#GE^^&Mbذa8p@7j(Nɓ'޽{!h:ZP 4wjS$V05+YJLb hVKR0IIـA)܆7I&FS'C 0ooox9wwK9r$?2c;g{%==)Sh1˨Q ͍(>33SRRXt2/##4 vBk֬ۛo<<Cq?M[===OSK)SV*5 T驺ڞdvcٲeBVV"芋6mb׮]888bHff&_~%ǎc֬Y 7*X7yfBBBHHHʕ+JbYY{f۶m?~'2fҥK뮻?~SvMFFr~…f͚5ٳ<>:/Baa!{[V{zzkol){nƎ˔)S8u+V`͚5,Xp֬Y$&&rIزe /2ӧOL0A5駟رc :#?$נ;Q@xj&9am\ U8h[! \!tLF =%7< /RQU]);{Yij&=<<cqqW& 'N~JNN 㼽qpp,388,?_d_޽{鉣#f[C׳b :v7˗3{ly믿5-[M^x,Yŋy,3k,y:t@||<3fPZ5a`BBExg8q"SLa}ʸq),Xe8pwr^}UFٹ`d@=P ӱMG._L\5dJ\t.\~\0M.>]T26uH¼TiCFwW)dOWs̙35Ν;EXX$(b90 $$$/|7 UAAA\=[XXHVVwyr^ЫW:,--yGGGy͵zQQx;$IɓX9yٱ={*wuuS.u>|8${YwqqDk@;ĔF{ s"2%xT1;G{v]o1!m?&[2Yak}Ns!O~A+@_ LXXW&88@=ťI[oŻҥK͍ɓ'+64ڽ$IP|???JJJhm\gyE`&H8;;+kYYiMkץfد5Lb… ڵ;Bwl"\5oUP宄滘BF rCzZآg%ɠ5Ip͚v\9 :#I5 W撚ʜ9s{֭999fc8rHѢ޽{6l#GgϞZI``? F朗-8qvfIX`>iiifbcc9}4:ux +޽R\ď?HDĭUh6smuܯZgQ!A@BwH@UOf_0 54\@AƁnZL(Yɀ Aٻz`CF}\|pwt'!cax{{ʕ+9{,vbڴifBdȑ裏w^Ο?͛駟njώ;?peE|vڕ;vo>N<ĉ*Ox{饗X`_|L}b #99xN8SXn?L߾}Yp!N"%%E9Wku$%%p/^̖-[>}zC.Uz=O<bڵTVVEVVr 2!!22yjg]}"CyppՄO9:T%IRGPc!U 悰w'*ʴ+qã~/Hĺuٳ'ӦM6رc;TŖ҇g~*+A%%-'sGjW|ؽ{?777 ˗/_~W7j"kxT>*85H^)bkZ#z}˹.ao=dqMƅ vC   `ݺu,Z)S0zh"##1cF`6l}شiRxᄇ믛={6>>>(:t عs' RƦJNNH6mbfbcc;v,~)>l/@P Bs ,Oo8On-UUXl=d7[* B߮:n6CX&tƺjG[6cE ۄV-7n܈Ve1'''{9^{5.]Dvݴim۶5cLj#ODquvɴi3)S~z3A kA{cr!25B>*&HT_y-4FllZ$d*+-aU{( Sꅌ=-ACZ"CG!<<\ab"..N9oK>|XqqqZTzcGfBm3fȬk5ԺVhJMyvlY U8hVdC(P-]D{C^gʕʱ ۷Raʕ+>}*ѿqnVV7sƍyGU<==4hk׮5LNNjsjl߾ZDŽN-p*3ݯ;'PŚ* h W={ V fץ_wRsR,Cl ,a7nv FA'v=@pppV`ZuE\\Çg֬YdeeѥKHOO?Vƽ$''+M(,Y¸q8~8~~~{ x }ϟO8p &L… ,^!CMk׮%==])))̟?0.l߾=C !--3f{n5z nLٱ,#EOw>ivRuZ^Z_7睧L_ֹ8`vޞ9%}t]t/'/睧ofAe$CɻWr:NjLO[j9i!~Z O;w.k׮%//(+﯌$"7OѰm6OλKii)qqq$''ӵ VLL ;wd̙L:Əς ,}{{ cǎ[oYqw AxҒBFCXClO̢L=n-7ȲDY twtg;~˱WXTc%w;vԇ[W[,#aaA}] c}pϼ;ױ5@ ܎zAHbb"6|fC^^^\,Yᆱw `*ui1B\}`|qvҾZ(GPgv:?NEJDy9𞚰oau&X~FnVdd$1~ySaj/{Yzt-vU  Y ,[15hNp#lԞau {!5l ئ#4^9hץCɲ,#KPeلxPeJѕ:f @ eGV ොߨBRCKЄE ֈ0U77Q #ˠkklz^T@gWWMY^ ⃷7gs"4i aj Jۉ Hamϙ3jG1Rujk"BF@ 9 LVXFKW߮XZnyj5sn! =p7ajoea*chYVÝP #@`'tٹ }9slPנEOw?+al镳i|L՜[RRBjclN#::fa]Ȳ/B޽Yͥ2ZEy*,*FAqCPyQ/#(F~>lHh%JJjbJs8d4^IسY`0iԂw8׫T[v .KkZzoה]Zџ:ڌn2AG#4TGB'# 8Ө^qEѫA8sü2jEJrD2{QQ?ABX(`4$Q BшR'Hϵz1rB ̞=[u Ùʹ)G6uw+mR&n:qa%Bzּ9*|Nk;uZC !Tz+vugf~zhCd0 ̞=[tj̑] LIIAqq1n߾;^XXBRRR$ݷoB )) ЩS't:ٳEEE!!;wvzM_CPQQYfy3L5'kj^>:~CGAsh \4+Vӧms[oÑ#G{f*}̙ݻW^8q"Ξ=7|DykW\xܺu+̙.lӦ ੧‡~!C^N `4{1"g8&2PTV3g43t_3Μ:un;u^0NBk;l_\?ѠA%T ?cs@M p*ЪoUX`m'Px Бl$'@ M8cu^>'ټN\= |X`0R4jA~)f͚+Wڵkҥ tݶh4o/wAUU233b $&&֦ӦM?f͚a„ xם?w˖-زe gϞ6A[+ٌ̙\hc4 , 1"bkkӼ |>8y$[%JQ8Ρ P/zarx2Ýj-l655R B`0`h )UUe3m׀hj|{[ZY8z%°0*/_vZ=> B{gA(GJD zA$[>#ZpzOJD \<ٌ `4% d0 ˆ&-鸖q5MAD%ӒnLhH0菍h r1 "i4$Djz篋} /ydDf`9C]v;qj4뼥A~An{>`0L2 p" 4shl,`ڼ4cWė-j qp IPN7{GpƖRsH;^pB:P ]n`ht#d0 % d0 K C܅+9;@xsch,C\+ah,C@*XEDFn7t-a2p8w'#L3cu #8SD2`0 F9mZrƊ؈NȰpqb nXFNJ;BD1Bc:0@T6mrs,qq)!d0 C&²sr4kMWq* Xґ#p_@ !//5q2 B;u/UJEĺ0 є`h@l)[Ӕ7Z(U`XSFy"NXSFոri,SXE!@+O˨L,n,c;`I^#4qrEG!a0v7`0 F BVY?fZ"͘]w5H(Me8Ρg| L7p Bcb%%P-,zWϟ0̈́Iro^|IiSAY(tN`0 L2 gTiMlvT6~=9A! !m#ӱn4!'8!xˈ3h4o.PxУ7$rR$1A Ꭸj;HDԵSQX8ְPg0 aMM M( ;;νq&N0o߾ؿ_kǎѣ F#L [a޼y?zj,((@߾}-["++ +WTg4.8T'$NVDlU:D93N]va<`ft4Mu12A(*+`#hꄲh1d;@T=xyztzFі-@A:=+ hJ4zA裏⭷رcoCaȐ!رcyaȐ!Xz5}Y̟?.]B>}paWTT}ȑ#xqI:u g޽{1m4W!l; :Omds(hL":ACy4% 'VCH:P;J\u#qp ):P!iVnJlaBc'IA93V]Ź܎)BnA Q RFFMԹl疖"=JKK./cȑ3gy>Ef(8+]Spw Zgץ1utm*PA( <i@\l5+vK(#X @8k >+ itjC,Pܯ^`0 FcQ ª*:ޕs9kZP0rH^VBFFy0vHOqങNLDZǼ 2$ώN8֘<nOPDFwW93.W^F2ģ.2n'\1؉MuTrZx󧨩kզj`0 )Ѩ?qIIK7[kjy_c5j~a㏶j߂~v]w8ΞͽC:dO ڇ@oGGT?.xwZaqq@i*l!'4*<,)4:=RFL)!^qLhM=;vl֭iNyz`0`0J={վ#û4jAbܾ}wHos-Taa!ԩt:;6(**GJJ *++q1x\$l6v***PSS#3k,UՔ 7n@z(h_h"ZBUkn{I`%4zD\0C"Ez Ă=6۬HAqFH7eVtE!tbO_=z$M6s`0 ̚5KtZ!,4jAɄ%K؎`ٲeFTT Ǐ牬|?|2֯_aÆA7o<\`XÇN{NjGTTrrraaa _|so߾ ۷EB׋hZUՔ\{+ѻU]fp ͽC+~9WhQ@tchэ5`zIW{$E'g$B(#E)to{/EUx R4X !8|/g~q/4ϟD( )pF2 j{XFШaff&F^z ӦMÇ~\`޼yuӧOGys 󑕕{ gŋ ٌW_}>sիWѫW/|9s&&OuQQQx'Cb?>!F^x… 8rUu{8ȇ:^s(k,\^1݊li*CLMGL26W x C߽v)Nq'SI rq|F Ñ4MA:r9P$6U NeK!q-nb6rhʨC~aOǰ0MZ5˸B`0 FS BFl)ftӜܥQ6rq GI o77"8rDW|!%"3:X"1^fh3i{TU%HT P/͜ǖ2 WWϒ wB ѠOlF륩D3'9 `4& d4mfpcݪ#BMl1WiRA({"1v_LT@:Bk,~~pkOBp*pk%JUe\.=@F8'{? Lgؑ SԘke]`0&& gIwE^}ܚF4MBr4:M6y%ȤZƺwloωk%#)b}[hI3'! !ȋC!(;P!&~o.ut2*׮Q,Bn[X:B7Ў"O|_ h0Ahpf38Bp8rA~A ϸ>26xk*2Ms&ϟu8Pvתb6PpR"Rp&(:u|=w kǏ55K͌h֥YΒ)2Q.qڇ,$b'$E]~!qo d0 &M)a<޵hkQǺquM bশ*΋lafT\z&+~`4:k?ѥ(P"@~g\؉zPT %;@.]ά0eM%n.$̐Î;PU+Ԧ`0. d4i8KW~sPRܶO й^sO=`AN.p @E'p3:I0 ~174zF@C,A(``2\ Ea,V!SV}z} VK;+N{዇U?#i`)E!qo]uL hj0Ahؚ܂ML"MNo9ˏDХ p Z]sW;fDfjUgYbQ¸qh {.Q C(2ڶ-M!Ѽ?5Jũl1 +* ?ΝsۓK׋kPC(~=8,(hC-Y!`0V idgg@߸q'NDXXѷo_/܊|ǎѣ F#L "q7od^Z45k ''hٲ%w-[(m w'kX+'Z&"mg$3:[EkLѡo۾ D:Ab^ڨbڼDƉKF !*E35l`%>.(/|IW?fysXF qGt|*.`0D>(z-;o6t: ;q!C`xg1|\t }ɓ'ykj,X&L%K0j(ם1cObѢE1cvZ*ƌh,XsArr2΋tudli*cA.wI%>AhN"۾"#iȒ?r:P |zڸKuXEBosGjc}.;gʨ5R|9=8Z˸ồ.|!!$D&Gc`0 FSQ ]va͚5x7oǦMSJn:ܹ˗/̙31i$l޼Z o3[bĉxװh"|w<7… x71yd,^ǏǗ_~={_䉏B̞= ,ի1a}paYa+@[F;q`0 FQ Ca„ c?~օ`0DEEEHJJB`` xffb߿iiiN333QYYbLCd2!==N#%%WsXTT{59駟еkW,\h֬"##*3`6]Zikb!u5\1=q~3P촤*XUq4.\w>L9J-H3@|_Ѳv4q(kAԋX22!Z!4eF  :_콰WU `4% ,--ht:n4q.\ҹl疖"=JKK.ׯ_˗/_ƌ3vZb 5ptԃmh\!nݑ{0鍬fG냁 Mg\B#eB} BNE !QAX'z__vEhܐ8mՊI$PW.4-M>VMsϙhBK:^F& 'wٝsgiE*d`0ZVUU鸟q\?ڿxn[vW^_:Ef3Ӧ26n@0Ҍi 7~3vG]<poOG+\*WL/LUԬp @˖c˒Ó%Z+ݧ<-(b&O6o 4吀UϽch ]>eOЎr8ϊ?$&M`0FsjK KBlZ)=c^Lj#l!=z4Ν;sΉ.DmmOIc?}l]176X,/ߺ%o Nv&>ACtH#h}00~Nu"W2y_}bѪEAj"@e5RjTAZݥ4Tk|bS?RzeM>8v.(a0 w2F-F#JKK[EFF}5Tl{F v|`UV0kDc`0Gg٪^)qv]F &?Eo7_ 0:Ap3A7j1.]&OD鉍aoQA S%zZWL/UusK徤M75*b_ls=S8\?`0<ٳEޥQ ”R0s l $@Ng޺Z#%%8*/]/DQCCC<***PSS#3k,UՔ̴+mHo. a/ޏRH_.(2tE\IwR:6I6 &(ȳ§Olvgn(wE[imcW&ޫ(=ZChU.B;cPs%Sddw˾v8{,N\=!Ok_%*\1 C9fݿ G0LXdXMM -[lDEEpq^d~~>ێ]|ׯǰaà͛7G^^V\{0WX pÇC#** 999cGF]]/_n;V]]UVcǎPu-zVUZM rplCqOT_@X;&_ @Zv <g>Bّtb`@|u\sIG`X hݚZ\g)KߝwLΩ9BAhMU"kSmvZ=MIۮwfRbiJ m#oϽTj yZ04jA#G⥗^´i"77%%%7omѾ}{\|deeٳxbl6W_Ϝ9spU |fΜɓ'c߿m]TT{9x'cСؾ};ϟK}'СC<Ә:u*-Z^zٳ_u fQ:&3B!n}vVS.nK5u'*c?!, k7 _#TS8Rm ]5{q(p+/}!Ӌv_,_W FCqH>AAṰjNat.)`0)4jA~){9\SLA]]tݶGh4o1zh;:u*°yf$&&֦xGa„ XnS>;k۴}bv9~$-83?e4(H[!t3(a^؋_ӕzƮ]w} h4zAヹs󨬬Daa!xk.] Ʉh-Z`ɒ%x"nݺM6!55U}rrrm6TTT .-v6mN:*&ٻP";UZ7ol]#$|]@uq$&-f3o0B:rqp (lEmۖm.~<@sI  BFlf6ݺ;wo۾(]k܊)Of&#ivuaAAF1!xl*DXv6uRt>prz V]] aaC [?J=Rv)^:*PAxlC ­reٵ-[*2#5*:}2a`0 F BF2iz0j,F;X{ph0:-XwH]mMkbCH220U@+%6J8?)P6!oY56QqP%1SӴQIb ap0o۾7JBAtˌFe0 !IqfQ ¢"-PZQ +Zgd6ae)yN8MVKSmp_E*Lq4Y#Va Ŗ[d!(Ѳ%U0Pp>!@-}X}D Ϧ"A(7 h۲X`09L24NXٮkfcȒ tIݮh0h?]FmPE(CXުddZ"@\IƢPcJq!T eץo۾phQݨ@] nvߍ7KO/V,NUܛG_h-`0M&M&S6V͠.MsKNĎMhڡt,TM?!8IuS>Ez-6WvXj5.G?GeduXT eEct̮0 ;=!6Ð!n%YW x#x|#nQ`0 F BFӆq2M]sd#,UIMOF!?X\q{4 y4Q,?\A,U֥ P^)Z>X8B<acPFBձ0.yVZ:{h-T>Ӵ?P5m[ {Rsk-[\,M6Ȋʒ`0 FS BFRC(8k[7'"#2?bQ2 BB=!lcq¹شC33%o7G/EaaX[J@^?+^>X|zٙ]WiNT>hyVT5zoTbW$AbÙ9[X޸b0 !I#ZCЮw'NBXcXZضrrhI<%.qZjEXo&U@+ Ou' lݪh;"qR7Q|]ՂpdǑV eN˗jZgOׅq]aAyD>Ob#N/ppUšu<>)I[s9QFn#ڏ዇qQ5]NcSp>@׮]KBgӫP6:8q0V]Ezi!t뺤QvSg|c1C'yu )!Ih(GPٴZI׶MvFs`0 Fc šL6 QQQ@vv6 q&N0o߾/2yǎѣ F#L 00o<XZi߿?4 }YE3bqgf桝;t\C8X./zO .ģ֊.c\8|š?u!}>,GUZW∀+5-J\9ձ*F?PuhXFv##v8w ^UK߾O5˟P~ ׬Lgh5ZLH h4zA裏⭷رcoCaȐ!!8 2WƳ>ҥKӧN<[[TT{̀*% :xgUBJB_?''8]9qR`p`NTQlڤ%/T,ݕ`0Hvš5ko7?M6!&&SN}:U!!t+XԧfɌ' &ߖS͕;Cۣsxg="ŃSeR$=^s32fF NeE`0F-ׯ_N &؎bعs'Ο?/zg}<c!!!5j6n܈ZKޭ[PPPc`0֎7mذ& &MפIp9=w\p^xA`cW$*nC>iR$tk̓3;>Ր'^emac%* V<W11}pApPQ0]rcsb2 SHyǁӻӁVIȈ̐yi3"zX{rcsauvG/3Tܛ'ӟ{?p`0&BEEEHJJB`` xffb߿iiiN333QYYb:СC0LHOOHII`0{qzMϜ9sb޼yU'f &VPdo3'cEqo9pˣe FWO`pS0rUU%PBZ>Z<8q "wΝ) ੮O=9—.6V% Bem%8=~]{v<$YLũCԻ !gήwrΒP8UyorBC4`0 FcQ RFF… . vnii)!kߣᲯi49R4NP8BńyP &RU,zBBd,_/Lo)NNG0on'OĚ#k8. AB?(ފ?a]iQAh5Z<,,|!  ;U5sDm)qv\9Q]`w$Ο {KKc%, xQEϒBe!< .P& `4 tlw\lZ)=ijyf|Xph Knb t4D>w)\~+̧)]%($,L&wo %&( :n]a;yhG e,j9h:__z}T8bwd{.h .=zS`t섃`uۥؑ^}ibⱔǰh"z@@v۶Á]Կ`]8|7b0 qШ?lrmw\B\?ڿx0e7N0mjkkE $:3Cu.ނpf[&_?AXcg|x8&!7SA(~EF`txWiK\zcݬuTi"MռX4vx0@|籴h)T^qC(NoO :6'=uii*>[Noiq ߪN{Xyp%T^4^C7>x" S}g0 r$Q BшRcnkM?[kFeee|rcĉ())AII N> 6)))t70 ={r;5xlOW{{PR[Z􉘷}[Ŧ2mIS=%HvG%n% R tkpnPA KB}XTڳY߿[ bVq2" IDAT[T\8{!+ULΚuC-/`0 k̞=[tj̑] LIIAqq1n;4۰KIIAA@7ߨ:-#2i 8uX6LK)f)xe0'(=zTi-`s#*]”dEeaor]?6YU!`0j{XFШaff&F^z ӦMÇ~\`޼ӧ}c=ٳgcͅlƫ{9sիի>̜9'Oѿۺ()+]Q*㨇P:Ri޽@yޱ  >*4D?ֿ\BG',4+L͙*d+c0*:T~z04TwFɅ23}Ptܥ2qJyK8_u×!Yt ~q5c0 FӡQ BOsaʕ2e 7ߠ{5h4?F~ѣGwԩS͛7#11655?>&Luܹs~3<3g`ժU=z쟅N4F=#cU }DE/U|pn ¼<.SB9αFDz`|lVLTթ4VK93s  ѤhdB~pk; PgdНȌv!>o{݅X;{wu+pRB^*^u'@8%AGk֨:mdǑ4Xs)??7jFֺqץZUCK1H7a@M;V{]Dv+#>S}Z!;bl7*yx׷pJs`0 :L2wBk|~h)!뿼+9uBMeBBS9A;7n߮?)AP߹%~bbTBW7kZ`(uNE;갰0q&(g` Xu,(,Ѧ ?j܈"E@~Sz>N_?3  c6a&o{u "o[&9,+pfSjtBs DOǾ@QYX<2 PweEӌi7^k;Fv'^4 2!Ns 3F!C?A8!?(t\BXrGU"VF;&LB-Rld(C@(Cٔ))CeA@d*A,@VPde: mr~6;74%ʫzsνOon'gw-"W/+^QaQvt&B%DtʜaJ :ǮAZI^>0ЄGкM`Zij]e0{i1%}H0ӳ'cPUM`"$ݻjRzn]8aZy==ĢNMt_:!ޝ(鉹T)+NDNk2 >3L_PB7)BO׽*~l݊ J\{t F=FlF砍7f-c*j}a&6ntbՐQ9?XޤzKӦ|+ = 'Eq__ab;|${qߗR!,6][qLo:c) !+QBHDǘ L^^퍲'/c\q{p۬>)&'5kMB] U=-Ycf%٩Չ%[grlЪ!0hAMK3^֖=R)з/viUrxRhU,aV=d5cQ$uG #;C.BN͇&Сʕh\I{+0rH<{^Ф]ybsbD|5}8F;_ӂK׫WXf ~3ԿHh H$ %1 7px8{`aBjW[1>y')gj {.H V̕. hazX=TS&=Fz!-!CFM;R_rZ?Vt`S}N" XKX­VuXq4˴/!{@ !*^w*U6mBI& 3Ȧ']ʗn4edHcU"H:,_ɺz{ż^ geq8K 9)S`D`"KQKMC(~E6:bߐ®2exgZVqH0&p9a"$Ǽ'>[;~Vt'KϓLw-˷ Bys }BP(@~8l`C0d-Z@bbβhܸ1r91fdؒ1 |pssCZMfݻvBϞ=QBrT\'NDU-qT@%:M,\ThQJ]*Tnܰ2:uZmƱ[{%`!7'Ò%KЧO,]2 7X1l۶ G… C4k 7*ЪU+|/FTTV^|:u*&O6m`(S z˳}СCqU˖-Cv|r4l4UT9JWC|x<כ={-CRJ%M0LЬcF@Ŋ&S68l0yWc/ٰ:8r8u*y{H k''.ZdZ,z 0y20o^K=Ee` ER÷PgW\9~O[))_ohŊ[| WR\aC |*FL!+&{%X"kԨ۷og ]vi=|/^}Ze۵kYzzڵkD"awsvvfG֪ߴiST*o/ 60AغuQ( S(&ױw`w=$1ʕc,11O_콳ؼbbEcY۳7 .ul{XŊ-(+SmYz5M[+2cJO^ 0־wd٢a7xc,-bp`]$|oJz!gW"c-wvL-b,*JXckWƾB[/n>`p[woƾV8cÃ;wt/YeՋcmP*Z;97eK(F{1ְqTլVfoYت0%BPx챍*=;wL&CTT昋  SNខbcc.]hyyy!22qqq~=ŋ8|0\)۷o_rݻw#''Ç׺q]ՃӴi|1c[Jy ŗ|W:|:o,|BwâanrJĎ;0rH$$iǣg5-؋0axaR<CeǍf0Nq-QJ9s ꋥlYcSyOaUc̟^9@a 0mχv=<9ӪϩM!(B!v uB`xxxh׼Obb"L\\x999SV9'''j9LJJ{NƘjɯ02X)9P ]Ss>DuG2Wl/(U֎?%>Oxn6}W8k0}I[R貽 g>Ģ)+9aV5fC4&_1DKӉƷYГp//0@~Tয়tCiu>֨SocXy{KXO竰 5i )ުe=^!nF7Z| XuvAB!Qd;ؔP]AoHNNs3|d2t`9dB>^,"!V_g/!pxXӓw^wNj#L/w/gL=:n!0B!D|:!ʂK㮮-UW65g˖-0qDTPAo@s(~_| ? ~#v^ى%^J ԂW=neEqnnqELK$O<К;gh3xߓ[,]V͉e@͚y'MT#Gb@ )Bٳ_s/HlP'~:LIHH  5X'$$իC&ٳZ岳uPdffj.}ܸqm۶www~q222P(tjڐJuM" WE1Z땪lEHr+ѝXC%L+{ٽwty2t߈=\L=cxQXXΕ{l<9ݸ8wX*WA!4 `|D(QvvC$'N< .ߗ`5џ[a?Vjƻ3йrg 2!nӧO~(FNu놜εȇBQ~}RRRp5ݺuCjj*vڥ9#ܹ:u)Z(ZjM6i=6l@FF>d2VXʕ+ ju֐d8pJ(aսprrJV\{ , Б{πg:ܦbl=vXSWIţiǏ=p֙\Ed!Z]:6< 5&k5֫&55>txzW89Dl$}{mr_}Ik>F,X gaWoD >t4* yhCs$?BT*5؆%oFNѽ{wL2Xf 7o۷ocr'OF*U Y IDAT%֭ի`oѼysT*̜9S:sœ'OдiSZ ~)F6mӔ رc7`ذaXn:t耓'Ob…r5ڴi[nᣏ>DZyföioChFB|_0ZcgWbFB.|LWtr1GG>\9A鈶7a0@8 zIO cݿoC[Ǩm ƟYxFzkO.k8P&>/y}/?!x,PM>]Ϝ`5wsڷ/O%[ڣwpXKB!6+6i$XzءCߟIRvmϞ=cQQQۛyxx-Zɓ'Yƍ;eGf:Λ7+W5j[+#H7onP(P(LckqKֲ%]4UdBqkӆ1*['=ɼx>k:wf;|];6o6T͝Xݺp)S8xrSٓ>96Q;VU(sue,5Uxd5c+u Viܘ8co3Vc^ݿ`0Q,YYU2OeP`ї/svfp-w0c^^sMEwbiSq6c%K2v0;3n3;`][q2ؾ}r?KLN휄6۪P3ϟ{!33 hժV999 :իWx9ڵkNÆ qqddd %%_ɮѸy&p3_RuQ }P2Z_t"ς}=ұQw qb ,?cA]lu+L;^|}rbw6`JibXxGPM3|mQMnAA|>} 1ĢN6u=..of-[#pEq:AA|hGY* UbX?!$vTvn@l,0>p`KԠS8|7Fj5׫T l'ٶHaH]B<=y\cjeƆF|xcmD'ɠAYl|%v ʤfIԪ̹KF̝ K%E^6z@3sۡsڥhu폟zF`ֱYP;BJ@ӦoPR%'ۇaGco-O>SiˆyOV.Ç,_&ҥQGkd.V'ŋ(`o%ӧEZU<)ˏ#Dp9;]X5h?a, Y:PJd_X"5/x3dhA8=4šv-!7B">Ơo 8vm![$uⶋbtӏNBs=Ԋ*T~}tu3}G׏pޟB.Р7:G"=Cl§ ; 3*o\fXO+7kwCPײKF5hwQ[q8;Ԕ:$`1WWvm~!/.\&OeAPի*BWb_E;7!b%Dt| cMރ/M<ɸq@^<)|Xgj8?Y1"ط&&%6M1eK&:*jUM{qa}mҥy:R.tE:~S;|=ځB%Dty7W3D| o6i/R?CqUT v\Pxy q3s&٨n&tvŐJq 4ZM$s[NӍu,[Pkܢ~)I m\J4ޗF7o.|,5|InO}B-6{$(!$cLo!wص!eߟ;qBorawXa5D^9ww>T6q߆btqsL2 'brG 70r'%w&sr߸:ɉ'~Vqw)7m*Wмls] maBqH1RFuQOyBAN/:u{hFTѿVun`jѭӳ%2~`xim>#s?#[%b|ڣmÃg={/֭`yr\y2WZY }~3l%!%DtʞC4 O^:. Ԩlb%BX˜)W8u jG#t]oiluS4k{+mk3ްի >e 6'?{jN)u_ij,..|;5klN G;vxREKaI%{(YvD5u]wzBn!x#Y3+/gK[gVsc15;q"V=pu^F&07Eߤ5s?^pGtcE&m1ל8?b oFbiЀyq( |=ο^ m[ƿ' ? U sB!oB* DGG#00_>8.-- C <<<ТE $&&,ƍC.cƌAFFFr1,X˗jժm۶<ŋ;w?f~T*~R\sL3kjْo|Ν' &fVتA RoOVAVX,J3g΄R4ߠ_oĉ%7scy=àESV͂96ed|y>xBq5ݝr)WK|,uFdHWr動{G`_%uIaֳK6[r={d,::Y5jԈ999'ORXÆ Y"Eٳي+XYѢEU611:uUVӧ3WWW3'Of aÆkײ;2A۵ʥJ*1???h"dĂؓ'OL322ar[0j2t ezdlH/cs2{1kc \v(Nq*cE2vq2t(c ^s|};|XXڴal]Xeg[}B0BBB;{ּg2V8cǒ cWq||̯gHr2c[ /S^ݼ:*3=[8clVڷ7͛yz2vܚ:ubD &L`52@RD6t*ugt`+N`W\a*ʦ'ŪgbmܷUN&{%X"kԨ۷og ]vi=|/^}Ze۵kYzzڵkD"awsvvfG֪ߴiSg;wNsիL&iӦx减'#A% IIaۛE}-c3 ߫ 35lȘ-?܄1l0X֌i^ƍ2n2ؘ1} 5e \˵ݻÇk]k{.N:uu",,Ls,$$-[:gT_Ϸ|tIk{xÆqqT/jir3gg>άM`0Mر# )Z"i}lX|4Ç .hI,>`pr|뒂%$kkWqU$s |Q|{|՛[R˖6X.6H ȝHʿ{  E,ګjcP 8w$qB?:!LJJBpp0<<?;#Xˌ+ [ʱr<ܽ 2|]G|Ţxѿ *c;Cf=o<bZǠw8~"?.ŻПbň?SijN'b/ o璓︿?cκ_ZjHNN zs"*5xNx ^z겕*U=cJB7}}0}{輼Dł$xqķ[h޲dN 2ld`_eVZԩ÷mEj &__^ [hagei@?xQkIJsYZÆ3ܙ'ϚXLn͓IIo:E:|8P6']ߵ%K.^Y CBW!!*V<=E%CrѼ\sͱUy>҃KO<w'w-Ve<ˠTR~~ w'w#b;:!ʂK㮮-UW65L9nӻ.:a&v fxrfe(<=ym0z4o_ިU J\,…|:Ob8kcᫎFFD_?io6^tϛ? ͛eʘ~1z׏_oQ#X7թSQm/[E LZ{W׮kX\WW~Sկ@ ,8J3?^2 JVB1{Jɸn?{/!5=ן\Gjz*RSG w[qw-bŴٵ8GQ;!wC$?]e"'=ߟJ8:wss 澆;sc[Hv$y %^ݻlvNx*TxR_D/;w>~CϓE`Ey,y_992`O4ʕy<ϨdI;sR|8\\>C|oQQ`/_(R{~Ϟ#lYÍٝ58AA{ ǐ<g!'*W`rP\_t9999 &ssvd99ɜPF"AL$L# ǃ!]Le&_f!## 2L e2d 3'Yxzg^+eTYa98K]!IpLN3R'8I d2'H$2H D@"B"H!H  2RA AB&BH!R T" $A HL]H H4> >Wгx,f,7Ϊz+!w^]խ@{JL>u`^5VBB_O 0X7YDŽuO} ;v9K(o'>Oqvo󗵖/?_ڽ/p!+Wݻڃ|:1M^OsoOib9{L%ŸN<2g_I[rVwݬx!{%={\БXD>= :B̦{?ONCCCq1k-,Aj #իC&ٳ+;;IIIѣ9׭[Wj-,7APF 2?P|yM x9$o$!$B!R7cAR&&V(/އ𫯾{TkذXrr2z*ɵ[ۙD"ac}{uC/cwel(Л4iJ.m>SNvB!BY =z`;v,*Vٳ8z(5j߿?6l؀[n!((~nܸ1._' +V;wp>ѨQ#TRC ;wf͚a߾}ZDGGcѢEBݺu?bزeVobzz:j׮/^`ĉdXxf{%KG!Bqd>!T(>}:6mڄOf͚3gZj)3`lܸ7o$O>wFVVñh"Ԯ];uϣH"ѣ ǪUJ*aԩ3cܸq_RмysĠ|"B!Bѯ'B!B,c]B!B&B!BB!B%B!(!$B!E !!B!8(J !B!AQBH!B!BB!BqPB!BB!B%B!(!$B!E !!B!8(J !B!AQBH!B!BB!BqPB!BB!B%B!(!$B!E !!B!8(J !B!AQBH!B!BB!BqPB!BB!B%B!(!$B!E !!B!8(J !B!ijx" IDATAQBH!B!BB!BqPB!BB!B%B!(!$B!E !!B!8(J !B!AQBH!B!BB!BqPB!BB!B%B!*!T(F`` Q~}>|ؤiii2d|||-Z 111_CaРAQd2ʗ/o7o DE;B!B*!ׯ,Y>}`ҥd@||z1DDD`۶m=z4.\YfqV-[`۶m(VM+##w#B!s 1VA OFW_aܸqW^zʼn'?ҥ ѣGFDD6mڤ)oooHRt/_͛7 6ydٳuA\\?.oL!B!9LΝ;!9AԩSw޺$H!;;[sR/,Y111dfVB!b9IoXfxx}}xxx8233qu;v,ZlmZ|B!B$w1ܿߢ 5d޽8|0bbb,O!B!p0++ ...j޷.c`]}1~x >!!!f'B!k9̤5777z*/_j޷ Ǐc̙fMR!## ,#H̚H!B)JT*1ƠR !8LVp_d@@@r;w.F41t0pm222hBB!Vz9)Ra&! űcǐLBBA@hhHHH;͊ӧHOOǂ 0|+W;wƮ]KÇ { \ 899t8[RXѳK액Lx{{S0 anݰh"^Ǐ( _l"4TXQDu صkv CsNt`HHHmgҹD&+'''jB[RXѳK)ϣ)QD<1=wرcQbE_gϞѣGѨQ#@aܺu AAA<ƍ˘8q"b ܹsgΜAJ4׸x"شi!B! C6oOHaDVˆ[R1ƐŋӧSڸoCB!8L/^4? 7âe{!B!AQa!HKD">*ܒ.55zaȀBB!D 4,c&qJ !B!AQBH!B!BB!BqPB!&Hgαn:mV ^z!&& &B!* @׮]ͮ7k,Ԯ];kx^z3f`̙ZגH$1bV?׳;v@*UZjaZ駘;w.^xQ@SQBH!B X;kwuuźup 1-''f玏G޽$ܹ3\)SZ5TP6mYDB!sNԬYB֭Yf~@\\$ R)~wɓ\ *`ƌP*~fIRlذ@!C^PdIxxx <<gΜѱc|+W͛cԩK.!""EǏk/W.]Uv5.HrJ_|P^=" SLJk޼9ƌh,Y5kx.]va 0,_\\ǎm6"BB!bWRRRлwo <W^o]1'"22m۶Ejj*ѰaC@ѢEaҥKvZ,^УGL0ժUѣGkgddiӦHNN?/K:qy͛78вeKԩSϟDFF{0k,t.]q}oÅ rJ[sѪaxxxX`>s9rDuN:VZikӦ N:u,<<OFvvٿ ysd!KIOwNz6_،, \С`ڑi Ej :ѠtMo]7#3@P1Cɭ@c!m`2`d(JtK AP[^ L0۷oĉ d|rۼy3?~'-- iii~hh("##C{ ٳ5֮] ߨXk_~6m45k&O3fhլYӧOTP˗/Ǒ#GвeKIII1___h B@JJs$z q@{_tۿtĔĂp]\|pzv W]-0GWqNN,$VZhٲ%WH]Ϟ=3Zohܸ1QH|f/vښdИ,|>sqa;z()yUR {hL:uիh@F!==wYVJ yCg>֋[ : Ý4hTU,Mÿi.b!m# /c$ ~8pժUòe۷o뭓>:t޽{iӦAPum77F ,Y ӧz˔/_ɓcL^zz::u .?Լ/4m9 S.ZuA08DZRSSuɓ'`,)x$$ctԨXt$}BB''_.٠A|gHLL3~Gfx-['OFXX*T[niU/5k"))ɤI'SUVZaS3fضm*aaa|2ʔ)kɩ75u?hlUT7ĉ(RJ*eK 1 MŞz+arX|,{/9}4K;wwAll,=zUʖ- .x1rrrPR%ؾ};n޼KbZ-[,x^z;wF||<ڵ x۴i'N|||0~|9O>>-r999СCAQfM̟?RTo ¾}n>axxxh]'OJB6mPfM?ŋה2e }]t;vD.]PBs0 Ù3g#F ** ӦM3XϘ `˖-Xz5BCCk.ivxvލ!C}~f +Ų BaՆ8֛ZLԙ_sP" 4G_ <0@c[/m_Ŀ@ci!%Cִ,H.x8{BW="##\5Kz聰0DGGt(vaʕؽ{78}c&q!T(F`` Q~}=钖!ChѢFx! 45jԀL&Cuڵk4ij׮E" :ts{!#{ZATaF(3v Cb.\Iplٲ~aɒ%ӧ.] LCDDmۆѣGc…x!5ko9-[`۶m(VsڵXn֭L0ׯ_GqQQ~_b_*P7 9yap}y2t`7sq !$ 9ðDJ : bIO>۷c޼y7o#GL24i;vS?O?믿B*ϴW/s?~<ߞ.wի1x`L0 (QfΜ)ƯL씽4B!`IN.>#u,K,/UJd=)X!$;wL&CTT昋  SN޽{z]tBdd$38Xvpww:VD 4iՈX'Nb""vq_ h]$Ph))"B!o Iolwxx}}xxx8233qx -K9;iwP{iTyEXcT]ݗ~v_C,Bad_51ܿߢ 5q)S>Q#o >rhȪ0{bb/B!$YYYpqqwU%ucÇݻ7*TO>N=v+]4RS*UA,u.! ƃ~_c*jɩ]29U=BHa0 ^|}K `)233Ѿ}{ddd ...BSdgg})J{~V !c N(^^蟳Fb vӰv w-PP>S{INgCH!o}mXf8LB|lRטlt.]ž={PJ#5{l+s졇Оz7i.:Ҟ &9UKIOBXYX!xگy7'0 ahh(_t ?>#88آcӧ~Wlݺ7<B5}t[iA! tтO;/`);[ {^=O3okrMӻM޴,TvQY\\,+z'" eqUDcVU**Pr҃w$I3L~GLd~^*[+eSm|(AR?k꫘9sf]tMxڷc&B Ǡ1χjŦM, ^u8WUUɓ4磺s[mm-oߎsBJӟ'{xq;cj` tEn~6 $jš?Bta Ŗ4붢6;MP+r nzjyl¯^1Xr%oիRpw-..JBy7A{O?cȐ!PTذa>=~i|=5,E4J |xxQ]]C_GYY^{5~+V-[PZZT!|p-㈍K/1Dѣ;LOFSS~i1cpu^x˘2e [o^nsm"pu'⌄"'8UwJi/)G08|ii G.HOVT B`chk{@AAo{pp0^}U,[ ^5 F~{{;233QXX_p#G"33[nw!J`4Bx7q}a֭{aG+p0 T*iQTO`lܸ=F#Kdee=x V\+Wί]#`7t钴'cRFUTb6?SW-J.j kBHۑ ^X̘1Xz5x ڵ * j_5~NNBCC+W:x ^Sزe ϔъ tM`@~~>{Q۶mÜ9s<6 ӦM#<{=vf͚0$$$oF]]!CxDO:VTxWp`0g|W4i~v{ȩiӦ{ɄիW{;a[t͙3ע(Ϡ2:֭CEEQTTӧy`ZAlڴ 555hii={S/^ &׿{l6SzA>5m5x™ ]C_alpi?3%)wS_Q\ xpB~8q+p Daa!fΜjTVVbʔ)plٲ?36l؀͛7,X˖-ȑ#-X0uTTVV7=aٻw/&L ڵkc~ԄkǏg==mXz5n;v z+.^ٳgcҤI8r^yxꩧxmٲObϞ==~wq13hRF)`M0 (o*GNlb:\i7;#chq,Óq:+Si!͘3_}~S[]A 2 ju5*M~WVVfa޼yHII rb ..wk$.55˖-öm"888Wz-FhjjBSShcǢ˗//qo޼8}4*ŋ_?HMMuFzjX+WtlB_|{5\{ ł*HPCHp)fii(k*S:ՌFDD)%D8o:,IgM:hl@tH2ZF9-2 ee´GT Z"M7 ´4 2ڭ=%[,YU9Ș1cp5`ԨQk1c ̟?^۶m6n܈3gΠViyyy~f X}ꩧ0b޽Ì/@XXo;08sL y_8q'Om+ڊ . 9c2PSSBZ順2(JaÓQT!t5:C"\9 !SHHEicbӒ`HnEm{-B"ˡeH6*9uh9pN1 5C(jN)F<ըzTT?6n܈G}@Zpc"5k0c DDD}Ӧ~111` dddoNJ+yf V̝;ׯmXc94s0Y0^#T5g¸a?TuakqU1*G\ZCa'v\jWeHd9UD⸑AP0ЪFɓj*:t:|/0طoӱb 7(--#t;8|0Ҩj1bO^;2n8 IDAT?~iii=8sJ18wÇcm{EXX3:(%ǎCrr2K 5AA0Dنi_ ~Dai?b e۲״=:GIsJP_ϟǎ;P[[#FqVYYY(//ǶmpYlذ;wnzz:Ν;bbtMo~۷ΝᅬNT^{zF#xnwu?Ξ=> zwW_7|{ѣG?ѯwy'Ο?'OĮ]O`ٲe>FWWqaX,TTTgΜ7`ƌ}z/PCH8kQD22E @COhiNzbTnVD Bp|ט={6rrrrJf;wҥK{y5k3 #}W!~Q\(ݜӒx]kJ(.OF9-H.qD:\i!A- E: O>DX|׮]ks=t:ǹ'1{lKX|9`ժUXjo0f-ؾ}뇅m[h㪫BQQk۸\1|k4i&Nu?!e0lB‹)]+ןܴ(sV*9奯- כ*JkP(w}Y et:6nܨ PCHp pBQ=j%8-CUq-J7-i5aG_># BIMM]wݥ~íފ,:SBʀ[+6\Qd vL$nS"g1n'5-Q.l'XQT;{ް 0= @Hض-PWU+l?٩S BP.W! h":ґ CyW_a 8]àd4, '/P$V!ZʖJGIÐ*3BNaH5f*]*FWڡ>[ԢC ਿQ:BP(5hBZ F AhCwu c0ZvqTpB!rQ1-:u[T9R9CHk) B3R4L M6 Q8pAn3^̆5uB%X6KP}SE u!"JFUjцEz\f#ӕ3 B ! ldmBբ)e6p9MHa%Bg`g*!P(3P]b#\TsfB UĜR( 2P2q4Fft&+!\*=^e'@}=#a@h}sl]AP?BTT^֢,BUMw$ןgP( 2P2q(ER%Yڼ#~T$RȸB5wmd=#!T:*!TP;RFϋu BPR6.&,Hd.)?9"-a@Cg$Ŝ*u^je(}Ժ*Qq1JSB!P(}DR?k꫘9sf]tMx甖Aj)ɈP,m ΀ ⶰVHHUfa%A]Rs fBtٺѢkNUrTa!tjQB(-܂nǭ^yyy۫_zf3V\'x^* wy'obT*f͘:u*_WyԄkǏg==mXz5n;v z+.^ٳgcҤI8r^yxꩧxmٲOz;o Ѽ8pnFirxb1tP5k:AYpQs_|E7k֬qnۼy3RSSqi :Tŋ;~G.fggcXbV\/77?8 33/"كkƯ.ӧOmOLLbAUUs?Mضm֮]koǞ={zߏ7x=|PXjo߿/hnn7|\׼x"{9}xqm?UW]?ʴfFgTĎ0/-3*Oˮ5"}ʊRLK{#/0/>#kQ@ l4*BhXNG/M1cp5`ԨQ(,,͛m۶+dBXX{7o)..F^^ P穧7|#V\\/aaa0G/Əĉƣ> g!!!=?&& àA|tSFFnvX߷V̝;GAqqq)L:{>& aH;{Car"`kD<>|0 8tƍ=??())镞P 65 !^@jn0b5a mZ 65R#RbnA}GZZ"Р3 >4^#:9MOFuu^ < ̱<-\TN|a ָ墕7ϋ73P|Z(Zm}9ydZ N|tf1Czz:VXq!33}s'77+" fjĈ駟rJwu7n?4ddd9ulnnƹs^>#Ȱw^!99ٯO駟g}&8`$''{RVVVd2yl7L ŋ:cf|||@_ a%MYv7-Q,"BV%C AE̩Q)r^sL$O1JʖJ%cm8Lʳ1N)HPc "_8>oo>;w>;Q^{-{2x3zΨW_7|{ѣG??wy'Ο?'OĮ]O`ٲe>ƺurJ_Bjj*Q]]6~| f̐3!@PPv.ߛ鱄J?. H: 1=>䏴4* :ڣFMn- Z-6*B9aF=gEKtH4B8|^v-D6;5BH !2 _ٳg#''+Ws=4K,ANN&Lш}aΜ9qw#//EEE)3gĴi`4]#vZ9F#fϞ\[kIm݆?e˖`0d2onǵ^\<r̙9s`޼yGbb">c|;v,N,Y>|+ Gbb͹lΝ;t>E^Mѐ͞Q{s,0=5!fȅˊ H(Dd)6R22e闲kM 0idNw:*uz:eֵ@6ッ:0CV9Y\8U HlZʻb`ϽG˦h6" G>ȊɒEF``6BsԹ=3*ϣډ`xS r0l0|'O?5t{q_w8=4%%Ep?1ٳg㥗^Vh>&ج%33۷o}0ۼm-=p\uU(**}/c+?骯&M'ܷc-KGUǠL&^5MUp_UUU{Pt:kc9VoAI/$lP-BD4a#D. vkۜڬf؁Z& \^@NÏrZAqZѫb0qFXA C8vX `ر^&ZTT^;v,qĉ E͕ +U)rb٪#4M:XY)̹#ZJٙ~JKj 6V4٨]R:j yFŵP(;뮻o[_ICCս"Ο?V6mrnX,xQPP"_UU'OGuu5}Zl߾sh_=4 ^z%W^yIII2eJ^OՊ>Lu{-9!\rx2,6 j0 piDӄCQTg-C&Z#v;#x^6 :!@XSm&քHm>hP(J[Rao?j ::k9[blقR` /[nǏGll,^z%v<9z(>CӧԄ~;d$%%_WX,L8|[۽*/ml|AN!P( Co&qlݺ G}+¹0PSJO>qFttt ??[l+Ɋi!f0ѰhgGT7WZ:V+DFZ䞋HG_/~]>- {^>9->$Y \p^v9M;eVjJq,t"g+#(^WZ BdDXPR"#G":$qZ@@-֩Su$66Rjq¡CQX&[3B47GjVfdㅢ$.BhwB7URM#d;BE"J?a:BxRWChWQUzLY-m*#b&KҒu +`i1 0K˨VF,]:D>3jM ; %u%mgTmV-Q97-bɧ ~\R&BP(R$%"ͥ2wtb61x\vLFGPtCŨ'C^R5M CHf86B  XL-Huv̉s0[h`R 6!ZpfkI BP(j)!O vbWL q42a\:ZH2a``h1عKDas 8^uulBd(K!2 )UUh1H5zP( 2"aiHiBD3SF IDAT#_QT&L$}Un-ΞbZbdGTaDa]/4@xA "OQC(vݦE܂~0BP(j)!l5a-F&K)\luaS@!!zt4"s˪Qj-] v{h} J-o5BP(j)PU6>љlDM&]FEȖ=[ˑ6t;q-Eqks+9e޵rT fidOP( BJ!R4ޭw #5"Uyn>ݲcqڻOu[T'UieIeQ9wY1Yh@E9ZD6M59UМGʨ"C3bw_bZdLP( e @ !E2 0SOiIT&.CK6iiFT" \G(l'XtRm^ aZDںp,ZE@e%KTAsJP(@BJ JBXm ~!K͑Z=ȸ#{W/Un |G_wċ!3α"o"F(.!MeX0;*{02JP(JϠpdh.h!5GtE8"n_:.- 50=2޹7q[_i<h; BtH46Tϳ@uyS9:78( BC !Eԑh0* *%t<#EN-II@Eo:jJN!F!-z=X C6dV |Q\%'{\+<攫!4}kڴsQvu+E#9SjQ9eu'eBP( *Cxb Xh6lFYfa߾}^#`֬Yxwp=gťK_gΜ{aL>xdlڴ PPP]vax1{lկ~]nw񓒠X,ػ%%#ݵh#FSHII@ULjƑT"gq4T eq Zs^/N?Z(3)"ZCũSiP( eQZ\8p۶m7EaԨQx衇w^c{=߿;vy7x#j*lݺչ#|.eyX&<(&IN#08-nqn\TΩ``,TU=vI>-hPRWB(S,St~="SWEP( 204Ά`mw>/ơC0n8hooGI k$= Պj;v,pԩ`{/;TTT?3I섳hK,[h6'Cݞ{Q@t(kQ0|F"#r^:XF BfR#RA@p|"RGy:|h BP(3vtt (ȳ>,88|o%8=0i$lذ;wIJe˰uV,_oB5imZԴH;5hPţ{S-9R}DZ-~rZQE˨Qkq;͜Jqqqlg#[/E*-Uxnexp7* BP [Et:ry;aܿbǷ~뮻< ?aܹxgclj=%bI2vҚXjp\ٴ&KJ Ȑzg*- _HS 7OfԒyZ 7vŸ'D̨Ly:S( Wl6kX< Ch2PYY鱝ۖcS}]cӦMHHH@^^osnNhh(t:c͚5=z@@3v8lcƠX%# PTstR;hX*mwOq.nBhBͩGT΋M nZDIqjunX z7亁@P(ޱfkF C8vX>TTTa0vX|r!CZ }U@PkT~iIH%kj2:~4~Yf.A}F6s!%hvGǏƱK BWj5,E!Ǎ7ވ~˗/?OL6 eeeX~s+V`ἹǤIp-`͚5x1m4v<yQ__S{1}ݸk_ʹߟ'z̙3>(6mڄ ;f̘'J~NYttissq8E?ӄz= +!lv@@\iUn TV@tZTMZɍFu H̅Ef.vW\܇aTӢR'`ظq#z!F|훗ݻwC͛d{qA̜9oᡇ| I 7CX^8},C. - RjCFx4MO@Q%\ ^-]/\Z*5FGݘBP(ˉAe)Av*SGkB(`MB8pF) !`Tg3Dui JH:7>O˳c@9/F9/!KVkbNLy8Tu(:X-@P4VNJq?CIt^3@y- B !n!/K2db&`|4i!y<ajD*'aFtdFgJi!иGQs M6@ eL;64<"@X`88Xco p'3$93`ggfID+BP(@B$jnC,Bh%Ұ2b }Ha3d1@$Zma šVh$=/%T*Qf쿖,YP9OE4DKCquqH.r*0GkBP(5ClvEhdd.ɣM(4\t#ń7Ǐ?J!*;F.) TƇL;;_-jcPe`#5z'ntq4JK%]IP( 5jT41mT uK2Èk{IP u7-@?ua !P.0s0y y8~8Vs}v yopii`SZp*ػ#=`sjAgȗZGHP((R2>.0!q~A-.] y\@:6l(*FE=gM!ŶdC BRX_ |99͈@ Vӳ2k60l*CBnu돖@jI BP R$$%--@s3k/cٔP4M@ԄIO(r!U1*Išad7 !P B/TŨH3CN%|HI2CaOQU%ȩVMar=:{rZD>n<ș>C؄8Y{].BP( 5Ifyz8wA %t ѨB9is4KE4@@euv=h0`k= +g"]FYsZ]-x\k|EE)7$ϐ',( ChiP( e@ !%p=mYvLiD#a##Q^VwﵰD XzFZP\r zM; mCZh,,t^X^5lTt0=}#CJBP(5ILΰc5=IԨ4068dpa919h27%p%vqw'h=@-O_ՒT qV.H-ReA!=24.e.e4T(ȫD8`t5N-%IP(e5d~_CH^R22X&QR.zjy l3`P$X-yNZ1tu%} LPQZrsQTheBxǀ5/JE B\NPCH,22q#pR2E(Ʋ3" 4!^ -z줤IŽPz UVK^$BEK&񹁻^E}h9jND C;T˄ 8^smhP( e@ !%vd#r6j-,S )Q:TpE9k-Fi cDtZ h0 4)Z t39 9ةQ9?]( ׭- 3zlP( rA !E4ݢ`rzd œut <8kESDw3 ta=1i"_:.M/ 8<@Bh}dl*%!CA-Cc'CS7 CT0<*e-ܵ5[ BP(j)‹6(؄ y T1*7auń9SzCuxP8F X~h1ii WPKP;KS(+A9`Z vԬZZ \ў~>@`ͩk}Z( B(PCH 8vo6Ш |, '&Nā^M0(H.XEkV/QFĀzEcq$MZ%6thPm~7FSS&!Ĥ8Zs4`CEDSi!P( j) hs1=1ٰ[@؉4M/ E4`5z&,`%ršc`@k}TM_)^r~R~@Ѱ#\DDG"#*# ]rٮ^IR{^q#рfM B F!X,X|9QPPݻwulSS.]  ޷oJd2{E[pggb…^Gvv6^b;*Fɓ>ɴI Q)qlz!W{džmZ %pQg$sѾj34EʔTFlJ hBP(!\x1^x,Z6lFYowCBY;{>K.ᗿ%θ-T>ӧ?<,YM6u>ѣx/b…xb@o% bfe !㫩Lb"­|*,P#T  ~'`S P|?f˝=+8FHŖ_~5b6&72?Hf0>j !vZ4G4_- BP(N4J `۶mh"5 =+z{gcǎ7ooDvv6VZ[:}G 4,]w&sѢE1bKt:uq;!:)Bb H;)W`}8>bEbr׶SӦ]7s֩]$'{ଓWsWpddNj?⪴VډrE]TO?=6?)U0[һ IDATZ7 UKdE_ ]k N-~jiZ( B( ۡhd綠 vmؿ?*D;4Bڵ ]]lJaKK vލE9 | ŻgXjt:::::>baNٕ# 61i"NԞXgFw0x3k۔)RC#Z|,f/Q\\D p) !Φ2Z >$P(nkfY@Ũ01ib>#AjD*W6*,ѨiAuku0>wZ&%On BP >|0 8tƍ=??())=zVIZcǎٳ @b„  ^M7݄^08w'_΀QQoCj[5槖@5K)'cz g jYZCKf&p<`6^\W(B聏χ(U 鍖İDDDh>iP( e0h aee%L&vB׺=op[YY aDu}SNB1;v+c̝;_h^@'N8x/s QQhlbwjqe!~kԺVW@ZLߔ}qIK7}pԋPԋi*>kaVIQ0 'A->>*-Z( B Cс :`GC44XBX_}]ߣձp4ilقy'5ko>|=)fn 6)pN&Le> &Ԩ4(H.\^q#`YpA<%_v䴸wwFG㇋?Q gbd]_b釖ɓqX@Ҡvenϫ #[BLK_M!%=hnĵĜR( 2040$$fԪN9aܿbGHH~;~ .!gSwD6Bij c↛陜2E@gY4,I00XԓJ _}')ZBZ|SaE~YaUߴ!7> CFꓖQvKaI˔)8R}->khqKKbjT|]u@R( l6kX< Ch2PYY鱝ۖcS}]߃||s{>^vJi CIi Nߴ]k`0 >}iFT0""N*K4!,OOO/&tؿ<f - kY; D^^ UƠ{A%/ s'W^(8ZC-2!GZVKQRj i4Z'lE DPԨ)r4b煦^ȇ5Fn-vB qeu _P)-ve¼ЦW"Z܀Z_u Y(`08U* dT n`|xE]8"Q0ֶѥMj7KeK᠌hoǵk2e`d섦lD͌C4/nCN^<-xE p(.=ByQB̝t:B4j>:"\,%nFC;&xĠD'<+ #0\\̺"+F &ֳO Gou@?oiOqZ*i6:a6fEFO^Nn@hLrBC{떰 pr݊{A;oEFv`0X@PVu;V; ͼᏋ`TFT Eܷ9n[$K1Ų@P xxiSJKO>+dL&MD? ؗ ޔ9qp rb.TregQ-$7K855\GYu{@TP6`0O, d( k+AOz6ٍ8 N`CzW!JhitAE*-)@>;{%΋VES<@3sRK hZZϲD`G%+%|l^+gtqENZw1%b۹mf `T%X@P4M+Fp`2ԒՈC "w]wg'4CBׁ7٩QG#9WqѢhgERj8N)iהZ-==ĭTJKCφk8rU8e0 i uI[@#)8 E1- H4X@oKw/÷iPB @ۆi>>>$-1SFB.+=ZKx8p|*@bDv7E W&Mt:I-r#K\+RH N%Zd `<% Tu>k f''wl*Qfu@Ǡ}:$Rhpwrǁy{Z8J½±+SڠoΗ  7G7rPNC6h@EN{lgoŕ{W$kj1:a"uĎ ;PTR$A?YZDSGϔ%(ѾM `<856F&%øC10fFUp;"R ~9+,W$2 PVh9E -<7 %>#ZHOsB(i ƸX#=|O:;)"\b⟻ҝKZJKV cD xa!CyJyeVP:e p)%'!\D( AR^K]Z@3ϧu+Ubi8E -&5"V-zu2Q.3%d ",Sya'`0X@P Lp[DapRp<TmG$.F]%,Dˣ6(fu Y{q6F(V37Pk)Ri9|X;$9H)t -gHݱfjak)wSb[Wt$@>ND`027i@b\@FF,ߒ'g'{)5Gt mVK\ey;UG:-%7ii@ӦztO/qstCk xv)NE 嵴 l /P7FZ(kHWl; +1egoH Z9 `>SǕIZ6Μ!i഼cF%* /t2-rf;F-׭VEאm`0tX@Pd&hՠ6.ɂT#%EwM8)մn͍B@ E-C-DtDO$uS GYyəJki^…H:/zzHU&츰EVRb-u&q%j$ņt xZ`!Cy--2Bͱvok#$p?z(,ԘY M0'pxZ4>=ҴP-R59,"TC uQ*Z4]CRix^U-՝#NQ `<ɰ8|y hkf^ zaMv1_@*nxx /VRs'j-Mnk\νL=3q+ߧj|}Z{YPKw7&͛ hl!^puonyaM5PЫ!nS9UҠ{J#- `<-T&M\\\T:;w`Ĉ򂫫+:t考6Zٳmڴ`ƍ3t:t{/xG׮]f;}rrrgϚmDO?EJJ .\~<~^^&MWWWEgeSQZhg񰇋ai#ĔJ NP. {5-gAX1yڝd-)mNaQCKrd;N\7wwKCdǃH9/"lW͍ b qٮ\Æի:-6 \-QQx4ai Tp߾}o?ñm6bĉ]iii1e5 ۷oVŻk[o5kbΝ1b}|_lӧOzl$iVݠ7֟wwt́?mT#u EɕH%&U8y$vHm壯?X`/M(~ M:{:9ǒjC%^"DDCi/K hILgq%Cxgiy.9t'*`0'*fdd 44"-3&&y[rm^GDD՚ǣcǎҥҺLVco5_4m.ۨLGA!AjE$ؐj4}kVs#_+6p+K<i''RHpƒcD+Η(S@6u$j18Х~.<˫!%FBj`빭g0 ɥx<}훝 ln[56oތT̞=[{ytp 4m 5T#!-kGG~9$UjII߂erobK ZZI8/s#vNRP pcɔgÞ/g~A^fSV{(f̀+Wjv;ВCQi8-[R_+TZ xJ2h񸓓Sryl_?mmkEEE0aF H{39Aր6yȬBx9G=%kn8=xfhp<;f.mEcE񼈺J-~n~9{lQm^ܽKqpQ։E _Yַ!jԠU/\7>GEgR(ވb `lQTTdZ\2Bvvy8 IDAT:du>byO~|d-11s$߸pjy1ʔC7UGVl\R>V-7W|3JRrzZ4 +TR~Tsݱj}vv6nrSf}4lh3hg0 aC }||m1___5ָݻw1c Ν;ą p=#Ƹ 1\3lVYe֢דR>C^e5rkS;l֚HҼ9B,h-ne۶ԁ'%Ra0 tZQ1T0&&}ɓ1i$,Z ĬYʶ{7f6099x饗0}t̟? (--1cn޼va2e ƌ$t 5ٳ_@@Z-z-ZTyQ uQZ@o/9e|u+1fJdF<\<5`-lWxtn(6;fM|ɨeN̓j!;hy 5DDy@jҲc]Cܭs8s )jjd bٸ/?QGGI6GV[RF%#@V`0 B `Ŋ?~b)vk8ѯQ? -vr2xz}`{%' ՝#\q^vǩpI /`0 ƓC 0sLdee!??HLL4fҥ(..F@@Xp!]\l۶ V_UVصkp̝;0vҥ#x|)lQybdž wKf/ૃ_qwT>ka5j [#RbcM<3D 9XI}Kqy3u{5kRHp]`Ipx4^/C_$JҾ=Iweň9۲Hn9oQ爁ja0 iJBqbg:B,Z;2JSF%i1OzhbċVwܒ2ZQb[ hk%\sb"!%Su$7J͟(g"#n2v''pGBZ.X&iR.$ʽƅp0QEgׯSbċ5VBZ@yD,Xtl x`!C8 z-(_>"#D, YΝԅ/Fo}kw8Nb+_4K+yxyy@0=.6s8LҴeS/}-[ts4琴n8uJpc<'} w A.v_-ESt0\ڃPZ%%*H-ǿ"-D[n$-4pẐv`0X@P+] %-ɢM In /Ϭ?Ę5 ZTm2:f4g>]Z[wjHJ.fy3KLrl:0JS0J wj$();/fZ,]9ɁOVٳ ǢZ[Ht+Z=Č'iݩvhpM>ҨV#Չ=`02Gk b4I& 9 Х /ԛ{xo㾏ߔoOks#uE:ugC l0%͍|P[!z ]CP4lHŃŷ5!Q2nܿ]7uu:jrوϽL57oJCHJ"ۚZUW/S7jx-_ǜsF2 B*(b&m995H൸/)Eb8񞏭>_ {ChO>yXϨ5:;󲴸[KjD5kf/3_[.[Kz@`a0)#Y9-mW'OJڍ8$}rZw'D⏁[O/E x`!CZ=E3@\νvXy<hHd-qSΓLyzʘsgK]&i7>QWcE9d;P:FөrZdjBst-jP)Zl֢ӑ@L͌(pUNLw \=wxa`0X@Pk5#9{8vLLS]RrU 2Z/y3c39ի2TFHk]ooCb!8< ?uTkу8PJgKKmEF@h+˻281$=/`0  P( 3 tq!Nغu,l9cډ2eT#{K0qKG 屮 $r$KWV-ZDr'@jw|ݞ]сkϣŽ DF{?%k)_oj$ #4i^͛%v"l>Fڥ4/`0 FBXJ^>VދS~b"Zp떤pNۥg%Gq>!Sˀ$pjNZd}.f,rb:'2 qt [,GZw1e `<8<_ ֞.~nsn2pTYw|}pv\KS9avkk Z7&$\9ҹ3?$XBoo}Z%3oϓ>">4XF`ZʣUR)ZjWqYrFz"`0, dTmĕ[FtSF oofёʕvحEZ-WKڅqfҢIbSrp M캁о=~=R5i@͚UV@]J׮dZOHZ KIw7K~0 `T, d(O)oPm TgÞ _ BŋԻAIZ  @ܻ'QJՒ ;i[s KHV"|2ח4rݣx9A?@~ZU Ie`0 FPBL4 ~~~pqqA\\RSSsF///C8xmك6m`0ƍC^̷'Obĉ|}}ѽ{w_v3Y6;U#r]bzwGWWgOiXjd-ZZHّfU+r={wQŭ K6)e p6S:Q# @Ulω]e|F-2p|c>Q`0ʦJ/̙!C>NC׮]Gd S׍G+Vl `08L@o>|bضm1qD}׮]4|ט2e F۷Cw5B͚5sN1>>s/fnAp%,\Ç믿tԬYӦMST(Aƌ3Mn%nIX̪>n.{P5R]˰a2RPUKJ h]@` USVK>#;h@vjqw')K>ħIb%8|B XL@n:t:=aÆ!-- Yׯ_oooӧ1OOO6mB6HMMŐ!C`0ʶ:t( 3i שY&ڶm'N~+5"5 ^hՊC-sp :ek9b0>>6f( +z-Ju{Fj -E5-@\:5Uj >4ٺUtSUBZ52|8NSqy5iyr2cJ)y`0L@P=S-<(cbbS9rň6N#""f͡)W\v;muPQYgHz7@C5PG @6PRY]]9q#p:+Q:D])@>%KBM9rQݟ@SZ;#ZR|[c'  ,X@E3:Pr6D)%.ݼY!ƎE\,9 id0 "2avv6|||,,@پ8BvBZZ  c y;sxsn];I\B :i%Z-R; P s:ɛ{MmiuƩvݻ99}/@B:5U1-#G_mW޴bZHЮy@cQE: 3`0 Tpttxɩy9<_񟶶z 4x7u[Ppvv^z 7^ ȨQd)Zf{5bDp3[#0nƁ2NOex^O\Z-V  >֬InjbOF@&im WTK@A|DR1-w2QG^f x23kS[Z{"G?07TӧOuLٔ>}лjidI>/i87XsD3g nf˕ST7q~f"*%"hX618UG~F]gNf5 Znd2ЎS:G,S~׎*$`0.Onsjڠ.U& Sp]tp}8`xzz:\\\JXCaf!##5xǐ!C}v|7hӦܷ<Z:u*MPB  A`PX@j^͐qZҿ?i,m[k5 uέ(-c)sZ|6QLFL(xAHKjd@"-$w\̻>{"U Q:uk^^^e˫2T099XhRSXXe˖!Y" IDAT..~~~Hϓ'OY&''իذaCcׯ_ǺuгgOzrJx˳N?zh]G^zzVRRk|WWRFeIeK.BVU#Lqzp?*SE2_"Nj ~}kWgٶ[I [0Bŵ D;wbKZF&>Ϟm-8޹caW+ QjkXFPe˜'OƤIh"$$$ 33Lo&&''#66/O#!!Cg̘7o]vX`L1c )) :u*nΜ9?>Zl '''ZO͓b]Cwmo#su%[@2piH!MQ˱c[Sn@ 8-'}ܼii[qo|`C @wޱӓ|} +Q#R,f,qXcff2 QATVXcʕ7nJJJyfnݺlјF-[7o&N///l߾!!!fFFF"55...0a/^]lC8aС999qjXm[zӧnCVBNԪ;w%r@~BZ67)@f3;{wrn-Ξh12y2i<|9s%w'wlSOֳ[`0 2T3gDVV󑞞Dm.]b= ڵkŶmiuZj]v!//W\ܹs- c.]_iQ#2Il׮Y^PT @׮6UaHɓ j8Z `b01}:I5qT!4~>eqqߢqVw7-FTOOC@SZŬZڵ# Cyj0nӑE7H>b]r'k M.\nї〥KoA=nnV5^ L fZjQI Bw(-t_WpE`0 -, d( d옂ύ$㏁TPQT^@d$F~>"NWZM~cV!Z>HOG³{^Z` `B8rϋ^ frNN$R57X_̀؍ rϜQIK赢oD2PCrfbRIh=~8"e0 2*wwʤ !wu񂼰0T Pu]̶hAƯYupO_%z'Mcb{Unn (;V` <$+SKT=,Zw&@#*@˫Rx rZ\-JWz a>JJ0`0 %, d(Ox@q{w`84ٷNEmݵZ8fkcF=#pWvܬY3$䨨Y3$enCbc+J힑.]zPhQZ`h "m;+BW?@ߖq?f9>ktASjżQ{IJ{ө"Yw=>`0a!HHmS~1YZ}BK&)#ЪZjjnHjTK|-k%lUmN\KB*: ]=sթ}-unxx?Df|^ը?^-|[ rA$6I3 a 5)w2>P8-gnڹ3uJ:%1-O''ҟXZY: +_K\n%kԉL5QHX::f uZ4 +][FrO適 `Q{=͛7n4P9Q)-: L2E~\88[hޜxEo?|81r/±E`0B 3lެq{etı|ԪE*F%2, C2zSݴZ_~QVQpV5-fkG.qȬBqst]?Ǐ@hS٢f0 F Ui*}.T~5k*[qѢɏ5~}e+!'}{e+!sӕx9ݽ[J*%&ر9CU-?e?Ǎ _@U]/K0  <>pעoU5"6 ?&Z/FSO}`{t_Wt+Z  ˖vxh  kV֭Ij*7i 'YoW \&ˁJˤjYTR[b` ,YL…-űX~h9 `0 2hI+*4Г3 u8sRG-j EKjСܹ~},/YR:2f 2`9UcT-#G֩"VKQl|Х p*/wq87ǎǢ7o Q5 tB:Hg.-Z ܹ^F)ZRRHJkZIZƏ'ύׁ9sT) YԹ E 0jѢRZ-m-[cǪ:1@zivEh "DbYx*`0'*bҤI󃋋 J9LΝ;1b:Vݳgڴi7yyyT^9s0d|gtڵ+ y]vŚ5k0vX|GA||<Ξ=kmFFQPPO?)))Xp!gqܷz o� w}GJ+}tyB# ÆuG -I04R+p2q"ITaHii){イVXPܜ9zNO>!N;ÃTyUrߖqm)JN૯CI #݀lQAnݰUkx?}\˻g}~0x`,k!N^? ^aJǮk$5 _Eػw/q?{ [o97lPXNN_F ~f>3޽/^k4~֭eeeeرco׮R<G}Eth /ݻ yiy~h]kMi1?X~G|TpݺutHII){Æ CZZl~zx{{O>eyzz_~شiEjj* PСCa0̜7F2{QFAZZﹲKKK` eMV9=2X8t;wVp`<[7رՒHRG;u._\-/Dw;9Zyps#G' MΞ%R7UhjUHS>RF\-ߖ{ lF"ѣIMpd4^ /{A9p<8ZOҝKp@GE`@nu>>jc0 <wX!dggq<vGvv6ڷoou_|27nlpgu0ڵk ɅO; qq޽I z$;ŭVz-Bj- !&O&cӞ>hڔ|.]Ь٣E%ϼy@Vd$Ÿqd$DEkqq~\#D+"u>v,<8rC{Zϗ^";IEiQEq8|5#aQp_o:ժ64ԺuXsDO|oԍS8}42d H' ɿ//jj]k]\՝'  r2hvо<ϗkmM_=U@[gâ>f]*HSw@Ē1ƚ5ѨQ b7h4*K|5DcW (Eue9GVJ3ug̜YG3Ϟ4xxx!S_T< ..|3Xҭfgǃ'[Z ->>@׮u(嶪 Ļ.[ƻ6h?whԈOSu[r[yk]e*BSPKK y yfrr1 } _*[R؛'=^F' @T#4{|c+?_{QƊ[ZZ%};>.|) `{ 7ŗ833+!o,'Z==9;9{XwLMu1604TU?.|ŋǁsxbbl~}º^.vv25.{E&S3/GEV}۪NiQ]EOwRZBUԥ{w=k9[wcc_SӢ::Eu);t4*>O*&'}87;`Z-*yz dh羂fn*B^*4s PfC]5i6$P.͆@&RLET RLEd" D` @AE!}Qx7 7{ؖA}J*_#{3xWwhT~ԭR Vx+` e'(M@haaQj7de9,,,۷iYegΜyW8wtF~Xiiڰ4o~: y9T۷oc Ń&L}Aff&D"QaD"Tw!B!@&1PPP0s?SvثW}Xb͛7).^HIIAFFACb/_=z]~>}Ν;qQ&MoO?$߿k׮C||>>y&&O ;;;;v ~-ߗC֭!B!=(E@ɓ'cԩXre1w\Uj{͛1rH7|b7nLLLܹ3>w B!BJetݺu(((@pp0ޭ"SNŀйsg0*u .@ }ܹsxtttsEhh(g{p߳g333]M6űc0c dee{8~xݺuCxxxB!BG7̹sYa9Ap}CKK 9992dBCرcu:w8X֭w|_ÇgϞ`k֬#[ׯXf7-((@vv6D"Q B!d((((uc сHkG:::",,Re-,,A14@PP6l]">> xamm]nֹsgFNj 111HJJBFIIIDĠI& 'BU 'N@ѣºBSSR /&/^(UZI]#JlvuoNJ]RWBSSSj]@>;w"44 ,7o ###j pB|RaH̙3С|a~~>bcca``ss2 ,[ -[D/_,&aDD/|'JmmJHmRSSѡ|%uMe "F)~{Xx1^x-[bt6l ;vXb_0ЦMW'''5 <:tqFdgg$fBǎaff7n./^\M!B!Q<ٳgc׮]زe ᅲh4e2!Bm[ƞ={}}}` ]U߱j*dff_|JtY%B!-;APQJPWWD"KA-)c 999]RIR"--]R'ikkhdgܚ4-B!%''CCڮ!y:Hͣi{!B!DIQ Gp*^|LD"ΝK-}K*ٳgAHK^=C1-BHΖwoT3[s(&B!%E!!B!()  !BOOOv5>H$ŸY# E^j\Cbʕ] R B!ݻϓ ̛7O HoVlDDD"^iprrZl#G(l={6.\Z!,  !BHJ~33d000@555XavK.aذa?~ý{e5k[[[l߾AB!J,''#F4hPj7?D4lСΞ=+߾ez'O hҤ 1,^M46ܰw^~/affmmm888`˖-xP9ydXZZBKK 7ҥK2H :::puu˗uƍ. ?044,;ܵk|}}K;::3g,wHzzz077Lj#+ƍ㧟~R Wף_~ŢEgϞEv KKK(((鉩S"00ư@ppp駟лwoχ;֬YP;w,XQ@H!py_8~8Μ97o*4i\?wѻwo,Z۷oǥKC*'&&qmaܻw~~~>|8Ο?w=|;`ݺu011^ž={5*zgϞ3f ""6l<@x"&N???ܾ}^^^Xpa-z.\@֭KݶdݻwZ(##ݻwGVpM;v ϟ?ǠA=gi#22cƌӧO']vs֯_P(uVիXlϟSNy y={׶m[\zFZ`&H&Hj*B bqmWb1`{祦2mmmc,>>d}٬Yc1HĮ]&&|cQFl e\]]YppµN6M̙3B/=<3t0j(ի>SѣknѢcx9?\|۶mq2 ,KHHqI*lӧ/bccaggWתU+<W:uX'OaÆ2xy[---0Ɛ"ՇBB!Wq%NPUU͛7!)6}c86RaW^HHH@xx8N8oooL4 ˖-p3|{[i0~+wA(;177dzg={sssTP"&u !$B!_.t8;;y~ggghhh >>ı4h /gllÇc֭Xj6l ߦ_Ů]w^z:88ڵk yW^-w5558;;+̰Y DGGcΝ pww?A;HNNǏ{>prr*1… ӓ:cxĉhذB0{( $B![:*:::;v,Oѣ"/ӴiS 6 #FWbɒ% kϩbʔ)z*nܸѣGcǎ%1a֭xnݺ5k`۶ms?Dll,:tHdZ ;wDTT+|omʔ)ǪU_G0ٳ'.\Pn333-tҤIHMMŐ!Cpu\Q@H!Ė/_.]o߾A.]Jqaaa1b?ׯ_1l0tZr`3g,Yggghܸ1@]]3gD˖-UUUaٲehӦ ڵk'ZZ@x^ǎ~zZ 8~8cǎExxxO6 細ŋQPP={044Э[7mfiip\v o1~x̚5*ҡCر6l+ۇ*f JH&JDR# B!lx:Xc>V[lRSSk*Un񈎎VXw4;kfuq=zgܚC-B!?Ν;?m۶aԨQ|wdL]]?smWTR'ODQ^=u%fH$*Shժ`ccyA&('H @[[۷ɓ'?: !Bz*||| 6ѣ+&M~ƌMv5H%(Ͳ7oƸqŋCEEQQQHLLT('doO\#Gš5kp]ŋXvBّ#Gb߾}󃝝ЧO9s;v&B#Gȑ#kUb׮]]BjR!3\ܲxܹ^j֬455q5Z=sŋq=>o?#͛7G+&Bj!$]ºJ޿s΅lmmaaa9;v@^^^U֭[7`Ed2Yw}ܿ_­~- gyޞ={4440vXHJJzsB!BȻ͛.\v W_1YfK.ԩSpttÇaee===#((h===XXX ((֭[Thذ!nݺ%ϻ}6K >n۶|;!B!T*C8``Ϟ=e. 7V^3gάSWÇ1c 00...طoBBB ɰpByY;;;xyyEƞ={׼)Krr2O>U([V9ƘBYB!BUFGGWooСCH>c Re544~Ɍ1,]իWXz5fΜ)﷼qFc|oi&[Jp455+-\cB!BHuPMM k֬W_}Un aܹs > 2DСCq1ܺu ;w.XӦMƍqrB---(Kׯ ˖U*k'PX$ ;C zF# "ZلB! 0KQeN̚5 3fg}q˫aaa*[%111_v3330ƐVq:_rr24h-99ڵS([Zn]Ф6 0{}#V Z7B2 $l{}e2_kUOu'*D8p[ ݻqj?`СhӦ +,;y)K)))ؽ{76oތ=zcƌQUU_>FNj 111HJJBFIII suuc ׯ_G֭x &LP̙3 \| ].eD">CCu\E!!Tƀׯy}׼0WZ*\l**:Z2_mSWUT_u[E |^CE IV*{CCj?O^^w^y^pp01a/!..^p=ƍBٳgk׮?~<=^ZZZN-5B---1#FGPcǎg}V+ <;wDhh(,XEܼy3䳂feeACC @SX GGGlذ| 74D" 0@_|+V`Æ _N$ о}-QSSSk$ ?] B1R) 0Qjj&U¤ ¤TZY55$9tf◜S>>+_̓hi!yHRi?Lݻw۷WDhh(M[[[yPCyZe rrr`kkA~[f`kk۷cĉ=*jK4i#88'ODXXF<:NY~{Xx1^x-[bt6l o޼CbСCnn.ۇ|B]RR0j(o˗_~ѣ wbڵ?~<ڶm~g`gg0c5TWFv5!B@VSfkiyoo %iip5Sz:SYd2/W[ /L+hk DRdg#Sɧ &`ǴiJH$9s&v܉thK,An[lw}0L>֭6mڄ -fɓpB$$$ ??1,Y7nDJJ 0{l4iN8X +++̜9#GT*ۇ4c„  e4>>7޽{?ʕ+hڴ)֯_mܸ ,@jj*郎;"88!Fv킯o|GGGa̙صkWGFFbƌ8/ЕL ob“q{e198t )UT.::EqРI_UxEka*}~~OE+L(+U+8 D"AJJJ)BVoӧO#GĘ1cФI: Hڮ!d2 ӧUTx IѸ1s/-h<05U 4Q\7Yx$)Oɥ>}ʛ_ML33jjZiӒU6P*"EYOYOK2%4U5Q_>LMac&O&jU!X571*?HƥB**,ehh0%222 `3 ?AUUaR=ԫWߗȃAG ǖJִ'bq|||gCQFGppp@^駟G^s--,,aoo(| ۶m[n@XZY?^!!!pvvɓ'KLRӧOxEƾS@X8/FȿB:uX,Ɠ'O䁿B *楥rrr>XTY@hnn|믿гgOyd|[6GqvmWW_ZW}J 4Ғ|Έ-{ko34,~v]AVPz*$‚'Kˢf?T FL'Ox$d$IE hR RzhmZN}AGv/ȇzzߑdj*%O,CUU7o,lW|x{HX /¦f^z!!!8q1i$,[L>[#GpI 4ؽ{wu(u0.((x( ;ưI&7n{lڴI!۷/-[-i&Jl+m(vwNA>;)ZIp>|8CV0ː&!']H9Ȉwg,Magx^rryxװ!жb^Y"u\FS\܋}ں(u o@͕qcĥ!!#Ag#>=dj¦ l xr4qWc/yhgmU*$%rakk UUU\rEZhyR777d2<{yFEE!==e ǣse3661|pt3fet8p  ^z!==+qZQp5Wq=x{{Y.((vvvعsB=ݱo>ؔ٘bjj*_233rNNNطoBޅ -DFFaÆ ¤E'bq_C)6Ap\$sxB c<<௅)!gYY9А]v6%3~}ŀsg@TIf*Y SFehԈС(|xB,#6511E)>z ] MW { T[Kرc1}t'+~qӦM1l01+V?ӧOe˖ݻ7etʔ)Xz5TTT0etرD7tuu???d2tx" 0|p̝;ZBfk:tHdZ pss IoJ he{۔)SЭ[7Z 8u=Za ٳgO\pZ|ř_4i6mڄ!C`ƌ022Çk.Bxyya˖-Oa``sVjIoWƔ)S0yd?bqs|xq]FyжEkW`hښOZd2E׮]iӦ Ͽ`aɒ%xիwww̜9o̙t===,[ 111PQQA6m.?ہ\i];bƜ9sгgOaڵ^رcѦMdeei/@")j%ŋ={"//666իn? ,Xqqq^%1}tǏǬYݯ"O>b Xݺuӧyyy8p?'5K`T:uN뗸 ׫!UC*B]]M{7`ڞ]BUaNaW|DG+ost7|EXEIef;ݻEI*U v-Y4jnKy"; "%iԳ3LSNkԴ=z􀎎ñm6[/_jqPWW?\ Pa6mH!%t8,!SeM- q{zz<+\Rjڔ'`x͓ZX~>exwhpqzﭭk׈|;x,wA~A>\껠e޸;Y3Gۮ^˗#++ M4?ѣGW5&MT583@*ʻb„ ꫯмyMosB>L]nN?H?~kjx !%0|w[WwwS=/d,ZZ9G爈( =MLxעEQX-~2)y%]q5D>Zoɓ9mT{!"(!U *"M* a11xw_}{{w`,Zy?RhhJؾŔ-ycZR e=- n܆XAkl-aeXk$R7Uy@8foQsp:zdd׮/.p!f7g`R/;wxa33l _j} DIWq).%^O4h6m0l 3Z'!CwADD u9==t M:~\gP!uT \']q`u<~5 &7};)1 ޽޽{xf͊?ZRf!13/bE\zr #dℎVɪ:Zunuu%PѺ[( $]$xrڮ#>)>KZUsgaC@GKM]@Gzm<< ee`へ8ckTprnMkd`!6-gL?h߰=:Yu’KЮa;kvU !KTy@ ???ܽ{-Z(۷OI(Al ٓJaCO^qrrxϗ/l&&FLA:_~o Z6=`<1tg#Ot @ PխzB.rҦIe^]oN?isOߎmhӦkCjJf&p8o t~)O4['c|q3gxx .kWުרgA),Z 9xz|J %B7nhF~(!UBXPPPՇ$<h'@ÇaEçw o"ebw,J<fE?`4ْl;cp,<.]($u'ܰrڮD8p@P ݻqj?`СhӦ k*:V*y3gvmHUHyx׫0f ub>RW1's9{(dÃcѬG2pyx)5GO۞  MHMHIIa L5 ݻW`L0GΝ9m 2X҅ARLZ,||l@uC>)NIM~[?`P*\9:5 7ۭܴ9}u II, C[ `y9991bРARJ$aÆEpY-[=ЫW/;dlܹ,??_̙3gJ=H$bW\$ $IIٯX O'y x#kۖVF36w.c֌3d cII|ۋ˘6c={2Xp0c X1ּ9c;XNNm^ s u$c+V06dc3֪c&0cc]**o_H9qdSLe,[]#L,3 ŵ]w2qD֨Q#EFF2___eƍ:w.^=z~Gbbbc1uuuֶm[vvM֮];ֹsg1͛tuuY>}۷ݻwc0gggv ceΝ;clҤIݝݼydzSNC1[|9a/^d ŋlΝs <c,.. ّ#GÇYƍL&c1vV\>|֭[LLLaazJ>>Xx1TTTDr K,+6es^^^Xf ޽xk׮-QC֭Mk8jទ]aJHwoX l }?]y|L$!,$i[F%ZomZ-[E[U[-ZľoYkm31~{p9`lݺ5~*U0~xSH^?[nGI"EKfر|诫X"Fdɒ痩~;w͍DnܸaO*TmڴѺ,|2 `eggk5l0|||? -&Mb.]:u֥]v՗土m6̚&h~rm5i5R)ED9#!LNξj|Z{tvVi"Xu/E_jT swWݍn狤*~-[3OK%lʒK^ 3-(9Y6EkΆ$%%QF 1ʔ)ӤPt4_'&&RпS-,,|]L9wxѢE @TT 4n8MIIITRoߞGҤIڴiCZ޽;7L24k֌-[Ҹq _s vuuEQnݺEҥpB5jd>z[Q IDATP H`` l۶ ''4N8^tDGGRBXj4??^^=UNz>Xbk\]]њgLdPdN#%K‚Cݻwϕ+W&%%7oRD4gx&''s.\x{{ooo,--|r,X.]dfΜIPP\y;w.+Wdڵܿg2e8|pcaaa>'O>5kHdd$+VȰԧ<Օk׮Λ7/:ty*Tg7TF̼bMn uyѼyЧO枯(p:%t*Rv _?dI(Z!>]`YcҥAhc_hE`eΜ u"c))+Vڵ7 :UA3tt}+{ui;/=g7Mµ0B.z#NNN|if-*UΝ;ӵkW*W̭[ؾ};*Uw)ܜRvt_?+ 6!CBݺuyΎ.]0zhVJrx1>ɜ9s&T\NǪUpuu=o Y~̜9{P㏗&M6e޽ 48;;3tPNx?>:u/ёHV\ɂ t4lؐŋӲeK=ztO8p  3>Ȕ)S=z4+VH"ܼyP>ܳgM4R_ 0T5ו<~8S333eڴiizwV۷o?^133Sn߾Rr ˗*C-+>OP''E8QQTE9vLQFPR]QFT/fʦLQ+gfdE Q:uEQ?*JѢr۽wOQSzVQzP{soTr (jѣYCGT݋+ UUSϫT sۅߔ{:Lah\\ҵkW%7( 4HSe499Y3fRD RqwwWڷo>}ZQʨn:dɒҴiSg ?<RlYRqqqQ}]eϞ=UH˕+( Rڶm\tIQE7oRreVW7n?~\߮Y*fffʉ'߿)v?(ڵS&Le={VQbcc3|bnn2(=`GGGFVyRhQeɒ%JʕT }>koXYY)nnnʈ#UUEIw(ҦMG/|ŊS=NJ6hΡS|Uڵ :Nǹs(]4$$$p4?3ݻwg׮]ԭ[mEDDE```L>/+Wocaa}2sά[LmZD޼yfff]x \tē'p7nTWv֭5JQI0/#:Ru1$FqYN:k #95nթVzEQǣ/$^{-Zx1C ݻEs}!""";ӱcGTPd9O??{̽{}f!11BL:thLόxyykNtss#** 4睝Q%Eo #ׯK_/|H2#xF͘1c2ݖ֊9BAb aҰZ?i 5qejwZ4k{kq~~Iݚa,uhfSBk͛W]Ԭ }n !^NQѪU/UjaZdErj2C/zȻ88M9rt.ߙMg艙8_"g>}:7ƆM6?3gΜ>oڴioo B7o^fep֋WIBb)ϓqqqk׮UELL ŞYG/EQj2xU>+Wz!Zr9/P:Z=v#F@0`8{V]r%m =TQ LIՄQAQ_ի$ Nsf}x.޻,:/O*})T{B cڴi<|%J0k,z)R*i3f4B(M}6lm۶9R駢(ry]FeiUBٳj"i| DG N3EQ8vǃYzr)Nڗȗ'"[w+!4ߏӧ> Zw'#{dC"g8u :v:udI58d[y`&~Deͱ4dO=J2(BuV*Tپ};[nOSҍG u۷àAOfȦMfŭ.&̳fG\\('_C<~#G'OO믵N'ޥqaaj"{7|)̟ƿzrj2;3+ 9B1bV (tRHFa4O633/Ժ a*VVHH&HGq?  fI)I콲gW +D8e yyB!14Iccc)78^[o+V7 Yv挺uDa6 -[[CGnfsf~-[puW[BR޹|=YBa4I~:?@Qt:)))Zt)P)Rz S$!IQ`.5Vg#WPh=ZjF)PС !B$۷oOݱcM T`)f9$q aH[G\Çᅢ#{m i"Gr;Ju* Ϯ7'OL3&%pq;7tXB!0 r-Zu„xy榠~]j*,!CO`:gZupEa|Ka󇛩.{ !"x&OLhh(n"555/jݥ0"f:3lJylMH(#ݾ Sq#Ԫe4^mƃ'ٷW=)#B=޽{k.t邫xt9'$!зi*N_n}c;c]7f1U!`lܸ:uhݴ0Nޜ)zC.G6?P+6mj4uuTc,a !"|Z7m۶燽= Zj^:5fff͛7TVK..]3336lk>|8˗5km۶,Fc.pCͮ]PǫDM(}K.¹p$B!Ȑ#Ǐ' ŋً-ZD޽iҤ &Mܜ .NɓQE-S̙3pWݻw3[n0d<== yܹڵk4N atܰl1|5?@NH3)t'wRH]>W!/C%B#Sz4PreQbŊ'O4õ.S._7cƌ^۠Aܹɓ'_PNNNl߾=uaaaԬYӧ3d}HHHW\aɒ%Yj_Q3u%qqq(”)S6lm۶Ν;|w1B?5t޼yiׯgȐ!z>gYYYϛJ.Xyf%.V͠r:*hn7n0i$OӠX[F"$B!4vbСtޝH}rмysvݻwcmmG|N,ϣG8vX}~g(RRR4YKުĩH ;v:aaD]>5dp祝T[9N-+ɠB=x34!<|0sMwݝ7nd}///3u)nnnDEE漳3p޽ x6B_d~2*~3zhƌJe7gg +Z(2s!o萄1ڳG-䤎4{v J*Mco>m˶5tHB!D?~0BKKKbcc)еkWzNժU"&&bŊĠ^Wtt4&?Ν;KSXt:|||^53| X>opӊ"g[l萄s~U]3حW>wKuRBQF1rKJJzjh)jՊqyu:W\ao^2cǎ(‚ EaѢE8::RjU>|t:>=Brr2.\xQ:LPPXbb"ԬYɓZTK%>8矆G'O` usA#O8Nՠ8Z;rIB Ê7CӧӡCyƍԪU+M57u1i$n߾MJXn'((H ?xzz#BBB8pK3jCٲe޽; .߳gwFQn߾MBBuR^=@^端͛xzz˗Yh|w -[6͛ÅxTX<^m萄$&±cFXM&p~5i$B!0(ͧ*50W^N^N3G&0h,XnnP k֨#&$95# >WӰxCC$B]BXre:???,,m:%%f͚i՝05k|<WW =S'w._2tDF :Drj2}q/jUB!f a6m8~8M6M^޼y)V9Wu_YU]7& sG-[#6{.㚎t*߉)\FBsh^Tft+++-/` nEдB|  Md+WA&6EaL3! !FqkuѣG9wʕrZw%LN[ub(Y_-(|9 `Dv8y7>} $xcC"FrAJ,m萄B!K*nݢaÆT^A1h Voֺ;aj ,&LO"'ؾ41c`hMïS% `/IBi8r޽ݻw9}4 4H xZi- ðA mXndZE)OG~o#`q1tXB!| ۶mziѤI߿ew)̯XZX#!!uCY(0cL 5j:lCޗ׎5Tth萄Bf BɓT&ƙ"vE8zz[78q2l`"kRSaGػdS7OQm^5=*ɠB! aÆ [ՎQwB!ЌSF .̗_~ɀ̞='#723`qn :§F5kԑB''(ҊpauyCGEQX{n-CJ-Zzw6ΆK!5L7|4k,&M& 3 NgNVjքEdBCx@;0VV4˖o(;ߡe閜iZBa4f„ hт@iٲ%&L||Ǜm6@TVիWw,ݣycժUt҅ҥKcffFÆ {ݮ]ۏ9aaaY~ƪqlt: uDjtsմץ 0{: ۲*V'O)x1Rۆz|raaiai谄B! .XΞ=ٳgY`gNǠA-ZD޽iҤ &Mܜ .Nɓyvy[3gT^wO?jժ9险L_ ?>1ON8~~U)3ț Tkנ@x#ʗd\ CwHs;X~=!B1'Mf˗0`fƌ/kҥPB^_n]ڵkZ}"7[7giP/vGr:Jxz ?]$!|UW”)x1 Jswg !"W2?/1gRSS;v,/}NJJJૈ#%%啟gl͙+USMA)ܹSMX\_mBٲSjbh}SYplۻnp†G! ahh(^^^lܸlmm)X }jի {v"#-jhŷeV,wNQ{|B!/ќ(22ssszéX"!!!„ zzzҰaC*T@||::+R~}6mڔꦝ]uK%3sgxm"""SNiѣG;v,>?3Ea۶l%KҺukvܩIJJz'*w߅L&&+4uS7ϥ k$B!dx+ތl2gΝKtt4k֬ݝŋSn,EpppuuU+K(½{2l SHdǓ?L?/ѣG3f {sz6n܎ 6UC:p@.br_2KuF9rG!Ə/( Gpڵt҅?cǎzjޞ19~\}s#Slgl鲅JoB!DN2j(FsIIIxh)O?1o޼4 J:u׺Lر#`1EQXhTZ>w}b`` :M%''snܸZ1?鎝8q~-M?'O>_+FC/ϊwaF3CfjP>3VYfV1t8B!Ê7C .׺Liݺ5~~~L4۷oSR%֭[ …?c6}sݤB!DF4Oz-Lػw/%J Yr%/L2,[,MEqfff-[sһwtmt:t:]c۷ogܸqo߾M@@{mۖe˖1sLbccqrrC}IO-Yyf6F}|Q3eO AzWzڛfᚆG!"G|ۉI&tR.\Hƍٴi/_fȐ!5j]g%y+wX+f_ݻYSG?e]w\ObBaL7'Μy֭SRE] T%϶| s$B!Ip跜B+;.Ц#`4xP5hՃ~w߅cիj29`X3f@.,)%뻳NKqI!hi>erDGG( ŊK7ew^nN:}0k A]K&iZƍS'g.^0>_έ[jeY[73F] >ӚN$$% B!An5$±cfx~ZvKrj2Tdtt,1 ^+Ñ#PT{1h >{5'N@}fJPǘ8|Xbк:]F˴\ђ.YjVVI!$Chr 1V 06nT7vРj^F~]g* 8;lbg=.#v!ڬlQzBar=HBhrEQ-kF5jLWp;V-Tv-Nd03SE1tP洘C B!ހxk(C̙3YjW\!111wjݥet:A-C{Tq-,ZX1۸t tD S7X I 4vVU3tHB!&Gac2c :vȃ:t(ڵ̌1chݝȥeB t[ߍ$ *zxkFSmfFbZJ޽ sQkA-]ͺ{3gBϿcXL5\9rɩL?I{'1wCk B!D.q4BΝ;G"EpuueƍTR/RreoӽRwM[;̛ب/^wPZAu[JrQoꏂBp`:5tHB!0~&i>ep\~PG lÇԺ;,o}:4KFh0̎j8nU*h6 f͸Ѓ?7cOA!B7D m۶%44~G`\!CN<= J x~4CӬ#^(!)KRٵ2#(CB!U}ۉ~J*{w]J2.jE+*Üspq6tH"ѺHHJ`k1VB!P=XRR={䯿Y&Cmޞ PZ5V^wy33t͛w2m4ׯ3ԪUUV=D;f͚l۶Mךp(^pq܏f7%:,,nXA!B|ΎǏSxq-ͲEѻwo4iBV077… 3tPu 4ŋLÒl/n>:[ͧUVG!99)Uƍc߾}TZ4 u/5gRSS;v,?~UhoӦ ;vŋ+W5k`aaA>}YZZҫW/FILL [<_qTp4.z5dP!""""\:uꔦ-=zıc2>CQWblٲE~<ښ'O{ǏĜYIII/|R[t$$r%C+yd)juB!}RRR2o#̘1RV-8!Cr ƍR^^^gڧS2܈J3eEQw^<zL9vX~'LBΝ>]o龀 ߌ=1cƼR{Lg_GX!gmbnߦՊVLi4ߢG!9EhΙ3y?֪U+*VеkWzNժU"&&bŊĠprr/ٳg3vX_>>>ܹ4eCk(Bzcin̦3 B!r ss aśa.]3gN .SZn&MTTuֱ~p'="$$Я_4v111-[ݻpB>L׮])T 4`ٲei]6ŋSkԨW_}͛7$88˗/hѢ7u<|[K[CcÉ'cyt!BJpС?t:ϟϖ-[Y&ʕ+Bnν&7=2z@8|p>|}E˖-]!2n]qQF0!oꎁb\q.QyV2eO?q"s(&TxtrCDDDFkܢc>ptD =f32~~~Ɯ9s F*vzDЉ hX(-L]gk[/k3NMDDD 0z@xq5Q)Wem_gСFSDqmF8 X 8DDDDd$Fo2Tch ޭ.v2uQ=F1`1GDDDDøqpq#))Io!*zŮ] Rl]p6tp1"UT2DDDDCTEB( h4cfg8GK! nG몭M]b%%#_>5&wqmƱ%)^!`տɨgYJDDDD93<}Z-u놔t666?~Ǧ.SFk2T*QF 4myr._`aaTL8^^^ -[0i$̞=[vȑ^:5jJ;wb޽߿?nݚP~}ԯ_ǎӭ _ TPׯ_ǂ Rp4nؠ:ݸ6\܀%-aeQ2ZFmaea]vRiDDDD׸EhG}[FǠAP\9cZjڠ666KKK!0|?^N<8888亟[heB8<6lgHxyySN駟 ʃ'qi6t@z1S'4Z vQ#8GZښHDDDDkܢc>˗/GLL >SCjЯ_?:t(:y$^֭[Wo_~~~HMMŋsܸqBȑ#*_>|ׯa0kF^pر|\O4RĚ7`ٸ𦩋/ZŨ_IaDDDTi46o"""2m4L2%222 1j[{[.^e˖޽{&zB`uBlذʕӍɓ'Κ5 n]ff&n޼X۷oǘ1ccÇ]|1UEg 6ٌGOVh B)NBXLQ"C0$"""kX*FT?ĶmPZ5 6 qS޽;;#Fqؽ{7=5k`dYM^ݑ" +V/** 5kСCuSi?۷Gٲe1o޼l_mۢf͚nݺm۶EJp5]6668s իgb0!zl:`C 7B`ͽV\ GGwgG>ÙgVಝ>wx}=_r4Q"""zeuډիiӦy 1FvSbxի>Low> RcԨQ1bPV- :TWq;v7akk/bhѢN<!!! ;q9?~m۶5}*  "Ȁ5j5L]"{K%TE@OOO`ѢEyҥ qh"L0hٲ%ܹshݺ5.\@@zz:6lggg>}ڠ}d'T{K%TEѕ+WB"((rl9U޺u ptt OLh4GLL >iӦL2At֭RJmҥK|DDDDDD4u۷aaaaÆaĉBHHf͚FٳgҺk׮hԨT*v܉YfغuA]pmڴ=}[lۺ@;&"""""z !V JkccHNN}A||<,Yɓ'vZ} 8Xnjr#G@R̙38rҤ,[[[DDDDDD'OD.]^NP <<uօRRR뫗ŋѾ}\5n8]G1( tttD׮]oЫW/\x5!===۶iii 5%%%4J폨(ddd=RI.WZ6ײm`K+q J$J**U yZjGyضm. tqqɱYhLL̆:*VXRVV^#FdۧBk٨Q#tCLL ]6 ?>[M0m4l޼ Ѯ];DDDDDDRCDDDDDd.B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2S B""""""3ŀL1 $"""""2Sf9rݺuJ.͛cǎzi:w Rm18}vvvQ h4lj5&NWWWۣu8rKO""""""CXEeÆ 1bz聹s7o_NPZj7oUT1(O>ڵ+-[Wb֬Yx/_vȐ! A``  ?~m۶}7MDDDDDx6yEEEEXhQitx\r@y5h8ChZܹSnΝȑ#ulll0|p":::_yYGߏjժ˗ӑS[GGG`|a>/^BzTZ/^ԭt֭RJmٲu"""""d}o߾ 6 'NBBB0k,h4̞=[]vEFRsN̚5 o֭['&& -B/-Qa(qjڠ666d!0|?ЧOcɒ%gXZ}tɥV9%MY>##"kbi}`BN 6 IDAT)9s&Y ݱcGܸq߇^KI~` k+(5P)2RdB'c!I!A Q ə**d@s5PV=bcim-l,rYoz X(kWM!,^z[n;w.o?;͚7C!W~?D}$&f8~\)$""2HHwL(UJ.|yFӔ*88VVofdȸWaZ0 LO$^jFbg%gh2Ԅ\=@Bj'iO8 Oҟ 53JK) GkGh#ٕF](e]J/-гu 2fpԩؾ};6n܈za˖-zԨQ;vĞ={ Rc1bD}*l'o?FŊ1uTL6-6m´iӰyf$$$ GvL]q}3 kJNÁrduRo4hon2r)]Y3PNNq`^o_S ""*22.YqE||eU.>^VT ㉬Ǭ%:!R ([,L 󔔧\KN֗-+#֨O?IAlrnI}:֭[Dk+W+V $$Gq{Gbs_#P ի'+.ر=ZREN̝[QB~~=s}DzVpb9WlJҥK1c TZqqq8<٬oܸq / GJBǎW^ŤIjui AժU1sL"&&͛v0x`\pl٦M9qqqAjr;%% .Ė-[p)ܻwǏ׽>o&Lk0j(s̘1z*|||0p@$&&ǝ;wnݺVZ!44T/m˖-q)N&TǣGzu+-VЩF'F2A9 ͮ]EW<̘4jXbceǁ'[˱y/ˮ*k2^DTÇ_~Jt([ʏ'k =O1 $=6&K˂ptt*V[;[n*Uׯ[lA||,T1vff)^Z-KDDO~ >VB{˗/iӦ`0hZ̞=;v@tt4j5ju챷8hBRwYjj*l3fEx˗q̚5KN@V#- օ,ڵҥK5jH=J.;./)))/*\ quBЯ[p3&n<cM?ނ*cG7/_"eSSdxXZ $==z0 $* ~Q'NȚ=ed3O?H gOpt4hW23&K#_ٽ曨Y&֭[*U@բAP[p;#M/| ,Y cƌѕ!?RP\ * %%4h.\3gasƌjcV71jqy*W !j дiS=za-B*иrc4s5[NM@ fzK/WN<|(5nޔ3o$%@m|j>}dmBB^T[bC9ߞ=޽wϞ@߾52"R{e X}{9ѳi P6=z[nah׮zi~z$&&ePkkkh4ugΜA^@FnB .J. ggg?۷ k'²:jҤ ۨP(0g;j 5k7oV.}84Iŋ WX~}xu~Wx1ԬY+WѣGu7)) >#Gǎo 6`=Y+2*&:tC3{ lld.ٳeWHcrv7wF2> !+rC!f ЫlIй3k?b^ո@&N<<.VٲeQ|yY+WFTT&MaΜ9ݻ7̙\x 'ODaccˣN:صkBCC䄯qqqz~7DEETR(We=z4̙ڵk|  *TMӹVZaը̼.ӧO[ojժo߾P*|2̜9hݺ5͛777>+:aM4k}a8zh>̚5 pssôiPjUK/ݩS0{ʋ {+g_>xO~DMMu͓жm2:hCu%[DT 0~|6M6]%2Gu^|u.௿+_g0Xd=?ݻnn2Kv >Lޙ3`Z۷oFaܸqX`^+++ϨTx xyya|5c ܽ{k ,3uT4k ={D׮]7)?DJ_8q" !Cm۶(Uzgks0bl޼9^ѣ~Ghٲ%ڴiŋ5-o͛cر9Y9׫W/,Y .DÆ vZCyn駟b@V P$%%ws_dz ɤ222`mm Z7=5l0(S7'vUsfNԑ?fkj6`Ny+j"[_U' m@RyAE()I˱mkթ0z RRTRd߰A 5]ZZ<<<}Ռ|}}ѴiSL81_t(&xi_|L #3˛kky{491F&Bwܾ].Cno/9qMDzݾ-G޲E'@&fST23e`'U+Δw>N:!-- ˖-ݻw1`1uQ!,x0B4X_vo|]^/; y-4xlQ[BȮYA`Z Z|nPZUU`f9h3#'Odo}}}֦ @AߦMihYdk׮A bAuxXCX|0 ,xnՅU ?23իԖ-]&~+/x۵_mի2=/+W\(+W]#oIZɸO L/ 8t(](Q9B"0 ,>8 ǵ>*UFd-ƀrfU' ȑG|q>|욙ߦ +}YA\?9fEWz<~ |8t(Pb0XĮ^>Pwyv-w~XEKD5%τow7hP j>OZv %^8P-]VL*/ }bak@kL'eƐ.Ck䂕PQQ2ЮYS֊<(,,.]sd TRSem+:d z,rd9-J2@OLOǥn]`R_" &c8~\Vi# {{tDD%B2;>ݻ;!Kd ֤|y*5 IDAT7e@Xؽ?5kѭ[epi=I`!.]ԥe߾Rw [k⼔QF|򰰰+W^zAAAh֬?y.]`ر/ONN8 $%%7x`̛7H*222PfM-A%ZZ6uQJore!!㋾`$4!RRx]!-bz!,b0!I!Cؿ_ 柞.'N~23رC-^]ŋx##(]Z+GNㅘ>]e[ N߳kYn_GG!F޽i4 -rdF@^^߼'KJH+έ ]E׍]ű;ē'O@$''racc#Ξ=+Fy}T*#C>}'$$'777dɒ|ӹsg.##Cw%QBRt:u$ ؾ}^ŋ 77")Wa =z˗ B\|9[˗nݺ序<^ٚu6]$%|V]_r0Ws0A֢u$=xP!em'MǍ:+%Ev!+?XVh;AjՒ!r0iOk_jy- 0K>ҬR)Ycrܿ/?ڵeSqiܸMȑ{&Md/ Mu?b;qtQtv E B ZjJ*Aiv([l;99H~l2{׭S(ԩSh\TXQ\R~źeϞ=YfVԮ][L!wV ( 8oSz-llltXBԮ][X[[ i& Xnӧu>+=k׮b95dƵobύ\`Zs\(;}Mv&2DV-G] ;^-!S9*FllG* 9:UcϭϚV+,@#HKU+߈~]Z~cg'G5`rXoo9rʆ  Grٳ ^7۸v XV~dׯˑzzРAj䞣o,]>qfV]#KbƌZ*py&hܸq / GJBǎW^ŤIjui =F!!!Z*fΜXмys8p׮]C@@ .,Y6m`ȑCLL SRRpBlٲN½{0~x֭[qF>} سgOʕ+HJJB.]SLAPPRSSsԩS2dq ^7nĜ9s} 'M#<<^^^ؽ{7>L0׮]èQ'Nm7c իhQ-[ĩS^z?TLݓwAQyAePа Ӆ ˖ 1{\b*!BBd۾fv"~]OO!ڷU'bH!Vvm!f]Eq" k رvc vk-ƺSW ] lTdPDb`8?:H0w<<3s'㌙2!Esƌᕯ{\={.]D"6sLo߾1HN>k. Y*U2|@ZZD"^z+++ܽ{000PX?KKKܹs~DEEA"@"i*W,{ljjwˎbԭ[,QDLkjjcƌ#{.1!RIII9~m"ucxzz=״iS,_\9@___䇶6)X`I%^cʵ:ڕ1"DPZZ<4HV{-֭!%%|kϟ˗e*>Q ;$/*%'`k!!K} pVKmCǏkhk+6KN-#gUn3i V^_ @orÓO`[0ɪXiH]t96n333VZHxJ@-Z+V/QdI;VCnhh'"(d/'ʔ)B=%KG.!xx{{m񝖖lqLIIɰ.Г߼?FD(!$v:D=aclejLG&&|ٳAH{ z&1{k IQA.(jo5jdaCpz'Off|?={2h*"Q+ի$,wp6o MG+b\r:^'F@_]9O$)h>}BXX6mڄM\"w-6mڄϟ?TRSSS3ptt;VZY^[0117믿۷o>QAvn#fxH$¼y윮JXN<}UT Utt4j׮  ѼBKKKo߾炂`ee9U<#BBռRsLk> qm5jPؿBk"y5 gC7ICWշ 'M&?>*pG"#anΓC7>y\`-Am3Woń3p><]B,o addף\rɓwww̛7ݻwǼy`jjP/_ 6u+WF`` z%JժURJ᯿BLL\BXre\v Eҥ տѣGc޼yZ*j֬+VY&=eʔ=\iBhذ!֭[rɞ1cv *bwŃ---4j ,@ʕӧk?:qD vvvh۶-qa;w.Wbcc+DEE1'O1rߗ/_F~F^6AwH3o?kOmm>S5VCH^^MSS#yAp:P>{ 0J%i, BյPV,zv)H$޽{q-`Xx98{,ѹsgb…PSSq?FDDV*CpڴiS:v֭[TnS0a`eeccc~:3i$xxxhҤ tuuѾ}{hiieyݐ!Ccǎ,_,\rڷoǏٳhР7ne˖ -ݼy3RSSQ^=7.$+%K6l5kuٽoG׮]!:u`ݺusBBB,"&JII&$I$oSig'T0ݷ̍A۶1`&Ӧ;vIÆܘ'`$$`ccZG!~ /M׉r0`ii^zaٙ5kb޽yEnnnǤI2|'P2(J0'pmlջڐk KI+lܸϞ=1|pDDD#봴m6|AI:T!,ۓ1#:y'Գ.+_ WnK!Z|<0i;f NB|{!6t݀FU 7o >c XplQ8T!T!*HCRjZomW_^)q==~~$\WP٣%ivaDB$_pϸr %ȣ,,nGڼ5ꮯ#O(1cuFrR%'eeoo ah)N8CobDh !~# u:洞]λ08qDxlxS/gO"e 7zV]cX{s-nl5#p@ *tXr(!$$Um#VZ6ѱcn4RX0 mtNsUouwWf&6RPBHH.,ymɨ'01B dSO?<ޝ/~DL!+>><8QV@حCE9&MBT2Z LJLIh._Qè*Wt5u"MARjUC*mپXuKn!D&4puk}}#0|,k;EZ=V%$ghQA a!GՑ 6 FFFPSSý{ٳQNYUV7n\ȥKig җKII9n߾-t($(!$DID"9ij :9…B2Ьɇ׭+tDrcX}c5 ߎ%P;umۆ'N ::nsĉ8w\> csss,_} GGG<<j,ww 1QBl8;6Tk7qo¯F6cƌW QJӧѬY3L2ڵ+^|)wmTTadd]]]4h7ng돕VZ!22^^^/ӧO/%K{~wt|}}e׽zJ![n!Μ9+++SN%J1fSNŀkHKKÁеktglذ!ѣbe ]dd$b/_@,#00SN^zBPP`͚5@%`ii;v-i&8;;dɒ^:;i8}4\\\PZ54h+Wĭ[yJBӦMb@DDa^^^ٞ۲eKfcc羬X:uT*=7m4ƞ>}*{ŋL$/H$ H$j(Oٶ;2>XΌ B%51//*T`PЫ f?69`2K N N1VbE;cdf/_dweVv]|| *s粘c,**-Yݻw+W2 v c_|aM4aݻw,&&/2X̾|cϏijj۳۷oPfee#gΜ9L2ѣӧlĈ@.柅2X޽{'׶l2V\9clٲe\v^`` 300`۷og, URy{{3}X,fwޕ]g&إKcWٱK:455ڵkٳgҥK:x-H*V^xƎXlllggϞejjjׯr矬UV^gqXT׬Y4̞=_(;R4Gx1 &7~}{+ӥZg|P$P)70BH8:=ׯk Q:{A]rk3hl4؟<.===lٲ0w3wsssbƍ>=zعs'>~Gq0776l ]]]ˆ{aܸqAʕ1rHtA67O__AٲeallT[èQ8\SLAnPzu\J2H#F@KK K.𸷷7&O>}RJhӦ vZ9,>>>hӦ QT),Y ',,,ggg,^XgϞR ͛OaJNNƟ ٪ߙ!222GܹsY&oTPzzz022Œ32dɒӃ)f̘l H$Bݟ&ޛ_~Ahhhk}}}hiiuָuV_(),pI$$d|!ߨ~j`Ѣ ">`&.@Ӧ|_ r儎Hc sSdY!)Nj*؟ܯၪUDx= [[[AOOgΜŐ:::\챩)޽{CLL ׯ/;.,11%Jd&xb|)w[aСAR.VZs IDAThҤsM6M7F:::ח/YIMME= ztǵQB Ϟ= $[СC3gR)Ν+;[ }`Μ9xvޝe?WW255Mwo߾=Ԅ+PLpL$^w*D*qO\{s !C`nXxWf̐: (Mҥ ͱqF!-- jՂ߸_F-Z+V(Y$Ǝ+!7~N$2e !!Pӧ,Y#żNKKK68SRR2' deyd8| fV9%%mM||<cXp!&LprrǏ)S>@?Oݻ7<==qFxyyAB }K_7n7=ҥ \\\`kkɓ'ĉ & E >>yBU׮Ef4֯~)[G..@NomLHtУi@I'61.\ <8W(D o'aӦMhڴ)ʕ+rbӦMsC+sBSS34`8:: CZ.abb7n_ora;;;G`kk9"̓3F!wN:xl!}OeҲcii W\PP6+ߓ/_… $,; ݐ@hkkg0}{&ז;3ǃ1,OrrrcIII~VjU8::… *%%%z\\p<8S]cl̫xjҕݐ 80Zo,]& l Ԫ\D)L\]eLĽ[~E*8dL CCCaxΟ?%"011Ap:t׮]S+WF`` ޾}NmVΞ=<~r+~ڵkǏeQ=z4͛aرsW2e`oo.Y6lu=?c l۶ xt ϟ?qccc0e;*T }g_tt4ʗ//w,:::G+TDo߾eXzLVCfΜYf-RVEi ~qjtd55`hO СOի44X>$}‚۱WǬCi~!/`|)  gޭ_ɘH$޽{1fؠFX|9Zl);GCCgϞѹsg Vq?QjUH$HRL6 ر#ttt0l0899˗/&L IIIϰL4 111߿?0tPo>ӡ 2۶mok iӦrڷoǏ-j֬!Cټy3 zFXhڷo/vF9::/sssYfY^Ǐ:H$… мys@HHi[eW%)x"߁Ӆ݋/^ȍ޼y3 4j(|@2e{.;Ν;5jhhЇ  C\~3Vzo7޾+ V!-- 5 dν<{`Em[pH1+9s-Z@RRV\xxxdymۆ>()RՖ[[[9:cU,BGGGiQvm>|X~/۷wwwXXX 11BHH<==eeq---1`l޼Y?8::]vpssj* :5jԐ׫W/hkkI&066Çab{s`C->xT~|^ɓ|ABH$C~2R|tC NTbL,'Nc 8w=\f$Rt$'={qqc@.*S;~xv"N>fYo]XBBr& *OvDK] bP4Ь/L8:-hNPdpí PdAB)(!$D`#;'L..MQ$ٙ>rf\!/LQ\m!bX,*C*(m&``pqƍ޽?!Dq$G%JO_[WB ,k)t8*[HRCuPBH UƝCM=#;//MQ͍WU2\ws_/FWm,LLLBECF QuB 80 l7&>@@l,pJzg+_"$‚V-hcXD<5PH6hܾ !#^}EUp}᷿پgaoj/t81 PIwCC a!GC&EU52tY̞ <{ء !y7?{:tc8Hp!* %DE0tL=?4>W7!$oN* }q^)$E !!*Mo{TLLWW` MɽK]ӧ*F : {\EBC!$4drz2 @0h('O Qڂյm@jBGN04 :,B !UbVi rvk 7!$g[Ĝ<ۯo~{{Lu2%RPoOϑ[g+Epu Pn߄] t> l)t4|N~-ɢ/!yGCBBTPRC"(sss`X`(ڗU lެ`Rj8¾=淙/t8BrBBTo!&'O? Vo߀A B 6V1i, 􇮦.6tݠT !%(5v:Ğ{p!v l )|hlYAHQvƫ*hx "„BBTX9rCF v޴)_Yq&' t Lj/냐B*<<%K&C[B`ݭ8~ BC!$(!$Dŵ+V.xr;4 W'ooqV- 4s00.\5Ѥs!ƟQ^BJ )no X]/.ӡP6};uiiŋض-и11mgHJǏ>>Ѥ!:-fK.BC!$hBh)~^ϡ>ã^ =u~o|;!CxX:{Wϟb`pEU!Ç@f@6BGN4wU+Jp!E*%}X#:ZB}!eiizw/O޿xR'VV|4GG_W3ӯ`cLX>~4sG: =9wb o(J)0t<ra)._OEO\3ՇqIc}X"?G^=\3/Π/qSHLɠQ<,] ?&B }{F `͚ Ж-yq&n 㒪7Rt=PBXчwpz~ wcZ * %cGhk+6eYK99&X"SK_B~=ΞðU̵7qgG @]BC)Wy(!,B~&M"k㐐06B2!c U?Mݺ:#F !ʵz5x1p:PѤ57¶ ۶BUu (X  *fyH$B68~N޽)!$i@`J&ɩp7k7J !Rbek[-΅: yݺ_:B W/`VZh2u %5KbABB!PBHH1ڼ5.G^D*:hk} w:B ^B xy:UЮp<8v솺BHQD !!Ŕ*T7WEހM| B*ƀC*Uɓ&C?#u/PVB!A !!X*piÐ므!/t$+7PQ^\bfh\B)@Ruǟ: y"0f _\`La@__haaaYc:B!BB敚#<6:y|q7ÇǏ/@dh *6u$۪BHE !!X h_=XP[01Cy};ԫ# H$<ݛWݎI~h!%bK.Q[@HHmп?0~ߋ:q'*R%;wB]#BJ !K\B1--QА#=w.R>iS`8CY}fRN/x%zNR%Plb,]֢A/ RdѶ-K;lm1dCc| ?77Bh2||?̌ MVV@vRH4iCRQ}H ~B2DC a!GHc象x6"@9«?OQOkDDMj*Ы_]h29N[#t8BT%t{axСK$&N-ջ4w.kNĄ/ӡU Uݟ 't$Jcixt FF4:B!*BBH:KUF`սU77K֮Ο~c|?DI}DV]_O0LC!(!$dhvX{s-^y-t(,”Ŷ0m+ v Rl m= ɠdeKL0[oBQAB2TtU.:Ṻ-S\R)0b3x ?g@=77ƌf0U ;:Ŀ*t4baرa`4j-t8BTTJЦM*U WtӲeKt?9c߾}۷/WX֭[gzD"IP|yQFk$DCOvmCHl=x|t.]ʺԻnr8 L ?:02^ XH]C Gl ݄hLk>MP!0uP-[`Ȑ!h߾=ϟ555<}DP,X"fff9g͚5}6ׯO>eynq!xyy~~~pppŋѤIܿHB̸1|Z`\zb+#=++`,>0(({ͅR|~Vw ć(Vb1>T4(([9'dBxYOS10 }"UXT\8N>%B 1VK /22VVVloժ>~{婯(/_`cceλ~:5j%K kkkʕ+9/%%H$PE HlK3t ~$t8b [R IiӀqx1''c6- CUZu+c+'} 4n̫|r)ʪU_yB%9CQհ*vPĕB2CS,Yiii={6۷o^#JstϾ'9p1!G%J(! bnĂu]p%7fڵIZN<~KgyBdf>0Q _fݺAﱷm[T޷ O۶Chr8yϥ+U=;_!g=zsuV 61;O7oI:K IDAT/OX-^ܻc1u#m@O!$GńgϞAMM  ¤I`kkCaΜ9J;w\ n66689sٳgؽ{bi I1o*/Bm\{s vǙ>gPR! GCXpv0'lժ|XӓWr89SSȑ^_B:o~K\G'E7Efa2^z#kk`|t邍!'}#l$&ԳY\EU1ܹpbE~}Q#^RyqWZϢEg :t"#p_3(^=qWWO*y?My'\? r1v7<Zy\BT*O2mmlttt&o\rwwGbb"B2~x0%6=$;)))HRLX\vvik7qoܖ4M-[=WAEB'G9xy 7db7 vu2im )JC'}}>|U+aV4ͥxI] ^dB1܋O 0Dxx@&_^A^M!;>}Ε+<:/ܒSm} nUurWcv㫽 *&x冹9iP~Fb͸?СBHL>SNXJJJ8na^=zNNN:u*|||͛ɓ'x-444W(Qr׻au,QKMMŋ/```reoN!\x1ƍH$FٲeF㫉RRn7ބ]9;T-]JB|J<~};܁X5fZU K  )L]s:urw-c|M|aܺxpsF}_wYs~]~|׍Ç<\[PIxSc鍱 !( *khӦ ϟߣv8|0~z_۷o@bb":xzzU`ii`Ͳ/_@0{$$$V1m޼9;Aѣ&OXXXزeBG]oM7W+|N%aRD$EW[ژAJsaCp4Pvή{igc[9gsU…:9WIͫHm&-%Z{s-LQ F  !Š߾}c^^^̌iiiڵkݻw˝zŪTttt._>۰aC"""X,f {~֬YL,g3{ls̘6kذ!;{l^D"aD"u{cciiiBRlXҌXV]Bc߯^1fdk1 ۳'m=ʘ1c/_s31a'QDE1fMC!{\)CF2*1rK|eK+_f|_|'"1(щg':F>:B!/F)caDŽps.^$>|D2ݼy|];Y·TEE$b`x ˗$&|˗ ]2'Fby/E}![4dr:)*Pnq9z ##!a0q"vv|ƀ'OGuSS5n\pDu^!^큾}y%U')wR=PBXч%vt@>[С>J8ut Z)owoJqdɇ'h ʥ* !UJ 9dy {:11@l,O ˖-:hZ`ZiBC!q&ջHJM:"ii P2 ؗdСB)B_iBʨT,Z䳓BBJIHIi/v􅖺B)B(!$kzK0Q) ,5T"t(BJ !*g8$ ^Ų˄BHD !!DꙢq8СN{aTQfTMP!ABT.tﰿO(4"t(B(J !*7τ$&a쩱X~)Jj:B!E%WB}mb͵BB`,ERj*t(B0J !*iDĔDC!D^yEA D"!RQBHQIk:Bn l?VeBHG !!Dew, ZiTPQ-g  !bBBrM5Mxt@PQi FEmAB)(!$,Hͧc9HciBCH[y}% ǶСB)&(!$4+4}WP)P@otXI BQJ !*M,cI%#$$!fR$xX{СB)F(!$Qۤ6_/t(WA8|Z !bBBHr*>^ sDOHFiR:9 s[EiBC!R(T1mߑ~ :âEhU!ҭj"5 ,t(B!J !g]O0G16%R m3.E^RHć~a:V:XMp!CB HmNcL97%Iaxl8oi/>}pELH֦n5/BH1E !!P'qAg'da띭9L306Vi/N@Ȼ&?ڏmh2!PBH)tqY8qos|mht(5ǢEwnj3uޝEUϰ !Bnb䂘ie. (eYr/Sr-515T7\@EEpAEBz{= s9#l" Pcܮq  ]CDDz !UINN82jYւǏd>>{XY9Y~y;.vAo83 ިF}y=JO3V:""s2QV#3NdggJFFFH'v8 {Ssx{O⃋8y$lNw0{,-<^% ߷s3 }81/?u;:"J a?=x(\M4ã;5 9I-=naBX񏅨]wVwBd uD=+;GP˪!"x[q !s<=Qݬ:t Dk,d* &DDϑd`6^ܨP%ZrC y}G.=o~%=@&:""aBHDT Ú ôaeyDrJ_F} >,EֳZd"l*c* #"Fv_]B"R'X}1z뉙g"#;}*%~?;<k{=*.&5weTF?=Tj&PJE}666BͱaÆƦnksƂc  ;^ӺፇcU;b$=IBgWV\Ɯ9Xg% 冺Tlʕ5j\|nʳL&3͛Iz,Yh>,vO?͛7ϳMrpu!;q;? ))VѺVk~{7:6-iV-8[;e:v:ꈝCvM7+LVa䶑UKL׾Ę1c`"Ajj*Ξ=[PV-@Æ aooƍ.Ά1J%J}">0˼ Wzau_nca8>8[q+^t]d j5BBB 2*J Dzz:T*Uz<~ eߗJ#GG?[dEQ C{r0]E襠 add$<<{]76jKEhGHzm&c薡5 *4?qppк q+Nk!i\055 <8ϱ3>}:'M!W>'\]]ѧO%=;;Ѕ'/㝧w0"bV^dT"abT=<)) 2 EOHHb+ 011)Q2H#֜.W`"*̎3di: #x$ǠnXu!x(tfNjW: k2-B@?czh^~D>wNS"P6&3#3v-̙31}eggAeG/2!|r:!V\ [[[4k O L&ߚHII)UL<ȷ.66۷oS ] *9"*_X?`=& .б&E5]߷hhOk7 _*uvEB`̎1_>&g)ƷyNgVcX "*cERЋO>}aaa>7n-[ѣ5\LL 777<{ 7oƱc0f4iDs̤$4hÇNJxw!DEEA###_~%Ch^; h۶-jԨ ._~*CDUQs Cpd8[;KO.ӱB>{OBp i84keI-vl˱p 'NEOw^MwU"" BL0A899 Ѹqcnݺ<\~]z 333aaa!Zh!~|ǻq㆐bȑy \^oѢEu֢zXԪUK 6L$$$輔J Jea߳BWq%m\U$N 2DD:{܊Ä Ve#r֞[h}aaa>7n-[ѣ5n۶m󕵶-Z4)) 4ñb @^ٳgƍhݺ5^jȑ#+愉Ho O}vL8ڵk{[o1d2YmFFF8|0>S;v ~)֬Yz mQAd֩<.TUdggJݗuKUiiiV=zk*%33|*ǭ82JDDD@j@JT٤kޠ.DDDDDDz-U\P\Yf*-Uuwe+ U "߼{܊g8>BDDDQ(nyTwm0&"""""SLB""""""=ń\.Ƕm^˗/G׮](/00 ,u&DDDDT1oV˅iӦ֧[n'++ AAAS\.G}gXrܼyU6l@ `jjƍ?̳}ƌ/SEHbBHDDDDzC&[WFrÆ F֭711ˑPl e-''܎}Q 2>Μ9>}o߾xf///~+8l0!$"""JgƍhԨPzuٳg ի\DEEN J^!!!:Y@.III ,,,вeK8qXѫW|=<|͛7/pۼyi&=-- h֬bbbgܻw * !!!x뭷py9wA=ЪU+={K.ܚ5k`aah|ט={6"## رcҥKu8vXu-[Dtt4K|.Tq u@y044J;299* 3 b.SSS(J)ߖ:u`ҤIɓabb +_k׮Ejj*bbb`mm @j+LZZT&M`РA2e |/^ ooo̙3GnٲeSVh{1l0O:uhZS"((H_F0sL+/^HXOJJ spp@JJJuNNNP*HIIaBHDDDrrc?R Ѽƍ:ƦrXhMRX4mTrϞ= =/XPxzzb߾}X߿yd2$$$(!l֬Y_t mڴɳ7@zz:n߾ڵkrttĽ{0B ##ㅏE]FbXʦ\{b¢EP~}$&&Z:t(z쉝;w̙3>}:Je655-vvvdxQԫWFԩS!B-=={ٳgY^:~-n%=d"֬YwͳݻYfu>L&+Et a!___ ͛7dž өS'|Kݵg۶mh֬LMM`DDDDA&Zzy4۴iYf066Ɩ-[=ubԩ+nܸg=QF8s ?~UFFF3fAp_>(޸p\\\P^|hӦMg |ϟGڵakk[꺨1!,ʕ+cccoAǎq֭8-"""*!::aaa8unݺM6ԭ[gϞŕ+Wky&q5غuk֭[ׯ_Gll,RSS l= 틣Gؼy3BÇ<5j`ĉF ;v,>|ɓvك#GjZ;w_ƹs0|pjG֭[qeDDD 88&M*lQƏݻwc|2q)7.~ E@P7nfffb„ ۩S'ѰaR)JҬ1c000/_JRJq)==]DzzZ\\ڵpppCORrqA!SLJ jժieeejժ \.V^-B.~7o666BlR8qx/^(œ'O4낃EӦMaoo/ Dbbf}||߿SL81O@acc#\\\Ě5kDӦMEHHf!WTThժ011NNNbڴiy=}||W1B7 ѰaC{<333.|q&q+L:$빩Sb…w,--P( T>}%///,YcƌѬONNFZZ5J&T%""BY>cF 7LP*Kb֭ؽ{wۋ6y[qe9Ν; KKK!(((üp B6>}2,hP]6N>]fDDDDDk5-Z0H v9W^F)SQFؼy3BCCR_jussCΝѰaC( lܸz*֭[Wd=;::;wQSƎ0*#G:K !jW^ 5Y !W_a~!55 .Ĵi4MڿKc3f -[ &e˖֗;gMnebbOj7Qi aTT|||O&!..055EFFg@ٳOFv =֤I/`߾}E&geeۖY9p~2nd+wYu{<Hݻ 3lՀϳHN=<)) 2 EOHHbkҤ 8y$7oY۷o>(Q r9 i=,/s@LH-Ȗjd 5IuhzNK# ׯ1g&ʕ+akkӧxW`ll|hh(d25rrrkkkԬY 3f d2য়~\.GKQvְ=}@aU B@FPL, Ԍ~V0FF sbҋcB>}aaa>7n-[ѣ5IWLL 777<{ 7oƱc0f4iDs̤$4hÇNJ+4ϟ>}7q9xQ~ ?T/` AR #cnBDD/' HK=<>ZкQW^ss.[7zSSiQ>BT *DDD`ƌիQ~}]6@3...Сn݊r4h0jԨ|ǔdV\=z͛O>1cfΜYXQۖ_HGNV Kի-0,;3lGD/(hu2all RYڈ\PHTRrw{@*ggJ]ISS|+ B,====_+ Ir9n݊޽{˗/dž {2 D-0qwmV{ܗ [׈(Kz, %HN.zwOzioRZФI667F4l޼DBBBuV>}:TV.M6+$$|~'X4m7n@:uJ].]xAAA8uO>ɳό3СCQ &Tn܇ XDO?3.-HJnnݒ^s[;w6689I^^@.wt`GxizaX[[uy֛`4i\]] bРA0aBxyy>rGrwi5t RVp g@6R Т0aa ,] <)p" +Rww&/7QF033CgϞ!$$WFDDfj(ԩSQ~}AAAPTիW#$$rk֬ uݶm$hٲ%N8QhիWiӊ<ϣ{D͚5"55UW_?LӦM1{lr9.]>}s}CAɓ'#..O>ŪU - k֬#Ν;VVV|й͛͛#&&G… )S`РA,-$$… ahh;wG9r$~W\t F)4֬Y'"::GѮ];lsEvv6ĘR27]&E}аa֟ KыS..t%.HM\\M<<=׫DŽL)Ғ]*I?tww)ւ92PnXPTׯH]sBT>OթS&MBxx8&O XXX0_Zv-RSSI_}BOKKCZZ ޤI 4SL_oŋ-[:u >>nnn~Æ Su!!!:ujQFQ]]]xbDFFpBR*&Tгgk=*u *k99@|r_^lx5[7.5>;Pu5,uCR9C)M2^B7/^u Mñh"$$$ ==999%wm={ `aBCC}KFccc|d2$$$(!l,D.]B6m{7۷ovq=-)xcQaBH{80|xkn mۖx*p˥KR+|xzo9,ӥghhh5J?8"gHMNbb3g_|^^ĉ\  ,!˱w^;v {ŢE0}tDGGť2ǏСC1g֭ JTiL&ãG ݧ^z5jNe˖ῳwlp ӊ<ߝS&EV[>L&+Et !>4!פ/1{ڷF^ΩVKBݺoO^1.I-~.}6o|o;ص !<} :% rDQ#i h]_B657}{W2?>$JՒWڴi6m`̙pqq-[駟X3XLGnݺ:ufݍ7SP5j˗/Ǐmd/^D.] /((nnnX~}nؼy3\\\ <ޣ=5 _^ll 475Çaiii,OϟGڵ5xRĄ]:xq1t'zVΕ>ͥdN23dXqw:w>4%(݁c*gϤV'Ia҈;K}zyIS9yT?.-#ulTZV?DGG#22~~~Q?Pn]ݻW\k͛G-clݺ5q֭ׯ#66k׆eQ21w\s΅#N>ZjUVÇ盋jԨ'믿γ~رXl?z*ñ|rd2tWFϞ=ammYfi5G} ?Ƹqp%cҤIŖ-Jvv6.^!J% @U +β'v*?fr\{NbSiϳFٻس8vLzm=c =)MSSNg|}g8KHL~66Rر [Wg!kao^쿾5-jSNͫ7G-l!ʅ-[Bd2<~g>U0 ֜9@.RkaIFJtw/_Ҹ~~ҷT.%ǎI)T*i];iz:w֭5wz9*G gܹ#&}סǙy-{bﵽHLCz]ӽ'v]eP(:VqUۓc!/:Ypq〛7273S t %~~`q1qԝk޼^I!v)ܣt:B=l&-;矿?ccve>Z¯t}@^pדZat-B۷aaa8uj51e 8PONl׮]k׮"C,_;v@\\ &`РAZYXBk`AK^5ҷ;_A8*JzL_J۵LL>Ɓ@敓#aa!QŻ}غشIMiݥ)! ;'}C!Ix"mZ㷏cm~e;ӓ ]ݺDтReń`Ǔ\F```˗/֭[ydpvvƼy߼ɩ:;3g{9s& i& <qqq5kVer?Ow-]n33Z.&c.@B4rߡCRgπ7VKVhQQRRZQT*53<\529Sq1黄`foykWP@tfeգW/ $Ds98x 6_ Wѻ~oGӮRNODU[OOO3 ,(r_ٳG.9.]ѣHMMLUۓqXas|t5gO?0@VptæRI-mJ7 X_~ \{ٜKaj!<ذAJ~}|ϝ\\?Vy}bnܐCw^[oI= ! ."T*pESA* QQ vfv BH[+>gɒ%P  ]ŽyT*dffM֥}Ƶk}UD8xaj ӦIJH@u|OT{OJȮ\)KBHRAR2s֏W1h+>X XZjq?_7qToZiz}__N Ql|zi_J:O8~8~;6m y a$ $"* ݾ#VBΝ; KKK!((5^r水#SիW/1*woK]tt@mi)MG1}:`4OgI7pjU&_񄐺~~'}_K#>.}H-)=XR:۬4R-γgRѫP4@̸qkH_21KZIv?x,[ܺ%=#жNlj E1&^člxL Z SSS,6o,*d26mZ}G%fϞ-l"~7ѷo_!KUÇԩeJ Jeԅ "ۥ[:HKE5kJ煘1Cww!;V(!T*+BT&ą Aj 1|Bt,įJ ![Oo""B݅TSrc ,ߍ6j!\^)3g7N!ڶ⧟_Ql!vb@!ͅSudB!d>+bVN: ˹bؖa"ZP|~A +ΪH&:Ʋe˄ET / qbBdx)4ƒ&ҥB4o.' ۘwaM^_ .@Qp"u.PPި^?@EQ{!nQ[ * gUP?nyx&8! }{='(ŋY36l`Gl05j1;M{5!b.~UV(ҵTRR]6t.H2mc&XPPP'}]UU^z{eLSSx JG2 ee|TTTk(2jԨLDz@bb"=z9̙<<< ԩSq5899᧟~*о_xOZZZWܦ*^HYO)ڷS6G-›1hЀظs^O)S6Og *k.17655$/ro>Ƨ  Z>yL«<O#xQw1& Lċ ]{fΝc4^[7kW>bE> c`xN^t>gAz:p o ѬC+d͙ (>%@}av ʚx<1<-=a+T4>4-W[[HKN>*Uc~N*U޾}o V%LѣqMAGGKYfhذ!=r%r"tFZ١C~qL,gq&عs0E֭$4H+64V<͚ؓ7oΔYjX޽YBBBțX,cƍ3֠AtR&ww!X)5jd]vF~ԯ_?6/˴{{{ֺukfffFǏX,4B3ַo_VreVF 믿/_dw'4Ύ9+WnݺϏͼ~hꄰ0,,,X,fmsrrbb8T<{D"vZη}'?dz$OX\ֻ[tGGڷfILdCƮ\a쯿 lh5blւ5mXڌ-XثWc~1Eқ%Ec$4q cfĄ'y}'Mb}EE1inڔOz1- _`^16y2߳ObbyРcRJk>t(# Zә[˱_X*lgŻYR?˳?ϟ]vxF٧OXDDKի b.\`jʸមΝ˚7oeC?&qqqA{LJ{9s;uTǾ'=bJJJÇ att4fK,a_f?ffff8X&5kdd߿g!!!,44iӦδ2ףGV\ÇX,fR_111LYY]x1W\a*U15>>B90g2HĖ,YXzz:Y3>111,9#G2XP!#55z}̲Ņ)))1KKB[WHRXMuwu>$䂩2sjڼ# =yeaإK_T>ߛΘ#oܘ/)*L`,H$?X>5/^HӧijWGV^:c25%l`Ufd,00}taA5bno(HQv;8Ĝ9Z0-zlf-Iy:!LOg,%E?3X̂s|^56l`ڵIZV?&{aUTaRD"}v<իc,{Biߐ&؛7oc'Y-Z 3=sN=z6׾}{pB^?c1}}lO>}5f1p@?F˖-ݻ7c~?,,,`aa}}}$&&̙3-ZjqP ܿ^:zcǎesΨ_^+ӱt=}hhݚ/"rլp`aU1>x{^ݻVV|َb(WرЁjFZ89e*=zm@^ujkΝ)|͢hhтi w֭{G|m4&M֯/G#<`t޶A<KKٴsYW]?DF끰0F<hh :);ֻ[eݖa ( [H$%%(ڼ-[?`jjaÆA#ɓ'm6}qqqH$RJnZqpp@ӦM--l~:ԲOU$۷ח:v##L+t)c]tA\\ԭ[ТELԪU >}k׮ũS Y>C`!!!A@cɒ%8y$:&Mرc [n8{,!ahh={:1E"Q/^D"ϟ1a„l;;;wq7=07ja}Y֨O.].^ D‹/rҽ;R|1t,] ?lv/^Aӄ IVڴ=xiۖ1{ziϞO\y;z4PZmdӧ T.`m:uax; w|׾ n\ +%l7>Mwժ?&)4F88 Y#F(L/ _o v߁z=qi/tXWRq A,ڵkŵkװm6,^݃naرXjLMMQJ8q6m*йVj 6 45,Xgm`z^תU }d}.+uD"oÆ X~=<==ѬYlGFFB$eKb0***شiS@"tuuUo)Ҡr_a \s 8z2?u&txeX̓~aLu~ud^}DGQNd3Ry_s NXy{<9oޠ:vsx^SޟqܵzG--(7oo4F<|""{譞?. )T1`@U' 8GaaNпqwʹ_"B~ :uN:aҥՅAZO IDATfΜ *d񁞞,X؇2m~YhNNNwDTӦM +-[}}}d$hӦ Μ9]]]skiie!Μ9۷oCMM-ct֯_GGG\v [qϟnݺ,ҹHRLn2=?~/9+YR&uBKK`T^ℇ;>ڥK|*1JD§Gjm--ׯWH$4j8y$޽{[ٳɓ'+Rr@50h 8s ޽kfff}vI[[g֭[3=>eDFFbԨQx޽{Wb8rn߾gϞ }]5)D6n-+D"^P$!Cx)H^xA^ۛ1._m+x33.*k2 }ʘ+c#FC{Q8:Ss[XnX͚V$DѤ cmR TkW6LQM<7E"%˗/Y>}X52300`;wx̌ek;ՙ?XժU3KNNfÇgUVvB,gj;̆444XʕYsŋLUU5S 0---D`` :thFMٳgg‚ihh0]]]vaֺuLEe]YXJXڵ٢E2ٳ'5kV} ƍcb8Ϗ$%%1 \uPQ!bLm,B HII)RCU%KӮx 78xRB31^1>%5>6oGÕ+|eHBCmC[:GL 0y2&~4վ}fYZ ,\߰իh=MwFߍ+z@jKfQEqqq^cF7rHi:{n={W\>$+M_ /`ʥ)x9%T[1^cR>9R- !_x֮ 4mZrO CBxJ}{S5D ]{{^EFd-5-*UX#ТFw,a(!`?SL:pt|BA a W,17G:`> /?_աa`!~勜޿˗ !l/m$.䋀 X0p%k.4*i`Ft,tXņB(!Tp^V^hY3q<鋋?D ^>n]?\ڵZ!IN敠֬9VO1l| ^0LK#JPq(Ƽ BըZ# g-q}TPa&_4tt ^ƍmB8x9ٳ7V*×/Xz})N!  M֮Ud0c}0nd.dB~@#DEb8tF۽mѩn'oҿhӇ7H!x j0ip0r$ .!.%ѥ^C\q>BW\`&՛`@ժ@N Bʪw[[޺{(X,ΏRLk? hԨQC! e\.hiRtqq|j9oyB%\BӝM-avٔ BH>h W+010Lxyo7PUjt2/}l$ۛj عPѦ(̼: ƀ&Ia0Ɛ tIEE%[~H(!,—16x).j* ~~rKTB!9HH-m$U+w]6n*CBB) ׸'ntȷ?H AӲBJ;w-ygπQ&cΌS4 )$BJb%e'&:?SBJT>~@`rYfMp5496+tHRbQB @CCh۶-Ng]ѣbqssݻwTb1e2JHy]y3ƹôK'z޼) !{=ƎUQ$If_gbw?`?T"tXRQۉ8;;ptti;H]?.Ŭ]v9sLTPE3od'xcW(ޱB``q~ǛBHfG3fgB|ߟ3QrM<j ! TT& 4mشiS_~ӧOtΫWbРA7op}iF}[w\|Kò%45sm`;!A6wf Գ1c鍥Xcu&8Rڕk\yYڵ Xb >>>}Ҥ.'3g̙3ѠAB,3i`=QwS]1&M EbjbtOOa&Eж- < F'Ec詡p7ngP2H!2FU􄁁.^ZjXlrL}5TUUZjaٲeH$Ro͈ŋe2H^ zy3n>O# jjN$&L6hBQGݺ3g'NUg=ޣ`jph.tHR*,޼y%%%?G-p888 -- WV__h޼9 y'N\pppMPr|Ye&ZY !56믯Ѥzj/0+X"d`,yC#>}1ZOְB51HmŊʕc ܹ֭s37777"""qlmm~}y~óg2:tǏ526pî%R'86 浴(C|J<.'FZ"ƕR=eeeeQ2xQ3gcs;???;v [l)+Ojjj?iii29GIegg~p, :Z!Dnff Eˁ.MC Ry KTO500ڶV-^v D52= < 222͛]BWWAAAϟ? q,i5j|rK}Ƹ1Xt_1ƍSS`F>}BJ;''`\`~`P;OǔvS*CHjժBD8:!Q,--  ==CCC!Էo@ۅ 88H$€oR\?bx -k+S,7!ԐHx"x pЪen,\Nl8Xp!rt\ +9AdT'1rH 9b022ƢbŊPB `ffD"۷oQJԬYo>$$$d۷oǦMФI]|y_ Hz=q KbspBHqFWJZ _koʋR)))AB ,8::hٲ%ヽ{f$]3ghP +۹Cnݤ.*CcR_ƬN2?4kpBHi`n [T:$B)(!́;,Y'OСChҤ ;Ќ.u놳g"<<bسgS$IU6Jk>}0$&@OԪœB[[QVBHiЯ0c` Gqst]By%K!B*m'*+=FXc%~iK'cc~}Za#YxW`V`X~} pbQEt+ƕ]m)4\\ !DV,-WWKQSl-dB%/"r[pqw<pRK6:7oY dǽ>gGŘcB(!$eF:퐐_企 mз/plOYKB,66ٳ/\qu2ưrBwBD!$ JI!aر|M>E?ׯ@BmN *%HIF?l1tzq w߁!B%Lj8/]‚i5 +IƍǏΟt!A45(CjZ*,,/SEG!BR64E`d E{n݀'OuuƆIIyo߀+W!CK>غ5n MR}g2TaU,tDS1`Ɔoס%tHB@m'J8*[p1O\\͛ RJ[.$Y$fb烝HMKɫWllBo_}{`fJSR00W!G !<ΰ)dN$AGGk׮ŏk׮-ybbbЭ[73nݺdTTIdױnGQhn!?Q#`b@__'(VB_ؽ[AIcΌA$ F_@ BD!DF1}AAAhڴ)lmmiӦ<ٳ'~O\'Oɓ'#ԫWPǠEw Ya,\z܄ŐJ(+N 1 &׸:*T+ ! k\)Yڵ Xb >>>}ҤG߾}akkz!55)))͈f#waXrE>H`*T`(..  .@ !Bf \x:::PSSCjհl24kBMM j²e H=۷ bذaPQQ2~g: itG$7*IɎ1`\￁#ߧ{jҭ'R51aVlrúu0w܌퍈>e/ڷovXl(++޽N:aɒ%XreqjqYWgŔTN>߾[3gӧiݻC|6lew.B֭j:LXB ƕR=B ee|TTTkHF}_/ $&&ѣGysΜ9`#:t@SךOZZZUV h25FP p_wdя89?ԬG!'0b(WB 8;[׮)\2# ;5 GdR%QNRm[V-eD`` jd6c8:::<ޚ"y+dרaoo_E"[̶S'n>֗MI pwtɾ]zי 0al'y4:LB`&!rV(HS5jҲ@!00x<44"ZZZy[w;##fCCpX\eP3:̀nvb//޸l{"O p?ر@^■-/.C !!q`i ȴj%t4&Ǣ߉~f"ZY ! Xt)/^syx١L!#G1''cpvvfF"DD"e<&H7n-[=hkSS]|\ tnI%K-Ѵ) ٤ 0e pyheN`2`$[~љ̀AǏBy3OVt F9 ʹB%%%UzDž yEԇ`yII #[*\24)~udB0J ɃH$FӍרkBT!0L G n/-= '>5 &9ÛI5*hf@<Ap{jx3 <9;u*>HIᣏG"2}~~ 3uNG۷}}3\6*]5kB]%%}Yлp} <dTPI$B kNË/+tuGǢߟ'X|`ڹo2>)Oo{Z/^ WWG M6~ﱾDZmBD׸C a G_ClJ,bcQ\%TPe^;4pt펩 BCZ1c'G߾WmAB!PBH72w7bc%u@ꀁreΉZj*o*?u*z*o)`eX!W777ٙ'ߍG4€=$%!AWX^d 0jh9P- G"N'R*QBHUWZFСHGM سxh h01-[U**{|U?%|dOvGkso06]-4'k׀OǏnxE@zx$ `tg 'Xz!BQ % C J%'jAA=6и1Plϳo}; uR`c<|?4hvn y zLVÆ;g'xrکO &.\~x [WXbcSS~d>,Bϸ4:КXBHB׸C a G_'[0l9PeQz:42=d;lU0tLX[y Ʌ%|ի|*WGwn=#Z6'[qzzK$&@͚ѣovarɘA1G/ !$/t+?4e9WZlU2E,*N*Nڵą '5"f `o/?|ڭ}&SSR%>U||x2xRëκ7nɓЁRRȭpB&1XF04!(!$D‰'CժśO\Xc|̝ = ,]* `hlZsKÃWwcV"0g^^yo.On#qGOM"ƎSOtzw+F<!RB %`x_J:ֹ3O ǎ7[9еkϿi}[}?OVrca^C!K:4sa¸sP:9xrex1y៳gȭ 񁽗='*W,t8BJJshhh@]]m۶ӧ3mӣGl?R#99h֬TUUQn]1/^(D6zs$={lG<=1 -+6#]G wyzkkULIW\}}Ŋ0o^V5=_!10ejW=!]%YiӦŦMܶgϞ+>}Z󄅅nݺ7o֮]͛7all͛7cƌ,%WjZ*tm:-B߾F"޾n#wO+ G+xƍXQlё#tv?x-doB ;*nU;qܳBC!EF׸S 7/vڅtX|.Ґv?hkkgzf͚eeziW^Rul#2}zɶm^K ?ݹvM}##yΓe Vzĝw($R`0 OOOŋсUe˖!ׯ_CUUjjjU-[Dy6lubƍpBCCq=١aÆ5jTq<`XEPH~ZQv5m>S'>^d;,\X\ j_ի'O(;'&:nn<9/nLU6q| wwNcc0u$6nuB f%%%$&&bhѢΜ9cǎa…Xzuƶ'NDzмys9r$Nȿ`aa?T0l۶-.\m074^Yiƚ C!҈+#}| ϼ |-@ڲ;Ǐ|jI<1,Xt4_{w4$گ_ظ8tOe-l2}' zӧUIQyyS~Qc}f4"1>Jk ! ]ONcHIIjۊ+ʕ+֭[?3777"""jkk* 00 .D&MСC@ 򍛾,%09l3>@b 9#S7n%h>]9waz“Ky\7vE0f =V`LGN~]1$%1ֻ7cz1-~^1fl̘c>>E#%133,,+߻ O?Y <9X=:B)4Dk||<RRR+Ee pATڵk#005j6cʻVynO>a֭qJ;W(!5#\G`EhSBHZ*#NNkԨKKcdd@BOO/PD"hiim'BiViiiRQ|||N"rӥ^tuwApHI!o_ &xPQtu&M ? SZ*[jhh;-[}_Oq⪔뤥ƛOJ;p`ί?*OWݲ_gd$x쫳i  !ҥKxKMM-PRx:!,#GNNNXj0!66+V̶"fffI$}UTh+Ѹqc0e˖elxiS˗%ܺ^n_;BᐒF] A$ s.k^GS7_)ZÂPV#} 𵅦<1._Ѕc:pS*"C)񔔔T6JJuQݻ7nܸkkklnnn޽{1ߦ^^^3gعsg񂂂P~}XYY]6m˗DǎرժUÓ'O)E)xZp[̺2 1pJ{c͋B:FF@Ϟ@jBGX`qh: 3}w q$$$`ɒ%8y$"##ѤI,X So>`>!ahhX[[g:^PP4h+++899e<7Z /^DPPлwo^RJ_%.%w5ZH!(臆Ubk߭BC!Ŏq,71p<dWR;\^Yp!5PBLjdIBK,\cCQ2H!D(!$D98B:.ڡ8SR+Bd+%-cbI%hUB)(!$D)pb YWg4) Eݭ0`RU QB*:)g-|%%R(!$DUU/bxS.Ny]?Cx[yc߀}Y&`TQ8<^!\ݩ !bGm'J8*[ ƺpjSN0nHIKǸxo5vh0۱SQscM}:vXf 5k_~ <Ν {{{]x^P)cx~"y#s!RQ8;;ptty:D"90qDhQQQ{.}ڵky˗/c0663888رcGqDB_>ll1cN~";{믯qb C!RQc,дiSbӦMyn~z,[ w܁QլY3TT ߇Xk.] GGGx7ԴRLr mkSN^w3(:b:Utr>B%:mj:BQHt+?4e4]v!==+Vc [nŐ!C`dd4$&&J}/_˗H`HOOk^!^QkMbIHL\Xw6|irE1oΣ{?߈!bKj zQTCZ[KoۨmjmRjW;jXJRDD,bsr*3{Og^̬}@<wؼyvZ)SFnnn԰atC)**JիWW"լYS~bIweKKo݇dJZ=`9m5nlD9u)H"s1:uJ{V߾}|rnZGG}$M8Q!!!1cfϞjJg?gϞt3+bѷ/|~WdST 2T7էvNGIM;]s_+;]^2a(111Sm$I2 Cƍ/Ij߾.]/BC bccm8`{L@@|||4~x͝;7nM/uڵ(ͼܼ2EU(\A^ٲs}ew]}w[5`ņҕWڊ4T\#FIzJNMV9M=|Aiי]nIj*@M5Iϔ{&;J ~_gvx5 G:(QB=z6uۖGFFbXbի^ekZj0 nݺgϞՙ3godׯgH۟b dU}Tʽ5{h#v^;;ko3ۿu_cUpz+@~ו4ˏrwpr2tP}w]t >$;+2 C3gδ-3 CfR"ElOuuuU֭jn*|DhhZha[GܹseUVO_}V:v옥3x8-}Zju)Ul}g)%#kQ|_^>S|t5q@!caI;'"%5E=VPSjXa^;;{"wbh޼nݪ}f͚Zr6oެӧO>vVz榁0 M2EڷomZɓ'UBS}mk_Գ>.]?ԩSկ_?M6-SNhZ4=V_[Kbs甜M6z*\4K>u}UMG7fgU!Brwŋʕ+?OH;wjYf?~*VhksI=ٳg+?jȑ:|+^ziС/ ҥK:sZ))*ZR^n^ZnkTo^&6 IDAT̜VMԽFw}g7?ӒN u9p{,xܜvV~SU.Z9W;UX3j%>?.%5Emon5݌. xdkUU =?Cfۯf61V 2r,t7۬gv?hY(_O_} 9pP_J S ŵmyZǣj:k-y9 %!GBzjUM'6jWYg8_>]Ž5zjfad(61VmQ!BlY-7?ߨZh9q.lhҮIyRRRS\yIf>?WBes̯j֡rRj]ՠt-TVVUb),*LIVM4_*@?hnpMtX {}+wwM|~bq̧ǿr7 'r۽]M5I^۠[Y5k_SP<Ga>/ (!9A?Q+޳{u-J))*ZB5KTrMԩj')T Sy-?S岡w.}N<+iKsTZ ]O׵vJ3r=k.X,zk[r*gכg&유nջi}Zm]!Iɩj};y8yh~#@za>/ oui{v؟Tmԩj'{nv,vvZ}/qIqj<="[]G.Q 10BEx[F=WTHq/2hW]+ꗫuwv6L{lA!!d:U~•W& j -&3 Jkџ^n$K^vX.~jRB'h wG=VВKTXU9}\㖯~J?$  \! ;ܸ9zӶiYkZa*dzB?ӊ#+3A%ũӒNZZr9pH#m={tNS}KuŶSU5֟2-8V^*g=_[xx@&v,7j꼴bccUmZ5rB؟0(I TbJ&DGr(e//kn^%Tn5ţj1=7.MkTȨ㖭۪?֪WV!dRQ{jLGWah귺=7Nkykv~2VF0Ʃ̬gThU- 2ܓm5ţznsS]|̇Zbx4%,*Ltո\D MԬY3.\X[.]]RRƎ*UI%KT۶muGGGO?U&MTxqyxxAZdINlRE;-֧?ՠ TmZ5kG.{S^zNU;=)+)E/},[EOxJv)w*R\F~|ksdvdخkI} EՂVjɱ~+CxfR߾}բE kNvvv:zJ*w}.99YZҮ]ԯ_?ըQC/_ݻ5|pUR%>֮];u P|rmٲEÇ3U+󫁼 :;O_PA*Sj6mtm/.C CKfr/R_w}/V}V ZjѪ2^Xr2DwЄԳVOHNPi5zV-RmE/Zײ}q{w8yV8?^Æ ӎ;TN,cZUL"Tùs_,[،34x`zq^{)mk:.騽f{"Y]u7mQ';g~9naMT9Rt3 C'OVTN(>>>+W.]^zI :qăǞbјfc45ToW]uQ-ԳVO ~0(ݜC.PNlz&ƪߏ4f0(IM7[O.˻(!9a_˲.Zq!a#aڵkULSÆ KsʡCիrqqj֬ٳE>xO޳{UvRYs? =gvkՑU`_mQ"10B ̖i(-PvC˳S;oD+j@u k2L 74`}' >-x_LC…eggxFZb,X>@cƌ$ZJ:t<==0 ;V'Oԯjժe˗/J*Rnݚm@d.:}SUҵj{V Mϩ]{ǩ갤^6+7{#&o>^|#ݼtNSzpP.UKkKs=q{@h3{ ( 04n8[VHHΟ?/͟?_=z"""-I:}|||+hܹYu R|y ԶF7ԡJU+^MvJ5Ruim:I/;տN i,___999)..N]}r׮]aojԨ/ 6AI*S5j,:`Ϛ7o^,J7i2bZegg}@fp-e//ިo^~^]vLgbb;g{ ZmFm%oVݷSo{a:pTK?TP9'%%Ews[d:iٙj%IUD4/.0te[;IjL9rH}7n^}ÇkĈ_Ȋ:uTS޸k`砢Es[AtV_~髊Uhe9pԥKJ5RաJ[V5 GzxWxbEDD|};v~4h!2M4uȑ7uTzw5a„,{r3Bfݑ$r('?jūe!Ys+...L;j߾>C5JE<9(۠l߾֮]WtaըQCo&O, #""TP!,Yŋխ[7uMsyz_ 縹@x͛7֭[շo_լYS+W͛5}têW4p@)S(55UMC=y*T={モ$jԨ<<<'O?*TpZesXC~}GZx̙ʕ+k4SJ(((HcƌjUf4~x[bСCJNN&d2k֬LBxP\! 7"I`RB0)!L@&E "I`RB0)!L@M6Yf*\Un]-]4]$;VUTJ,m****K8qBZڷo_KIII_ ?HIIш#Wȯȏ8=0.Q3k,W-ZPvdggGTRzwm풓ժU+ڵKS5te޽[ÇW*U2gvׯ_Uڵ3]\\\\\tu9;;gX *11Qy])[W]Gy]ɓ0`yM8m'N۷kǎSamܸQѣx?LôiӔ#GJ_~vahСԩgd 4H Owڥ h˖-pd4ָ XV=T?@vJJJJ_ ?`"bQԻun rc4{L$y{{*QDŋa|tnݿ=o!̇^uXB׿ٳgu O?6 Rݺu,;22R7>]vM~<={@>3.^QF\rU.+coћoΝ;5p@w.w訙3g*T(];.rҸqΝ;F:wLڵkkݶ?Ry +w6,1qD۲7n>>>FÆ 2X|=۽曆q۲M6Ř1cFN KLL4Ο?oaŘ3gNvFѢEvڥ^3܌+WБl2;v{iw]䴝;wIIIi;vptt4wn[mah>l2(P@-sppP>}sNEFFau@zJII+Vm۶*UmYf%KV0!{{{/^2;Fnݪh[i Tll֮]}2;voIMMյk2\EN_~?>>>z'ua2>o0ٿ|}}f?zQѫW/QM6޽{m뢢t…tSJ~Rt2FoN:iթSGV<'www*TH0`_ cy*Z$>o73gϞW^^^2 CQQQyPVթS'nZEաC4a=3 U͚5uYIp IYgϞ{{{yzzٌ\vJMMO?Je޼&E^?"##5zhI|5a>/tm끼֠A5h}۶mձcGըQC|֭[gB䕬x,Xqttn̘1iUR%}GZl^~eIb"9rD PÆ գGI|53NNNJHHHƍbŊzuVag<2FxܸqG¿/Y,4b"7?^mڴ.]*"ۼF glowkwndZ2eׯۦd4)A䩬Q///{_VRR.]g3 Tttmc%&&F-[TLL~',YҶۼE gjժpƦYk.Y,ժU+*/""Bruu+tXFU H_Ujj*X]xQŊ-c"7$$$m۶:~֮]ʕ+Ym"3:uRrrOn[ٳg~i ;j'IիۖuQk֬I͛7+<>#d1rϙ< .h" >\ᶫqrvvΫ/]v:w ,cTRԪU`.\!<Ҋ/n*T,+f[m۶jڦΙ3GZv⢗_~Y3g*T"EwI3311QJ.-WWW5h@۶mg}/Vӄ#G_fRr(55UǏJ(cǦ׈#T\99::t4hmjU֭hѢ I*)Shɒ%Qվ}{yxxh:q:tFsΒ@9rDK,V\VZ?PŊխ[t#""O?iÆ Pǎʕ++$$D;vP޽ռys=SZl>s-YDUVչst4?qeO !1Z˗$uIׅ $???hܹ֭N:ٳg*Y$w5k,=>^閗/_>ͽ%JPժUӴΝ;+..N*TPj*iT%$$d=pXObTIRll (}jMRWW )Z._.]Zڴi6nܨ@M0J IudIJIIհa,mwСlAmڴQ6m[oOjժ%ULl @xL<[*UW_}U=zЄ  .h˖-Yfzx5w܇[dԔիWOΚ7oU\9[۷Ep!౐/={z_~~~СTl ֭O;v,^s…5c 5jH5kԖ-[fyxxH"##sN+@x1=)((H1116mZGW\_c}̇+-nٲEÇO}ӦMt<*[ ,nݺÇk:p]ׯYFEQ"E"E())ItR /TZ5UPv%yd:/^\~jٲڷo+Wܵ}%lvrrgg ς TNyzzCׯŋ3l_iڵiҔ)St9I7&+W.ͱ:88dŋx^}U_rBBBoYڵkݦdɒ8pm۶Vtt._hK\]]5l0zգG=z4Ku ֈ#tvKVXӧճgOM0A/^˗ժU+۟yK2eԡC4իZz$['Op2_ /դIY,5lP ,'|ݭ{j˖-ggg*T:xe*\rECѣGx}g{ VZt رC)))?~.]t׶*+VLn:]n-Z-޽l٢+V(99YIII:p$I]vԩSx 2DvvvkZ3?W@@$iӦf]%5d;6K >>]  еklC5kk׮Yfn3h mܸQEQvs;%5k֤{᭧&$$k׮*Z4g*[ljY۟0콩 o`RB0)!L@&E "I`RB0)!L@&E "I`RB0)!L֫+IENDB`PyNN-0.10.0/doc/images/examples/varying_poisson_neuron_20170505-150343.png000066400000000000000000001461561415343567000253360ustar00rootroot00000000000000PNG  IHDR^sBIT|d pHYs&? IDATx{U׹?AaPQ ů ㅼZ i30IH2DDK ʤ6䠨c(vt::*p3 9Ͼ<3L~W6/k^^kc# ; 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!`eĉb {w((M:{)6׍={?~G7n'|xCi&ׯ_o7X`]r%O}A%KeYZhC ièh WqUW '@k׮ 6駟\>nBXg(z!9s&=ON8nv&(mٲnv:}ӟDhjkkwwwӐ!CvMDDV18ڱc]qtᇓeYtQG7*[o=XJt衇҂ (˘1chĉDD /۟g2e xdY=*fEZN=Tt:Mԧhʔ)f͚Ͻt%ȑ#ɲ,*jhh(rgftUWѧ?iJ4x`2e .߮---ty@M86nh$Gy:,:CȲ,>|8M8"yb18,N,8^x-[F'NsҫJ> kCiFcƌjjoowt9͛ \ݶoN&M?~<'|B?8}__:i;S]|twScc#wyMk֬>LUW]EX&L@Ç۷ӓO>I ._|VZ%G}}==3tyyGx95jY/_^tG}DW\q%2Er߽{7}sw}=\CѕW^I]]]};ͣ+V%IzG^J&R e{W\IͣӴiӨ֯_O7t=_$G;whٲetGW8p L<M>'|2EQH#Gl6Koyt 7kt)oA'x"M:>Zf s9b KL7tr)t饗?OzϦ_~:(./~4dzW^{w AD4}tz颋.D"A?0-]׿?;DйK\iҤIFsm۶>х^H|A~>CEĤIh|r7n}K_w= 7tSN9?|ڵk=쳴tRzꩧhݺu:#[mFz*R)6muuuo[3gb19sfz1bF3'NdhoׯgHEQjkkY$af7xc],n,[n]]]lʔ),e6n"6mZѿYE"vW}²x^xᅂ԰H$=PnFOӢ;3Y4eofo{/FlժUcs]vA8aDؘ1cX6}3`H9RWnǢ({gcT*2L͛"7Qw^x+/?ŋY4e>m۶?w\DX]]]z-6p@fYVA_;+WdH},WDUU,}0WH=m߾=.|EQv}|'I&mҥ,z1O<=Pꫯ|5iaÆ{w!º}ݲe D"l~K,ahYFWgܹO>_gx{;$~:]|&"Aѷ5k1_>իW '@/.|2n>lτ 裏m۶[jE"5kVG),c[$o¸ٳgS*+W=O?M'O#X1˲6p޽V^M 5jSww0iVpDD4gE28b1D"ZdIIj2oct=hKDt}yGv=4v؂hʹ`: utt]k2n8׿.#FpB׹F~G?Q.3N=TڴiSхF&zaQ@or97XÇ'"u۳gE"k&7*P]]MW_}5teOӟhE>nfjll͛7S.[$~[Xω'(i_y0auD"60p@za֭[F67L0qo?5vE㏧3g7g^z%"WDΠA6l@owqu͘1:chtgg>+_ ztE]Dg}6zE1iǎtiݢKD4ydonq6lXe̘1o1C_|1ytꩧҐ!C\sgNX,&l˗/~N?tzGdHDx-[ޛoI1ڴimTN:vA˗/z2 .pOv":(߿s B@~ᆇI?$assH$R`ɘ5k}{ߣUV卯ٓ?l߾N8jooN:ii۶m|reD{OPܘ7o򗿤+W҄ PU*DF dl{wӖ-[hҥqF;뮣%K(/%>餓֬YC۷ox>a7^ѿ}'Ϳ)L#Î῿s_K/{G> M:y?_"OGynJ>,-Y}1c=䓁_瞣?{1׿NO?4M2E8&۳g}'VH~'LK.kFR$GyD:mnfjhh( .FE?OQX?/8"H>)nsqtW2L1;zկ~Ev 6yڴiF۠,MF?g?xt}n֬Y.\HO>$uQoң>J^x!Nn6D"8qTqgܹsc=.BJ$ Dr0|~|r?>?ONCz6nHs -[LZΝ;N#<]vO M2;<:Shܸqԯ_?׿E}]፮vo?:iÆ }>hbt]$ _y^yvz5e B@-qwMmSO]wE]vѰa訣˗g?Y{կ o|8]{E)\tI&vZ| ;ZZZ?1=tR4>ƏO_VT;w544?N?1׏+3`?oӹK^xaI3` ֭뮻LsΥnduW^yx 裏(GM- aݤ `1P P 2!)0LAe B(S`@P 2!)0LAe B(S`@P 2!)0LAe B(S`@|'k.Dz91F===dYE8 }\.Go7rԯ_n508ɶm0Yݻw~G\D eر:::h#GRWWWg,ˢP㠃֣0l0 ܰF:rn{kb" LKViym}'"J|mI&}dc{na=T{=CDZsGDmjvƖa|@#F0*sǎTQQ![l~iTtmȐ!TcQ 7ov-Cw=1Yם%: ir#"z)`]VOO[x1,YŒ$b˲X[[[=Pò,xbӣA,,e26b,h}>&.8644/U.s#ɸYNyR)6vXdH&,J>|m)sA~ sWztVf.sSީ +?GGTw:}s;KeZODmu jjG:fl~em"7n̐q~GwSH82WS IDATuӮ[dC\7O0^eѢEmN]7\linA̿R^^fԣU/6bR;>&ew޵nTd^ ,080.A-o cKR̲F]|r9֦5gl..3Qs[nFZ.Dzcb\*bX,͖133Lt:wo___2P/rt'^KyA&d2N#WXL&STeYlOβ٬kem&0K > B߅/&RאCX'&CnfB6:mף۩Θ?m6FDl۶mZ?'vӲMi.cL&[=񪯯K"J$Ia=24y͹(6UFd577r7nDƀor!bǏמW[-\H$جYu;B,_*bJ:e/lDOeee^fơ{ggg5|Ⱦ|cft ]X{ӭФM~cMN13Aʿ?ĢE"a?BTG*E;AN2jRVt{_XMIk&?n(tww껬M~u܋˨ z.zq6_^7' ܹ}3 v5xr=R ۭ {IPPqF6hG_tYD75wߔmtߪsٸ57]zzzw2{˲na\* aLn_,t18DϟϾl6Dm/^.2aENﲎ L\qhJ*{gL ]tԭSO655'jFe܍iUwYp?hkk󝒁-˱&2YQd2ixXss>cϸ\Mb4-b]ttDchJQ2٪Ҍ\U23M;+ ;<>OD"!mW"`ZɼիW+8P;*^555wya[ `q`RR-=Kj}ï7gcflXѝ?^晗RQu zxA4~2'barҥ:4N119!&ɲ,a0ㆆ`}pOkcPϟyi6|ewuu ] }TQ&/|*OTO</ UG?e*/xm_~%HǺki*3?c?kWӉ!&n% /2=VŅ՟ X{c\,X'TGn}7|`q`yoYM^0r.vbTd嚌KTOEҴ=NQ \k~r2W'GKoe؋d ̪mݺU&:,eTVn 2==hE| n@鈬?Tuttq(}ӝd/}}WǩTfr } h.h<'Q4@p(1_/[njOXxꖡ7*+(]Lc‰n㢊rƱٳg~0W/ed555A(I0bd1V-FE'gV[[+^uuu`:K񯦺RUUź nyn2Qn\ҿXP/s@w.82PQ[[Pv=n Ar ٗNޠV.>AQ NQ[l궧7ݟuD2\x4T'@M:OkL.:+_[{+(zlO5m51평v'rTq "O *tWb9ARQvjlTiǾ*Kb h}}}wUcy\N_tuR -MG_L_dN=й˴/Χ3mLeev$?Ŝ=n A3Ͳ)I bq1+Uc#.Rvwk6ՖtϙJXMM kiiʴ9u=Nt :݈Iug4eL*7L&KU1X͙3uttH],ҪϋƸwvަ]Gk*ow)}nԩ!t*dz t9Oun$"X v)Nxln2]#̦MDجYrhnn֒9s\c>,khhPzp}/' > B$a:$o:SCwTo9S*t02oI`x"SYYs֫#t#}4=!t&I0קd2s=Ǫ RK |Bm!L&YssVBӱ% 1*Z;]e./՜ pë=+_AuKU#[@$9a׃7|`q` :]E{~Sd;lM򃖳WEF7n^'ٚ\EE ko[*QhoN[L~7^c>080A8]zWo`9zDaLݴ~SUqYp%;IV^*brd'#M e>FܭT5Vw#c{Ot7SE'x\$HFOoRjYmWqԙ*)p7I`o΅ yjj=r݆"eo ]dYLfB׉K/T[oS^˲X{{[U4֪9bYkii:װ } ,?^X4.~IG`ŵ56͋^ZuyA8eWUU7hD?+>|O+vPA)u|W"ȻtIg>f2qhTzJtdjwi/iAuJL&LGdQ軭lÆ H&v9:z,f<}|)'J6%I6vؼqݱUe1S67Jud`Y驼 > B/S*֯Ơn==O.~멨lk Β-Dŷ}_7&o؃Lt GGt'Nc&ޗ{^E6~QZ껖ey},K~apa!j3VI:]GctjlrkYoen$2Չ+U θ{J'Y쑪^uIGT/04F<#SYY)\n^9o<ڰtY6Kӊ=n &+B[FL&GNS[[hr=܅ʭ| kQR$𘪑#G>RYy˨M/2 qu{K$FyèezfwFzD*aS:Y?E2\<gs1N],U4FlӦMEkL'u Fc*bg.I!e#3F>g v:f //*{AǁAt3NOCvw]jЗNa ;O:0չndc87o6;vl(qW]\އL&*h=hhh`-*zoy:ٳg (7TJ^2ea/`DW"b3f`---t􄐏 -ȱra<ϤqحR>ttt(SIGsnuYK}I&/ma!!8ܶ0"7Jץy-.Z:~~2qkFo荛˖hDltQTUUU݉tYg?w^)=ӏx<._4mI:7 "Pq֥vS7`t `< Z~MyѷX7% aȤSWeYͫ-& PV^Y~4~P@cy[[[z*r MMMҾ^(ɭNze MבljNeT: KV^m.~\Gyʃkײ 6]c-++LF>DlnnVͦR)v|U;ep(- @(kK",hݑUss4MF}{ ,mnn<$kmmu:&[,{T}B]Dвz+{x\ޤno aGG d*OYz~J۔L&cįy+$ \GdQ[ JWď>M؂ / ].^77_'e/>\VTM_dSTUUZ[^XwGeVVVsNF=n)AǁAt2.m_qW!KMb EQ(n'?Fe1:&ybtBٸ7N O:1Ӹ7Θ68 tydJ3ݲ12[6?ŋ^tHE_-NE1"qK > BCPqiit))U.1LX8u%Jc NƠb}mn*ݘ j,mӕnجH$"M١SƢq7Gb1U&d}0[?RvttHoTʼnrOZ*ӭ[? 19LZDTD+X,:::NcXAeYFy+p{{;kmmefY6ͧe&P?:oAu~ML=n)AǁALk"oq_#s-ְ¾Ig>5b1d~ưֳn Qx/UM,7BLd̘{x$Y̑*ǠL܌_zjG"/*OqvnY>LWEk@cc܅W1H {X6wkllľ29sXn̶z:e=X͚5KF%o3L{R:!ucZ0ܮL{,Lz^__=:CY>!M7>JVi|XAog"ݵTyNtOZѝzmc2ormn> =*y e \.ǺO9^£;MG)uMڰ«#4) UѤ+OcrS9+ _ƨc"ӵĨ?@7Hw\T䦃::Hmu{Y)߸ab9HYY,X`}nʞk !wz~mmmRktPǕβsjy[Q0ʚd:u,Tm>}2~'SQf7Kcu3~feE"zNdV߇=Ͳ 6{kK[WWQ+n9L&S2JWՠRxi[8χiѕpp۔(q9,Y }]HQ'ĈmB{ n ȷDzmTy|a !dXިM[}/m&(Y6-ԙn\0Y◕e??[*DkU'OT͘1C:t([pX碣N׺Ek@gg'kllT~Ǟ˅+Eݸq 6Z?6lX\r ϸA[WWWՕ=:/-i;---l۶mm۶ RBKoźQwmC7Bo#wS饏6V?tDuer-9J+:u6MdU:T[[UUU>P}GGN42[s>n]:c"jmDu H$dc[:`q0Y AU \#Emdn!H<Qm ,_-͔2L\-rޟzWvS2[sOUVkk"T*6o, >{Ydt_v~IjR#~z"Z{t-ţ*@**:3Ydh4~miݦ]]__w' Mt\Y=䧥L)aDtvv'xa[ `q`NQ sRj ~\#uDn9{+P ^3h7V&ƫ۳]jyՆ_ioR}bTJcΩOCUݲ6DB;nZ .8) T,T\nkA"<:Wz(5t)wam Xmڴ-RCN&5/VױD"URqa!!B;-& Juktۯ6:u u˯۳['a<gg?M=ø%RuBhzcLwt#5yB.3iM^tR) ]Ly_d RtDr=n A\m8A\ OD251^0HJCv磓V#q 30H)a.jiNo!a"Ɖ1B5D&t"&*[Jqa!Eo.ާ2jkk]$v,l:$R?ݘ̽QWn0uէ+onT+Ȳu:o,+۔fNAFR8k2*;۩=̲,6{l`i,;꤮>*cQU,s#=f ͸?20}hg6mʧPqpFV'%I]qa!0I>\%&KG2V~7ei??4^ގJAɌ*߲eU챉dEѢb^2~"TS]eF m5fYuu\z+o*ʷKVn2,xOh3zI頓 6CGRAnzs*:*Lx080):?~\KZv^%/eaN!6?[aU~䣺KwO{ Sy˦1d]_QQ=6uy迉xɾ|d2L>X]XL 4c!`q`tߌ긱MuNtNIYil~>'2X,ƪGl筘\R.e4Ռ|SY*0LTi:dsO$#?sW9Se!S k*b3nu IDAT_Wĸҷ<6M9?nz#cQHߓM>Vu֭eAX"|I[bratqՙSUQ555èr,oi&25w2β:tb2rZ\euM+eu>댁]ƹ\555I7MMM^WWeWlw.766'L*D xqtU?7lk1qM՜T鶛!HHu`ڵE>3O2*Q~u1"aiAX"$ַUԝg \. B`=f_#tuy9 O[UUUi뜗k:goAa f̘iٲeO>U&& 0vQuS`5AbOLODTi= p^tbM쭷H2wR;tzcщ\ݮoii)rg}G6nv78~yۥ:kىU*ʟȨSz[y43===Ҹ6"Һ996: d<1,ecw6=J&Sz>YeiѲ,fqUu&2z)nsۢ4&`{eGfFbt͞=}Ǿd]d80 2? ?yUi=t7eY,ɸMůW/XR'k,?x| fۋa;>X({̟\ˊ߳+tKDOOOQC^^&5HHkkkmqqC.37S6Tif񧩩IkltF٪U\=cݺuET/ rK&^$I6vXgtf1*+O7<Te2׹hl]꬯{Dx,L糝;wW_}7q\.& 0E OUove媨:!䧏JyBX wPT^Ë{~N`Mf<4n}ų*cB牢::c/O睝ҲT'VNt׾?^6]w{PNăj1LNse9M\.AY%⠃b ;od2\L`I|drɇMX+E a).a K?5h#Ӥ?AUqc-S=w˝!t#M7M]w=Ggn'+TfXLkn{dJ6Z-0Km=LP9sd?tPɨ3nq27<^U蔕,Czy賭 9_#kNDl(sQv*(+/HH]#ss(;]x~75Lg<婾cOku"Wƪ*[Fu\qu\[=]I&Ӻַ k<蔹,nsվꖿTfeeVsy-ay7Gcå2Z_H/6SLFvB$On&IL&d)3pL4gyg$v ;e^Yo}3\T񍣨^A$Ag]]rAI&H bM:::c`ު."QFɁp[6ho.xpѸUUUkAXGGu9!%Lbe`ѢE{RD|l,h42{l_0):nLۈNj~Ľ%G/nNaŴϻ}Vuu<81֑}r,dr^d29 (B}r t*tUjF,O']dBwʍ[T7: %b̙s׿7?c9s0 x"Ѿn S{AMI]J{flQVd^ΔtO2fK{c2MOx'n=Ѯ/d}9]t\\|ue'Z?Us@uPwu t|r+w<ѽө[ Ns92y>NhO:s̛7Oy{o\U`>0Kİa/ca[[o< +S_qesZMe8L&YuuqEƱ[Iϭ~y$kQ"J;ƙLPWpǞ>D>?f;SCu&˱Fe-mݺU]nPCC2-E[Leit-K#X2,t""V]]-]?˾JUAg~ɟg齬AFlhnsG5C _urer:]venD%,/&l˅ALyzd]aOi[ yw0KdCL]M )tU됎 iꥳ&i쮖&sM^vNUAO^)˚ !(q7'#AŦm7|`ꫯf57oæM.B lu < _ '?.魸 6A?6nνf<_ ͒_ә:\q7} ^ u^#L/&O}}q5OtZjvvvzr(_G߉-0Kī***ؔ)SX2d]t3f 6l{뭷< +jkkً=קNݺt c/1EWVmrqMw#Y鶉o"|S kr9"}n7vL\FE29%Ǔzuq62ݐ_xpȟ'z"HRSpym2ƺ)p/tGv^#Jun(fٳƉuݎ#Dtvq3Y3Ȗ-#Sg|UFG.ܳyK >& \¼لi~({.^EKSYIG/Չ#C=t#A3 yxiCй^qqʝ Suqx"?ĚuN,#^zETndt +, LL]L]8^ꔆ˒mV\ۛ]US9ʤM:ɩMĉ]wu.v,1(}n/+yϟ9nJV]v9[Neǥ֞ZEC|#')UR[]3u ? "\ٿ٢E_^Tzw]emNa[:`/"H߼@}T'@Hwx{enl,.+ܝuDd8?{J\<ξř+{:1c2Ous"K2dk<wu,K#J&c-VvZiqrɓQH$tMkf2D%&)^STLgk׮e9TƭlÆ }talٲ%]5X"*b vQGkzq{uAi}S0 uh2A}{'J,ttR*. bLLE&5չ狗X,s)$M$Ǫ*D"rt:1!BU9gVMq}q=w^Uc[FK aY{׋3˲< xq{:ud']]WJ7dwܕt ov8:;;YSSЭIE.VPL'rvfv E}]N>tVG$n':R\NyeYl鬣C[WvdUVV2˲eHRl޼yl6OU;Mvvvj鯨mH-Z$ nF2Tn&k(-H]?;;; M2Y8?{vIK/nN/7t˪]ZFGGGQ_tO2SUNDDTVV3gߺ̙3YeeraD]~(/}ҩnD6#˳(ztu%ɂ4%*yɤ29J&llݺu]tEFYss.yq51cu,e_"k.@?jmUkڴi}q ԍ׍F//υPv=:{AX"򗿰 6tPvYg: :UTT/˅A躾:r4qq믛U2 !OPr6*mǕ:A)O=q뫎Bv}4~iFwzyAW?MNc^մ sg~{cus,FMqa ʕ+YCCkhh`wu} \noOflзٗbzt ya&?n Le!OPr b~ʠT1~6~*=z/.O:fƍ3:eJL/tt.˕dt_S__s;h#S Akz`'nY[ [,V& dSFS8 uΫh){o}ɳar7)Bͦn]ME댬ݹ'mnk7|`O}Slٲe Fl[tZ :7Aŷ0&O soU="nٿǵneTv͛'Օyi0I`GƍsuQ%룉nOJLu%G\sn:Iiwjhh` ,Pb鼟ͷ(8p}`ӧOW6뜪c,;?Dau۬YOL}9nk;3s/\Du~M:fc?87oZd`>0KĀƟF Ӹ˲XSSS`d2i4tRPfmV'NuE9_gBݔ=~=ekR3P6mT1O>zoH$"Օb>n|"ti dmqLcmc^vuuc9F:;ɸ#;H@/n&h1L&Y"(Hw!O>d ~իWަM\K+++٦MkɃ=n ,sawyg 7ުyBkE1q 2AP躁u7Gan</]t+ҕ"ME.ՙ#[aMϘ{oPkj LkH>2}"Fy E #G\. B`Ε^ʬ08oJwX,2>`zM?+ U7=7Յ=HB%;v*+gwe[AtyRC{n<hodb: +#I]v;etjulر1·i^GQnݺUӧO/5u=WyG]ݔ<~x@X|L&yET^scV_MĦ.cǎ- .Tr[+XCCC|c[`*c7|`q`/Rl+dҎ0N_0wE;(7Uեq*WNQz '\WD:/^9^,eYұsۜ9ib|ɤm'mon~.Sqz1&'6щDBĉ}# vD12t>/tfGGܺ:a X,u9j-uǚF"6g!})\cT~|Q}l6kbyI,ޑc~mfZm6F=n)AǁAvOԽj;L );gFuܞTʐ+**Rde YtLZ.~ڛN} (xr[/jtŋV};T2-0C}=^d arL%2H”'T8ݞdMkޮߤ릉{q}UuNxevLoO$ztN '4&sS"R],Mt!*0ײ,]oT.>)ID* U]8!,0Cdĉl֭v{&MT}.HcC{gζ6٦0RGJ/G׷6di"v!vY{+&:2Љ3 ~ŷ,577F,lK[ IDATcU1\xݹuec2Eronn6JA7~Q(w>k3Ŏj|uگZubx'.FWcMYsTO<-080A؈>80}.7*NX 7NutC: <&n5StN抉,tN0uҕ@gLI[t")UR)VWW',ϣՕ$)y9!tME_PԏNGwQ)&mVQYNQa=n a[C`Acu7Īb'ySY":W:>ُ8%M xW~렬|;reBg_z#OEF:7|`q`01}dNݘ;7:m 7 -&1;n7row{e)|SƟrCCTLvycsD7k`K=#TvRt9aJRo.5e2Wd2l޼ypc3gfH>sSuFE'[F=Ɛ?>[`Vݼ{rX^5ŭVVmD7|`q`01kjg!k{njngbn$ 阈7׉Ò}&LAbMLY =έ6'{ݢq jxeo.^;oktJ$?1hDm(l d27V]]]^[[+eY_ߛ<2RЗd2ChY{Lƴ_mLxu2 QAwUy鶇Il.2!9CBW|lwE2,kY=d'Ȫ[. gl)I]5n@!lr(Q\TV\rH MeYzY6B5-ɂO! -t(*ܚGKނm;ͳEek;?LPCդ tP(Xɖ# 8H(}!uԿkB#b};a֭c_.[(QC3 3DoMUYLêP.YχNL8b;Dd2dbQ+ont0oZk*\De<3pUG'LM]SΒlyCenm{fY>+ T*ULTec9FjnbhudR mwerX,w¯lٲEi,EirJj*߲${qh|@!뮻R)6sLvG TNt(Q}9s*2Lw,"/Zח;|D Q~oRVwCtl31k71+z;QQuOWW۾}{M7-wo[ߺ* dRB=!Lltt 8N儇،}cZyxCALpU&T MeݷR-^XoIezoئ2yLY^t߇+XF˜_zPAiH&Ab'FaD$CW[_գgtcGۭķ5vKOAIRVW>"T*%:e 6𓷮.-yQteX|ސ!_p$~^ڷ~5n@!ɓ'Gbbc^#ٖ%.H=/h_D!LwF4QfȉudP݃~tBHFFFǔE25T8ΪCUD!xSF-C|>6olT. rddDy_=D26t۪UQ^sWQܻxP5"*J>&2~;eb=Pcbҥk =](0̙LLb'$ W"G2$]^tu}a"'&_arEAV7ow)LUVUg/ksw[ڊW&#*?ʳ ް&I;oa!tT*U_Շy; UcR'D"~az:c˴u}D}TQMF5}ѳ*BӫBR0mg۱”J}P"|(MB`B=CؚAʞ"0g|^B&Y:fFM8c& JF\f/5VA uǟ4+kcccSKFE~m.i~*<*55-6ZLudž,>8P(Ԅ7E7=loog۷oʢf/INkC*pÁ2C!qF}LT閯*~J;MoaUtuu>𼤒C;vPv` ,r\M8 +/#7M7:::*/?|́҄BLw6?sQQ!2LmjẏͶ]*CT*%<"CX}Yz뚋+7mt:]#69@ƶ>fxY:*d ;& -ʕ+ָq0&կ &K.y;d'؄ ׾5tS m`"{mFVQLŗSP(H"sv˕ST-09wC|нxt2j 7ɴmT27s욄Pgp_ܸwLdE#,iYFq-Xm߾]kgX5Bޘ3guP)D}292[_)s *4k?aQeG׫q_;FtnQk&DʪBgJt_E\~&XoB"_Xy|U~\&ι_G^y Hr.8 NYdv.>mָ0&~ӟT*N8v饗K/p ,Nn:](@ٗT%tWJ\iڢӢ㯡 üPuvI$SǟPL&kven2Ơ_ʤ9x˭\Dfm޲R](O`:~E=Lv9]n0v--P(Tf5pmmk>4tW2Ck";.n?yvU;XFyعˎha䐞B`W*nt>,2e m͸6v[cݾ`*_d1Tmfl.2 MRZr16 ŕ\\f]reM?@!(1qqDZo=tB(֮]zgҗ7 ÷.j>na⧌byFx a<ŢV덮io3QyR lb:u _.C&Meaϕ?Hǂ0˱i|rʨ_EqdtnBeV*i2Sa|nSS6U[mj+'޹ݖcmef/\~?reY>L׌W櫩|8l,ͧe9ꨣB!(1q-<}d@e v:f)W]@q GrDZC6y;ݕpYT2 mZBƤ t籱1iud! {1c[fA\Й鎌Hي+|!nvvvV|и:00`\.gj4kYyUb]Cʕ++R,[r%{C=Ĉ˜H$5W2l9Y]Ցacr$w>>*ʹIIҴmnO$cʑΥ2ikwُJu3Qj1۶X4 aJc°#TϮ^ڷMB<" }!*,05n|@!'|RzقTz(FeV:_E;@aDkqeD#*Ldϔ:TuR4c||\Kw.]*2sM'lV㬧|tXBkHKP.C\k3L<9@-_*;:a>x]LIQB&V###kYdd٪lUyYz>rgIw'W"ͲgyaP(@lpp1Yet|S~9aŁBAxT 2eLdPv4JNp h-[GK&JEU9D"! f7txppP듶7TH&a/ ڌ]oTbt.\ai"|n͔L&&W6 B \3!2%[?կ?tdelly5clɒ%gq }3Wo;Q_|qLKyvM>XF˜馛-P f0lB'(&2 ԏdq%#$c%B] cSNA.k#Y!$"٩luip41[s5n@!SV]'N8O6:](@ Y&ZӇ0 J^M/|lFu"K6Lv| kx! cˇCs8`hȟQY_gg1G ָEw}i@!:}D>=aUFmn#rU'IGr}t12IOeklhj:*,s7bhKSy 2Go9&:y.Q˵'### %KH: gr~3e&:`"7bd1LJOLdOLbϿN%K:LmyA-3hSmG5n@!3 oxP ?o;}~$0lD/ڨvMOu<|MNRlժUCB3xr?<<ܥ許Bhڗl IFFF[NA\ԏ;y .3&f՘vvvJc [ռ7M7Ͳȭ%^qJO%"Dm!rPNr_gٜ,'TuQɐ[VML|3q ayɓBTJٚTEm,Q(]U4OeC@QT¯]]]Ri(#̹CN&2Xkoosjދ8hIza'D}sի-LT*^5^L̗MǧXF˜;;]s5c'xuP &&HQ6oc|>itMWuqry- r_U5JUMȤR)6:: j1y6 *¤uljj(2;ALU^X2rJ[XotjG=Hܻ2S^>2V+;lG vBzY:2rZٌrpxxjwdc~3TɃҦn "~3=ޖ$<{.2j1%Ӽy_fY٩='7>{خ]BI8>X"ʩgS'5U#i=[!\zՂ=J < ˷NVU2i⇦CT2\wҝ7lҴ8P9X˜(lɒ%xOOl. P!ǸM&_T xD2_MTyۢT*|>_l곦k&o?5ttleTc'tTviVd}85e IΕXPȋjLL6'GV륳 _gưo{ǖ#JWZ,m/4ɿ;[AwNr;t,OU7>DP`]]]lƍlĉ;` ,NdIQBIߏ& GY.|ܯAQrTϚ֍ Qj1GLwZ IDATC24&\Uqu<2\.W" r+WV%LUV$U{e26666:::m;u/%x[=_,̲D7S?WlaҲ\7w?#Y*T;xrJF5n@!ߟ1&MT6oތ viס^>F-0k$G?oO3Q>Sg:\~^|/tHtvLǠ|d0qQ\N/bbg.;;β>m}6Q*MyZXF˜hkk(n/ 2euP-@WnHؔFhFAAFUjTʭXSь>Q(Q+c W[]H SqWA Wi~> oqCGq7#\ C ָq0&9vUW1^Qx +>'puP-Q:⽯/|)TTB}+Fl!~!~lYÇMт!S?zU^mAo9y5Qt* -R>1;+/4/T'\u5uŪ:KuLllb䇧sqRypW/kkj: +UΊYM{b=PcbƍlҤIcr좋.by{ĉ<`.B(|t{{;/G_9Ez#FAe'C^cdH__ > S422~Tz}YLm+o~zj!mG>oD{00qXWWVR)d*zFWHF9q*d a0Fqv7Ce{._(M(ىӇPel梶65ai{狟f W@u b)+K\ oԤv)Oa;K5!ԹJ߻1H=21aB@!Qn_zΗl6b>2[ "z!vɟ\߇V4.qOxm 9Cb8- UT&s\ՉUzѹ( {BoE ٢S:wX29\.R1qՙwU;v6eqj{ N(MB6a馧R(b2ykly[& _ ,O* %\zŶ3iFE6.S->'uG{xdԃ,(oޓ^K,|Tm޲+LbC1hL.]_TbGc^Ve~P .[ )75mcF5n@!D"ɤJRC!a]h{4w&&QȂnyʜ0jd})t:B`ybaZ^3.m%L&B3B`CE2D|4T{+DøEPP*} ;-CP#;֬YS17 !F 7`{4wC(9caըffl>TJ;Āʇfѹ|rm?Ҡ;mgglrqtZ6?ȖiY_("3f[y09-ALm?Kcn۫#"q axiR[h{'ӂB$qE${GUF]L}D V,O,lҗ;lH୏ɊAT*4i ;T/r[d [b,۰ap|xgyoݼoJKe>L&٦M >wrSS+"q~% |d 3l6|MX ʃOKR3۴i+s|ӎlllt̹1'+;͗5n@![?e2=CӄB䵾C...?bP.+1dyu$Iv 3{ )n ~dCX[Fy<_6;@\Ƈ;qxD+_ G-?FT? 6 ˖- ,/l6kTA/d}h"ϯ(6Lqqfx~~I㰞;6XFضmKX[[;ꨣ}ZPAؼV}eUF|l3lL2ۭUy!-Aˢ*_߅5n@!ŋne BCر9:;"ޯw_¶6SxժUĴt L͓?QqGgQhDԧ*g===F* 8NYTY5i']?,eeê |6::ԤaeAgfZ혌]Fe1w &!ƍ(MBC3A/T^W֮./NL?L?~fnߦ(Ͽۧ:rnUN*RN!CL.1"gY|R_zHœTu5xγ1g~WH$ ;.q ahԓ>6ӡNe|T_S >Q_~Y|0f8& Wn ݯaʴI"T/˗/?km:AP'oN6fd6֩c\AUϺǣN>n٨Lj|6<<,mOsc=R:].Ӻ.+Jz;44$|֓,t䋟tkB@!*- cLhb͉DL^E?chҟBYpBP!ź*_|:taʴnT9S#Q2?2$SOSY|4d'jcE_0|3uaQt\2ԇF?~sPkC(Oo ,S9Dd|\s$֟9&WOuzIyҦN njK֝ kBp\.;w fEH j`bRoװ0}sWYRmBwʌyN>:fϲtL/]?4݅sslSշʇPG6U >VCfFu J*a1eB=Pʮkֻ4R,@9Q___q5rLO=kcZB)t(U) Tu<>[lٲ&nsGUdيϐGL_֧rwZiGG f2-1oo1tUVY͓sIqW&(C322~_ T}(7cH+WZWq̳uW&a700P)l\ bvʛL&ݲ3ۘl[l"Ў辠F&;F8QwM9"Fxy' S;2zettFyZoj?_#ƃq ;Ö-[[_oԦ=ri٘3eCw_{t_70w9: sůlR9{L6()AB#8s=lddJ~xdOd2CteG%%_DQp#~v3 ;s‚Ȩ8lO43<2#BPf}M$+[ӳ(=jև0jLMWf>|MM$Es{{aG9ojkt:]]:}<nluL5ƒLfuf2D!/6}0.ukB`fUѨL^Mw- p oa1?eƢ6=B.J7\.ǖ/_.jw/_^eis$h]tmŽRe;.CfrW0vm&2+kqXwww.j\yոOe"..L^,ʆovU6ΫT*i՟]Vn˪/l[+auxH#9;ָ`|Q5r>Cl`q~m066.\(mc?s&Q]em*q;SuT{qcz\6vyrX,V=fْ%K؊+LŢR!?~M|>_iL&# ZG;ton77lxxXk\ ȋj\_^*bsb-Vl6[/UwLײ,*:, U>tZKS˴EG*"|uǜfaLlkWW+VK/Ql;QtUߘ.wHNh Mg>17>69,N~LEG_a_?88hdKaGtLKmFvuttRDjd2R3etAIf>M^.L[GV\e [WפZvowٌqe>ѝ,8LVNt˓hܪkGGe}}}Zf~W*b>Y[~d&&2 "p{8ڼ?a˗/[6$>r3ϔ4ƍ(M 0/il8LQhz//aFVAF(qi||'h/VԈ>ý%[T }":B߯-Av{zzB ήo;:e"]F;ܾ"9ںukڅ+*iӶRct:]UqX{{qy!L5qttTrttT{Κ4܇J%xdXG8:25n|@!lr0X 25 >7r+JE2BUT'6rk*&ӠʠL6td&*BUm4-% -LB~.a\>WCB0BC̅bjzjqc atQ}mv;ثnz|Rg6x\* _0e]U(1e[vctBwu[%:2[_Y,̍ueƂ JL]x( 零 @K4&EQ$ҐSt+V4S9lkkc۶mcDX& !e˖-ŗ/iS#'źM=mQV^QgQjɞ_•1###J9mo2]SWgܿ_GδukR~~]GomB% ]ׯ7˽rwka1g no $#4Ldq~zPQ:uc=ƈƍ(MBKX;_9eb-}QUOROT_ +J{TQbxKG?'K(Y;Cfe_%k~ɹwGʹU]*j r,T$_"q0m8oߘ.yxx8pDChaɁBLÇ0hQEWoBSzg+GB3:d S& C?LYmTWɐ?A/?pF^utWeyFWM669,-[6lؠ(ʗJ'2'ٖIgRf~fu|Db 600 }^eWh~_*i5T*I}|xd& ,T*U٥{+Vte;?l+w㬧yH^Vl;NU w~eנ~zRS˗+号RnCeнc.=Sտ:rh|zƶU7)^{B*J67822Sɡw7>69,xQ ӯ.,eŦL6>&e5200PsyY0a˖- 3:ϼtV ØC2+PY2L&z{{}^ƏwқgՑKqX&Ur} }c.ܲeO;9<<\#]#֍{)ݦ~y>Tcq a`j_ 6e4 ZAL֢B/le9\ژYɮ ;|eZUy&²SOZCPT\.uϤ̶ݿ6r)96 2~<'~cȤ5kN/ָ`8!äLQ+Ay^p_У W=g{l1T(qaokVfcS sYDدtyo9D] R #m$e*br٨c&n Wic(['}v7>69,n6rK'h~8}e:D:Lָn]s_+rS:ϛX,VaӿgR+5uVo|L|dm 3 bQJ:!'tvc!]̘]/i9_s,UeʒJQ2.eag.ѡ?ƨvs,>NU}$;D|ZP+Xp|x0(TU:Eڈg&ʌ.{on!t=WGm8T!y:?ʍ(| ָɁBt1QM˜4F!D"af~L'm=lJe IDAT¬O-lT!'EC6a:Ϩc 5F9?Qm/{]Z/L&q4V,܀j|x5aWfb7 q`M;PdzAGNd}chU[Ϙ18bf8.Ud9~c.ŋk+:a."97oY-LƷoEClI|ӦMϟ6mT@Gkύ5n@!lrT_% FSQPI[TUv rvGlrVte[6.˾}Nc!tS{n(Mm60X!L(pQȻM̝&2AڷzNEn&6s'v& !ćP6aˈ2z.T-$tRq-rTu45laGƇL!G j ߖ֯=j'>7 *S}X3Q^u aȻ,D%۸ :.,nq a":e$ԜW1U :qkު2m˰gkdrh*k6&f^/fYwwa<15s "Bd;z*LnSx!ߓ[{>77R4Bnsh*kAw~YOOOΉOU2*ƤHnoCCC܇~y:xO$,Ͳ*?._h[9 o>^lY1$Zhdݟ];ø!`5::ɼnHv kwO4VDS!\lv,MY L"a|@!lrmnf1)s Y9Eӽ$HVvd W5LMƻM6C7e0~]Ut.Tzc;_붯n`P C0DҁlpL'MCվ+o:1#˃9s\eaZGӺwHtH|9*qIA-JV)՞&|vu hyMv ldE'2өx := _Bwy8l+ߨ,*Dy䑬JR[bXLNtc:s\eg[5JR]B\"ov(MN\fD԰qqx-N'>rymҎ=m D"!لN~Q_:y7ԏĿ.Bڶζ^ʒVQ)ʱiꈏ#)6k+w|9oXSKȅ\b*rEe-[(MBwbDod~ad˛γ۶mcDV\YfP(XDi l%_1P$@9u_~򥪃Ѷ6-Cc2.UV*r9NaXvr^g}v(ubcccZeOTYlθ}"?>]UGU6L&Æ*2 2?u{ vn(lSM2*cQ-nu}MERH6//h6>uwoy Խ)m{L7Mƥrm=}M|~$΢}l3&^5fqF&ٵvz4~L,9MT12d>"t7lEf.>= lQt Qmc(Z&jj~J*~JdRQWX4E5UP5θA;e /R&ܗ8%~&"9Em|*%KX%2_v9(ѓ`1MK\I&M0AxO2T*s@k.8q"9C;w{.a_9 i̙4667r}vYfŞwtx *J4e*JFu=k#/a96?Lv^m~"2joY̟r裏1Cg,=5m^۷ov)QY}ʩSְq;?vM{,ڵK+-qGq5km߾8Q9:hW#ocv$"G3ct'ɓiƌh"z~ߥS[[͛7oE]P.2J&7o@Di&:Siƌ4qD:kq?L/B'NC=֭[G;v쨺@@9֬YîzvGL&~׻h 8 6{lvE~.cfb&Mb?p徧~^fַؗe6}tvU^;3 7/^Ww@hnѾ[~7͛7no}}}/˫Ҽ 饗^+8no|¿C^瞣.oN +'W>k,J&8A^h6_h޼y4iҤTZg}k/""zg瞣7M5-o|o]]]UuuuQ24'{B@G;׿)SSO=ErM4LB˗/1"W91FK.z[nk."jkk$@!l2>S>C1zgP*Po~֭[0"'D++/"ڵro*(L&C3f̀Lk;[Nw "ڼy3ڵ>I'DvykiҥDYr 'кuWuvvOs +@V;vP6=UZ{zzz裏EѫrL&C;v쨘L57/"]>Ӆ@^K/D;ve˖׿u"":hll:K!+9sqGgqM>.h֬Y|r M&b_>,r)4m4O~BD^Yikk;w !SMΧ?i1cyD14s9|~:C~\@?xeNݻwӚ5k@&c}`͞=;":Q*ORDw}7͚57rӧS&ܻ{Pv^x2 K$Ϋf1 x,˱͛7W~it: yB؄yqv%뮻ogw..% |?8O={Aľo/}KlllΝUi^},L n-Z%Iv]=c|%IvYg-\%I|rd0}2 9s&[nI'Ē$}(MKٳY[[{[~_ջX &?xL&}/7<;٤I٢Es='Ln`z(rfW]uUugo|k~qv饗sl6͛'[ `~v)ٳgl69v嗳ݻwWyI0XV !(PEB- BhQ@Z(Т@! !(PEB- BhQ@@RJ_ϟO8ꨣo,} B M2T*EdJRt饗GM?hʔ)u)5ks%g>Ck֬,}IE9s=WǴvZ*ݸI&ф U<~Gz* 8gw}{tI!ruD"A{w &o[J&ћnMFwur!4qD:3iǎtM7ܹsitEUyܹ>~G&M:~Jw-{*e _uvvҍ7HpMD[l7 t}{Zt)=7ӟoЭJϧaJ-oy ]q!kqkiΜ9DDtgCz稭9z;I}}}pBN驧YfѪUnF(ghhfϞ];cnF0aB%bHDDtW\A}}}7z)g}~7R)oMozSUgϦz*Vdk &TA"3gҜ9s7888Hwyɓ+}G[lgǎj~3gNo̙3iU_p!m߾ΝK\pq{hϞ=466xMdH$ٳ^z%JӴi&J&N47ڋFGG~QX 6Я~+ + "ziĉfeUBQgg'޽}Y:裍{GB)C6SN9N9Z|9r!Cт ]Px4Ls-ZJ{~GNo9PD{nz[J&L4a:*lܸ k0ߧE?q:C =>s?L7o6]S_Ox;#7_MFDD[n￟,Yb^1f*Jt5D'?Iڶm]{!O}SUQ0sLZn]yh=C- vEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB-\"% IDAT BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@! !(PEB- BhQ@Z(Т@!%4'䪸馛36mw}~G￿s~@ aXSO={o:u*͟??A:#)$Q"кohh$Jʢ;^;Nꪫ9gҬYhڷ-vMSLbHDD?)L=CDD=M6ctҴiӈ8mܸ֬YCSLSN97:ꨣhʔ)wn?%\B7x#0>@SNo+WO}StR8q"uO~tYgի^f-pƌt!Pww7'?2cppg/~tUW]E7nGy~_wQdhΜ9tRTn>OODD󟉈gRDg}6=.Bz;GDD7x#|ʹqFz^zIw*c=l__tAU>)HT."+9+T*]wUIst-?O&L~ϛ7>Ч?i6 ž={'? 4o<"zńxhh>9Occcu+3@K ,K/^SGGmذv )LgMR?|:u*>tqUpٳ㎣N8ު{c~@w\eGs]я~D+V>r]~{n?֮][w]F i'qsϭXv-?ڱs_ONӧOiӦi׮]Fir9:6~DDUٳiޝpYf,1QzzeYOH˔%NdmH-) 9"3MH o~R9M3Ϝ= 1r E[[q~~Th4Zm'~J3dwW>3<OTBBCף0XYYv |9W...0͟GUUj   \^^`0&ߋua2PRR%ܽ`oo\1k=<<؈9k1JKK^K+n DnnE@2Lutt$?;;Zdd$nooӃDH$AaaaVjA+AxA"XgR]]]X]],$$ g4_A\\Pc&""""b4Q__<>>yU077'^\ngg}KKK= @~~>z{{!!!J FwY_MMMWXX,//gggPΆL&G*uuubYgg'144+L&`ll$?1f"""""y_ 899Azz:<<< 1??/nRꃂڊ<VۆJGVVX쌆B.cttXXX@?  *++ŭeee(**B||<77Rll6#%%>* t^yݾ...ogff޼ؤ$AR P(ˡ011$Ajj1yy ٌ=FDDDDDD DDDDDD6F1(B""""""@HDDDDDdl&T?u}<-IENDB`PyNN-0.10.0/doc/images/neo_example.png000066400000000000000000002511011415343567000174400ustar00rootroot00000000000000PNG  IHDR.2$iCCPICC Profile8UoT>oR? XGůUS[IJ*$:7鶪O{7@Hkk?<kktq݋m6nƶد-mR;`zv x#=\% oYRڱ#&?>ҹЪn_;j;$}*}+(}'}/LtY"$].9⦅%{_a݊]hk5'SN{<_ t jM{-4%TńtY۟R6#v\喊x:'HO3^&0::m,L%3:qVE t]~Iv6Wٯ) |ʸ2]G4(6w‹$"AEv m[D;Vh[}چN|3HS:KtxU'D;77;_"e?Yqxl+ pHYsaa?i@IDATx| @H5t"JG"b vŧ>Q RDDE:(^B = ]ev3;3;3;{.03wn9{gw{@`L 0&] #`L 0A~`L 0`5]ł2&`3`L +.* 0&`Ņ&`L5XqqMWL 0&+. 0&`!keL 0&Xqg 0&p V\\U,(`L 0& ?L 0&k⚮bA`L 0V\`L 0&\Ct `L `L 0&XP&`L j)SP͚5jTNZl>`L 0&22u{Q^:ԨQ#={6=ôsNeq&`L |beӦM+[رcx皙`L 0 u֥ӧOܹso &йsh۶mԪU++V%KRҥsćqaZH:JΎ9b:[ЅϬM̭Keڈ[Ld"V)ml1._LѦ^GO_~% >\(,7+R|rrVDӛ.]Iu(_~[1%LjEU.믧 :ZFYpuGf|ޭ`n|2+ʔ˶!*k׮zjڲe ?N8A 6o@YUh*-9f8,u:=%[!eF#.={PJD_>ԤI]v,wGNw KI[!eF3lK(.z 6ϟ~i%>gL 0&\@ *`,JϧMFSNXD&`L@& nZH}K"EPB2")YOj\eѩ;phRcPG P7<2J.;ޡw@IcƌyQzz:/^ڵkGC%Lh X ݻwo={V,޽{VXĦ|1M]BS…~l=Qc ߙρZgÙ}RP۸@HHHK KNJ~;͘1CS#s9*&`@w#6! .Çi^ j`LIx:~ƸW%&&#'#\l( 05k楴8{ɢ) G2pBj~kZpKϞ=)))̙C۷oʕ+C &M{'",ˋalJ?Ck2p?Bj~kZpSEX=fMeⵠ__~b锡FEi"E(CL Xor 55WjӉcS< J)ˡˡ+T \WL=zTqAрX<',NKdJ-Zo0 urz&EPߚV+U$5E?LeKD?₽{=?=so9Y,ҳh"_~vT%pR҂H0 #r&CPߚV+U vC<5|Em \JN`L}x:8wǎtmQŊ,5 uN^PNdL 0&8-O?XE+W.@J/lZΝ;'߿Os^N`L 8G\ĆPZpa-x܅x$$7'L@#nݺ ?D8r`vgbjh4m4OK.fnڴbbbhذaTfMA#LBƝ0GV^u3_*C}͝;Wxˍzꥹ2jxžG;wMHv3#/X"`ߺgzzhfnKd9wr[ɓ'h ޡV+.%ƍo=ׁNVJsdؤ[j s ҙ3g|iM㧊8Κ5:ErƔ)W6]H j|,?8|q́ XI9+_6>s 8nӦMu`cŌ a;b׿ksTL 0&"PǏ5$n`L 0! tȑ#!l\"G}TwO}ԼysfXHD%צFMmHe=8PBX]e0s94FZ.^(N nӉX&&++'[IxzCkcS۶m5VOT"b)NK,JlrhL]Z%"r9`X񹲢LMqp"f rhǯ*wd05Cu֥ӧO{aE41.qBL XLw3qNUkk(… TdIQ <1ʊnS)33seENL>fPdwq.>E3fطolْʔ)#V$a"0`L k޽{Ӎ7Hiii{ & ?.0 z)V281`L 0&^Q\233ňK:ū`p qW#t]Pُ^mҤ YFCʜI~7tbb {1g`1k5YBy'ᚃ3 8-Gp? :{lz-+Wjș3IFhСtwir0t_Byr pJ_[pׯS]~Vq/;ޡ*v|95M–)}n9]<Q\9cǎ!뮣OPܹ護ޢ *%!ʙ`L 0Fq2diӆׯ/0U{/P%D/ DwU?b%8ARG7iHQ #PLnQf@5ˡGI0&IIYdwO(1#sN niX&`7~(Y'(hӹsgʕ+믿iQ.ŵӂdt3 E?7lV9^J*E#FNSE:uҐ;̙3Ezcȭˡ1=%_\;-AF1cyYsfz+BYQ=J!xƋrc)\G  >lb6]. #|)Qf6Dӵ\OuFtiJg95s vV;+s&`Bwkl\v*F\d=@TF 6l}.J.1rƍ 0&`!UE %ʕ+%ɞ###`l%TV>c:Xy믿%+aL 0&B5 t`PۧOzw4vX¾CZBBD^9-|.7Y8mx [Z]/qW-YڶmKVƍʕٱcX>]lY.BiFiEXO`L D ;ޡqksy¶E)&sF&`L4*C O?4լYSZp`L 0&I5˘1cĞB0ҽ|a凃9'~2m!\m^+ʌ!k1 NŊEbQ\!++F۶mݻGbMM]B'OG\GKWۭ׊29vb{IϝdgJ;ظMڻwXΌ+Xfen޵2ϝpup݊z(3Zn'Ͻ'=wNmT8f%33.pI{˥}fU5PWWfP>n f]kͮ.Sk[5s-}d1OLi1S_~w{v(.𜋕Eٳc[!s1FMO@kP|%v6^07Lmtnzεh>-2 NY|ek;ޡj'Ջ,X@N5N!>>?cݿ⻲hfn1sfn?kPÏ ӦM H`L L j_t|I8pͨ]]>YT5"ȁ JP r~DԶG>zY4h=z6md׳z._MgHq́ %ϓQrn:Qa󦥥yڵk C߮]zp?1yEɒ%4SQf]6e^Ђc }>j 's}FNPG볊' XVb322,}\RRVZ~. 0&@$*U*  Bvv6A[Zj$- 0&@ %!PR}сǏ-b`L 0&QaXv-e„ ԴiSK`L P(.L 0& DT[`L +.0&`!keL 0&Xqg 0&p R\j\r7o^Zl͛GKUF?0&`!Q /^XhCF۷O?;wNNG&`L%"Jq[r^:DOݻ*SL╎/`L 0(E 7q'|"nchsN`L 8@Tl8m4z護"ظԩS˧-NºD/\/*RڵB9\-MQ}b&h˾CݡC!$oiҥTIIIT"'O:~'m8NM֮$ʺH/?ߘL ( E믿.\.Nx3wדyh۾C%49&b\A97PBԭ[7ON@YU+sNw0s8 8WC-*DMՋ,X 6Rҥ .\Ko6㏄F0ޝ5kS#>n nm}eD: 8WC-.#틨 z.]$(-;v*X ٳ>p:`L 0 DԈ _KV"\N8.WVpY;Sc*9'ƅnc5\ 6VU3#f1W-փx:f%33ƌC𳒞NŋvСC)!=Ǐn^}U_ ~˄%o#7 'yq'(_|ٓHYVJʶU Éۘctb? ~4#f1Wʭ?4y41K޽%e"NXN%SNof̘a r5^|RSSnI5Qnҥ#~i.f.K…tQ"aSD._LF Po)9(ۮV- tXCD'R;r+?롰p{^(jۚKzg]4h@k/>P s߲>isjtK[U} 5>+H#hK(eE(+9ƅRo4U.N%\ ֫d2ݡ[ex:FqiӦ a M }b'g,q`3Ŗ-[%аqt|s8Ao|Z7X`}ˋpGGL>S1 gj['ȭGF\(S?8?u?Zz(Y'.^͝}b üK HVmڴϟO&ě~a~\.\(G s3V.*Mq7j Wo)[^_G8 g?fʯVZ_{y!(8e Rpe}yW Nh7މtTrˎY ?+ϹpϹQOP.X63Tv,°kzErj]pa`[Z5O6K; 0&@꘩;vmF+V0 (@p#}%5kGiAUҏ?HUTƍk-1&`"ťo߾҄={нޫkŋ]%6nHP7}O۶Ã/3#yRN4qL XC@QOZkRxEV[b"'뫊/^5?e˖^t'?{@'[t;{]v͖v$8:X0GL-KZ5|F*UZ\dڻ5j ZRPv.QpzZ#mdS-.ZurFѪU< [۶m3:O_?.vݧ>L 4oPoc)o|`L u̪{pl޼YqŊܗɒ׾\Njd޲e ¯ ԩSGUi\'dL 0&B1˨Q4dqZHSE4{l|?:wߴ`L 0pqdȑ4yd:z(]rEr HKw\AwU+ЉMTxmjd){@[[}71"3[ةɩ^RZ!3"c {;w{1:t P7BʋՑ#SPos 2j;gdzӛ#edvjršW%ewmd[V‹] `J #3w׾YŋWס2sDWDF-O)c{#XpL6o}79RFF)naONQeZ|F#kL30;:8NqiڴXE( YiA~kb*J򨛣H`zR*ס2)H/g÷<ڎ`Yw09H?9վGis-vmƏak#i7`UqSEaU<p>w^OǏyX|ҥQᘢho7߲>:|S=쬖G@7>i";[ةɩR'$\'L~":q ,X@˟7mDŁ]»رcYfUٱ]bdL 0&rvC7epߥK11fX@ʓ'e5kiK+oMk֬!`L ޽{Ӎ7Hiiib^r„ 0/^: tqzgEL 0&88!bM*;S2)!;VtUn}򽶴r.RKMo)<.\}5ZEy&DžFq#.[q5xO?SHE%><sϊ# uMmu[&.*߾9Rc~wZө"(/X`d 8n>vE~;}g!SNӴl2Q<k 3FZ# u] eVmUܫ|HWC@kiM֊P`W)R؏'4i" hQ!.ZN8Au>}.\Hǎ# VqϬkLeK>i VߺK.^n^%۷V>Gj֯7ZZNk:V쾚L~\=۷oO7FSُK~X?b#5EsL JSqŷ;vn(|H WC@kiM֊P`WB'8?.b'礤$Oj֬9tE|yvJ 6^zI(;֠V̑L 0&\Nwl\-cǎNeƌԲeKQM7Qܹ(w܄QL 0&8Nq2diӆׯ/sŨO>|9M$X$;%htƖ6H]FX Vk! .:L=+SOzZ-7>[=rH.6FLJN"Li Rre7`RhѨRZvHu;wpߪe$U[]Um\ypZB=Ik\~x 8nĥ4*;N(:. #-eҸ*H䷺\jUV'YozՓj| \&OޡV).Jne*ԩS>cQƴi^tmFŊQ[Ch?! ˤqmU0R;rL 0&"PMa2dX\F 6lW߿`zH"b#R:XiyU%`| 0&;ǭ**X %I!"Va(##:wbƲe<\LL 3Μ9CgϞ>F,N`L 8F\G~ _+p"ڔZ-ڵkGtec>捔fJ25xi}fvM#〦[5ID@_ix|m+9n+?3]V`>2#A]\ZϜ"pO6-uʴ3W~-cg.% 쳧w| ݨ(6Y]Q%F\&NHCKi„ fŌE>͜9-[#-2JGqGbߴٗ"A yik* f$&owvY:_^{3W|zmuZg:fz;Bϟ?O |%A# $b%A?PZ-)D8.2N项18i0ne]NosϡbjV1ɨ't_|yώwC^{z2Orʉ2ϑ24lPu]hx:~W_XKsֶ}3s3s _PBx zdСCԩS'XetR%i80&`"8%|ܼy3-[Vl;Ç ]ΝKyQ(uq^&`L~[Kv3]x-Yl~Wk'\$#ye++ϕipoZF@fZE~qB%yRSZ'7o^WK(E;0%0 asNa\&ϐ8:X0GY2ل? ٔyNJ'ϒ;A9 \%||?{>2|}}8?.5nܹT ߀Q,5YMrJ:̕Lm^].#y8*+}Wg?9fL=Oe"Fd||'=%t{rn%s3) ZǎˑPC+.wEwuO+PH&`L "vHozKО7oᡗ`L 0&. ^zт ԩSb)5@ce}Գtʗ/a?$V\B!y`L @D).Aȵ2&`vة"r=L 0&}Xq5`L Hrv&`L>ǚkbL 0&B$K9;`L 0&`W(.֭uRbb"ժUVX@7k֌/Nqqq9cU24iBwΑ#`L 0gpKFO>$ 80ӈ#h֬Y9wI.:=ܓ#G0&`&x?.pWVmJOOTR\ڵsݼyy9y}ݴg˗'NP= 0&p6UcN*VPZ2W\b۷⢆;A+Ws AatQ͚5=8Q֥a&2*leS;fY)Y$̵қlmhW\ 5=6d<~xp~zZp4T0V`k\`!txۭX̙3 .Ee˖sy;8K;0Ֆu">چ~e\p3s{sy&(,u 꺸|&$PhYʺJ&( tk'w9`Jfʕ`u%:p`GiAPvDZ:kڴHf;5i0 /Aê`lŋamK4Nᠬ`.oժUEлv &q|Mj޼pu*$qli'=}ȉbLL XL &k9rm?;IxEq"%Jw)"-[U6F[.7Nk=?1zj/']>Fd%I)FLxZ,W\=Ċ  :0=z (;x(k#q ӇM0=adLF/z")؇ٳg 3#,$&&z}e+~tCw ʔ)#F\4(Syk[0߹s^#Lgٓ+a;;x++qߊ`zBYP;W ;4rE #-ʕ+ _D(40dP8&^`Bt)*Z0.b8/`.%;oMC ?}CÆ 7^8XWS,*x]SLP^|vi+{ἶ|sc+_H6Y 6YTMg̘!`)56z=Y*V9%`>]37Mtrt,Q<(3r8`A ĮhVZC2ȣ/ʸ@6YT "V6a7h5kF/Vft8c߶FbM/7H׼+SvirPɝ{n(S4@-_UMF%Ϝ9pmEP8JҥKuVfΝiʔ)bEِ77H:>>RƢz}l-z#(S2%Nj)2q nZ90H{b~ ![Ѣt 46Tg?&O#D %kbkl=M'҂ЧO1ee~+C6RdmfɭK>BۦM˅$PN2O;LW;_q;ݡ-*駟Ғ%KO>& tm&mڴ… EU.ҲG@G=xg7>imvD{y5QR*}B$$D} wQK+pJEMLT={g}&)~Xwc!"D D^{M+7YP:tHM4|4h Z*!ĉ)"o4S9bn{>1駯o*mȜCɹ'86OD@Dp f=D#bW^Bhٲ%ay`&ڸqު#.}@wT:l(V7͑#4(QgeQE{ьwM\9'ZiͺԩQo 12bbpGý!RZ>e0m۶2sNkh2tU~tj,ӽ|= @)#곏Nݡܚ+HK }fg͝WL )tGAڵ %~mR|VϭjҮ#<|bg,BX]80#`v|FЬ+>qr9h>N`z娬l (OOS/U˖-ի:uDg3kWꄤ=mMkW="҉'7Q9'WJ2ۗ+/''͝]s>~f_4ɝG򠛥9[Y+,?{|?.}W*UEu5{W*ҥKnݺD/^gy:#ю[ z`rK|Q|oU$l7-{ 2< Ai1D 8ә7j'Қ\SrJqI_o/#x}Z;j3gl3l̀\4us-\qyh V_ר-SejµZ)eRLM|h2+ [m?SDJTIZ'XNR|- 堹 `'5LS(.JV-O=<\RVRn|GW-]ҎW%K~Y3r_`/NN0cghL,<3+EÄBi ?o~:w*Sh_Ehdi!_JJ%zZ]l]_s*8|b -}W 6hbJ|+)ZOKz+5ף (-G/ :Orɭ]N7*\ FX0_!_#@R'.G$/PAY[.ff|U }X$B9_C3rұxRt+ĈV(%KLP/O~:uͨ6Vvвm8)7n\ѺtB3f3r@;Cɓ>=+6Y}QT+ gcG+mw2RBN䥺~ KU  9bNVf&曖(lЬܣ 6)XɃf2H6 sM%K_Cݘ*|%D߳fZ'CqY`c1}RjZjRVQlM'o:xWE&Ho@OVU,//D<Ɖf⁙u7ч\+QQZj{5P*6\K-*Ε;th-5ғO'PteI10[jTY!T/uetRjh*"J:v,bԹ &_3G)^rog2L ]^\]12TEdѯ~6odH'y;C##|7GLNNʉo˖-0믿˕+'N8AݺuZNn˱z:vu5i$ ׆|uJ4v1Yg WP]R 7_4'@25,YoS̠e]rU/Wty*i{6Tu/yOL[!m6F(ҚL[jl4UCЖY:T4MjI լMzׇ6Nj.I7,杇mܠrKx˅ F=dG}/6`' #/cliCU d~ 8-߱Fc.M%H*0_y2p&6[}YڻHC1*9 6 w/n7XRlٔ?UX3?è~\K rܫX<<#ebɳ6}ZYtkōZe%s.Q/Z&sD$.ް|ĥu0vǎsN1ڂl<\@0J6f-+@Lg,ZF|`drMJUMmPdZ`2}ӣk|R*)1°OݵA euoxb1W(U;CqJMi΅򥣏ekz`pB'&$6e=M[­ƺwU7k>0A1Ir _8bҒDѸ(%{+*EJIlBKv:a$+Ց> ~ЦL5鳿w/kT)WT*L-UiC+w-Aw0EHdҴiS?-qz%0$ 5:_ۍ)=*ļ!?mwA [mAT$LF!hA $_*qKĖvߣUe;? so~n-@ 5\oj7oP)8Upg-d~`G\qo߾˗]Yy_5[s ӂ4yZ~ƒEV5Q8Nt)gѾ^HE1TMh0iXY6ǖ_tK<-mL3wn!,в R ]-&GmwV۰XvXúhQNМq-Xː BhqԬY3JLLUҍ7(FBXD'E04ƹh#mP+O lrN=z# }isbzbxlI:/`:i=/[?fԎZlTrnёɉ-XTu|lΏ8W$k&F/V8A gܙYSiEۍ(∲R;Kƍ飏>".2Bh1_4j(JKK;F )_JMhvcS0-_SoGeHbnT%bYݘyԟBzayVt(c; uof^ܸJKC#j-ˬ =T7}䎗qX%tn/??(4luZq9\_}Kmf&3hQEg_ʕ+f͚6l6>RJiFpdסCO-ZaIǔijҋ0P^LxJKO ݖғ~[o&C$Z|C.0HF኶))\/U63qJ괜߭yP+y[RuƾgQYkגT5MF]"g%Kjmj%F"ߎ`Rn]OǎFi?ЦhQ (Ft.]>:wIb ڶogHO 9So:S|5/HMЧjaQ3OIR&҆6ڡ7VZ7ё#CڔO ]=ߨ < mJ%7[TnCi CibҚ\FtzU4!ll([ֆZ,W\4h@pڵ4~xzMm5"9{|9c&iJ#]8V\=:(2Z9]tmKޮ9ulnl7Hם5KS2=J:5-,"rr;`zj1RP!ZdH636ZDEPZ'$i@Bw04SCQ飦z޴?}djKzڑWBE#bjA[i©LN t6ȑMݎ4mF#|LVlJs-R5-~z/PM;ѪK M_*7nݙVn_lTovXCl~`KzRWL⵶L-W\7o.:pbňmI-/4eRzQjĒh3A*uiƺױaO,˷I/k҅]a/D2)JڄMĥM %pgǿ&YZӔ_ZM5; ?"#KOJê- SoZi64kzE+5֛kp?d̖+.YYYb9&ZblQ/M<4l ج=~5Pn- ͞Pӎ87Zr5OA;\2G :wDFJ8hV6g#(/jT >/:Ѐ6?gZwmcػ'KSUݒ nI;m/~dW\qٴiծ][@͛7 t  .ݡQ`ghݤIH'4@#{\//~~t7h2u_,f)Ng>O߮dtfU~4WhOf;"1Yb{A%Mk]φOQrut)}CL_@]٘#h0m2$ShؤS8l^YSW%\qPuޝ^uѣ%$$Hk:ݡ/_Lwy'޻bhlx=hߎx'?<})0WCsit']h-/K.YC:-0B /V8}lڰoiz$imRzݨioM}A_Zk ݴz7gI4Q;  tHػ]WN职}HX"zc:n:|`b4磻P7F0y$ %O?D&L#-]v DuWiӕ+Ws[jwՔ_o";^>ʝ;7ukҏ׺%nV^i~m$knĚK.ctC[rxֽ:c*IlF@_Ъ~Uwkڏn),}%JBC'I0FЮIFAO ,B%w$ܳ*{qi#_h(A=d_1\%7l Hݨk>ÑrIz8(p,srFpgNK1sy/>I˖]!_lAPA 0.czVޞfFm̜O<b###ú-qTzE<V7CF. 0&@%Tf]re+, $Ie80&`!xŌݡ۴i#*>}7xZj(<2,)`L 0p Ь]ONa싍,V)-C#slba ŬTT)- Fb80&`! =8YR&`LJ*\6`L 0&.XZ&`QM~n<`L 0w`]2&jDus`L 3v3f UZU,۷owW/L 0& x%ԝW^M#GyΝ;僚 0&p!Gqеjզt!8l'.6MDܪUO?,._\$?|0UP8 ʒ#`L 0&|dqǎ[36R.˜Q*.vteA뮣aÆRRR?$(@[nUU\xwh}G5>rM\&aߑZ$?-@;CCYy;7, ȗ/jP?z#!p… )KU%UX-ūZ83Wbid||pG۸346nH7o/YYYTn]`L 3vF_ٳGtɥK}R…]M,*`L 0&ڸ`I&і-[5j] >n{M>SlYZb}a%:t;H͛6l(\pDi9{,*T07F 0&"lO<R:ɢSMB*J֭[g?K.>s[xqc=ݪU+]u6j(wyUB5k̑_2.QD5k=IY4h8OKKˎ޷o={vvs^xV8K1sg1_vm2leΜ99cZlI;wf KmFy$ez)ڴiʢo0S~}'Kq+FZ3Q ˪Bɒ%բ9B`/fn/o̝\iy= %toBA#SSScI47zh0a(8DwMEt!76m*ϵmV ,X1&rE&7zɓ.]̙3өk׮>}P6m.HռTNw}bT?Z唏|#AN{WZfゕ?<aHvD|Wcz%(#6X#yt)®Ӱs`L@I,:|+2s7_+DΑksL:N>M 4>ǔܹ9 Ŕ)SFÒa3 Xϟ/FO@s@T@~GQA{ƏO7ʌX6d?6?͚5g=e+s&nz`cǎox|ʕ+i"Kt~SBڵiaOglc#wUGquMHq'H  @p  EJH@K? b-Ri)ZCwwA?alvfgsgW;9s=$9!!!IB'bNoٳg $]SKx\^ u믿OA^$'F`F .?OgwIFݻWjH OQoߞw5z!K-.qָD;+wvtո`8VF7]2h =bp8=Tx#0'V.;wu|Dも(.- =#8GY+< @u#_|R㢜>Jٵk>6Z#G`҂9xoC.(ӧ%wzՓN.)%q0#D@3c|h#ǟmAt:LY9G]uuAO‹mi߾=aqTPs] .Jl: x,+] )Z ߢ$vDZ[4̅`F`|qg[N]j M`Džg6a PF`&ȹE;M7O?wα_|}'.#-MϞ=iС`10BZZjI ;jx]f͚E"Kcoڴ)M2>#0DG(8_+Ź{:%M9:q6-1pR_ q-fuA84h(д ^NF(`-\\W}N\J6x`Fy$=Zn-A0dT3ܽkYc}uF`#3U 9?zL.>_…ΜIlap|!QIN uؓ @Pj9!BMpҖ-[4xEIt0]թSil¡-BL Dh悱nwK;Q`e/gvҟYaF~t\󣓢cu츹phݟ;v3fș^Kک֧D!y߾}t iz=J84h B*~MD;>‹KǞU8N4D2MW2_Kq\ C=_q7#{GWt`3w."2D5ZZ36^d.J&D]0 D#1ޗx]t\a;wA"uCWq0@+b\X1|rBTh[- riGB:=Oj7|,~Br|ܕ._ʮw _;x=]KW/+w=|漕V\nuk!w.sa9t52d!ƮEDXi'sQ*B/ו6,!Br_ƽSvd`?|Ux{fՄpj[9-}%Э٭:Xmhv݁ر'-eN{}{263/+lg`H!Tw/qDO%0 )Gi9=vc12 75MWw!DI'%k)-ޓ$r#GL2j(Y.k֬IO?Ծt*VM?L(O)h /=$ݳ!,!'GCyCP>"}d؞m;nyZDAhXk&QqW8R &5F|D ]pپ};Hx[jE6l5BQPSOQjdq6lLKP5kУGm۶MP4iBSNQG /ron2V"uF_xp&}f/"%eaVL%09"Lҧ8ct#0Yi ;s` A@cq\қ*HxN*7B‰:_R5m6*WQTT4T;]T(446~̙#}d6ŃoۿICC-t;ט:̟vKNF CufIFgDKɷKJohZKqx[+'i2*'\ Rm֊oyrEA߳gۋg>x8'c-Њ@w孷ޢ?S&9C\FY(sQ```.߾};!GR߾} ⠛pV ƕ!_-Wh43|Dّ3ͽ=X!,9/"4uk{9y+|9})x/K=vpp1!liU:o==oOKM#9ql-.pa%e<-\xw4hPBi. b,DٱoR `ժ^֋ gG\[F Ka`qdMit' 7?G KeU_¿zZsщJ)_ΏL3VWrq1_\!ݸ܈jՎgw]p?>~N*]'BW^-ԣ'-X"##e"7x\D+8C;% CPvAJE;R8h_ "oKzEk9bp !bd w'*9Blq/oʴZ1TJnjBe@<?K*Y:$4mZY/o6H.T`:re˖Mr̛7/9<{"zR!dT/bB;fX)SEH ?ڷ?񍭥%EށkB.++mСBXwGP1Wf1̺^Z<+Ъx[k~]M(ʺeu .nkA{%.{v@]y׳k\rInJSL ԦM5sL2H"fpV F:ڵB ѵk.5`}ۉ9^{lnl-]//Ԏ{3* \*Vq֟U,ҨZY|0h.aϙxWygue g d{C!d,L_00;0Gr劣C#i":YfvG{nRBuFxS.zj(wޥeJ̙2Kʥ]WS]pko# 絾@kU*a~ OzdZJ O^HyU-ᡥI|AꉙJ~|ͻ*1jS#Vl(BmMHFKK;Ev&h\.}/ʬݻwOr;w\. tց_&l5:oC;k5|l?J !G+ .rwW9^\!Ņ).4UjU),_~{+Йa~!KAR@6Jmw2 ډG亀ƾ ]ƶF/Wh @=M1pt\A^ Ǝ>lے}%zԽ{}ھ!u\O(8C+` h;@v;vx5g\.JCA~mtE o6$?Q4[㨸\w`æ8zP1pI@z58J; YWؖmVj,aif"ڵ=^BO5vՓ\}?+סʃV^*j]B#ZO}f ?,C1]r:9ZG84!t3hX:r]إ:uBϜ9#_-UxE96|892[4ovI^pؖC9 \pW tJ|cέ_.&puQjp06 .`,•E r6m)#t&, nB#s"jZ"25f s)ԾF2 -wnQէYH.ة_5xhK|dVZhRzG`wWC-^&Zړg%@{9u9qoᑎ. ƍSٔr (<ybC8mlBdO 8pB5X[8|.ĊPosɛ/MϏ H0G1\Ž3;j λ:qi­'VZ!e*ur]j٫mz7L"jЖbYS0kUˈh7#˲e^H/Xq(۶\.~*hXb#G'I)ı(/ 㛴W2TDx3B~VsAq4n3T@IDATd_DƼxi˘62Wk:avn 0p# ή tիz!I&D{(s>7"k 5[$ΙTPWF[Q߫WjWJy\ҥ >LL~4È *X E3 7hҟX.>R26-z/͔S׶nOk{sLx9s7LiBָLc[09AcW$~l `Ж-_'uoJ*2EOOY'!jՒ o#ҡfMi6e<>avn&΃m7siTAXk?\vu`mٕ1R@h/NqOuTM^1RoHcm+W{ZFU?ZF[]]DXM<֣QQ_ .f͢={Pҥwޚyi ͛enF⡲ʼnKca[_d}]TΨ{gϿZh c,P/Z^m'LeKzx9yרa}BU[s Ocq}:k.͓<.]v%ڬZʥuHr S ݻ|O?-_lvU㮟K` ݺm^zئ?Dիިv͚Ӷ]y)d<3A߼3e!B _XhH .^VÛ488oGxϮ x/g"kT2U|RrKΝ)$$/5/O ])qA;ZΟ?O֭#W ?fh&EwxN{_ޑiz/s0g_+^Z\҄{3j>б~cztϊY0?xQ_LYr5}XWj1b5Wث4svkS>}IJFIW_u{>ʐ! 4 &}iԨQr={ԥK-[v-}ʕ+iKF\n'7;|pPNOĈ6!00U;:ֻ?]`샥K.aOce- 6}N6k ģgԽ[*܍ǟ~~.P9vOڝ @Ap~>|ĸA;>S3][0A_d54^2 y6zGBm~]~U ;Vl$]v?}=ѻ}v8%|$:ҞZr-1vz.D o"8"7j8D84k߾=C~[9kX^+9U,'"@=x 7iԗac-> WcB4++u }ǟM0N`UU~z3#C>+0ɵ$zUc=y?ouД)S(gΜnph:᥋ Ϳ;N8*be8}@_e|ݟ,s+d̑Q/nKit@cK>&Npik/ ǟzW{]@z?}>+& \3=uF1&C囔ו\|\weܸqi&޽{԰aC]p%ڕ'욎 lEXݻA]}{;ꗯ6G }6=\F Cc 0{ z} 0zcvF+5(Ѕq~K|0kز?]Wzm2z]qΜ`o@?L fQx\֭nHg1s)((dB_}&=j wHW`NCIGH-}ut.V^ X?9C߻؟/tVuیC=,\$w4]ȏϧ_/ 1H. ~oĸMWtD* hzcʸp8hˀ WyF~O}xOHF?缇Zw Ν[˗J,IK-x*IX+7l$ϗ B@_!wxhu ruxq fF;nٲG|6rD6Ϯ5%]piҤ !$ LE|OYaʪaNcm!ڨckƏ5׽rUeQ_D[F{ }SNº+ i-vJ=֌ٳq<|;1;w\r֏xX]=އQֿX*Z>_^e9+zWn=Ǐ?/xmIS![nRS m*iN:E-z Ny Aҥ]k[\lDKv6B[cg17oƘ3#_9qm5`AԵ}Unnzn:F`F % {ǥx'%F`F0?\!SF`Fӛ<0#0 .5_[F`F M#K~<#0#[[ŽeF`4>!}h…II }g$ 0I }c;I`̓@C;SdZ# uٰaٳ (\rxb]Zp K,_'^A7oTu[.m޼YVz5 cscpV˜0f=W\7dzSIÂ,J&#GHRbBٳg'in`F`ROfΜ),?>!"FndQ:h2"LM 0#6Ɲ;w=4jy6׫ E-`hI<.j&M)_~RpF;wnNFH<2$i TP!Mc8pp'<ԣG ަGҝnQ׺Ud n&S1c @:Nsh]s5ŖٳgE!tIfލ$9wƕRj4"slG +^oPSM l2lcnк ZbzU0qqqhhyF@@X̢^SR`Կ(Np0 uFjph5 @pn:YC[n/J4߾}>|HAAA[>M-?}r8mZ!YcM_#ǥw}p,a+˖-bŊQLk4DիG+V$ 'x\ԩC111b 1c(<<<&CR=( M1]Kצ>[1Q#`Z הvA(#ie:K"7|n F(>@  aÆI2j(lѥJ%KJx5y lW^^x2fHPmuM?7e)@ sWwHu>`eׯ/uog*UPi0\x۶m?R+2w\zlٲr0a}Vެq R!駟O?$!\|9!["0p~*W2=_eʚ~JNCH矎T%ѷkFӭ7Wmsֆ`ōG_n_.5zne;`N~;BSgd/@u4\4hܪqIƢE${72剾`f{n_5>|Xta7ɓ']vWݻw)OMLkOY2e{Pv.fHϟZhAp]P >s(11Zp%Ah>[PÆ [ ,ժUX",~\<'$$DJ֬Yen@G#GY<ݼ; $**J:B2<}4=:)jՒ{왐( !Ȑ>#&-zc k3N&ԣLn]!,fO=3J ~3:_ڝjT@Evci. ^؛v] VP+өS'ڻw\!gΜ9̙3۷_ (FЃH'[h]q>ǡ?Z EH;lҧO/ׯK'^z)'NH5پ}d*>}$S 4¹lԾ^"O)W-Եv_ejUmx[tڑ>|]wԋJaFzZO2R]?RtlQƹbEXA1쭭\i)g<. <.A"%&rCVhqԇԾ[m4T ^ʐ_;\ B  e\ /=ӺSểgV7X..QhJԹZ\ 1m=8, W.ME-[oF6ph8 !˦b}YF{Uxk3Jզ'Pf~KTHMS Z8*iӃwI8LYt6U Aω9*[0>);;4ާF?-GQqXzJ65w^?CKrji xvϢMGWRJQNogňw!z >c:͟7vޭ>dw]\_ *>FnwuE ^psq aVA V63QTX8Bif=+BW]s TD=ɦV1񽦷S'8.;VX&w7*ߊڊQu-)׌^O7o]ufdۗo]w͢-yT~)?PMEjK׮]!'9\TSi. |ĉґ7 b#Cc܊_8^ 7\C7NNԶx}Ăçqg-L+h?Pb5].TPTsOzjM[,N K~DGBKQT#XTSh !7\Q4\iA08j"ߢ ޜ ,FC&jՊ#P'v֭[{|nr 3"D4˨&pӇ.\N͛7oOWlX\qn3*|y}7*~IB<%x|Q;=Ƃ&-Hb<8TDJ@UզZ_3<$I%|b~; m8$ .P{gYk&\|&\\ YqDg\98%wΜ9ChAA4r?qH}@ "pM]7?Z|*-v QHoĸxEADrB X;C1m$¼;W&h\F;-sݺw2He UA5SxH6ݙRHDW6C0LpY(4 *0R`q-9r/7nh"x]8*%d(f͐%L &^,XP h.ۻeLI900{_ Sٻ[Ų~>SlfLBT*EkRTk`#a l-[Fի!3o*lA5N@#.C.H2e$C7o.ðG/2EFFʗT̙iղChB Wk.'+#5@.ț՟Rĭ 5/NpŴKz FPPNDApIr/Zyhtre's4/fڵ^9L9ЖѾۅ.e͜_9I ޜ1r$.7ZGOouJ4syʏiRjFCu׸l߾jִ>rvLC:uJjWW^ѣG{ʕ+\"}a y82 ,HpMk>Bݩ<|e4|`)L˴~rR,~ZNs !S`z{ XdruK4!| @cuc+se jWzf=;;MZ=B$)婆X=6l.3x`jڬ))?c͚5wőPpgJ]N FkMLm̑۳Ed+Y^B8`@<C,ynH;MY;,NM˶InTw) m'Ԅ=іSiѶy.)R%dƇ]p#KdFh/_^ phWIK<.b#$pd0Y:S#TAը!|/cd\=ΙDğ4g4 tN\jJe T  XcA̙'iɵժOZe+OTT B%&8#)(%,XdX￟dD84B!۷oÇ)N((orAp\D/l;6XC?RAV@||5?L0>ȱ69`Hj& /{F0U*^4L˕R χI|ć ,X)+.Os^?I;lImE?g!*'B Qq!mEwKJph0"_Ҋ+m۶4f Os-) 9^Pq6gW 7r^$ToѦSk$w̄qcهYrSK$[J|5DtH~ 9m*B0b[ Ԝy];H'"ӟӿ U *BY2f `z}9s&uڕƏOe E!8+VJO$qݬY G`DA)X9՘;<C 㕧 \`&gy6m*I\q-ӧSرc.M>ɝ;UoڵricF%`T+,x4 WZH_ BèB f' JB(_@<"GN-V \:D7oޤ˗xT.\@OWWy\ Ǐ;*`_/[ԗ2Z<KSav|;?#'0 q pB8kzATw]n 'ie7ɟ= (,S )-^^g* .#G#h_F8VZҌláGC;׎3"Ca'!(U\SpD$ᲝWfatQGХ[rz_L7EcC07|kDp.,$9_OR}842e ?/JvmEBzb'N2DCpҥ4=-\0yfΔ!`m,yx[:q_=DkQSl/mF0Шlۭ7)[s^r5Eyƭk_^یH`y!-Z<*ONL-<.*Ђ- |et< Ejv=ی#`~@b&,\a"2aƍ]UgݸqCFAhAES:$T\F`_F0W.9ٓ*%BBQ ?~,`Q믴o>BdRddh (.#0#[XرcaÆoԨQҙy&L <.Æ ê ƍ)k֬'[-(5?,MT}M aF`A0]DC_~aZ]ph{<-` ֭[KuV ą`F`| .¡]f $={v\F`Fw0W 1;dFH w%J.ܾ}[%SVF`o!MӛИ *Bvg-`ʍ2c̘12gbY@gfmF`F S̙3k׮4~xB(3xXPB?|H8k,߯ϢEpʿQS+A 3RFA8ҩ2S OS% 3#0F'c6¡eΗ3Rɒ%iΜ9 [hAEDt|9y$/^<+11T-MTBR(}$LEݻwcǎ%7|/P4x`iZRNܵk V O=rQt0#rRJIȾ; t Å`F`|k\tϝ;'2N&LEBn"ԪUκ'N .EF`p8tbo*reLܹsT@~:jJ=zt˳g.7mNZFѼyš{m̌3挹עz'+i2dB $̟?tMx>ԭ[6oެO\]s躓1^3vaug\tW^( Ç_~+.70#0E5.b̙2s2Gc:sʕ+?'"}[iܸq8 #0@Etg˖ܯ+}^pP>|8'MW^ #0# %e8ppAW3#EMĉYqoxEcł̈́!zt5b̍ ssb xx#ޡ>a0bkF4m`U`ʘ\AFCYp1h2F`F= @3Ͳl2*V٦oe.zʛK6 . F`F@ ѰVcƺ]W:urҥѱcG:y$̙3~onJfh5mڴRnddz73#0"KEptt4=TJ*_<-X@~+Vm&^z4w\zlٲr0a}zst gF` C`Ĉ\apAgںu+}R+w'Rf&$.Y$_^R޽kv<5&&֭[GV/B'}6O:%|4 D XI m 19s1T] ?Cr09bWh1#0@CMEdQo3#0iŕж+Py`F !d\#M|Zp5_0#)Q> נJ.CA4F`F@OZ;wٌ7lcV5>Ys:^p,jT}IU… KdRTOhѢF1#|.YKoK]:Qlk0%?UD:ylAKD_CGQIԯZ4av/N"kxp8*Nk%1)y<>j`F[F#brD Zg;S/ywA$3@,D'Nb1T<5%sf͚%-8LŊ]Z\IRJIcnРPL0#0#>DyhԠΈ:ŗEOR%R&MHG[JOc0r#6m49]w%"sp[o92#0B @7ڴ_OGZUhj*C'O 2UOhl=UhHUS`]rXN8AݓۥKv qW-*32߿/F`#P܌eѧtR8n!VWh>V*Y4iBj"W %"U!()B3 dn`F m!Es++@,i #wGUiӦe駟,Ȯyil/ E%iC'w0#ٝlR%}-X a[Y1";ד,td ){M:uY( hkR߾} Y0m lmF`ԅ1$=׼0T#ޡ^7%F2DhDKK`hNAs%F`F4x]pQػw4 O>Dٝ/8`qٳ5:5JܹSx;W%($$޽K3fHn>`F`̅MEUVA J@gŊ F2r<#0lG/J~T]UGX)c6遼)o/_9# 0#{V> 6l#:rLcsm aϟ F`̋9"'qTծG XLiM|~srVB\l2ʞ=Lɟn?##0$ UfQ"J/ LWB'yoBp)F`FCAve,?9~ZiuJY*(ԶP%!UeTfM:;f2`tă #]"Y郏c~]?ϟ6CmL~ԨXVw^G_~K&u-T3ӅFy}:LG7x`jڬY=Jk֬%J8ju ;/(C3gLs̑iرGn" SLԧQHixi3f4+L 0tV,%KѠwbM+G(Ug…"kVz&G;PB8}&'OHCK AFQ#61xy) .\t^ Je 1r(١HӧOݻӱc[U L2W. ZlICoFuVY .B~Uz|*,U0SK8Lڹ/xihnݲF'GD3XU,N]㺒%,rr|vcb(rՅo}x飕1ܞ)x\]aÆё#GWLZqi|+oZNbT?u*sEK98Ѣűxa0Sf O%1MmTE3 S*V`!/Mtτ!8PdM/gc+ KB~R0R1ѶmBd5m}5dѧ?z`&Wmy͛J /nKn(-q/ɈBJ>3v '8o1IY7aG҇Å'Y//ЄHV΅:Z`E)"|~B)>l|-ݶp>6˓Z+p „v||8vxڝHWOb˝;w([l\\ܖ~+p7*4&p-Q ҢE *Pt=ۮ#f/3N͘KC?"48E0#_E`]oɷî@aQ⡶O4[釩!2DRt`/- 3{j|x;xR2[LRfZ Pq gB`wg>#6(zvOYMBpS gy6m*H$Ug4ht͓'M4{ BK9_~It7<(Np\73+/lsJj^z=3ՂȌŒ<XpI4kU( B8Gr :]/hg̊Vi"@~LB$QK~Ԣ\ؿ'S.7oiȐ!t_](ݵkW?~͜(;t`` wɴi&QJ,)kVuq >MR E/`#XCx ~6ƍŒxxܴO4+~1_8ILzMbx:\Y[Dp.^Ҫqt%?#Su/"`#G޽{TBҾ}{bGWjQTCWkw0{$RO Kće Xj}C/3gΈ!UiӦ-%$$Љ' ) iq+` ~+DB̋?4 Oj d(JYφSjYQ?g4_uj1t OtsM(Fb^߷u;̹+C"xD+_0.N"5KF@k_M6 V͵ﵽL)hB@C df5CSE^(JY}"6c1K"K4,Z*eNvWb6POuX,|0l,["~L<ٝ{\ o0x*oG$G-6lkWH0GKDZj߉?Hp !?L!>2#raGc؊8t8~]J^MQv^rdoDq񤫮YOwyMK' | Yh&XUŅ>Heֵa}  '{K ޫ`nj%1؞ xO˱2x/e "wK:biVj1SSl9'}/9F2G#Ќ;AU ?=v˭'%w<*Ks]*}=7&eb\.f,cXP7W.tX9/JWVHRV_" $qY9:/eJ ~eJ -sƺjD68ftlE2ےoqUABWó`S01G)_J,݇jEwZ OCmUupyMÿp&˒XPBF~3,QI;w&0gk)Jа`+Tacv8ȶKA]lI@ @] Yrea <rJ~-&Q%t Q7a0I@!@?_8V5/2W|[e ep/᫶r%_Yl J]c#9_^H$C%{ȼD@" d*qTeD@" A@2.zВy%D@" T$b?H2d,scFms_sd\kL ;od(-2>Y@b.17k4b xѡk׮Mʕ5kҮ]4Ԃ |TlYjٲ%O`{BID@" ϸ(ѡϝ;GÇ޽{<|Сb :<,)AoM> H$#ЌVt+ ѡմtR^:խ[W$wscD@" HL4(:>}J.mNZ*JII1KpjlD@" H2w h+E3S0GO"$M?ldSܙӚQ,1jk@ WCWX֯_o~ǎe8XKJx3D@" U PΝKEZjY`ܥKo>>ej߾Ey"H$#AO={7npSƍiBOa;Xq_lD@" H$ ϸ(D@" ?UGP" H$5qQ!%D@" k$׏G6N" H$5qQ!%D@" k$׏G6N" H$5A|Trea gsFR^ua &O$D@" :66 *$P5jТEEo .rիGcƌݻS-H$D@ iAJ\~}Y.GԻwo3:ID@" H 7?.]DÇ{W\x"5oܜRJl2FdѺmz`] }~I̍"sFGAjLHdd k.3ү_?!ʭ[k/^jLxV[lAɲH{H!tﰵW2K/\ O/9 C DjըiӦi&)SN>mɓ4ID@" Ag3l߾5k&[C7oޤ߂4ID@" !qqG}D;wpѰaèm۶vѺu넥ڷo/-*K$ Gׯy/}۽S:u|[FA8REM&4/$D !nݺY׾ g^q$c>uI%#`|sE2c X 2Fje*&r1W_5Y c%ମ携H0b \/j-&]+_D@" 6AŸً}Q'˗/'w) D8LD" H$ pQ 3gk+Y830G"_\&#qp)/\;n>'˙'Y<[N%(R3R'/,ɓ  HO,X\2֯_LYE]5a&IQNҮme}-3σR⒙ZTpqW|b2uș1a*XZZ`SJ6eQ6ov"(@e$"|a.=LC@ߒZRKyguf"w(N[q&ִW޲P XaixU..]*Na>k,RsjU+WSQN*][mVoQ>lH* V `¸oUt'1 3\(@`ɶ]-c%^3m9YRfRN)rV/`鋲i8mӇҐFpwvC.^hQ*.-unܛog^{ pޢ\9S!L'/\5E];Oy9WOG n9^;GhW\EP>*0[oݸax<f3׬,qSAb4So:@YD0xL];.\>EzΥçjXM-h)?J&oرT|yIZv{&x,"C:Fݯq9Bchuiڍ;:.gbNR¥4t8SO+iT6j=joH'.ٗG 6EAs7~FrO U_oJF;Nm nFQ*AE gnl:륛3cu +WгOm SyjʖDᡮ-9*?;h ѣ8qLIG@\ qYf U\E5ƈTD.w}5ԴZ+[gE^=K% "ΓR){*7/= 5{_jX!sqxy[!ЯI44`&FBR<|ДM]C4$X w̕TG0E>OKV} OIXHPx~w!ٳg {jlk 3#F1q? ю;Lu__w {tۗ \$\hخӴh)cC7q7.͟CW)1!p%ʝTs76y<yIqJ|0@Kl\ xޒ4/Ҳ!ZY{>Nao\9A /ƞkc(e:x/X=̴#Dtk]XSи8 b&{v}đkZn-s-s_v.J5Ȭ-فt=KRgdǝ(Rչ[τ@̭˔7Io {b]ʙ=gr]&1 l+jww(=Z=հM}s ++Ft0+Dž^ 2݌Aҙg7ɋn!p-3\ؗPͲhliN]geL"'Nr@ܒRTyRjoB>}AN+W,~ ɈHq,?,\PDC9=ry+&墫5p νꍺq +J@`T`t<$m,#H^#C2#瑕qZB _-qY?/VddI*]fշr *!X̨ؓ4d嵿jZ4vdeRÊfUNDP"&')Ct\1 yBQF ies;g@HtT LѤIDO.XO+74 wT}e 9#>҅ʛ>+thvJ Ol v򈐴[%M"&wʕӼDZ0p-e-wW*â!Gx.ʛ6ދ> +UבKҾr=$2(\pHpuarp橄j}p_T}Q]G`H:/PBBapu밂 h`x-{? ]'xB/$9I˱cǨo߾ 2t_Z\3dilp]!8k]3͞ȸ\J"q1<g]e6X)s2Myn+eH V(ZU/M} }O^Kh? ROi+_a4.D#mžsRȿFPsWOQ՗xs:l0' _؏~hoX3(:s9`%8TC6>9U.V].lfE\V W^TlYaܿ( Q1aS25+'(#0)XA K[ӥd2M`AVB#1 (HʑGĜkF2.n۠y)PV3z/} tw~E5/ }?x~/w9?&>G:gyB,]C&|z>ПX?3|*tKY|+藑wLԻA#*EL˨wV?Vy0PZ1Xrb j ZVӺ;-f4seL"H%E_⏮MOmkߧGlmN {gF0.Z&]c`$xV)*_n[x20.UϦ|uB"Ubjedǧ.J[Jc'1זW|WXhA/ԠJ*~P =}`W_ӛN &R]Ǟ{%j:Y \Ce^>L[EkNvjV a\v%"6vժUn3?+0C)0|qw "ayJ+mb&+̸b\ҁ%߹ی,wߩKGaEUt hhfp;jz3-{b33CsN׼Jz(O}D_cRJlI75eW`P*]srcm24Oz̥'% Sudvb#5Av&3k ¸(R髯Z*<ŶSʕ.u#""E"VQҥ ZIM)ÕkW)7@oRm8EA`Y S͠޼s]y5NQ2 .Ĝ{Kڗzr޽-EϨ{u'Ԯq7|bC&[뮝z 2R<_I/% ^Wpdz{m^mtV暧 *=b|vIc~faeSpE}X,~uq!1qtܹVF/nJƍ#@Iw߾}IAXE0F-9h2Np|Lʘȧ݃k֒gG^?OEbCr`5{A~ڳDUwѣ:HA1R=%?mv;$$.)_|C$չvi2|Z0ubn_[^xiCUKҺhkl)*Zu5#|C|/RY %]Xm}*{fyو,h2תG/'=JZXR#VC[W>5g`ޢxN*sQ(PB+Kʸ@M~Per-Q*B$%V଼H^X`s6땒yvfT=K#H^Q .lwͧnq YFwZE[,0N߾if\0N9-w.=Y E7[1<|tszlF#Z*k2tԬQZykuUt5X 39sR޼ycǎ}ɉXEǝ<)BGDW-UҴ~/37^܎7[0 u.ajܚ 4k%rU``:u5GPvL?>mXZ O T W!ǔ}(\[JVNʜ(_uh܃[GbPKp5mn5} [EBzI٪y| ֦l$| dy ZKK+!?Z>R`eeŘShbApLt7Q;m+Vb|*IG@XM?g6ů,7I=~?,=B<@b%6JGhet&AGXv^(*gatu5g6I‰xo*>uV}\r_)a¸ :4[E5O:{޼yBAЋ/ ̎UtV4p0MXJ?IF4)-u(upw*Q0ڹS%uWҴu&Hg`ʙx. (ȑ ,U)F7)TϔHSg_L֡SS>t 6I\Bҭ;B]xq!XЏe2X,`! -8\_gɹs86uVw'ަ75gU&9s(+ 6[)*:>7ju39 }^&zibUcact𪫐2/z_%/la;4:8눕.R՘7q0`uY){/^z%sc2+cRRhrn:V˗&ѷ/FK8~fct!͠/U-SbS/|tG, 'BVGGdls EOlQd" XI+J(]烶Jn $IQ7.P|.K^BLoϫ4 wmTpy8Q֖&/5PٖR\9LDk[A8Ɩш%/9b-TAw(6) Ж#SrM6㮳ͬ xVvgUXe[\ɚ5kk7n( ` Cy^ytzʫr$ǯ]mu`017|u{mV! ۬Y33gqٳE7:ҽ{wZ/W.~IWvsJK*X1}a"u[fG8"l-e1_{ @W\ -J+Z׎fl\wV=%XiSY o"Jl(daQmc~<2=+{BbAhC,fcE/Qދ Z5:("&H Kו6h߸ W]͎N,?%}i"E&hjT)[]uX{AjMIW׉_eEŚso50[ *.?.֯_/>~aԭыZLy{ΊfD R m;pXXr_7V;;_q5_cFzz EEB=b3_@6^a(~(zDJ~cb#Lъ}}'3ݸ!zŸ|7n0w3¢H忻:OZE9>NOONvz|7w^8@Ű^q@; n( KI×WtЧ ͆-LJwmcD~E`>Y82 I9|x|MuIl7qvǜ6y\ISюʹzWG$yZ)\FwB~Xm[ zZcx3k0SzK}k00J`HO1p~CzQhծ͜Ǒ?+r:/jRiࡦMxrnZL`V|^S7.5g򦯸 {a.7Aa lV#k9sΕWS v^k.6[-2nyu+Uah+ {h՚N$4K`iatwX-Q^¶Dbqޕ67*3/Sr$[TfvBEy*A:~,"`:Vqd_v탚 qŊ4k,:t(=4e5+rav޷?WT"/ەfɗ ϑa1dySg嚽_]yM^͟KcN"_yEu+#=$4Em'ģ{=U1I#V!n/hՎΦ}¹cUU:S[H/>B ԱQJJ5]w=Aiȷ=$}XW~oTGpЦy7sY]OM*=j>{ oNGWs*Rbuќ (>'R ӧO+iWku_z5-wi(UP!سn{ȣ>a'<i|Hچ{PJ`zɴ?gH#@4`Gk>Ҁ&|CI3سnڠz + jÑxةiZBbv7nZ[5b5t/&M +Vsy1믩Zjm%5u{1+X;#wځ1(mV0sV6kY69ҷ0z':]TlK8nw@g^ s3Z %MEHctU8IP?9 h?+WY'o.Gv}wPeϞ=}1x}뭷@w2j(BZ.ڔUTg n E†!#k:8!^$݉~^`>q2Y*n Dl*&4{4BX/fy NI^u6/lx2qT  E'6;?l=vmD?s@W 0OFՑO*g_Fl@wfl„xsĺLt\6B.P~2.Vڠ4__^#xU=- e\\m|i֌rԕHk1xgSS4c0IϭbЅ$S!}j3@K# {`[|(KOɾamTd-ôdcd\t?3gC=D+W&Vĥ+lt0}|]bE:f6Xƶ~Ki!4wtѨh1Q}ˌz J[1uޜ+<і 2?@#ĴiS ^{kA!q9r$ըQ`nO_LWSƽ lOzv+R@=wĄ% E\>N1A}*!;q<Ai;lĤx6$u}q Ծ vO.Jﵟ^L^R9ho(fK[*.UP?.z'oҥٳby?{MϷqRkEj {魯+vFԲf[c>=NټrLlJט)z5pn܎;+0QPeo6GwY2't0r/݅Zu7v~-J`U.x)*0[ܭXpRSS,z "R:z>5y0)"}|۴hmvIfND@"  AMQںu+mذF)[C7oޤi$D@" <B ֍7)44rMPEDhU =y]" H$U8JT" H$Y*OQ H$,d\ȃݔH$@0 `xD@" "H%4pD@" dUB7 ).+&dE+H$G (@˗ӝ#p;y~{7qDgZF7X/V XhyQSRRuڕ6lHwy}j*uŊԡCqnݺ#LTQWVoQW)T7o^tHra$@AW=_b5rXEp<g̙S/=-R.>,fN n]3uJCAfu#ȋ_#0|d2m7ڻ4~r*a%2q›XEvp{{dC$Y <9L\$(|Ka;q%JP6mhĉ"Gw͛7d(% `3|ժU ;!BA")"!emx4k׮QTT3fi`U/z>A<,I" >n0;q#b0>nUƝ7ΝK"V~F̡π~ gp4lfKKŚ(2Ywg5 'OI< F*%K.8yzg&ry_eaۢKRX8 r4*?\yKgʻOP⮗tțϷkBIL܂q)ق .4<*K.}5kr/?iThFH0yp1J(WyY/ݮ TZ6 FRLF?D, ׵un!T%?Ho3w},!.zN:7L;H6-C&ᣒ(wIԱ tzLemޚJRNc 8M0.Ѐ2y| B1jرT|y b{:jw»?3.?F_fBYVpJOVI⥮Mr7L zj ʷ5NH c3&FW$.sI纆֕bN[9H\> 4#5Uq̸$jR?c 4(]+u(fߌ7އ̞=[$VWf猜1bh`v!l{zXE yW~O)8(X%ݺ͎Z2DZd4 O]1Fܯ,іS!2Bg{Ji{z3˜VKAZ/c!.K\# %Qc4P^<-L[x/H.L|3@ET4ദRJ / p֭[ \lK^hVrŵ>l;ǪJy+h_"+1w{>ƿgiyci2}yI0j DlM#??l7~d?/7KSǮItoz1M)Tv6J9sf,٘i23?֩}cE^P\-mQ{?mj͋*qgS8 &J.2j4 ̹4BI$ SZe[*>|B^k1oOއ=Çʕ+> "_B2-R <>~@sͫ-CLlЈEגy^)}44H`n9+Wz/Ν# E5S-tliA"E6dV6nR#wNBxü0Q RAhT:6+w{ߗB86NSx֍7LW!on@Ξ_kHe{N5ˮ9lT>j2]S^Aߕk3}06l@ze S Q))LU/ N"x9իFK'3FObRJŌM4B ^8ú)SoϢsl ٣,%?ѷk}*=殮&7Ҙq̯+eXA̱پ$W8Z-6BuFҟO>Ø1'Zd6$%eM{ke<gC ZwĪ,u<`uA#G蠺hQ[XdX&ef/&:f m$9}r؂P3,*Ytx.r.7Mpl4Wo:߰I[*n?h@1`cRt'"Xyΰ&R Ż+e4-zf]FHLg5^3.'Nվ4qP[[0*E0)t<+Β>@c׮]=x1ٮZ-BzSre  .K.-$o +y)!u!a{L*Hdc[vڼji2<[5Ke h}Gh}wa{RsiaxW=ϑ)[dث wx:(Yg,v>x3Ad$FJU Sc:d]fwm{-.XƁ<\~pR#$9%Kf]z)+ۺu+7Nx-Wk0Kt忞N>L1RPrӺ"=9h ~[v  ggOOslhj[kUqAe3ٛ*n#tZ2f,BfQkSW̃ɪ_!LBPTVzW煵I?΁Z˕(E 3R;>:=9y4=ec D/Dї̘ٚ 6yJoYRM& xn`W AV0K.Ν; m?o `Z@㗚j&P\~sy4"˫hନS$j1->x?J]8ҭ,KW,I+)sRDadȎe:sqt7}Pʄ?OzߢQgu1=y\06<#I~t|鬜<ŮT FX&z xnǏ 㠷y #:Ռ*JuFuV^}}xi2.sc8oLϜ9CFL@qփ7:vOt}͎BIfu6dށC8P%O+qQ9R0>GEɼֶG)_ub5j%dYwI[h0lH[kZ!ԃ[.ju.;J5<`1Td9}u'_>O!0 !v _9J=~Y/<}vt4Hw(c'czSGuum}gڜ8k`kHd'2/Axn۶mlL-Z HaF-J֣КY.C0 &! ܹ7MGҜ9:tN5+9^@lSiY;T8ld< YϲڷiJ U]/+׮m?[^Sj̜F/llArQFߤ7ƅQ6I7,CBO')S{h{ 2gF$Z#{e+U*[,X|[M̺͞<~ZU*/FzCg+@ݬߤ9c'Pn]ѣ@q$1:wcKb\j<&CL{l8{zb€;>24PS{ޢr<(!?rC<ՂK,ui Ν'oUOZ_ҿet+VN:ؠcQ~=ѓUlo _SPN9͞?c{mqwt9*cu,qfe4_GQae~ZG?rBA԰aC7J&0wX"U^_R4vX1P\c[Rl $.R>RG7rxƎ7 W`i:[w;iYl#–'諹4r8'?- o$}1/xO*,{ W ro!\$ ;0hJ {ytXqTڱS_{8UhƭUWt^J;8ޓ#K.HNob0~h]jJJKImRVf vjyJF۟F]ti 5/Ǽy g %,#WYdX[,ZKΘq.ϔÿqUi3$0:w,۷KH{饗ܙ/E'l ~VװycXe( ad9qmbN\n/ʃ)8Qi+nC|mǩ- W d2LpU1`{Y)eyZϗINaNej+( pPD[@5G*uX`Ka>l2nmB_Nֵ@O(s5 lA8|> +üvW&?bi*3^  s,Ν,{0:,qO:ה@_&)JH =eS7ڏt¡)K+/C^inJEox?_EJUIa~<BVguCILގ,}D=-91cͣM_ *¤U-`IV(ͧ7,(`:^p ] {sKUNZ<|2!:Ù[zMxOvԿ{>[;}w_N[Qgfabķv(uBr EGOS5_$=[*_…=&;(&Yy1A:LY$<0M ,.R4ܨ 7gmתzXޢ ?l9"dR}|J5y ֣{((^L`X3ઠTIjwzkZx1~*R\?ä{c_ 5ch#O:)rMo ͚{U lΘd@Y^.&Y=9Y_wvu![5d[X@c8+* f|aw_CBVW ^v9w@yqHƼ&"ϘBY*pHƬuUVUQʞ:)\Hm{*mwyeYG ym ZKO ԖFcj_,-]@F )v9Ϛ-9P_%>^zTvmzW't6GvŁ*V^b΋*ʗ==u_Ю=ZӄCOe0~@xҖfl2'dẕ11Nk>?=T«p4H-hEO:*gLOKSÜ:9tTaz?J F8W-fH󖋲KV~ޮ[<_+j(~I ܗF{kw ń]W(;ਪ4  J V) 2 X2JuCS20) Y\X+ cUt0ImGw;>9wn9CS4BzSQ&F x!Zayt;E?|D.jD`*K:_3[Utt> GbiGm_ӬR۹u_@]K|L0A,Y"2mڴj ތXEϟ[Ν;Zpʅua #Kyd6{7eϙ@5X|1x T~HՄaxLl[!)VrӊL -rJk}A9sLkTȃ?/A#Cǣ"i;!& {}jI)= W,W3R8gB`-}e?:wJ< M?3e>~ShТ3d`=hAChjϩ!Z;m7F.U/HC;/:&3M('}.C_0h'Ou1-Z[ߨ֜F6~Pdgg2trVC tL`F-\ѣ^3t,3j829w~ܬ{")wrtf6NTf^ԋG*3RaTנҜgW/ 3iryza۶W>~05}Yby%_{!{.:R>0g~]!E֚ ;b۷ zsNaT.]~nL+TUTYǗ_g!ia)&uJ [XTEx2& òLY>6.zGG\" kBL(]a}oPk?z]7 k 8)Wug[sW61TNޣ[PQ.YxM#s֫rtsu`hB**hJK]Rn t1/]dMP6ڵkW6/iۅhk^Y+M<241<-LO#.>u4G}m'(Aq3f_^f:ڹGc4yԏGګ5IJıVwM*,)4TGhiWhH<2^\ٮ\moqe P0xpLeH8 k4j2;2{Zi\.LC'2 ZfMQID^QI'(aAWXy.hM[ 8ե|pCN7mQC#F(,`_dSӾ 5 x.}$CNٱ+^IۂSMϝWsG #sovY, :L&wW+[[ 6Ѱ_Unՙ%<$ m)Oa[>YīN*_\W0]&AVdYY ||҂Oruy(uPȠOumU¬y3^Ԝ~F! =zm Bp9u5W6u]V^a45kíO t).Nexܬ&ɘ踑Rz@'JBKl.6| -ZT~b ys2( IDATg[,MViIGB3JxMhIh3qGǨ">uKG4/'*A9uv??m@. +A.3f̐.]Ⱦ}Hϗ'|R] FFH:D$@ILw7#?XITyƯ?M2ppECV՞^U¶۫7tXt%.;;֜ .]@*HHH`*f٘M][Hi4A.>]k Ӯ];k=f1Zng]$@$@$@p3!>AuI0qw['%jr_ZZ*f͒]ʡCKAAYTh<,\|gIM9~D B:DDq <[NV)eyf˳ahʔ)2{lFɐ+WO?][O$@$@/_9kύ|o{J &%1%eSSOޟ 8;vX={JQQ֭ FFȋ$ "}ؑ1XzmܸѿzCIe˖qv0`sssoGOk '0޽[(2h͛7>c^~e2[LGy2s,WsaLgϞf6WRRbtB6u$Q]vf߾}҆nyש>ӦMS^^n؎HG1W\q/lj'pj*EEE[oyrcZ2|MǎmXg~pE2O };{ܼũ~LEEEL֭~6v4qЛ]u ;IG̫j.3x`3w\+`HXy~acXyx^˨.\ȼvvgɞ={NwyP΍i+'>VZY_x}Z1=^#FFprolذA$pV =zTQ{q>rssr: :Pnd/j#[$fܩ?tvɩSloDD'J8ڬY3!堩SW_}xq!^֭[E0c=fÿ5/%''it6n۶yDU#[)M8 Æ 3SL5Er.c?|@\K"?3g47xїgЉ#n4'O\y2cI ߎ 'w}LsGT ÇdyZt(2`/Wc!ÈV(iҤ8RFC0b61?dkH ^/NC[nrc4G[joy2b \xf5CRMN{{Mt@3A$@$@$ 길 ăxPd$@$@$@ fVB$@$@$\Ae B+Y @<PpEA$@$@$  .`f%$hdh 5kxڵK&LP_ꄐ'Uܬ` JII8nc7ސW^yE^zXN9$@.Y 4< ֳgOAVC9G`[fҤINeС6Pmf#gu'.8a[  Eݼȑ#_~UVI߾}E=3˦M'Nze`#"gc䆉H 5PpI~b+I Y#.? x1vX9t萜;w8---;S-[fΝ;W7mTVX?+8WCHl m ^r/^,{%K?o[~-2g |AL$@O # "Wc~ۮ#ү+bc_xQ4hdPyii]yyyrzvvͨQlɓ']we !@%u-%"Q f 9@~ʲ9s-"gΜqA('ptǩI{|GVhр>Fm۶ * $@%9"'бcGi޼8„\OYM62c aYpYqiժ6N8s -9!@L ͛vHB jܹZ-_#j sE -& '@%-$  L \hHHHH I PpIҎaHHH Pp g-$@$@$@IJKv E$@$@$NK8n!  HR\c,   p\™p @f ΄[HHH$6HHH ,T|bIENDB`PyNN-0.10.0/doc/images/noise_source.png000066400000000000000000001260221415343567000176440ustar00rootroot00000000000000PNG  IHDR ,;-:sBIT|d pHYsaa?i IDATx{M?θFȝ$RN8)rJ"ShRJ"B Eb܋1Ø`̘1ٳg_Zϳ|߯׼={<{iaaa,laa)?0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0e0 0 0eAAAW~RSSe0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ҥKQB?)))%>gk0~xdggKaaaJiӦuޫSN:t gF\\f͚HlٲJqaa&`) ѭ[78̙W,M4CZ.'eQZGcr) .Z.]PT9R\eA(uw@뮓##( 5ȑ# VTTT1o 7܀J*a߾}x衇CG1duDGO=E:gdg;@P W̛w_EˑGU}mZ/ @9?,g߾ Z7}? \_*TzpWVw/p%@5|UbB߇&Nɰ0_#@akݙnrae|b_HMM-ޖ-[p}WޫSN|WʺeːCZ"/cIIr-,]Jn3g)SA)"$1,QTD-KMLN gX:[HL7YnY{:^toB._[+/wo8W̤qRNOJ*|ԭkFi$v@wS+M; VkXrxm;\)L%={ƻヒ `ر[Ti+VhZ^y;iVPiz~5ml),k2M{ Mkz\qEE//q<<~=GX'=\3k;^'$ JUi;o_4m4MkPwԴjZZW_i3fX';ӋXz?;[>D.]5-'Gf͢ef 8ѵhʕdc䡈a|c2Rؖݲe>mkиy3[ZvyŢ"y]i<|F3]˥EѺo!k|W/*5:mZmUdoѴY3ל hӦL$s>B+a㏒' 7sl,FvExP]C* 8u x98dyL7kԠ2qUDV. .o̐MO4-U5ĸ8>%wU6a5"F۱c SV"&chHNX[G z?{\TDN]GuBd\YDgl,J

 oNUP@DXEtNZO$G>O9|]#v9y>?xIuԮM%ąW2cR㪂l7` BĜWw=)T4lH.-͘0ssS]Fi(_$,<O>I"gBxk^(7L,7ZŎe!cA!cH<֜«D^ӀOWi,]JU\QR&ȑKX͝KSO**RzoNA UHzQ mۀ>sSȔOXal[o[g}Rdn-o)EE@hMzK/mǎ4iKV1((]ޕӳW:E}^ 4=z %^gD~ \Wp5"Es̒*"I})]J$k`` {t\̖-~lI\Is(L 4:y'=3W5vnǔ8%"ѻ,W9 Μ:E 9sݟHs*Y~sG9ʟQg>ʫGy(rK߫T!pܸ7lz=B~^;yk  >o}ȒJxHӢE |xi<<;wRN  QuCl4dN{/Y%{tt=ndΝE]>h+-^y!`䠐a}ɰy3ū@N(p6vzIC^z~P)9SS3~ fÆd xzͤx?Z=ᦦ(KYŋ^%x9uBe:!0,6o&8sy>(-\d!oÛ_o)Q15zmK 'aV@U|1ܻrRȢwo -gD⣊w=y:Y=Z`VVT'鈉Vd?>'a2fOI qVv`-{GEc~[jv^+<ШjȵŜ΍EcaF9 0B XbzXbӦ M"AU #wsMv#0̟Oթd]}53nӊղe̚EbEE)))- @}4y3"ԪCB;ܺPqqj\W-[g}ʁъ瀔o؊-[h3,F V:K".+|OX\9ƾ}~C9::#<4%U;h{D38zF}5쓍^^^Xrbc}rlV<-1 E)exEWnWp?/^L0Rg^E_sV<M/_sGς4uQRfͨQXqyE_U(]#a 3aQsI7~ 򈎿ݻ, ɓ4P ;VX۱?_TUV摞"78 حu+l.sE}hۖ_xӰ!}l{T3[x1uC^H!`alGr[r,k{֥/,SJ6VS Q%w`hn*UڶV.yM?LңGdʕi//:ǻw?$Ex`aFoТ9 .Qa%F۷ʯZߧqcsof7U s o \ 13i 5qU@;Q2Fa9\*zD@@T^kgY5Rd}ma 44nԫGJhTh.'h|cǨ=9gSvݺ5Vĕ ׬q/N~|~2wQu鞛9ӘM<bb ikִ3ORSK6f潪/T)9 V@.՘\ؼ\9JC_'$;a4ZPL@>E&_Ocǎr( hJ?'ÆO #k% TDEU+{3S JQQ:Waaȑp.F@( F&i_^R]s:{( פ1?0 uEgvKZ\-5OkwwF{snȁFY%}}@"{ =܎F26<@ڵem?lC.툿Pi4F]o[z927rFaƽiE.KL y \7 *p,;l""sUHJ,?G╛zWKKf/6EGjQNY5ƭ[1Z0Cf&FDO=x_$>D,+YrfRLS@:={رcHKKCPP4h:gϞҥYfQDpH`"㥦睱Qf(r ǜ SEUzU|_.XAAW0i>R11E Asf("2znX_qP$99×_~m4TzꡨϟGo-[ȑ#3Ϡ 3NaDVQkG׫1rMI} hr~cQ w4})+J•ETE$s(BIYYE9d-F+bn\1B" ŻkW0`1i$\{Xp!~_qqqEbb"8_w.\6mU~8!%WL?maKIo¸8UlI13kW0očQUC(=sxVxubq+"0`t ,@޽ѦML2E?Ih .P}|YYYICrSXXܫ\1df-bcJ]lx-,VsNOHN&kuoDn˕(|=¾R*L-+cRؕ曭!?G߷*"ЦwoV,ڴiSKؿ??_YYY8q"7o,^f 5j͛W^ѳgOl۶m1e|XlzӧcYL1nCЍi4RgQ>xez,~չ3kGq3f{G|`dkװmh E?p>* f㋬gl4qI4+8~<5MM_.L.o7ľ}b Z Gs=bW4*jk :CE?(Geӧf̘O?-ZTlÇرcpBQƬcӰa0L:s㍲%pP5lV$F&_&̣GiTsLDD`d GQ5tz`hRhpz'3ˣĄ'TҾlQTd BVin5i\ґ+t옣P6+ zZl6mڠYfHLLDh vB? h'V;ѫW/aaaen祗^… }2e˖jLŊzJѼy Q%)p"nWvڷ/|+]~Rpq+Fy1r/$WwCx@ޮ9jE`Ҥ o|(*b'LW@.\kbŊ駟PXXVZa.{@9(D߬Y3DDDf q3 jc} *(5ʗF ~܀dx} O6ȑS%RxTCغnQ9ƌ1T2FV!m?JN=Υ TV$C.a (C4h:ͨ) H^^6oތ+V`UW]1c`CFaf(QQQ IO'%Q#ْ&8^-/;ns)'P-z>8Q(Qa]F+a_Ţ9߮zG)[Ym0$xY&=B&}}F{>_4૯GS'V@섡 ۱b ]ϟGjpcĈ8p *U4 %-[+}TRUVi>}:0`d.o8Z^+W4Ο/W=340$]ٿ:l Ń \4JO _3kBd$Pa*UW#$%;wRmllj4w,X G{ .TP}ň# j2r^ѳgOt 7|3ԩ`hѢ&O|sǰa裏kENN֭[={`رҥl^aPʤ$zXOo?jLx}6lņl9pxuzmbXUKc^aCuF9|E +Fӧ6mVt1G /hpƑ#瞳~⋒K]R%YQS2~,^Oٳg2M#*i|}[zDա;d64?MƢ%BƎ5G?OZ*yyG'ʖ3D,U;[oZW -#7DGS/~VQi*d;tHLw̞=/W믿+ U>0MNWS"Yƙ3͑4DF(+<>sNo?O͊'UhUW Hݻ˕h9iddoJO>Mb(t1,oeX\*4 شɵkWV@i Ȼヒ^zaӦMS8u?(=}xP+Û?7^+_Ntjx]otFl-3ޜâ"G*m*F˗;^m{5<ܿZexccɰp%֥ 7vhqUKL;=1b+L J]ڵ4˕ md_#t9?}g,vaছdK >Bބ @Ϟ,TUXf(FsB/K EU}t=""0r%;G5k󌍉&j ȷhud7֟++|R+**5/'| #[*8s$_U~J>/Auhh ^+>k8J V")OvQgkofg/Six"kN_|V7X1Z/*r.cdPGisuz@˖bLS@ZhcbҤIZjVZ])XdٻTLBIv Os Dq0]g]ԤQյٳ^P4`׮Oe `ikQ@|`Jvlwۻtacƌhٕ֬NpٳpXQ$QnWmhTٲ-O @OTi,[N\^-͚ɕCBC9,xD'%J'࣏h(Uw;t#ņb`{ذa*U㖌 rDRmeX>t-{Vn[GNȑr0Oŋ4Vf=U( xȾr%aC92VԦ2F-Zm҅3gʯc);&(ȳwḆc@Z%?߯Njdd[K/ѨR87''uO 19+KYxz""qmZETP4z2r "QӴ{YglL~~d9x!Q%4U&-M(rp'W_u4",ohrGPD;%/PHl,gPֽ'ƐM?1OcHMMEFFF|!M$WgU@+MS?g“CBi`O(hp p㎽{G*]މeKrxJ|z<} >p"… ^jk*x,y C?W_-[ Ǔ{⯿h7W_4l1--p3O* '<pu(6k>tqqd@k9̤XP@}Vva Ѽys<رc|l۶ cƌA-b `F_X_w֣\KQ5TaN,9@*,TCx@̏* Qmws| HYhyR):J>7n,[4 22EiD*^}zQnw0,ȑ#Xr%{=,XUV 7܀֭[^z4 gϞETT/o'|*g gngΔ+}WUwiK TwQ V:yJwhk?:ص p+)Bu&k԰Fx5zv3u~E>7C}+?) *T1|p<ɏ'5t>}(QL }l)8<V!HãW -"q0dQT{deQ'3rcwwo'v/Ciƍ w\l,M,5kZ'ǗbQYpx7]ēBcǪSH:v,[F Ka!)fu=bf;:EMxQ]|c0IjW-!WƙZPoZd "fEɓu^|Y}9tb} ^q^СC Ann.fϞѣG>PQTdu"D )QQRaQ ,v 1 WGxd.f4ժ/tT2Ů=ү*繕 tmԭk v1ct50l0 6 Xz5VX?sAv0|p >mLƳ>gvɓ'~عs'j^{ڪU+3۶m]weL^-d( &Qv+̷Rb/,LvIOU9JK~޻[fgSQβ9^li,*y4Hj}P-[(6 cǎŮ]3gzx7pun3s?>4M[or01#GD͚5]k14efa4@p Tk VUaCu\Ql))߭GXZwjڕ%&&qҸqc$$$*g i_%Cj"8?| h\zYUYfw=G"afByq=β ECeWex Ƀc=|<ܙSo{tN7n4voUVaŊ۷Ǵi0|p2KN4KVV #j̙xf< |x@ժU]nK=\$r`aI_݋ˑWg^chBiR|]fr#! ܅W]4Cf s%KL -U)L ΑB`E};yW!)e{4h@ cm{ U@n:X۷oGAA>|8uwڅ~h׮W#<~D8W IDAT޽QzupFesٲeWW%ɠ:E!OeIՕXL23)9_K*$D$q8p-m$V&4q-e& ˁ^+ضF3,BM_33ڵ>9* 5BNNjժuU~P:tҥK=lu4iSNe.BD(D4i>BEń#{|ur( Mvׁw2+iTz|ˁjaɒ%_>nf@:upw⫯BVV֕.[ ܌dW q&.X"gG:^שc,fr,6$Mꡡ4Li?X;\HxZYYV{C=RVΝ44m|#۩9.#zPżypwc Eʕ|vƌߎcʔ)7n K&)I~Hzk_>XƣiiT=)Ӕ'OhT6gsGq Rߗ_hǦ⚫U<2h8BKJ'3rK5ׯ?7b„ HII1zbڵ+PzuL0-ѣJ\@!E8PU./ +tM*Gq% U IM%k*+(xI{&RNU 9U<_iY,%L P|*Ҷ'uRIeS6txٳ1{2?۫W/*\23i?B( aWXx)*.2 cqqTNTU@WD14A*$"ѐ?>*ё#4 Y@ M*=o9Ex@s@kQM_( v)-99cU9'/fLqXaLCDoyWQײ%cd16M*&o7J(vYԸ>Y+,Af&~޺B/(lWO?@6<(Q(xV=7Ƅ|Ue c*( @ʕ]\~oYFtg=OMؕ'ΝmHZլ)[ر_^h];9x%̉V@SˣcArUb4~Ξ v}XȩS/mV?p+ \*`*s}¯\qℜ=ZOL 0C7+[oFʓ3a) ǎEE 7ȕ9K+!_Ny@Txrhp 3ph )1k,6F8"9ZvrFEZF5z6nlUՆ?G*XaLA$R')IYZ٪*5Iq>":;=.^<8%E=#m1 rӦJXaL!&:P\{,۷+ѰM܎ RJKqFvyXOT_Ґ}陡^NK^6VIoE96ovV@SJO٨K([ 22dgSȝ28tع|~=6ԇIч`f24Zj";I}W_XV c 11j,jKk|'NP2\ QTD^'V@J"2{Wv+ Bւ cQ[n]yy]惂3 V c ۷22hR))Nav 1YɖzĂ( 3ko![ (_qqT>XVe@U@yy:u[a()+ $&dӴlIoE.<%!FV@J""u}מK2QLC!-:ɖ=49 Y&恨h;ߧW_+Gyp{ʡGx@T2A`Nz9 ѨR/و㣂$+Ɨ_'\(7F6F%M_rT k1rfB$CȤ$fn?:E%QM# }rhE\";ڼ ~K,#VL 5@ $#CժQJa1LrW4Ѩ9DٷR3F6AA4bqq.?aAKR,̙理umM PlQjAA4 Hi+(rϡP+"Adem+*>=%/}, Qvn$ gw8 `0 c(4!U l} &[4bKlHnuǥKTݮ<* #G|:s8:ȕLU@`+GYtL32$h+ vmْDeʖ_zIl,ˎݷ54g$*`knPWydשoV@CINVӠP.S=S[[E"Z4*S-J7mQv51话뮓''kA鏳 [APHyԬgp}# sBrwkR>8d `6K,ѣѿbŊG&͛7Ghhh@.]*NO~ k23iY 41^iMg34nr]u;ѷl J`8^_CO>ZƳ>g.u>jdɹs4TΓ)AfXq";._6vP@h3XrP[X٪k_rrh"d--3ѱ#0c0}:/SjՀ݁]Hǎ4}شi&NԫW>,|HDDFa̘1:u**U dBQJcyOV(VL鈘kNn]ENdey ĵ^< 偠 FW\Ag6*[XHoЀ*&lIct-\}5MvL@###QR%<4in&YӧOGAApl۶-BBBйsgdggc՘>}:"""_K8hfU eOxPqU=iaiIjmя% 2oI &#ŋ%S6ykբ׵kEulhK]ʂi9s&&Nx?'OjhѢb>|8Ǝ _D=y5tJ  k'Wwj%[h fhkqo a(٣)gV_wV7PoF~!Tp \`ܾiHܹ'O.w}'vmL@ӨO.%qEVds85bЙsxO8q<2-׹|-dݻtuŊ%åKʖl"#2k<|`~ye5 سK2±ѪaHtR>I&8u96lpVCPZ2D̂;P ԭ+¯2*V 2OZ\wl |re=ZF0ҨQ#ٽ{w+aɒ%_>nf Jhӧ#(( Nhaa `l#$$HKKÍ7ވcPrcذaxGq"''֭Þ={0vXtQ9aaalm}%;;SL7| o&M°a*Uј4iۇ$TP;vĘ1c0f̘ROBOIIP-aa)W@̄aa2aa;: aaa`aaa`aaaaaa,aa+-[ ƦR 3f@ йsg٢0 0 (?>&l [ Ʀx.aaa`/2zݻsVt[ `> MݾR%deY/Q -[ a`ĉk֬9si{@b`ӑ[+[L:"#p>ONEUC| e~Fu/QF+~2e,z( e0R`DGaa!ƍ1cƠSNad㉍x}XlQף>n3;ˏ.7t8tG b79gyQ9x+3~K ۦ3}~fȶ/v3d[Wo([7v![ fcŴid(JAQ Ț0$9|Ds WdpջWaⶉ`$p4(} .bʎ)D C-j"3" ٖ*+!\9[`l+ IOOԩS1uTԯ_0h 4+W4ABIɌn?7zޡm0iBV$[cE0O~/[¥ /sȃwgY\̿(efv1MeW_ȼ A`k,B Ms.Bi; eL `ܸq>e˰qFlܸÆvcw##'^ssεH"b_ }i\ '7t_qn_OL 2QXlQ:1U.#qnQ~̫D6=xW^AŽ1ULC$Gܧ8<({"1!|乕V:|dHk./(.*g sNL8cj-)8\{N{G}HN:Nԩ ˫B,cxx"DtXpfӒ Bs]q9 EQ2f{zߚ=83gkJ)|J~pwŋ/$xq zɉhM/O,P;쨥ywfOo1`ƛo{?XBg??/<#G0dאшlwbLk]zQ߲@k| :8z(nyr .(g _ .Ė-I'8pVou=~eYlآWc-b K$e ֱ[\F[)R7F8(e9f||2;s80 ]MR9tom:+P4p7J k[/t_Wyb&pM5eakK.U;YRj>Egx^Rz_*-Bt-NEnSGh- >5=#cI9@˙``y!~=+ƇWÄ9k k^R')S9Pn$O5@(MK!'mمqж貹lzF)yW8 gO95GxrZ2|{8,5lR8!w-[$`so)Ӯǁx;8#@EQX~qw-Ka쾳[wCjLwzgwF4ׇ潎=< s{ABz @ԛ(I-꿩mh[}'fW)BR K!RV=~9 mYn#m&yqIs^쓳1rH.𩻪._/);(1@lVz’ *9-oD .fi^.FH0c-'T})|G̓3EUWEDǸw=ZnDĬѣG1`899=z_#G{Y&0`=zԜM%L~;PPyb&O{ @Zͫ/P(HHë0x~ҵjr;o'ЊC/70 'ֵ۴#rjdKv躥h<"u}or=>¥d}Z%џLc.2 [P \Ea"/9,Δ#uhGi꯮"N@ڹXtnyEZ?(0:oby8NRk=pZzb͕5٧f#19A l A'D)˜Qňg'_Om2 ?4hBtҕ?Ռ;RwIHtKx慄ĔDBW O>p@>@:bڕ*Khjw8 RB^xysL:.>WG܅[va,QqiE{揯_HWf%p;+)H=3UCy7 qF/F0up]<Ͷ~șqMP3&<>)^&w^wuH+ᤴ$^}VT~ s{WGtIAB~R>O|.jJi/EJ;!Q#xyM޶Dl]8I%|Lt6gwwzA9kFHY?8|mvHmg}R K84>mx/[3z魢(n+/ijw$zo߯B8'yM9eY))Z>w칵'lfڈ".R瓚v|.=o|!ԼOzD}UdXyYe49+n+74GݺuðaUʖF>]H{HL̹Pi ?ij9XʙD|{mVfvev%3K  @8[ITLpqi8;vHm*&ۑ{GL89I8Qh~gk];~P=|Z(mЬC+i{jdۅm3)J-7%3(3|rzįoc{vSrF=:SO "n"͑#8}Gq89b.z/9aGt..Aҥw{ ߙwlhmԻ " I Z*JG8&ȍ+R' K$LBpcGJ}zݛ?'鹵'-: TqQǼWj\}\ֿރ]ĔD~!/BZrc^ ! ';'}x O rキPzX#@ۘEy1 jժXx1LM">[DMS,W\&U!nnB/^B!pΎY葛\рi&h.IeIO`($[W/ngVsv쁱&K]ٔ݊ŷIpBזhb`@+%pڋ6ʓi&%KNPXE-]@$Hn"4Dhsm]s[i٦89HpWVlg.'ܻjH{p o+$%-hit>|@kH?B9W^:Ғ`ge}䣄GO}6C,R'DΪڄH3BRB]G4 eYz5ދC~|5ՔZHnh(S ќLwq&0 }.^QFFppp@r( r&?M'/o R?A;i﻽sV"7bctNJn  NpIR:p/ qvVfDE#'|mOib3f_ի\ިz-ܔ"f9B"#-# WcʖէMjE-ák[/gaUд7I 4rq9}8!WqX{u-g+2gf뚉y|B4'XL \!4O=ELb1P$| nINKZypUZ45#2sfM6v'ZyM5 cr}l.'$k C3c,>بpƄl&r& IIIزe :u%J ؾ};ڴi%K`8qh $5qH;V)qb\4{Ѵ'ׇ Mr% 3? ='~Bn1ׄE!.5JL\[t!x")fƉZW^I Lƥ$ػ(j+GZF/b,JNR,w\ *z_'ň=#j{B}ha.76 iP)5vC.’|aY믯m61(C|R<ﴜS!嵐c>_XQ#9AC*1h&=}. {898\QP!~a~v{ ]xvG)PQ^# 8tŋgϞ8wz,GaՖWK e.1>s#% !a3g H I1,BJ|_=745Jǩ؏Fk(6܈3Nr"l!X~qdxZui?|Gp#rfҥK ß/ջz¨QаaCT++5nXXŊQzuDFvR%#Dd;K;>:0c>əߨmZ]Y='l , qM߆NhJL0̺4qpa酥 D_%}R?p)?f-ٜ1s4vsSotYfVqCBs Vҧ onBo"F9C?*_: 6_ }U>(+V F^ ),˗ȾE(ai`5#t!<1JB[waJ ]dSS|ר[[?@mNflCS#u9c6"˔f ;jrG& A3pN#rza 1ҨQ#\pvvvܹ3/^6mN29WS21\{ c`˗HX/^@bбcGM6,mիGÆ ujF^zk@;`P>  SN5KZB*HݺupB[ί+\N܄&LRp7Lsz7"faչF&e(m,,8RĶ0kgb5y<]=e"ECߥZY*x:⹚K5Eu*ⰋXJ@)0)pup:4s+ w+9UR\N[ŀ1j1Dțh\psBhPEe+9K? nR d#՗6E-Fe!5v]X 1&X_ 6>CIߊ6J drȓwOL&N{*;U}O@r[n%!CpY<{̌˛l-QWrgXb:Uئ0 Db5045r;,ggԐQnwYnGv#Ù15SOUt{S6 {eGP_$Xbϵ]k+SQUkl QЪ|+ݫF疶[m⍯n+{k|J}G{zJ^WXME&WG\:SL\?[ qNcbIMFjH i~/q~j@0 `I^DB禫>>9Ejɨ{ 7?L ¦M&sEYիWQre88կ]-gt|X,քeY~(&>tZ%|f\VLYŐŤfrjQm}Q*Ҽ:tѢRr]v`qZtsTV{Ms* S/^u IDATj0wSw58Ʌxes"n(^qݿnhV<]=q~y9H#:7(@}t+o}+W16@7jtگՀ0 cXݶB[&?{vhNZ!G#&Jեkp]uB}-C+5&rf@ʔ)iӦ!&FӧOO?lYR%Jh=<[ͯTqEǖ-Q;(IOH…d zk\lY:4!M溜#b^y:(]8_rڼ\sz/U_<;v0\I'H}h"pY.'N&7}~R9 r];)AŨe/鬶F\_VzFO­ 5#UQ.E2DYnAߺ|k0!m^κpwi%tBQ[*Fſl NѵP@1~SU][3}fb,|eeJc5 JCj~OSPi^bF6Y/-5H'A%n|udk 5Â6 DsPԆRBsRfL9z'88SNѴiSn^^^pww#XE\\qEܹsTVX-mllwmmm(߼<KKP^n<2-_ G*w gIa,ݖ n!&^n- 蘋 |1N0awo|ҍp:'<]=1>\ޗH"WN86 f>zTM&K`Lߗi-aP.S#X(jrau e[ 3asfv)L !R)'u]so=T\*_X0Y`ugPĶ{ʟ_?b?b1ei6wmrs|)˷G1,:HQVs &?5 ӏO?w-Zv@ Cb&HBm;+ H\ zlCD;1An^;S^]G'18XElܽnwTȦ|d貹v]iB X|n1^~x)[Q( QϪL2(:4[̠wf[NSZnb!"6Wh, ϘwZA90dȓU%۬ԸdĔP\ߩR _ߖڈkTQtVp-ɲ7F0wbGݻn:̚5 )))eѶm[l۶ PGicggdIIIy]' ގU7_\ &UKEET\qV-뚌5ѷۣ_%ӱFޚk kKk%m(V^}P&:hƥ݇9 I^Uئ0wbgMJ k3E Ų ʢ}S,6_"KcErzΜ"ͷ/ة,t Tii`r!F T=2uARHu#z[TwOQz~ca)8HzF߉ bR#_ 7^jO% ă1 FŗsWOymrq_V<}x),l~kJPHNWsX酝Q,qʴ$d]JLAA낒9ػam4OW /i":%vمK(Y/>qi81C 9x?ïB8_\8ة,7Mp!T+Z mni㈊Q7Qm/ . zuGr{,ޗlDxZnDJʵpkn"`ܹ3:w$\|x UVEݺuy-DvRD I3+LdIgE MO쿷aRN6zlCғp'β?3ԥjB@-P=I2e 5\@4/GU\#TjľbLc̊';(_~9U"([,[ "dyy#FߴI-(4 w'2k(V]-/EpZs2( )%6-۔i-*#AȊ+Ьl3Ϫm90N?9~I^۵ZWtzԡ{kcYegpa4^6O?q)QKUٶ32߇ q/ybV m``[b5QlmmѤI 2Ǐ1d4i$GS޽Dq6N~Jm-In,pa([XR C»7Za=pAx!Y6C(hk ;Džs vVv/GAHm|&j_[K z,Υp|q/sR {189 ٸgŗѴ`?(ZC)t}ႄ+,\n ؄=.F^ >q S+W.rEd#Kb[m|4=@l$|Rc`@ٸg.oS /sZ* \~sZh#|ՊVCrQ *BpmX!F񂬁c1ק:T'Br^-o_p"J*Vb"NU;齇_E?Lj&/@qH`Qhy W /$oNI J7}ZJCM"EHqR|I@rݺuCzz:VܱLNNƺuШQ#*Uʌ(vƕx 9>΅:D%\,uv>=XR1.p!ٺ|k 3X€ZOn6Fg\* m+_>TEKZ>=g6F;/o$[f(o0T"Eݪw?3 E%kT|}gbT(Y$w7!,mTd'!6+OԿ<2/,j.B:tR{y}m3>#淙Qض0(fy/*;WUyoL.U`j%մTYfKe%Tv)ͧ47f >/xAɂ%unVpPPPYlᲸ7(Cu0DYAmxLvT Nes)`Rʢg͞\3z!*?eAw?5Q_]"LdV$C`5,Js p;/P`8ָ,Ԗ봎 5 7M)̆lDa6F н{wL4 ^B ~z<~ɇ#2}+>=p!\$Y\"#{ 3'#(H8raFsNPYh '| s7e&l:|F>;Hٷ/oZSB!wonԺ΂3rBѭz7l Uq5z`YZRASN9Шy\-ZãxX,&l.,R2 YU;;t6 gg8Gъtb $^`ki ^sEqϥmaɅXtA캣ȧ\tbT^;.YN?+r`Sضdd-ghQ}o 3D6{zi@#Jó3v~OS'65%J=7O=HJ g;geWq{g8;cEXsUKS[6P壣!;"?)Sašvسg6~G@E%!pwF,hTy\ zlCJ`ci")M0z_)?5]-T+Z M?l-mlU*<%3CtDBX BxAӲMtgk^I==N$jiƮE E!è  {{EBr2sߋ +\JaD=Уz-#M ЅҠ O=5m^xCbκpA}2E.!S7зnfddnpe}e.͛Y9"c}Fߖ{_P砒 (3H^ (Pk"Ʌg'.$y0o (@R]P Jsp=wqSn8:W$9 s{vj@L"vx ;h_=/H3'M8&c+ +)]eR&/vVv el-g0 6w,yNƙpnWVk)~;"dF2u6gGnyߕ2:ܘ-?C) mՎe(JT7Fi8#ѤlPo<ʭ$D_E?Fgc mnM|<d;9'uYSpvdoAnIfh F >2AX*$7}Lnч>bi\Y|sMӊB(n="AaV&1sZA Mˤf0L4*K&PX \qlyA/oe3P{h*3"c$$3E!-W~C+h0p`ea+ +pwFFPՀ 3VdژP0!\HmqC10K~u5Mc1+k2sz ͱlȕ+WK!"O!Ԁ4.gɱg-[[XKNkt^N<ܭ9b5:z! bsj@mW79ЦȪ..95؝2eQ{G V.р+s_+Z)?{ҽ%W~z%)nN($i'bR6C7'y Δ 0HO7ܩui*ЩJ' 58K1tk>$)-Tu))Zӻ~}^z|Zq5V]Y6֛)S)Ol:sOy[#A#Rc"e[K[^o9c\;Ǹrg/G5Px-tA&r f@~XB ^pX~9ꖨ($c~!;5ՄGqeY׍A_ISs CٓbJ.#5;,級9H{A>WTCP,]l0ˀ4A k kQ8܆ cfb6dZÇ#,, N³g ̘1 sIhcmx~߁N+䛟TYCnIenJD˭P.\^Y[X+ε~JeZ4J"!'0aѽ{wTT Dze˰g\v ŋ`Pyug H~!ذw4ѐ*5RD./i=|1"/B,^X+᠟7oe˖aƌܣ&OtAj Έ[Y컘n $M/J>(](s\rƥ)_LANj (y@C*yfH+SPض0&Ff҅JP; TgA3ReFe˽U[JgB޿D'llT&`8ڔoK!(QB;Zkaކj|AYHŋ@s7 lkH ЦtD%_ ,"9Y>[[h'Ni-ZO~`mѫW/Kq{ 0x! 7a|>رcQT622+W:֤Iĉ(P@)Oh?+-ZFAi A܄8s7 ȅK Hj+*ɓ'h۶-? A4x٣ CԀ˛7oдiSԩSPAwRҀشiP tAByPڷo?zǦM"CB?]/MO>x"DDD ,N(/AAAd@`~79777@  Y&j _HIIAllARA!', tB'  sӱcGڵ i@ƍs}|(A .:i@  1( /AAA9 AAA$AAcBAADA$''c„ (Y$ѨQ#YD6L:~~~prrJ%޾}~~~(X ѿ1k׮Ej`ggʕ+cٲe5lŋ5jjԨ+W*K#""ݻwG P@8;;aaaZe0k,T*xxxh>yq1T*.\s}% gϞ;~xvլ7keeŞ:uM#ha͍mٲ%0 ~zrO]f d/`agΜɗA۳9#Geaǎˆ~/ pya_~%%%+Vd2";INNf_|ɲ,^tIV9r$[@ɓ'pavժU?lǎEۗupp`3gTѱ(֖۷/zzze˖Q Xe֭[-Z`k֬):G}@mۦ\^$?ZYYsaa>}j9ŋebŊZǫT¶nݚw^av߾}rgϞeaCCCMp"bիA/ *Ǐg---m޼'lݺ}fG^bW^Eʕ :^~}k,"3Ƣ^zZׯW򟹿5zyyARQ?,/_#^~cѢE8pƏc6ljԨu͠APpa/_վac5bbbPD ܱϟt\BLL o"55VVVKuҏ xIDAT8尶3|@XX?3gAƍêUXd 8ry#'666֭ڷoDDD`h֬Μ9OO<7H1O>F븭-<~{} >}}lllq"##7 @;ϟ?GXXF;;; 0gΛ7oO?᧟~d#'7Fƍ_~%uZjaҤIطo_$'%%W?쐒"y$Gy/^Cptt֭[0 PJTRзo_bر ?G-C}P}v,g@%$Sdɒ9$"> $&&ΰ˦kNII۷oQЮ];{+I@@I3&** WѣS<|>DRRRRR#Q!D)S)))C$@:up]$&&?ifRJhѢxֹ .F:u@KA(;޽{سgV*:OЄ3uPT?>c={ 3˗]pwޅ;f̘A}!,X?iܸ[F9ýrJاOrMn"뤥VhC!?>O^zu,%%b]\\ش4e|~ݱcsNߎ;ؚ5knnnΝ;G>?Ək׮VVVlΝcyob =z3\fٓ'OiD6tRvƌȑ#Ya؀vƌ3؄e33VXD֮][++LW3Ι3_~-0 nذAϓΝ;ZbMƮ^1c[jUVR62B7oG˖-:3gdWZŎ;gH\^$HRR?%J`mmmن 4wl͍eeUTJ~_.""e (:99`Y]z5[jUƆT믿!LH-ORR?6mڰ̶oߞ *KhѢV"B>daÆ3keeŖ*U߿?{}yo0,˲9gEAA 9AAcBAADAAAA9 AAA$AAcBAADAA3p@Zlٲcՙ2e 888$  "R;~80ntL:cƌ}kee ̚5 9V/AD~As6n(~z:t[XUN6Q';v@@@>}%Jh (^81hР "BAgƨQb ddd)ԩqqO , GÇPT_tR@h۶-P@Q{?~e֭[x!n݊]2eV˗ܾ} @&XA^w РA@~x;gϞ:_T*4hGY7o )Zj vu֍>WHBA0e~ڂ "88z <~{EӦMtRmmmѶm[cڴi&kÇ1j(*UBZZ6lKKK:tM6u'  adIUrW^(Y$Ν#99KFf0x` <x)J.m ???޽Ϟ==<==o>>b$$$СC6^ B Úr  LLFFW=z`9Zŋ`ܿ6669Z7AD~|@ \Jӱb |1MMMŢE?AaBHBAADCO IENDB`PyNN-0.10.0/doc/images/ongridoffgrid.png000066400000000000000000001075611415343567000200010ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATx{|TL ! *^PlE VZ"RWW[mxaъTt˶uWjN^P+uZŵTUJb:Lf2gfΜy=>a$ߙ|>7b }3! g No߾]͛7+~UUƌ$mۺz!t  Ѩa^᰺ }r`@9yݭ>W*&˜"R3UXs܅~> ϏZī* 'tRA+ A] =zA]DMx~I|NW2e $`m#{\03 I5?؆0￯ݻw6#3fLo#L 6΀ @=B|ḧ́3%غ /C@+:?Y9 V@6@4g3b Hg`&cef@8+0%ڂ.X@23hܹ:蠃TZZ:_^~nذA'|UWWYfi˖-k(Ld4o< >\UUU:sgRvL뮻EW\q}Y{jkkӴiӴnݺ}]M:UHD>VX͛7kھ}{53M>]֭ӏc=1bf̘_|ѣ ;J5?[o׺ut뤓Nҙg~Z#FВ%KzUQQ'|R3f̙3f}駺[ z't1ǨBpX3g+)ShȐ! ?~j*ag?sg͛p8>(w?NdmڴI/5w\M>]==P]y1%:hk$ o|Lv?K"|I͛7OC n״i#hҥ37o9眣QF.SUUƎ+ItiH7U[[#Gz~P?9rf#hر:zVTT?_ .֭[5j( f@`HZZZV'|$>P{{<׎7N<:;;UZZZvѨ~i֬Y/j׮]Zl3gjҤId%lܸQSLqI6mDI#h-X$G֦kFcIҰa\;l0b1577kĈ'w^5776SUUg444d=/ :TC-= qLeΝ)QD[ /H:jO_~_;wΝsz뭷 }}Ѯx@~~muuuy睧|;*++ /|+O6W\./_r.A{֭Zp֬Y={ӥ^:H_Wh"-ZS_TkknFG};O+WԪU4jڰauI'nw'IWի}>q53B+ /ڂŋ릛nҒ%Kt%~^RϧwvܩP(A{0a7 UW]e˖Q\pf]s5Zv}YxZh^x׿ּy4zhI=!hѢEz뭷cootGKR7MMM4i>#M2E'pnݪ.LSNg9s 2ڙz!͙3G3gF7H֭9rJI 7ovHwq瞼l{joo״iT\P(kxS zE: /誫ҹ瞫bM>]zkھe=[JwzcZZZ =XC.B}ٽϝn}9S}/T=J]zjmmSOiɞ2)XIՂE u9v„ zgx7jjj$ '۲:CL\+Q<ФOɤ_8]3SبUV|WGqӃcsSXXe„ bz|?{ァ<0/j~*//믿]{.*/€ﵷk!$BϾ ͟?_h"m߾hT]vb,X.))jǎZxqۺ]{>[uuuZjUeJ+M3 T@,z϶0I9t&M+Rr8{SO=M6iɺ+\ہ.]^K,/I&i֭z5}tY&'˙hΜ9:5g9R/6mڤN:Պ aB$~6V@R? UUUr⸉.]o׽ޫ.r!馛~UP(e`KQ.\_Wz4vX?֨Qf͚''g͚kj/{u77MF3a$>oKq2.QXsќ9s^hѢgv]xᅺ 3~}' 7 I;vl>N>d|>ذ.jذa:S}PX&f@Ip @&L `%ۆ`-;?ط2P@9P+0!6WgǾ rzB73 %9Zر2`* Ahr{`V&| s؅s@X;wH:T\-dτ m3 } /B m߾]z7 [n|;N555;~f.XT@̎ ={N(fSN`8ִitGo`2V& ?pv}'  ew>?%G~)m-XP(.X@8[nM{MSSw {?зP+Q(3 .ՃA~x%0X躮< x4ydԨRƍ7<`Fg:ڪ+B￿5f?깟~i}_VUU5sLmܸQS8G}{'a]tEzwtj***ҋ/() HggnF|*//A -F@lI]@@ٽ{:;;/J˖-Scc.UUUi͚5kvZ=*))fK_W}v͞=[pX=﫭M?Sw*++5g5J/&OqƥsNŋ[wq|I͟?_<@Fs8dT`* h4:̙,-[ޢEhѢ{ƌ1cFb15j{>zo:׭[%%%뒶\bb|v͎ H#!@l׮]f2/_-[h֬Y+ QA d~%1d?|z:ѡ_~YFUESx"@&P cjzWK]~Zp |0 ,@t,h4jm R=z4CȚ Ķ H"ZsedSȜIĖP؆ &7L^IՂ\>Z[[T S&| o f "NX2gRĖDZf= L4bkmxceK S9ml CAs@lI9 @`X,jI_pL AЂVkkk˩L_2fm޼9੮֘1c }gJ ByB 7KT,HdBFT?C`e$k!t2!tww[@ ?ػ:ʽB)SMgp!`k$g #* vgub+ RẌb HT3 ޮH$ІmT@2C `{V' 93T-Xk@h6* I /3 ٳ: :ď L>Aٻ:!h5ӂf@@`-Zx|W'ڂY>:::\\P)omoJ )wuBUp H~ЂŞ BSm$FK"p H~$=Ї O 2e,f@`{V''HCM{]WWpĔb[$fR{{{ Ҵb1E"7n L 6̀$6":Nۯ6,Θ@l oN,C QIIkيg@R {V'88mpƔb[ VT-XT@ :`(Zgup8JZʔb[ V"ZwuB E"JP W@l /oN{:Z \Syf@2A] J&;`I @AP C耿ٻ:!2@p)f@nK Pf[J= f@A oN$'-XPHUUU  3 `E5Ѩ!~ :ib&* NwN<;`ۯh4h4}olI oNL?DSւ F;`ű/R j )YxG[Lmxس:!mrZ!Hǔb[$f@v@*++E;Ђ M3 l ؅+:zI~H`pI H,`@v]"p)T-XT@guBe;. @lp!`{V'Z蒴~z}i,@lI oX!rW롇Jg 1)\ 7{V'V,sԂU^^/ZZZytL ̀$D7n]iz IDAT䪭ыPX,&Go)Ķdi ~<ȥj׮]Psss?ŋA@0#mN*TA Ɣb[ V HbC"YX g} @ 'T@r8wuB`Rijjgʔ HPf@Db1GҤ'S&Mس:!:::D^J f@ Ɣb H"c^Z.WROoE@L T@_e ^gdĈڳg@L 6̀$J5x#{ѕW^SO=UÇW8ŋ\7o<XW.;`I=F >, ɬZ*:m a$ * RЖj,* ?Pu]w裏̙3uw$B֭5Ǎ/ڳg>CL&kf*Vرc}mذanަkLyl H(2$CG=5{ǎS^[TTc=֫[ $'%^U6lHXT@dfrGh„ nZ^T+ /O[ߏby G!C$iݽ }s|4b[/"mmm1b~K/$u=~Hcck.EAxOWqq5k,mڴз)<8q=Xy BztR=sz7TUUU[=7%V@k.ե|N| ݾnA;hҥ:+hܸqL 6V@8 7H\z_Ə38Cw}3{:E'@$eFU\u|;St)q38Ccǎ_GywITEyNS]]^{AU[[ksܹsy{F V(RCCCoq:B^ZW5'M6rHM6MׯBN{QA6 x1'}K!9 EEE)>޶mU\\FIHeeef7 @76lĉ tGI&P)?[= W6dd'xB}&MT[񽮮..XRO1bDuFO>s=g:jJdT@SOi޽36mC=$g&}ٳ5fb1Zl8}_/[!+3@؊ȃtkfEE,X{W~I8 M2EGu*++qF-]TpX7xc~@lI6NK.D~ԃ>P(-[hСM7ݤ[* iѺKpBNCwT;`I=#G @[3uwwC9D+Wԇ~N1BӧOu]C9P?ΠL U@J  da˖-iyG=rm6uQبO?4c҂dɚrJ\ז/_['okuB`z4kȑjiiIT@ܱ /op8ݭK;v@h S* 6`Qa@D"jkkK{]UUUxU|],2bc V_̀fkݻu%5JH$S $)Y@؆]X?k"HŔ")3 T@"w@W@`@Iwe" f@pk ޡCk0.DQ>u oѨ$C耿ٵ:!`B!UUUm_IR]] @֨xmxwuѨ!v"z5f@Ķd-XT@"W5XS;V1)\I wuܘ`I=9S kuKJނ% ӂ [& `66_q dĈ`ȚIĶ H_DF Ν;F΀8 T@$r8ivNZwwRYY9hAdQF]>@"SH* %zSSd}0BZ$2-<I耿@n Vq@HdZ6ٻ::n K=#IjnnЗ)D%{u܂7~`/$* 2cJ JmxkuKJ$UVVJ---^GЗiĶDEEEl s+]ZeZTvN HII^oJgذaZxgJu$Y]iȐ!^ԝ`IR}}}B[+ ؆/W'Çs:@lq /0Fss]vrԂ40 Δbk V"Z{u=. ~a\ccvڕŊ Δbk 'kuuljjj,1Bi,qZq:_I)**REEE25Jpi; /`V'XKisV<444vȑ ;R`0ݫ|-;`׫$쳏$稀s@kuU;Kr~saii)p3 q /@`B"Ieee1* ޢݫ|3@ Ú* 3e,[g@؆]+;KʬKijii* ✾w0Rݫ|+-uUUU_d3izvˢ S  0޽{]*hǎ[$^im HT@ނ|vN駊bU@>, @H̀ߔy "Hn755Irv z\cc"Z[[6,Y$[@؆/-xHZ@8aZ 3 =[`'-XPHUUU/@29r,9$"p_Ph4I|j[JҨQ$Q) H" w^G/*NۯzЋ 1)ض4VTTD) TVVi3)P`2W(;`I=$I*--Umm-@!o}] VIIjkk2u9 o҂ `paf@ܑ*  0JwwݛJ/jjjʸ+ ,+;`o:1Gss"HIT1$ /W(J>Л$ev z\CCё򚮮.>q-XI6B O%_Rv$޶5X$)dB[l +|%;`H3 硅6,防 VPf@R`JٷBU)++СC3w8R_XQ)//WIIǍͧ?,`'̀ 0F[[h2~HٝWYYj577zT@mx{طB@8su$e y V6JKKU[[ '$ VU@hɾ t)xa@:NH: _i$X,I ),a _rViiJKK?={֖Si@h"o/utt8:M<&Iٝ.|V__ݻwzT@@҂E#k=~ zD$iAtH* ̀U\ȇꔥDB~訣(蒲ÇK9 >5`X3aJu$eeٻwN;OӉb)**J@Ѩh `Lw@Ra/+$]wu:]D}ݧ뮻N/. A>!ܱcb3 ---b)DP߱ffP bN=Bp҂ TUUc!t{Smmm)#X3cZq$Qg )һ=F[g@$NCX3cRu$U-X˗/駟r~I?M6lX,;t HMM>$kfvL `% /Hmm;0]r%I F=+sC9dȐ!'*))Q]]$* kfvL 6`%ۆWX!>\|zG4{lzb|%NpxB~ǚm@T@ X@qz뭺 }+Ë3@ܚ+//O{kf~1=3 |P 5~xoϞ=+uꩧj )ܰaN>dUWWNfҖ-[\? +T:;;yrLIUUUN8A?k*  [̜9SKsCR{͝;WsLU^^"TWWի}-Yx.J}}}u4T^GM+mx(2ceOXz|wј1cX|yFI,sT7>&A76lĉ}kf*ƍKJGz@܂Enr"3VIvZG׺}nCqqM@PIՂ|VL9}yꩧw^޽[RN,=$OWEE/^c9FgqjWcccH?OeKJJT\\jS5s{?Q$i;4{l-]TÇOSzg3|2):(U |V|r%?bT(Җ-[ /J{5}tz뭃-.;`I=-XaHEEEie~cšݭ~oKKKs+ԥ^V?^O=&O{ΔI$hZ2ۖ-[]7a=3yrgD8d\rV\9덍ZjU] π0}h^T@3 @IT`%$HٷBhN* PHY?Gf@OC,DQφt,ob HEEE/{Ukkk^+ 555ig tTuCr¸Ri݃,abZq$w +ȧ~Z[@^H{ V,c',X53wX,X, c j֬YZf īV@jkk%q!;o@aca^PfϞzJgyo?]s5› IDAT }[ p8og>X?a̝)x+Qrk֭O}W7|ƌS? }Udy{[innNy-Xܙ@PIu|PFַkiƍww}W^xFo}[z7 } H?RVV Z`ܘ@c@1+/~[?Y>N+VӑGY 'MMMyz$҂kffiw0"pZsJXguN]z饒7ћ\گ.̀H4tks}$Y1wB߀W"q\Rk׮U4QG '_UN1bBkffLЂE qFXBwo߮}Ԃ 4a„B^`x1!$ {)b̎)3 Alb+HKK~_hŊz7 4e-X@fRyyyo1ptvv>k V`#x!t FR{{g-\PAT 4wa\Ң/]]]by53w πPge1ckƌl8=.@Ѩ٣C&w,d3%>G/+?\[@'ްH>g@$}g)Hgg';SHZ؆/W(\4utr|RHҠg `0yuuuy:[$1@y$ gq%%%TII $sHf@h FԔta2%B 4Q*HJ@aZ 0yˍB0%ނ(#+ I)**RYYYN D9S-XP(D!;W([mmmi9jT@ 9? `;W(ыA;vPww'$D555jnnNQwaNB| I R[[Δ* @pR}$,Pg cxySF !^-X`0_0V(gHU@Ts  ,*  +/+ UUU9?S,)uD  'oz.Y!pc c8B!<^mWZZjaMDP CXPXQ2˦&گUSSC@@ `P(G/ Hޝ? @2/is@! o ҟ  [DH ?vP0W;`I:* Akb^B b̀H=TJ ( `^vP0Wuvv9B  [g@ /O H$~ z* {MY)* عBN* 9R ^΀)= ),vP(NGGn$ SIw:-X@0@h`2JJJzÀW* @0@Ra,\Pp^ɋ{_%%%:D"Nb Hp83Hc缬xo\iiU]]=VasA9@ٹBl>=.Vxi 1̀DA^x=RR[[@R.XUtvP(8'rZkkk.-XKvc":::^FC* @`Qf r~GkkSB.3 `v u^HB(oڵk׀ 㨀vsf׋D|z[BI@ỳb1ڵ+uT@i{wwW-X-]P0^"7HInkۯ АceXP(iǹ* ]P0N* EEE\l۶M / DuuuRf@@hd*v@\y1.q*++SEEEʭxv3)^IDRD[[:Pwª‹!`/3 N`6\T,X,H$mIDZ0[pV)x[q!-Xl U 3 2HЂSpV)xiqե;w1.`/Hf@C`a\<0o%^&f@ApV)],sTpx۶mTH$={$ˤb{ ]fw}iI) ; vw@7 555QeZy$Qf#5؂Veee<^6+  L π$kFٲw MMM~HπTUU8e ID`U yWBH=+ /3 D V؆qZpL 2b{ V"")8*-xtn۶moFHeR+.Xl <٩H$:?T@,ͮ SHMMM"QleZDDp:VٱcѨI :`+3 U rZq + uuuP[[[먀v2)ނ,J! q$#* r,P`*O Ɍoi@И@lqz@ '2|۶mSee "a@@lI6?gBD"GUzZL> T@1) 6,.&g@R`1򪳳3C"wjjjR}}]}\7$T[2Ť,f@ *WbC2s ޸x)//Wyy^GbZ R Vg $O^xy }{9 V@={/׾ ?^<@?jժkh|3 S8 ^DZf327|Mk_җ t7)ddȑ>[OCg9Cz衺5w\uwwkܹiU4v~_6lXn7+&[؆$ƌc=зBU@tQGnI ̀ ~_gի5gIҔ)S+М9sҾY>#4a/n7kN A?g*X,f'1̀ Է GQuufϞ]t>kiÖ5Tw$.bF3f/\[rR^^ aWWval[a6nܨ?|'ƍ$mڴ)c~*..V}}f͚xͤ 3 8tUW{+SSSN.夫ѧn_}駒FW-}+ d@;vH:ڎ;R}W7ۻ(ʴmWu'Ξ"!%"(zmPQqyWpqqTqatE`Ye IJw'~?8ݦӝt-]u!Tu,O?%K`Ϟ=:t(rrrp!B"cdffUW_ <3fL>$&O,k~F ۍ$Ԡqqq~ eݺuXn߶*sz3zh=!C`ĉHKKܹsaÆW3ʹU+)0[A褴pzDEsr!***~˖-K-a9& Zpڵ TUbϛ⢋.Bvv6njQ=} H]! Q1ΩUW ^`{J̢_~ؿ@o}6zz} `qUTT`Æ 0`߀pe7@ Hxn7nEI&UV}2dHw|׸KCy-]wЉ`)dʔ)ڵ+ =zK.E^^x /E zell,"""L$ȜƍѣGcԩ(++Cnݰn:|Xf;[oGcǎ'bĈDLL rssxbX,C̞=sEqq1zwy7powؽ{w\ жm[5 ?8wKf2[,V@zJ@6 Qx2n"EhU)..},!2=% FU`'D) *  :8ΠY!2& 4D`(ESZ iUp5t"#B&^ǀ1!<GĄ6\kOLL<a,"sQ#q[P raHߌ( X툋 CI$_7,o4`?V@A &܌ `F) 9-NV?& X,Ed`zL@Z(0!ٸP gJLLlp*Q{.X`,x<vvI`HlZV@ *)2!2& EӉ;w%%%(--Uj%#a¢+0!T@$Iny+B~^%] (~ (b1 rT]]R[hLMM ***PQQQ>{(C & $BV$&&&O߄a+)) G ? _())% % htIVobQSSurzҧr8Ni&VB.XD V]\0BrcBD`-Nëcǎêd_‘[U˅^x ,wDFF"**HDDDZZePVV?y Hߣi8ޙ3go>EmZ$DGG׻~!f̘_~wu K$v{]n.@uukW^|QZW@µ WRRN>t*& DaLO ƀ,aΞ=!;+J%Ǐ?z0a|'ݻwȮ-I``ِ35j>P8NM6!?R_3a7=% f(I`Xii)vߣnجm'OĬYo###_~%Fݬk"}aBz  |! 'ƀщK@*++m۶&=双DNduO?4.]x+; ݽlPT@l6_P Uнj' x<(//(11MZߣjeq\ضm[>V+BXƛoٳgܹsx裏tg$d555p\ ɘ7TD#w3ǀ(G$Bjl۶Mv~!I> 8wy'qA,\ɇA+JQ DJ̀պukEί]Ndf ۍ;wIeffx?FBLL lقkעs-:/qMIIQ{& 7=U@̐#dB`(,,lqСC-**ߏ}b޽xwy氛vc@Ayj'LшbȀ4c@: '}פv튮]6ͪ*,_O<n7,XvY$_ p )1S`5ߑ#Gp&Ӿ}f!?L>G]w݅4ƎRbzj$%%4`?& D yN84Tdff6k.dggcҤI֭ك_|1,?)4$ V5k.XD#FW ǀpzc޽M:&)) l{Ә2e  gb IDATƍѷoߦ^2 㑕*t + DMO7f mbܹI"++Kj敕7oz> +V={0nܸ\2ǀPQSS-N@qܹ}jjjNDAV@+//Ǐ?ؤncȐ!z:x<^fBaa!x̚5+`^"sE)j-,,"l+ g *Q<۶mkR߈deezиi& <SL8pK,aAA;JQh*^u`UWWq DK ǀ H㪪m68NX, <ѕ:I&!;;ؼy3֯_K.Mꥇ5@$ #$2=% & qQQQѤ~!%%ޟ;N,nvtС%I&f(Ei=0$\3;o]EON,3;}4פcRRR0`Y^{ EEExG{DL@(8_$''Bd0zL@.NgbM:&!! q\xq7k׮ͽL"& HJJbBd0zJ@: bv/3& %%%رcG^{tt4 7dժU8s S;JQiKN@$I a,,]b  o5faȐ!gŋq 7W^ͽT"!Tr9ԄBV8X!2& 4N'm֤Պ,>fڵ8~842;JQȝ7::Zp_ݫZ .o*S}X_J@$IR-=Z۷ok^4ho}*9n7-ZkΥaTUUl5U]]0hOOmbixn7v؁&&{ƚ5ktQ}ɴr`yyNKdzJ@<i3$ !{n5&xpB7 jұDae($$$(ֆ% QIDʐ{Fb`.-D(}פu֬s?c⥗^jD1_F❂72I8Fvb `)9r?ޤc:t耴&% 0rH\veM>>PT@}_ZǹsLXD황 Vqq18ФcRSSѯ_f=ϱsN|M>!T(=T@ ~zRRR1 L@ǀϬۍ={4阤$ 8Y !|I\znD aȩDEE!"BO~~>RwuYBd,zK@ˈ G]? ^ YYY޴ilقO?4CQ 5IMMa%1`' Be޺0 oii)=*{ݎ!Chz `?~|ATV@ȏ`5 6x]3wӿCy<ٳGk@VVVoA0~& ?c@u t"sSfI@2zȑ#X,Q}Az!` Hbb"& DF,]2 cǂNZ$IlNN6l؀{4Ҏ#5 HDDD؆PχjmqY/j Cvz]DrL@gą+++`n|ZbѢEܹ3n!B>nbcc@Gjja>HU@[ $::Z"SvM!+U\\zvd$XZ$iځf!66T@$ ĉ(**fffHŋ]v2eJEG*M? c@2\ $11+ %\ǎի1}t1TT/=̀"w5tV@'7999i^z%KUV뮻Br>0!T@[ yp^eY,x[Rri,ro=:uV\~XD^TT/ P H Hixv" Gz!4B,˅}޿SNhݺuH^t)bcc1uԐHG*MN,ŢhP˅Ca\$~eѻw_~?BrN"9H!$&&FSo'L@* fRj .IɯDNO Y+ p8@FFFG;_|qHITƏT$D$DGG+z~U;$k[ǏzzzHQZZ3fDMeHENQ+ f:w\~B>>a4l HT@n7#{֭[cǎ!ktgm݆Ν;DMrvOWqB>e`]¥?lZѯ_^u 9.XfI@ub(|-1kpRRRG?---UUUXd &OݻDaHE>Q:q(,,4`]U@& ڐ; x<ٳG{&99]t 5^'OĬYBz^0~Fɭ(4#v" zL@0prab 333{%Az\b^ǁX,$&&.XD$¡ ։'PTT${̐WՏaÆa!=7QKbrfRUU!j7,D|z {x9߿_\r C~_}oߎ/"&j)?*b` 555j^5ǀ킥$Dq<&&zRZ,Xcъ%X19\e$@_5`})4n7\.jjj|7-{ۚsOI|7Rr^tޮ[="!9}oJy9뇈ߊ}wNT@"##އTc@ !@vttpri}k5LOk3Kl ¾}dߩS'nZkY`2331qDEORL@L{59]΄LX1`]_nnHv{V:mۆ_xwY bbbzsB`]Ο?*l7n;hŢ q >N@jS]… ѵkWOR P`bbzf!11Q [ *Sor'p̙z шJJJ_hvgjr}\mO?Fo 6SƀԦF<*((Sd߷o_&u駟W^Qdf-P;TA3g{=#-- 3gč7ި_m۶0l, 0C$h=A̙3q!>|G{ X4nTT$`ƌOqydffbU-1kD.X555ɑ۶mѮ];Ů穧Bpm)Q0Qе^;v`ɒ%ٳ'֬Yɓ'`]7@ir* VUO & V(--rT.|.ՌڼS9:tȗl>|ؗh_|ѣ.2;vDLL)kqraԨQ(++?6m^qW_a FJfH@ꫀԍYJ`eddd(^8|0֮]^iB B>3|WXnI߈#/`ؼysʇWA.|HZVn߶ HQQ>3//;wΗlx[FϞ=qף{޽;&ow'6kUgMOOZ_u۷[l!C#GDff&f̘[6ض(eyum۶iq9An+vŋM6;k((dÆ _oM7݄m۶K/Scp!QjNt=l~O[ UTT%Faa! ]z &G޽;$ vvѾ&uݰJVSRRT 6 --͗|r f͚Z:$ zhnEW^馛t$Iر*3R7khiM8jTL@ү_?[.$3o߾;gtMklLbb" eJ֭q!SSS q Io8g)L<9`]vaߒ8{ls,8jX0vX]=wE\v{S:k!---hC ?ZQ2_gQL4 xZ ۷Ez%NVe4[dd$ѱcGaРA1bƏldee!==:uBJJiSp-&M}v߶:t(."Ů;z#Ģt\'99]v2t7GԩSQVVnݺaݺu/f]ߔY,;l>É$I߿?vڥAjFJA$8xw⭷ѣG}],__=/^TXW_}˒K.UHϞ=QUU'Oj})~0x`SN@L@b٘;w.ѻwo;<.99Y"""/Xٯ(** Cůb?^#%IfK4bbbI!#'zWf믿ƌ3p}0`6n(kVqb !!PXVdffC(,,Dyyf`Ihࢋ.F";wΝ;5iڵڶ훵m-:(Mgֺ}eZ#cN;˃7kZoֶ>)CW|m}j>& DDDDD& DDDDD& DDDDD΂#N~M/))]4i[ڶ훵m-ázj`5)׶=AB?)5kp-h}Ddo6nf/#GH-Fj`#/ХKDGGk}9Dd@NǎرcѺuk/'GHiFj`BDDDDD t"""""R """""R UTT@`=c 3X,?*m_ɓѵkWl6$''cؼym޽cƌAǎaAW_U`^{5X,+֦M`X~m߾]?Jj 6 ݻwW}7eʔz_˖-;v,]b̢̙}v; _~Av8~KGGGix5vbǎXd z5k`x<}PZZg}/XۥHOOǝwމ;֭wߍ̞=[:}4y\|(++Sݧz*wܧO]v-n6xXz5pa)ܹsqmBફBtt4X999Fff&VZ֭[7ϟ࣏>RÇLj#{vgQbСQ(X8z1iO?$wyo1cDVZ $Ibt:+BkkSN9qDq !IDAT5׈)S8o$I>PN:%bccŴiTo>6m$s*άY$IԩS~׿ IDIIbm;VtI\.߶JѮ];qe)֮ZGiG0~,e8qT 삥 6 >>_ogΜmTdhmڴ ^zԩS^Wrr|rB|py<裪ې_wyXV뺠͛7/fmȑ#?_Um50:扥0 wi2224SZZ;vR.|xԠ:6n܈GyD`ŸUiS"227n*}ƿ;য়~Bmbԩ(//WJKKcԨQԩmqHMMŽދSNp//{G>~^mQjQqq(qT "t=`{V|?7iӦpoxԩxW\xh"w}=m4{Q=d<1bZjÇcҥ9r$>S3FO>JO󑕕;wb̙X[NSvХK|78q߇ދ^xAѶJz<lٲ@QjQ|q7ƈЪ ѣG1~gΜ$ŋv-gϞUr]s$˗'Ν;7|#xaZ7}DTT8po۟gUƀSQQ!.ѿEѣ$I,[oK/$$I_5h *o+77WQFO?TlݺU<"99Y|͊oIC=$ E~~{EDD$I4hG`,qq4hhС"+++`{nn$Iꫪ]$zJk{衇jOVrѶm[1}tq9ɓE\\())_x@H$Nbm :TH$B$tRڮkϞ=B$ބ D>׭['$I_~/YD I$I9s$Il޼YѶ8z^捥b5P~~x<999}jqY?k̙^xpI(,,DAA.]VZyTVV"99zb7FC pjM] \4 ?J{D߾}}+iƌ(**Bnn.~|(..F\\㨾(X8 QcaI&UV}2dFW'|ǜ9sk}9`Z'voŦM|_~-Ǝ ݎM6aLEE6l؀d*]w`ƍ~?3ProcȐ!HOOW.]`޽p\~۽;w5DFF"==;vĩS~z/ATTm+qT_q0g,eU㨱pƍѣGcԩ(++Cnݰn:|Xf*O26n܈J}O0ъoƍ &`֭~?WrQiӦ!)) CEVPTT?of̘ڎˆ#\VÇWm*]vŠAGbҥohW\q&N9s@<9g\uUm룏>¹sT{j> *;> Zj۷cӧƏXG}#::999Xx1z'|Rv8MKGGG5e/0pڵQQQbߥK_F/X#GkbQ]!x{,VHNNb͚5ې)SxYhqqqj6mڈ뮻NرCp3gN:HѥK1{lU0z3Fǫ?|ƍ";;[$$$HѩS'1uTqYEŰaDbblgϞbܹ󊶫&Q捥FjPy"""""2-!"""""0!"""""0!"""""0!"""""0!"""""0!"""""0!j?磴4g#GDvvWܼy`X`Xz\s '"b, Q#|饗/jpU ۺu+6mڤz< lق5RK"plδ4 qYYYۣG@||<5"71ѱBԀyaƌK.Wv8~8, .]%KsΈȑ#qA\.̘1۷GLL J~z\z饈C||<ƍݻwtW]u>HOO'|x7ݎ~;k`سgO7DL@w`Æ غu+n݊$I Z_|9vڅ7xWÇ?z+֮]^z 7oƟgc-Zn  w}UUU6l"Ivލc޼y?֭[ӱvZ,[ ~!jjjpWx0n8Xb{^#G ڝ6RR"?3I/#Flߏ;&$IC oŊB$q7m1c$I !8q℈>~Ct1 Is"..N={ַ~$Ν; g I| B$|1bȐ/c)c)ǀ)`„ ~ٳgO:VZ/-܂~_|ѢB֭5jl6[mۢSNxꩧPSSaÆ_~Z-"0"Rvd~~>_~A^j7|'x .ٳgɓ'cɒ%HHHhuXJdLL@tDO>E]ڵ+VZ8z(}]}zKDXXJ%P>r "M͛7O<|eS !v1j(#''GBHY\@O"##z&MfߢDDZb,%R+ D<effѣGQRRF޽Um(KT.XDDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDD& DDDDDQ߆6IENDB`PyNN-0.10.0/doc/images/release_0.8b1_example.png000066400000000000000000006417341415343567000211260ustar00rootroot00000000000000PNG  IHDRF#3 AiCCPICC ProfileH wTSϽ7" %z ;HQIP&vDF)VdTG"cE b PQDE݌k 5ޚYg}׺PtX4X\XffGD=HƳ.d,P&s"7C$ E6<~&S2)212 "įl+ɘ&Y4Pޚ%ᣌ\%g|eTI(L0_&l2E9r9hxgIbטifSb1+MxL 0oE%YmhYh~S=zU&ϞAYl/$ZUm@O ޜl^ ' lsk.+7oʿ9V;?#I3eE妧KD d9i,UQ h A1vjpԁzN6p\W p G@ K0ށiABZyCAP8C@&*CP=#t] 4}a ٰ;GDxJ>,_“@FXDBX$!k"EHqaYbVabJ0՘cVL6f3bձX'?v 6-V``[a;p~\2n5׌ &x*sb|! ߏƿ' Zk! $l$T4QOt"y\b)AI&NI$R$)TIj"]&=&!:dGrY@^O$ _%?P(&OJEBN9J@y@yCR nXZOD}J}/G3ɭk{%Oחw_.'_!JQ@SVF=IEbbbb5Q%O@%!BӥyҸM:e0G7ӓ e%e[(R0`3R46i^)*n*|"fLUo՝mO0j&jajj.ϧwϝ_4갺zj=U45nɚ4ǴhZ ZZ^0Tf%9->ݫ=cXgN].[7A\SwBOK/X/_Q>QG[ `Aaac#*Z;8cq>[&IIMST`ϴ kh&45ǢYYF֠9<|y+ =X_,,S-,Y)YXmĚk]c}džjcΦ浭-v};]N"&1=xtv(}'{'IߝY) Σ -rqr.d._xpUەZM׍vm=+KGǔ ^WWbj>:>>>v}/avO8 FV> 2 u/_$\BCv< 5 ]s.,4&yUx~xw-bEDCĻHGKwFGEGME{EEKX,YFZ ={$vrK .3\rϮ_Yq*©L_wד+]eD]cIIIOAu_䩔)3ѩiB%a+]3='/40CiU@ёL(sYfLH$%Y jgGeQn~5f5wugv5k֮\۹Nw]m mHFˍenQQ`hBBQ-[lllfjۗ"^bO%ܒY}WwvwXbY^Ю]WVa[q`id2JjGէ{׿m>PkAma꺿g_DHGGu;776ƱqoC{P38!9 ҝˁ^r۽Ug9];}}_~imp㭎}]/}.{^=}^?z8hc' O*?f`ϳgC/Oϩ+FFGGόzˌㅿ)ѫ~wgbk?Jި9mdwi獵ޫ?cǑOO?w| x&mf2:Y~ pHYsgR@IDATxUK@NDEJ@DT[+vww(X` ݽ=;wv޹{NΜ9OĒÁ[},,,,,,,,,,zX,,,,,,,,,,,`dˁV0:+grrrrrrrrrrG2rrrrrrrrrr F89` },,,,,,,,,,zX,,,,,,,,,,,`dˁV0:+grrrrrrrrrrG2rrrrrrrrrr F89` },,,,,,,,,,zX,,,,,,,,,,,`dˁV02d,XoժU9ZߤCRT)VtMu ˁ@:cIݺun\raǓ'ON8A7o./,^X{9={|7ae@q FqGrGF}JŊeԨQRti]6##C k׮ۋJ'333eͲw^6m$?'K(E]?cz@s FqK.Cz9s愵4m4ٳguQa)"-[I&yJ#.A0*[ү_?i׮_RfM7UZUƌ>x߾} *T=i9`9`9`9`9`9`9`9`9y5լeӅjeΝ~ŋrg}gNݻq'O-F#FzȮ]B嶑SE-/Z>hJ{/޽[k]q׌F 7,F4Mwob Doo^WSse㎍ -[ )"J g4~=/<+s#u-,\(J*>Jd|,“e4$}KϦn]rֳEZ7lԂQ*U4J\2?o<-Zh!  JYgJKotye;v*lAܦ/o)X v%- HFieIi'7/=o-uOk+ʥyNڻ/yLc~gM^8s VC;^=Q9E^|@  nϦ{vX碅&8[czjzP9ӕ'7ErݧwD;LJ{="H~A.^iU=MV{V;5eqSִ67N:)u'xe˖嘆w'gR|m6;jn*]~_{\wD=_%NdqU F"vl1xGeC=OU_)|Bnhz䇔sŐO;n~jnux J;4Hdڃ 12LȷA+=/W B9 T0oAud#e2wKa\qa\q%7ظ1T(.y,G q7{;]N҅#Q][nvBBW'@N:餮K,v5tYcҴRS)ʩL|ҮVmҦF]-8v*e:Ky?`qq!m1lW8.龟K'$UkgKÊ zXeVyDWqCĎ.#Gci~?D~E[AL`e*ꧾ\ANQHuSO%&uk<޵Oe瞋u C! 67Sޖ:=?#mkëR` "MNFemۥ!-e̜1Ry5wc3###\jg+ ;S3d*0L店y 7d* gYIeYRdtc3?Y1ǨiL`9#3[hO3u.*V񼖟O*MFjΔw2aܾ{{V{?p@̜i'MC [7Ow1<+Atŋ3w:3۲G4~ffҙ+Vd;欝R";63n̮/ǽͿ;Fޑy7ìm2r\6B/rۗ9n\֣׶ݘ{16~z =_^y啰"XG"4+*_a2>OҖ+-j/-K&f4DGvʩItX+w99~|17:7-Fk%DN O~CBnɈ#Rk{#gŲe!$/gK*Y&?2s&@~Ѡ[<{Q(P<{$FD9uOg_ZC{̃-^k!Gqw0|=CE8sa49PޚZ]w cΖa ӱ=ax7,{^=VCN_:ޘuK_e \٦PeRX\ܔnZ9汁΂vEҨb#]B MG0JCyFz!_KכG,,Ekq(\BC+T0;w07Bj8/ioҊIҪjnaRP M K CQ 1"1EC歟'V#gTQSVNΜntb͵`dقvˈ[AiͲe"`t7{Ӛj/׵D+q]Kt܉ǒѰ|6DNk)DsE }{(`QH[@#>Ab/JgI)(`^J:麔Ҡ>4.[`Ykwj/sLvVc, ψ.D~t1 ]=0⅋K K{4") n<@ndϘTjJ&{r s FKoZ/2 p!•@/T/3E9>OD(o\仿tR9scνG21%rS02 TGmO}1;)ebi)ֶ)+'1F\ǕCZDtʋsxfP#Ec0<4ʠȜ[B="|"ǡf-1sUZjD Y>}t#׬9mYv+u~|?.!!snRRc`D yX^s+ӯOǂԸҁH?NRϢ<(yr2gݜ,M!p,ӣAAH B7-u# R?:3sҏ?®"qPGO,8u(p񴑬:d_y3b尿噮+'b]J%@ňs^dr- ,Fh]zihlߥrӹ@7$g:ōӯQAwdcPADN9EA~;6 p!U/xfKd0"pMR%`Hykb^a|?0}?GK N) j,F߶z2ؒ+(ddyF ~`@u[B^tU=as$+Q N$pupxߋxוJB' @ᠽZjCʎ{Qd%Qhi!8T d; I] ,%n+賯.:I+ >c*W. DPi|R27(r"oR8ۍo"I/Tg~]C+~*- 8bjgDܟAV,NO45}24b]-/ 9 7cd-EגMK4X5|[{-so߾mi'f*򹚔 -|яv(_J*)ZhzZb>)a|C0$1\XDV.Y9@?дӲa4:E LA&U0oN;wF0Jv B"ntZ_?='~}B_(aq"r!Qg ;샑#E@8 JYp*,4P9}}˗DK<  !AA*(}n<~A[PM`Y CO_ɮA(#dX??%Xr[~.=]jQAX7:$VMyoPqn`aÆ\g\RD(J~"GVyI޽f) Q!+nEBi Jg~?,qZ fj60FWR߳ \pw?ys){9E/r/xބ: r@_Ý*lSz7i/7+ұc(fN#t3gs?>׊`E |kW: z.B)ޮ]*++P"3ês7߰љf by2V+e\Cpuz^{.)_Si"jݍgt5~suH1X'y1o$b+ x77`q$k5u?[uߌ%P ;, (#XꫡAL6k1ںk=9,mknhӁ6@Atъ}݉,`iŦM6AE,;,۷QiDe1#z'W]%hH4G4gkE3TؘK-*[K 7U',hvۯl<3"1G7OtdHE Ht-9$L Tf,7>v{YyRiM/K. G*2m4eHO7r+nViLfTVnۯ_?yu\i*aPxt});ed-)J'* nerF3Xz"Ѝo.\[+bcA1FJHfy/baϤdQhͬ392=rZ:V)AD|c~IYSE F/]Vl(M~MXA5V< @nLyw):^ hF/-:;bm#˫u+ePc&?q :q, VkZ[W\jAb?&IY0b!;!~7vo4#mlݵMnv]7Q")vz!J G%矉_}ie沱hdS!XbƕkwvbxpM&B< +C`{֤ UUb}sVKOv!_:uSr9(SO 09xpFGT a͋VI˖-v2ELv^Xw` S>&߸ҙ$aŖZSܠV*^B0ml%`wGɎx`R`@YdYv,{4~h1_D^e=` d\8c@nEAI:+7;Qn/:"plbtX Ucn^x.N^ U?"\֫CSY;(@:5"4~Ǟ8%׌ۗ|a8 %WN2F )/2uY VHݸWώfOS;%-dٞ]#ظ1| ;.\p\^2cKg"7,9n2eA 2邏NJL1QHiӦM\Mp1;t?@Spj]RJ9i9n|,`hǞ̏ϔ+_T.UYCs3h$VJ?k]ݰ~]4.E_1Ks#獔eͰZX~,x#sVQ@)X(߽p<;,:+Vt͍X-Fpk#?A~# LXrьjF5/8i]q~/}~$ "qXjWm]%{ " ?Ϝ9gI[q,xǯh*L0J72H} hM@龹KK| 1VvǸ쿠ϔߖ!QZHVyW $ٳ"#fHE1Bw67#Pތo% IXP<`43/G$q1F`f&M^ʷsˏ<ǤᬃՈXJ2,hܱ>-A:-.}[^,wo8A*&P9jaE|3P%!T/qND0R!YP;a\ٰ gJ}ˆEe F2ylر!DTB57xOitƧdOEdBsV'Y(c#ďx9" YfWT}^ E,sK0j{y7}63y6A{u~^X[ NM%-2J}9xA{i1B04ߚF21c~_ Wx%\?-}]oYk#XY'Xs^0]i*F`PS@~3g0[\ݪW㕜*Tc#soLΌ.ӿE_-q< F¯G7s#m*yAK0"eФAioiW)g}ӱ uuYN{1e/Rdq7|#'cP'I>c3I/(d⿼: Ae 8Xg.DBI'К`/"g)[dݱxf3 %*JŒC'h[nr+`W0Sdٶ&sayu«2`黛ol1X `b-`#Y@rsgQ-?K%aF7"m(xBt˫5s^h`qc`~=rV l',*{VY[*=]ͻߝfeM _|]n*Z!/68Wi,pε&&{xNj& :T8i:Z;x.oo8@ym1 4<.B=p?䤻TV~Mn ~ ՇW^T*^[bpx#QK.SH( %*A;5aCp@{8oߡdƷ zdiHa}@˂Ӑv}X`Σ#jP;/__h6[\~izbGWekֳ@D;,n 欮Xd8ppYG@6{L&@\H+y^p5蘒JQcJ/T6s<}Z wJ L@4 /GNӎu-rqQO\^H(G MܫI2Fryrr>n-n]h3RTSnvz ͻ8"zG6mS,bXXNfdvkJx:/$} X a1B0r#h1rJm݉E02uaQzWҢI1O. =9cx}c& dXe0?\ 5<td-,FK-LTmYT:se:ɰ;91\RZsSt阈eX7"&Vb A*6x@xěj1NB)JI B,[Y4},(EXnt:NXYE$ŔUpĺLJB0q}.SAVqVixmxO ~] r4-y]}pնV[ngsoC1_ە9.Q)+~>y2sO'3>Z{a1B0"qN%%=(s@ŌҨH<`w춞倛V0rs$nKJ%+DhRM⼰n,FN鳎ѹsj A/jҸu%s,¼ ̸,c K7-WyXfK0`-B0*]:޼,F4Z]Ă"Fp5w9!W]Ym-8W.V-ą]pwS %d,>B^3w\JB0µ6l,%v^^л{Or?sgaqUύ׫ osFhȐ/Q2V.#"Gߏ+]<#bI8#~)B7Ґ;2nB.7+orrk{A(<0"р%ߘAF%ٕ]{ X/ܫ/\#`d`y.BjܐS:+y9xP`[vƿrWJNu:I\)Pm_IS!J*f,Ӌ0~~+ 7aXr%/-UX+Nz\J5byijߝD>uyy)}m1RVcO֐A!٪!ypD#_y"K3ӹ-&4x[tR5e,Yr~\JpHn?7&7|{C5>?bpq.޸X)9o%=Jɹmi9;4Z~ 9㡜e#2M#W6RymܜRf{ Hӫ7n.DPjƆ,oLP@Hcp>_X #l#f-l `N`%eqn5y͹_nWsZk!%_\hAљ e'P`8腣7XI~{L3rEN>`N0*V7K^H«DY(QQ{vꄁ5xc5zu«:#Cd JUÚ- ԞH|flɰ FKm`Ɣ-ZD{F ~ǂJG$.uOTپD Fγ#j\Y:*y״&nԋ^U͛ K'hͷD[g*W^ݤŹ[g(˂` $'] bDLN$5}PB &h(cPCPpܑjA#A,F<OK2w;L-* Aq,[xvR& m<][JL|m ,t"@x F:ݑWCi"PSn.|<,FSHx*?cKV0ʧ?nShh"0'M2ˍkbyXp%֭މ=`NEh$%\DkyXBOu9%cnVv–HyNY$_R"x]p1Gg7?[>SGݿjqSRhw}kU|۝H&ܪU.owɂ3v rAʀگAh^,KHD!# ]+[*!l}M(n1z"^tXʗ ԕnrOnkǿfv^Cx񫯔`TMYg[36r,tGygnQ9J s˒E`a1򚫒ҡO#YQ>蜷sYd_~,uЦFb|0bM0hHa78@ $7V$ct0ԭPWiF\iKB0Zuh$kG$ݣ8,X;DMLÊ uR]4z4m4S`|f/>ՠLaU(4jP͆c܊1#@XMϏ{^hF$j"cZixk8sAQ` F[X^qvw [hrj~P|@!9Ry@ rZp_nO˔1Gѷvl{wJRJLCڐk\\ iTvB'& T>k^d&XhrZ=w]z^AƂ(\Y9`7ܤ.hqTz^Ţ`O=U%TWf jeU`p`w tF0ruiΖ)i @1$>%A7E^{-$H0^f]*Br6P 9a]E$Үt%QN$ .Vj5G]#WQ EX#V]wuįOsjeZؾdn%,.1#\2sw[nQP?@rk\yJ%@0ڀ7iO$Dz]r|r}%RzVQz.QGG3ޕKTnG;&N̋KK~s]P0v2Z^*a!W޽CrT<Wu#Ո9GňO<!8Yd,FA&nHp$}>1n^nnβQ^~9 ) Op~͓Wf Fb-鷸>/}?3df]-XBWOsᎈkgKA Emج;J SՖ_)A4wvmLy@IDATB[etX1L~wyМ?xWl7:oI%T/W<|B +ep{F 3YH  yPJ wrSuW}AN.Yuxnj#&:nt1gbc.­3(H.#kM(&+έ'OocV`mXu;u\K￯ُEM-OJ}wx4 ςI.Ոg -奏&kk$|DJ:kг#m*5ՔƯ4jk%IםԤL=LJ΂ n<#$ K7%r"G5:*](O>W0K}sUMl2M)>R^:c +w}r-*L`tM^|EׯRd<} LVF*A/{yRQ>X̭]Jpsg7-uL2RZU梨k[˞JbFW *` oMcOqFYk4THׅqu)c?<B>B DJ$[wm1E~N:٬QbdP;9;}{~]s&)8Gy7u2OnFUo;;](!RJiմc,F"O]w:D[f} ?/ 1ȱ/~wMy>GŻWHXJn$͍د[?YyJm-"K~PneZ+˶:b=Bz ~>^4&5qFaq2d%;餓#ys?\8guk.?TЪdOq IݵQRٙ{iQ ƽI"F ;-?,/7ߜįr|8# ~c;`p+<4^~e  ye!B99-FA252<_JFqxX a)yGA%5_qS&~b'[S,;GG0"WY* zsO<F۠1h6ІrG]cH:hκ9Y:Th!5j1UI w:%ш C?!\LoPBgsH}i]dvଋV(j`4{v(vΣU0E>LD)E5K;w"3VϐbKQ522c<&wm*ǢB)xv7='7Yp#>M;_hK,w܄kqKiKƙMz^0}&_=U-pu5ٻ 1N&Q qƓY4F*f6{V {?zRaM ҕ+V.%%A3$V(TAwc@yn+&=(wK/wr+=ǔsÓ#/#/RdL: v^bg;FH<qcǹ$G*-t5#RY1oCۆg(*fx%';Vy64w.]as@C:h"/h.&wQ&'8[p3#tqcL~ Wxc~!ZX#j'۝pW'!/ FTHt_Z/coJb}S[p~Ȑ;ƻ.<Wbb^⯬rAvԷYR!鹿hf}XkoN}?y'3\Q_}#.ӛY=&.ψ IЂS{P`c}R>\t]Y\^6;&V:\O̻ }Tͱ j1"nAƸQ9>уR3= Y{;N$d={ꯁ*,oV_HɃUխB0 >jϼB.=Hˠ7v^}^0Zz()//g7)Dw $F` r]77 ᧝lIlϰQ%y-ysm:zrm)ÕunW=s<2 +0JE@ +_>-AO;7!auU{l11!fN78)(a1r9D}*4*q D4ᄆ1h ~*l:7+S0B0da) eIK,}ݏ] L\>QNm8Vv<14l>!`z)6Ta>u VYL~qFcܤ-bZy૯VX/ڬapp g Z?i!DѲh(5zIpxf}J"?X/.9AܪT)SJne>^$ެsFUVPhCQ܊-|HgxlNitrGgȇg}J $"n^Ŗ* '(:!BK0" m~*g ;JsCa@~!6&;)w*pE]ddd6 \`^{Pi:Ia"xG~$ƇG+ -1HQlxq|^\aycce(**b={v]$+n NW0"#J7+u~"q ck/~B%-'c1c΋ūWpx,hROSm,MKA S#@âb8 Å$E%V%` w|~ ў >z]iNύ܂ ~h;#vK:6 YL3E߰r\ B5.QK7/Wv܏:4 s_yJ%_gUZqaXJ ;X`.?r풗ww +]+Z_cD';QFQlh(4Tqj^3ΈU+Aɺx}/8\(+nVxу V"?Z<{=pK'+wqVc )7PYYw:*Kv@땪W~В!/'EHJSk\W=amb!`\8bmvXbt3#!!;u=bEC]R;<=AQ`!FkbзB[J\rRhyj`dH]a1BH%6ü$ܱX\sry+ U~ltR9 2 &c¢G׌{bl3bP:v Ƀ]C*:Fƒ˞J"\q39A]Bo%*Aw/:|^r9&@_24mv`4& S7sA&^]ZB4 +/.c,F^hcYxs<_^Z~?m> F],-ޥDK-W\ƕ` ~%t˗}unзoHy1 1vo]tY9kUK i r:pP\jYP\.s&i'MQ` -Y.=yƘG܄υ]y )u ȸY9) 68{mͶz]wbtDHg D~*~+ *T{!QXDCDܱfT]Pِ7Փ(EYx109ovK0ZBsUvngKK}]~{)~F*[qF@ VK3`,? NxU`Bޭ~7ypԃCᎍMU4zPb]ҁXh 'CzYiϊGX"ERdm9U-b&z{f,~[,Oɢ#I<{"?/7' A `! R'Z,y3A12 u] (qSrYp8GǪ5dHH+K. D~84ǀڍƣnԂ=?eu ]:>!\`y1&cA$2sMCtAֆ"b o@>.¸c!RXQS`0h}xa4$bk߀wgg^YN&mС,7]jh pL'b^fr_lX_!!ܹ Z HRdRB.qEZ6u 0~]lS8U(xC\&s”XbRx1ȼJ9$^B Q~tAirG7̽#}O:$9cl`3T3Rj+oJ<.WV(p0@$bqeP1Oɋ'(VrrFj.뚆ޏLuVg%/\7s5;̿Xj!N[>j"E4UJWQrAT@Λb27xG*1nF c,A(3AΣfHL{kN@"n":ֶ~?3-܃0 )cq6(@~Q4JeoXx+G>t93qW:p%6~]7NUU+ hO|Zv?[\%IKdOkyʐ-"!/b[Nh]5䫉c,zԠ ziԋ Do qYJ9ڤgEF")Yb$sS yBҷojbg`!lbn>>{ֱISNuQ;32<WF.}>hZN,jijVBw`kdeC\}?'mkz&W?c7"kV'`@#PB}8873k,Xb] F\'ĝ.h'n)ƁWHDr؟r1 {`I:v;0~jT|d>7 !NaNd^S&ޭy⩏+gnǸqe7QBd~q#z/t g(PoqNx]+*QkdͲ  *UI!`}vfB\iI|%ɺuVL$JA>cG(LDÓLY 1~?cm\$%vI*˹dv&yNBY*+tkVa^|G{)(8U d0 -@!v 2y|NdmEbK͍=u(j<(e,F5B{k`=4ڜd.sf~_RsJoiǝe>kk/lhV}D9Uѝ]2T˗/OW&m\D?$vmD$^(jvlrh@,`dj;կ„FB BP&OK~Xm5 ̶0_ziT)6h#(kՏ"qQgѦEҶY~关H^рPͅP~t"`ųEPZ3|:8IB%r9wZ(ʍ,e G?X*AXؘdX锗tO?l@<œ@bR~%࿗oY.k).%ȍ@Mant~$NO489t1囗.a5?'n7P/r#i2ߐ 0 n2q}`ta(GS {(uqE9BhNy:K#[U6]Ϲ't׸.5/"-&[WbAK.qr6VZ4&FOsjE*klSѸu"=$ ^~X"8D1V2zN}Wz4!7VRsD?t$(PCˮj#'#ٿMk++I0I񴝟DH5xpOtD"K dc$˛w-;o{ZKNlYtsbkTV+T+իWg|ALVs8vZ GkQ> n\PY=x8u]+3uLJs`N 3mAiV%hawǔY_#(6}F0 3y4mX1bdJ'+LWݝ|#ܧx@}= s:wD!й+R<227ifŵ9Aٽ4{?϶U%x^teh.b_rW;^jBkSl=,^w@=:աC+Y"mmlל%F^c !=n—灸z2`(qu/g*LFB`W3HYuRHoU-iL &R7bO)ITeҤ µaf(vXlBfC5X9i5 hQܿ7kgE!( H9.3̹ Fbc4+sb137cKj{ 8%8"ءygƩkWml9=vƞt{`B[\_>^u>Kak(]sFMX ' qpzdD?3 yln͠ kD4zMtW'>Iny5[y6ی0{֭r@ZL"1~ulԫ_dA볻7d<bNn"a ǥpFȷǢjٟjk9xD1X¡{0%KJZ,I;Glʡ $' q=c i} }eAv;(~~g >gN87,vMi Oʼ "#`@>J}+g5X7:l23]>$1=L6 @髒96c~Rb.L`5.|4r_.P#FCfw g>}̶)zoE_ 9xZ@ 'MUh<rȩѶwy&ib N{%st PS"ロ~_|{f1OV/*`"ATD, ͂ L2QQ ꫙giY0bKYl[k-/TINs2eþP{I0_rZ懑.(,Cc-0( Z[^>[@ `|Y͜vQk[+-bi&pu0aD* 7Fz29+? t^  *NV:jQzv .XC/kܧk> z'&#Ё9Ox T[G!COmLvLO?50vh΁Րs.B؝ne1m9Ķ4}3N&FFqXL}QVf.16f~_7 nrE- QԦX|#ڭn|Ǐ={i沙w xH01q,Bh~/n)#!. c`O¢Fixt2g3c"]nliyѾ9wqL>fARcg͙ H5TXXhwU7pEf l"G crcj6.joLeNcE-[x\EMDcKp$ޠ|OBJ F[Җb1®FCWV%=(0:?A xfMع5źQGTBY3H+qf.{##"n&ar l7b12X~57.svǪ1i;ʅRVCWO`::nqDgeOel $eu֬Bݱuy9ŋ3u,>lw :4O3hEJ0#xW^*ҙLHjQ ;Ϙ^01oذԌv㉞|2b X3D2yd >EV-Jwg; *aqw#0h WRA[5M%ۊ /,,ߙX i ;pI"XNchD}wuitORڵﮄ6$<`JU/,6ZE{g|xUW4jhqr^a ۩o_m64>2/ q4WC *j0:G9 uda$L+lZ S]kNdj`z~}gXltpgTN|WzJna~!Z &,۷wy^ ^wU߸.~bZy-];ZtQA0(nX}CU`4aݎӬO !(!{Y |v!@wr־&B0әvcL"$T;΂nPBy_oخ9$C)kW0,.ajUhZbs bl|È;(fuY#Ѧ-8ۖvu velaJ)#m\O ҇N8XL-(>F`0| D2r)}:ۅ1߃^Ƽ^09u]gz%<(s^ +B߂]{r늱v~V.pʁ%+z Zʮ..eS{(["L?|ZlV=ɇž5k`C`|UQ|ST;BRg3nt=K{ҜUs}Gv.l3u]c: h`B-Gv0;U"fnFvM),*|#`.BIhfe[kU},덭`s1.(6 J0C̣r-?YB6ʦMX9]߾&98L`84^#Jtl^cg=ї=@8BpK  S Lcq,{v5"$|QO9{C)U^G?Dq?"4/XFK^^Gɐ흾M?;gPaٴg?6olt0R)9x} 93ݓs4 Fbi 54H?a%,qǥh4{چFr kcqdhc`vA1qgIW̭S-b1`'*=s!@"C %I?x,ndu)G>r$UUsgv:?" 5*/달Hc%_cX/@cn ɺ4F`-qJ2vt1(G|S;KޡWJ?Tfp+aǎ>!ϴ.`nwf!e&ֺ6LoWI "aYY'ւi?SakrxD5~ U{D?a#`d 5.}v!02 &v5?x!߸n?vѿOea,a BJ{GE3u \ H9 }t&`JAzUiO?b64 ?թk{᮳gOZ7pFCղMu5WopAwnʊ5 4J<CG{/hڛj#W3 uZ^>3eYc"^sf\G|?>t):iIhU2\cQsͿ?>Z~ͧ`zV=B {DaR"Zi郠uIl'8ػѰO/#/zq]5Ѣ6}v& ;,%I2jӧ sr Wq{Uy+}sw?kow)[VPM ;ydS#;%qdc,3 ­G 2>iVs%cdQ9K(@ 1CHOեIB~͌M} =3K[oTmߪ6Y@z>FJ0xÄyL ?ѯ3] `Cr2C(l ;@S3#-G{ }> &vrQ8$و[oƠ\qmj"BǞ&$/B!>M詫e"g?b粭'+͍*배ҒYwCKs1pR%kM@Uŏ$a[4F&Wed*ӟs{,bJT'L  HN TMnr+M/PW ïdƥ3[D& ,0ȕʈA(ꞽ/[մ40S6oLU[͎n#GA3Npw;ьڌftA<QB>ق_٬p9<˕`d O_s{WPR F]remy%+TymLVhN/ n?6/Œ&EC@9d/X47}tG;L:0zE-6J؅g3#e.0+qǣLJwc%:HY~!Xp:e‰6>O w_3Fx[m4FlQɯre|@;f/ftZǎ9.t7 bb7;EIx89>sտ㏭`G FDgMd) ? MQӯ/2n L>])6`<ӱnHtD'2 B޽+v37rzG\/u/Xiws]cwGw%CAՑB Bi]ƻ&ͧQ=GL ՔNO#H'w\"V0z7Dse˦t# twIYw羟Z1xYa4V#;O"cM7պD,F4vFd^RwD81f;>C0huhsݔNLE]kJ='8|!b#2z*{ml5FFD?zzZѓO>i,A(S;PչӢJʺn:)t; 1vz҇f$s3eڴih˲G̢HsνwSEN]DguȰoUQtA*y9X8&!sU=_ڄOAshq(Pv L>>._?Fᣆ^?裴,'6f")TYY!^cw"_X/Мmz؂iK{ =b腋IW螣VaZ~ŋƏq v誫2 )MqK>F@>uugǎҎ4"YD\tj1B%%t#oa5"gE&NMtQmmJ_0b}GEs&э7Hv3y-twNdCPDV4AC0Y{ꆏZe#uJz~}CWȨ$?&38KvTxADD79!+3,:n; J:u#7:SW1!:h醥?^g=i|.^5!bJ?@cC2Է~,I=K{s8/ #cv1GBI}3ϣ'tw&aQPY)m"XEPو"_}'gy#}BٟmO>!ziD 2YT~1!JB"HW^y|I'E]bGr r!kC $]H^Ƙƨ+6}g>]O̱{]=>p QЗ1's2 1 (4FIT;y~L]%4*&O<FJ0 )]J`hfeJgYVPGQ} mV#c FA՟z7blq~YC#|EʬS.U0`Ε1 ҔNU}QkaJC$T_~>='.(tao S-?#7P;!^L,~gԧ`g'b13޹#:X뫠1:,hx;É@TL-o੫"['M llJ'Fak6 ܔIuVcT\eS:,C܃rlVRȜe hh|{ 'jk>zė57n$:L@b+#klD:C(N\{<moϓF͞n),^];3]D.^&zm]іUWWss>̡V P%8o\6chZ}#ဗ[C<@rmlUc#DV<_Ou;:>¼=/0fw'LDGibGbHAI[_RS'!K n񳚣E[w3N}MxHF܏c+k6:$D8X*btP<,ZZiPՠxt(^0H4 ]׈oK E0)`,΀w}$H摌 FAٷM1H,|(S:T&Oҿ]h~lw+Slt+nj [!v>U9-B9>\~: 6iXlz^ĪSk53nê,J?9Wc$: hd#'3JԻ7(! KΕ`Df? IlwM+i@{YG {d6oFF:IT9.҂RUhU&PK)tjJ'5k2,tghiKhGW`ܾSBDFVMMMTlklҿ#`coѐ!=_P̥Sa~!*%}jt#s'M/ֽHV@kF7e!/2e7S"A D)F||Ϊ9Կ?߸h[`4}Ntj*--F3gΤ'v@Si`9E!h\,' F,2O ^p+ iXvZ:#ĝOݏZ[)l"uuAsZq\PD@/XBNEG{pEiJ(쀹0{3k裏>ꜞ9ûP\]wݕ.2i?H)y>ZVmr뙻1-f3;F#jW)l{gK ;8p iԷ<[jB"~칧s*yG@0򎝺R.Sn/kJ9D Ea\ FX FoN{k#"me)aq/h.i) (SN0Edn 3^~"FYI F~%FJ􋢺^Qڑo:c5VٳSM 4F:2-;Oho ڹ'RiHrI0&3pƼuhhP*^”c fH1()W-M L߉̪Ǚ[kʕH'+sD/ةkpcgsν/Ƅܫ)zה8k$Cf ]nަM׿5ꫴٓutttv/ꅰȁS(ٙ`E1=\%5&7&fxp#5FX FUMg$lgo-FT ۔%pˈB0< eƌtg2擾Yf;iS( t_׹bK_k~b4g:#3(Qlc J]=1zO 7cOEv5,ݘ|| dJ9V@4FpaDoͤi.*nZZ2fRPtС5%!=ҤIrot#N)6\FԒ Y;ms8MwXc|#yi]T]#z/|G }ʹag}VQʭ~T+61f?eJDG4FI2sLj'_5FTn97.+Nq] $HuԣGnGڵcJhdh?Y =.{=I7z|@ GT1!*@žS/8(: yg`s2ˬT[GiZ4Ff਼PV ޽Cm6`JW]\Z{ZCd[i}K:`P0pm\IL`t 75\C蚋I{dlӎ4c+V64R:ڽjWH F8;@aoRiKp#ӓ*3 /^TpUڸ,<}Yݴ ˕p =uLZ[uRǶ|M/zmk\(*)`LM׭ϭJuuuxJ)o4)OI11 oE>Vɨk^NZ>X7JDľ+`Bh"mةL>akhUX Kv:Aѳ#ꣴbhRE渨EhSEfNъ1 ʢWᇉvW9?jʼn1:zwVōKCĪ;4F_t̘cQIN/H~4^|8@ڨ7,ш)ۯre3œƝdQA3U(F:To@0J 0-GZ?5cF>~X<~7+!]{)_|XN #KB0&sQOhz?>{냷ҧ384M*l_;&Tvx=`8(>j%H.6@0{o%+F4m'Ek]Vc׳w}7]qT_#Ku2l[@JȫsdL6&u2bJܺ?z(7`~fAssO 0Ovӳnǧsi -aˋYbC@y Si.*"'41&q\6Lw\'|Brsŋ'%KC=t5T#?jdV0O7b݋4v"Kmܘt[6NwrmY옝rJ&h 4Fa TP؎$'EhDcL-\SWU&Iwco#J: $҉b҉U㏉vۍ 뙳jNhڢd?b糉M>2T\\9:~HA#?M视}~t裃k A5X;̫Ǥ&Qb9&\񫶼6A3mWh/Q=FE4F)Ĕ AɟHFh6~z.QjP̯6~*b+W쒟 %əUh0za tHFÖ ?cFG4w\:Wb!s^׼zRݹ&i@FcAu,Ҭ1ʔa5HYD*I`TTTD]wZ#F S\!_ܔN75=t/ F2ݗ^":h`ԫW4ǰU@oh{F+pbK|I1]͞N> c·1򂋼ъՔc믿l9.2wә\(h5[|W[?g<6[)ݺy4h tK2zXYiH)~z %l./WXRtlihd"QK 3{qI?c ^2W#8 tj!r-P3 %Ҟ.d,f ތJ<.Ֆ-ɐ!%RR 0vl4!PmǶ* kՍ`PTQR eتG,mJD'iJSPY}vC D:v楿8"=qe?ﺋ꫍OP`# hۦLvՑEH: h e\ [V^*OxGIԑ\F0Rftr&U-djLR-.$S:NDyqSLF*8`4uTLBee;4lذłq&N$bxrW3:kPBNX3Q<~:ﱊM'.LΆ%ւѵ^ˡO9*..^yiٌF*dЅ;<#θZ< y-c#*(p*3gfh*8 ,FTc"4F ۖ[[RB #/>Z7Pe 1'=Jc)f1E|A-*)!UdBya9u68={݌uEƹrJNm6׿{Qf1'fiwftN7t7/h)Lr7n6Zd !&ӕT?xj*-g"[A-( y (llݨ#A0dՂ6Eu*TRmF=IJN0];fc^",'L3f$[h`܆t=cԻwxxbN>m4.W^I={7x3~wy<.afwyϹU~!!M ^ǔ}ef͗sJc9@[ǣNĔfӖM'!*@rWsM+U2x.0{v 6C? !>c kTm[t;ֲnؙDF?PF\0C 엉s^JKKAR .,!h&r( C vE-[]uUĶ\# wӚkRK`kEjݠTԔn^)/tn@Dϡ>e},ϫ Y$U0.OkTm:i׿ve5EE\>aJ7׀W_놿_V7ߐOuuuT[R֧OmfB!& ^r~xr]:‚BXuNLU4)] NDc~1BNUhz.(BD D1 C@0:h^-`Ϝ1*R "͠isi.( )7Jcd 5L`kJ':' "1W+g7 DF>#95E]Do}[,(HԩS9M7D> ?#&M~_K'O&?/~.M2?@J0jTfn3JwP4w)݆< A Fnp eOQ4"2ZߕHC" 4q eJgDE} $ FN.XG?H}!kL)(o6懲ѤI!pO?ͅ{(!#<҅av.z'?ݹ( PUE ݚELmh̓ʁҺ H6)V:qPu+#q\J|A Fvsa"f1A/jmcR"Nm t@1A Npȩ`U(i6(o jyWQEcH6g>y(UwK[ -۸ ϮX}sLzv}@!idh̞yEH#NaO)1CO>dlq.'TFb F9dJ'ri[@~K.EInLW ΰjjtNW,ZNuyܟ(g>1<%U0+\8SiѸq:;~QܯRFqwlڴi @c㡌seT:ў=cjUƚ0,5mm"ee!I Ru#Y, UULՠ@R#D@w=$ramSST@"{9g}F9^Yp8.s `B[0r| SNEC0/BuK]۠ j`F'-Ľ^Ϳi8PKiUnB]Nkhijb9y>ŋsrrFl0IiG2NluA! i 7.*3:H"jH`d۴ew˧܂+z56 ln5ֶVgŸn:TۣdtVԾ6lF:/Aݬw n5FV/ =?QaX|A FYf}qQ YЩ/#g96GXԔ%n=n~^@#1(/Uxck# õ_{x0k"Mۿx”GIAb:Wж4{KJqd1S#wؚV#3TT^T.Ac;) >Б+3:k`d:0Qh4V- +ͦtn|"*W*}My/hF'UQ1jˣvu-]C)3:k`d:0 p#&Ջl޺oɝ+/WQy"LO4Fʔ²حj`d\|uB _4FL̞*`Jå.z:VQTȫviRA EP7Ƿ!lQCl+[GԳW#15LEi"_ֶ-N*d! dRo1R? ?`d7NeJg?|M:ye˖Q=C[oُP%54.IQuwP]Oih F *;EVnZ5TpWwؚVtfH`䄙,S;_}L0|"'xLB%%%4{ljmmx566ҍ7v]@K S9#]I ]U[h@\bE6$ް_DcajWQvE9@hY0 K ڔe5S:EI֗DF7pq@;85kVրԗx dG 3:hDTQtigs`mu4ʽ`y'O FV3n/1R9v\QG1ң@y.ac ''_( 6mhPm$B0x'O*jJX! ".Qkvs#/Q= FVwt @cu"{A|A F] ʰ*ȾYԫ4UQ]!`@#a06@8ԛ)zܹ)}͉2.woX͆wWG(#y\eʈt" K) /Xk)aݶ]M:h}z%IRU!4 FahV$3^jQ %B0:/3gр|K..Db׈WAX^NH43_? 6"IR "eQ\aXҽDj0UA i.;}Q12{bfuu/r`67rm޼ѥ^JӦME`#1j޺ymA1URi=0%_8f1(gT 8mTA006Q'4F~};F>qFeJu¾.&?9,X@[yw \aہ4cǣ%Ѐ +G1 y(]S.f/ߏ-B'LP@0s B?P(S:?DF7N>4F2MdCs!1FU_Ҷ5%y"-1rI1r )wZV VȨH)ݖTQ;4FGG5{𨥁ǘn94DW_իWs: Vh.\}U A ;u(g4o<:%}ƁP#i`{;QY5[˷oy_BJc$*"ix`fhJ12b 3f̠3<9eoXPI#Sڢ(["I<iXak[+-k2ӥF TBS#qp!1=uy{شo*;'B!QQqZ_#$+ ^c+8N+]c]>m^m]Fӳ>K&MaqtrEqխԫR.6^I('o!m>`$6bu/8ieF<zkRRD%@LH`dc5kEp皶6qk(wVFDlW3qvH}^(FQh4V-'nU r*X` ^.xA vP 1۷9ƝR"JLY0|4n֑TvW7smQeQea`b,nk96'L#ve:AļjQ"V=M*k|C0mta6HHtG^Yߺ3S:;"cUFȊQtX Vk#;֭D FVQ/)ܱZI)ݭJuuu,Rp- : :Gٳ;#ib,C0:b3~KFfW nQ`Q|r#wZV0*;0h4ɂq55s3f))*3tNj9]a ԉ;YijrWcd))]FNlc_dW#wh`JpW*mLaQ!7>ED(UW64TkMj ;tƛ4"օA`]Ui1HikT9PZ$ S|&7KY,@IDAT,fUzAQ FQ/clU#"`eKش/$3:O#xnUM~g#fA F"St.1R=8شU˹2UB! 5$vEzUV;lUL`d6NvJƨno6ziɒ%92cRlpz`”.$0cuK#y_~!q}tS:d)]S14FN r;_jhzT" 9`yG@a Ffmhg.)VO'0|Ɍͣ.wԹ.*TҔN'틵_^c .AW`bXL 4Fv,OYD:vt5GV+?@#YTv3`ĵbƘsG念>::SiĈ4az>9M̫Xݍ7hDoIA0:}E#;E}%zi$/͂Lٳ`UEUrVD!1Zr%rU QGE>,?V%qQkDu4f850HvP #S%`y-*Q>"0p)3:$ ҼaJC)邑cd_X8e xՔh|r_~! QG9u0WLpg#ttwA;,A~*:dw"8he']ʔ %I ChK0gcj;);8zW9ӦMFIgy&s9IZs;Q9MBǶZPFT4gÔn` S) ݙ1oaaP*Jc/?WoftʔZY0#0]VMm1;wޡѣG1yN$8ҭܴ GIdp/6**2&53:;YmHq!}IcL tSI!,MQh+ۦVhѢEo83:!偕]MnT a,N$8a,:_8&ۓ}4>4FPΔN Fx:(S:q,UIZi~w'8{% 'H*9o'"#FiϟHVnY Fgh”`GXd)S:kՙH`dJclt| )6 FԩS駟4@uQʆ DGTKn!#ZerahzBACM"3t+/QXi>xL!  FB7=;4ߥn&9j.nFDz-s=3%2 @\\LTR~Uk3hX0jX%a~KA%yhӡ n^`j1ə6Zi  BUB@ FvbYmB^䅠' 5b[,=$j5k͞= J0Il3lFР1g>/A0P+"Kԉ0> YҦ12S3;?aJwWӭJ?i}{O~nŹ혭87S$&1mfM۝E:MC02OrLa63F b\+h"АPqB  ;F%fe^"/t]wi։('L%%uMvJU[^3vXF`׀=Mtd&Fɬ}]UF ݎ슖{F%hY0̽g~iKH`u#jf_?Fэ}ˉ0ʂWNmmmG ^cT1UML0!3:;`Fi{b~5FfmaJg֮>o5ԫ>K0@cn4FxV @S-D 2B\ىs]c pA~R"3852&x^}k:>e}Z={͇?%Q0r2UʔҍY]|qcE?9-_Dck43:+)]ؚM>Ӥ  ^za̙ܿ6ruU$Z0ƈҩD4oTQXIDHd`$Ҏ2tX+x9#ZՌ0A`d>FfdV0裏:#tfsHuuuS;O?@j)]M4{o &E xBZ`Jgag冖W}Bpt&0X)^'0P?Hlb+b#P@5F̔nbDTg?Yw7^=JFvP}yV#wwJg-)*UE^H]#u_XͫvҌ=9jNctf$/?q}2bxZ/Ef6! j|3v@ZS"nO o E/~RlBwuB5jN`d ڔ/qN[m?^uZz5۽ffϞ}U1@񰵕-FEs7ppnJ`NshzEɥrfcYixpXs?9Gk)F: '"a`P%`,.6k=M)]+=\Np '{gaڽ\23@[TY-TXC \b3_a[zZ׼Fɳ2mF!EUQuk3ni5uӻue0N*U0D`$z8Z #.∆W2ѳ>KhҤI!Z@`rr2ޟѨ1 `dǭ A\ڍi4?[]w4Kc囖+v?oЩ$ `jC#Y;f-t+JC]xݿ0WXo^y:iB*i=<tcqwGf˅/qMra.Ƹ FczY$7@8iATI F1`ԧTsj+ƨ+&*'z Y)]؂863S1oK]vy4tXxL?p馛}>}t.4h/?a/R#,z< ʭ\\c 9!-Z~iZCJ{y<'tis~{$.bFT' 2%0C(HNI`-BÇgR*(Aa szx/^L^x!M6n6ǖNs,"VT=ҿ[ٴRe1;5=1ļZa2*@  `V *AcMo"SO=/_ε.} *6Lfz^&+c<+moo'T!wk>Z0Q"#'E4jxe(Ai7+7U@0ʷ7PQČNc[)Q4ZҢ1256Rec (a6#qJc$6~mzwh]wU[4vX)OS.p-u]E@7o0"Z:ꫯk4Q  q Zmj19u3Ah:;,xoy_ҹW8g@te4NBcix7j(N̜F -$aJ6jb6Q%be>fm,ssΡg4qDz'藿%э7ި#G!BǏg,\M裏v;غui;) ф TGKӰ0D_ـ 2CcToPj;>V *t`VJ! 4 F-r5Fgg,L+]YaB&XKiGYmIb:"nfK@1>F@p^ȭ<*GC1B:㹯{+;M瞬O?t~@w}7M>gf_`gkN*?thDCH(2`ds[Kovʕ`!!܍M4FTԽ UvI)Jܾ~# Aཾe='`~aҙ3 7@/kG4yůGq\һwoN lO1c't\=4wµ^#")@h: eW2:+;.X,6l:ղK 2s%Ցs`-6,/ ei/ʌ 733\3HJ|&NRD!  2.FcTo ʄJO/G݋ّiGx&F׆Z7$~l2C=DoV@c'|2o{leړ{Ȟ"F%&SL>7Âclz:q<3#B/xࣄF<n :o+WƯhH"y3f4e-mbB nV0bEA/`Ci?x bm!kpcUw5R)Գ85Ũ^K)]f&p/ yn%hW vs*l7M0fX[!=ޣG"ni5F 02M0z`7ZZZaFGO>`bW_U|_;s\w; 4jagDĮa0YjP#+l4F\Әvx=i}s *q~!}a}^F(r$)[S _xGoS 581ӄba~!056jhi01a F”NicފcԒ]T߄G[K6U(T#Aҁ:*M~qEAfN.1f̘Aof7||u=F8>Pꜧ}9P! b"LzO0#c7ni@)݀>)1btoG+HC_GăRI!7#_AH{B0+A)<guVւUU_.s8IcdpnliQ< z,(0 N3̞ 0P8σ@[bB ejYH`DReF@J]^ʃ1`dgX>e"&=^U_QcQUq5g?5͙)adQi\]3 Bu8phzQ4FrDؓ/uZ% 8i56RUpBQ/q/w;uJc$UI-Q̭Q!j(M@2+nJ59rlJ,m*~a/ +iOY{){ MaJSTt>.[#="*+ !u7xF2L(uty#-?r"1ɏ㼹~A0B3smq1RvC5i[cQy+,$nJUE4JiPQyq@ ՂQA0S #x(Q~ݪ"UYctݹ4e+Q(*Nm|*# wV&J0ri&=="="7O)w$M>H'Gt'7 HcrSË`WS%k nJ H/ڝv64jPM9~;Z̛'VU" @Hƈ4 q1LI*׿S`H:bJfeBٰ+A` F'FtRSևaQg#ir[}K[G[Nk}*qs3M~CLmm5ǯ:87*6d-d,$U=f1%uS:mҀCeQŒ~%z شW9K1:hF'}[i>[O Q*)@#=L R1\jZHeqjNїX=B>GCvF ?Ms5FBMK/'8⊍+_E?Cȹ:rh2v5#6# _E1R ڬ;*#Uq->$vc,}8L0b~J02·w9S#lhݠM;äJD@Z5F Յۀ,lS B*+ Y`ӸTM'Ld1 [ow)kFzV*1j >QY锏Qwj/r4ⅨkTi cf#ug#m|)Yl'5:yh)gՅ =ZZ`hEiFk 1z1nTtnS3@cx`45s&\_@xЛh2M^%%V3\>)H`>z!Y{"?h}֤g?FB nn +H%qXEcS3v#"DPRe<#7S:=0y|IPJ@C=v O˕)V3b|<j Ϣ\ߠ1@F~#+qxdBPgP62ӏ3n#OQqL#g(H/N Fr3:KKX( C(ʔN;k+;Sٻ8)K E0 Ts:9803P1gFNŬx* AN򩈨 _jzggzz{lwWWWuUz^pDN\W*]U먕3nʍ7 J=F<`Ҭ0 cZqa_M!-DZ"n䝥~cf"!~Pj٠,.k h75 XE 7S5TTyb*݂R1Tجٍ')Maw_3c%F6 /P$Nj<輠FD[BM4%Ξ#v<GȪ^pDt /(wܧ{."gȬbn>ְYg_3cɈ&ZSv:/c5E/[ڶRdv/U:E@ĂRT¶br5RTJ$F lX:'n +qd+U:-1rip1Ǔ|h cT(dp$Fa%su!dN=;#e΋ Rbd+ƈk !Xbdb{(>ML,Ebd"#F 1~cd3,{?V1*gP%Bt=FMJ(G VT y-1bDĈ劶`o&\#=.Z|1142 B-s?X'T~Ua U:tkBt^AJ@b${LD7sc G'ߘXY $qepCYy!cTl#~P/aaY#'d 1%c*O io/H^i[ڂuQ^&c׮tA ,\I46V0AV(B`֪[bz7BF#U* fV#t=F\vR~X#X ZE/ªuyekFC$FMb8L1Rl8F׫UW t_#!Pɵ_ 1BS%(&Tײ(.hZתQ+ߨ$ ;V5 4F0WPn*%XA_ cTPx%0#f-JvFFN;xcI6x \cTO1wܫ[*].h9yԯ%jtS@ i1&a,.`Iȿ8PNs¶QA;6nLtada8]Ed)Wdֺ$KTL'Hu;V voUmn^^dF8! "aE /=Yq)t9ezvS2);`1 i4R!̳CaŽAJa! 4ͥ|#5d9531tb.'HSAN$F)C,Ӡ?9[$6[c#ƫ{G9sCTrENE4~P0+6uM\ tiA}%~_1 @=P1-41 0ׂHPb( &(!AkqҿH n\i,0Q.Q*2^ 뾒|3Ĉ&[N+WK֬K%5ɷEb/P1"NSqeU:-1Z?6`BAT ;gg|P+:ŒzmT:|ڵ0F"-0 60+c9@!Ṵ${8}EbTqh^-q#UŝU D:bId{v cjU: W6PobuJe ۖB/\B՛0FY!"`75. c@8ԨV#MPW1$FG][M6u!^U:/h U.Xg۵gnL{܄1*.-//׾"H}5cfFnVV]ICeuH6u%F0- JgTu^Pg?UTiS ʗ5]ady޽UWv_~%vajYZjEW^ybPȺ"S S9" 1 zЌQQ۵+`a|~m5KB@zM#nJa`[&t\ʞn&ZdIJjyK.;.}tQGQN?;6o1)uu~K\*1BxSJ%l~q^JתA+~Gʍb#!1j]FzauJ54{& v_0FQG'.t҇~HsL=*c$)URXΫ#}|ϪR M-vnMv=aU:2'JB/aj:ЦL ̿/SO=5իiܸqtg&" .gQi~LN?tntڵkG7tEA@A@A,r&ҫJW_}5mۖ~i:iԵkWǴ***褓No D͚5#GRiʔ)'Î=^z%:ijժ%YoOqko4o}ЛoI6mJiժVK:8@ᆪ!C~z*//w-΂   PLba?$<dD{FX04fJ{́6ծ]: S~hŊق{A@A@A@19Vb X_%dm߾u~I x7x#9뮻/"cx:޺uz{j}B  @Xv+^B$ mExA+~3IUG\7,hĆ1ڛ@8ȉ 'LB#gĦq&~B@6 c %yH{a$ i+&^L4uÂRxDdF֬YW_};vn>ԩSja ^ @3yck_A@A@G 6Q^9<TpkΝaxh"9s&m޼9a/^LZ +t)ꩧ}KHd|I1LKnA@A@AFFz,YڴiC>,͝;W3G u]!̙3vi' y睧lm֬9R34C[ogϞM+W;C?CC_AP%[n?x7#3:?'  E(`;HTU.6 -T8RT*9JujJ=Jѫ~KqW{~*U]+\GY9eʔ?xxg*Uϸ**?\{T*wyJ(U*iU?;RU\%B@&t[UHȆl{F@ #!W7^).] s!E]V>M 25ɋ"U4&ZOprR }%" W8cXWj |ϣDXp LaFUItqIYEmdȐ!3{o)%6GJ`93Wsk̩žĨ5!}?W^2$#A@A@*mUF%3dðqccαV   Y( @ZA@A@⏀0Fc)   dA@,kA@A@A@?ſ  !瞔xYaKɠuf('u'5lؐzEK,q2^1F=I.A@A@"@jըN:hʕxKꫯwyƌC~)-XN;t'  k>hjٮ\q@IDAT%u]_NգvډJL8筷G}mҤ =~Μ9Z /Snݨnݺ/Ree%vm;j&m?{#Duؑ&McYj=SR~Kꫯ#,Q$M2-  FԨQN?IϷ?L'pݛf̘A/Qm#$Rtu93gΤc=z!zqwq?fϞmo DӧOvQ~<?L2p>=C3t'NdH_1tI#p%Dۇ?CA@!x5Yx!0.={$Ha bIgq]qԦM:C=sTVVfLy睵jfO>Զm[u^04p@͜СC~#0lvh"]65j(vmG/NqCͨf\-@w͋F^% Bi\w_RVBj>=zе^7|9FJC8~W4-tAIW R׮]n34iK>Bc UEƨ*ֺYM $ j&Q*!]ku&[.QjY {@<1~f[ҞC-cpI/Sji3U#SGĒUA@oTHA@(jc FU}nIhB[~c?Sqv7`ZZn+1c0auܙ=_<@H7.isj5@00F!ɒ 'Dbڔ xE`}{ȤkFthp=~ax 7˜R3>{ {:t蠭}4zhӛƍ|0f}ޥKԩY֌K "1IEJ1A@\!4VK0 se#8B́۷/V;K7|3]ve'h M;i͚5矓yZzw胰zvHկ< qC@ rJKV2)   0F`njcTRR̟?Ӯ{O;͛S>}7$VZi)1 z衇$ wB@UC`&MpA@A Ć1~v%M;2cZ)튰8}J~z5k竮JWw!A*"#Uڗ2  A 6.\ ͱ-X*Od%3,J.}ᇄ!SbeDeEEÄHTA@A x05}qN16R 9+թSG;mP3\:ʼnCYjKi=tʽ~Wj~O@ĨqcQ+  Pl`̜bnFĨnݺsV{''SСCvLzAb$$VDZšKɗ  +Vh̜$Fadb3ڛ@8ȉ kU`#͙3G`R!.Y."B&!A*Db l kXI67[qꮶ4jHeծ`7u0`Oam$MꫯcǎM{:uj)m7 ^\1<8ivK^{5m^r%^@$cE*-)n$ A@Zs]wټ_NգvډJ> yz뭷Q_(S8{$y嗩[nzȋ/o6q_1a)L4)4hr!v#=F:u޽{kjjmڴэfܹ9⚺ yG=5k֌Fu,q={^  O֍SOn?0^8O,@lP[4 c*  PTEask$?wy'~tW$~g:hذa3m_~9SO^S/k0}  MAMŮj@ 0E{^n*Z5C9_Ϟ=!C'L Ҥ38C3NxE]yǴ@N`飝n?~f~a`@x0l^$#M#p׃A?+A,O \)\0@P#e*]J(@dL߄x!H@gԣGkӢohƌ4jԨ;_=#栃JzAڵk 7xF&~ǖ-[{h^ c7b:T\ce n[%A@XWjӲ(P%1A@#)4R?tqVg uTA ,cw8xy U଍*J@vaC8zh駟BBe z0q;A@8VD.zu'9A@aUth"8̡C#6au„ zO=?5@Aq)ShQ"Lc[YY}_k#L0FѯK) @n>z/4x`/4}t-8x73=ɐ0Rgb<``@ V!XwF^Bx8P #T 8\6lCb_iÊ D0F  c E*ia`:^Jqw}O?gqZeK8XMJo'@`5-zުUk`TV?]8;7+B, Eq>>[G_5oR4!]Y) R#,*XROZ  Y hk׮d $B@: 7S-' ]C=6mUA ᮢJʓ,  U #l<xkgX裏*VR\A VLA@1F5hР$cƌ?\k/OH⁀0FG) D BC c:t萬ձc_S7t+^nA 1sѯG) DxW1#i@ujAIIIɉ'R.]ϭZҦr#G@$FB) EA:k׮EI[#3 WsΥYfi m֬?Ub_A%JA@(0-3-BT[աD|*LQ63a%0reX^H:wbhajE8HH⃀HSRA@Fl85jwH""ctR RpTnhȐ!)Ş?>)n F@hן^A=В%?z0>Nϣ>=$ǂ 1JZy9A  I0Ɓ1J2F2N;鵼!ƈ!x(B(YA@p$F3&kB1ph]vqn; a`fuD%OA@A=``|M#gԩSS0ؤfJӧO￟ NA lHT6)k4Dj%$F.^A@G#, E;!uiغukiA"VR'E / ?1Qt6^A{ɓy !08"1PIVA@<# gB phdVZE?xԶmۼڨtw%P8P %7U+W.ZhA 4=zдi~t>Mi~0sL4hAJ֨Q#O>Lb_!t(# B @(8cԤI2M6N40}҃>Hgu1B3.'x"M0!cmUTTI'DG+^Zd uޝfϞ~C޷~{||I¯SNЀG}<qDTQkX$ 1[W/0QVTd?䓔W%4&<x >\3! $Hvvی/L}1cisl2j׮p 4jԨd.\Á}/$ MB2Qkt9;^aڠQf0"Z;3v؁_X!$  7'%:qDrPH` E`j\ADmp 7@~g^e˖IާOzԦMIUVfP8 6lCv}iA(n5*A@pB.jN=UljlVKgL;̂;12~z5kSP-R2% 1dQ*L+ 3X *0F9CX0FIѳ>K+V-4ܟy֭.7;i-X1|:FjsO׿歽UN?93q->8?1] *9@0R O h֬iF[|;S#pO?]Re$-h:u;ц rPvM[wz_kFL"}3Fb|!  y+ʩj`Bw X :ogmH1¾/X`>~I톅:=Q]alJ`z@xDut_n6Ԁ￟bgw0N`BH#}CzAkJ$@4XSFgtԺaPe1T6!7߬02G0F& {+T2g.59PQ>aV`4S`li{AX@ךENQTBf0a qX1Fz$P{F8p3gT WĵvkW^y*WM g$9z왌[n#`\܋/A@fJ7;9IAb@юp Y(=JWH:uD{믿`F` oܹ9⴯Sn=s4gu:uR#{yi0fCrP}o魷gϦ+Wwܡ!y¡z}Q:C&d_wr/D12m(5*AtSx#uXvOblzi:;C;0PժU#LciĈ v`_۶mM3nsN1osO81%=#YKybUb$h1T) ED`sEBӇ"f%-iAIjJ" D63ExS5\U[:Yk"R5)R'%.%  gOǏ:F7~C!9NelY-x "C'稪DqcÞn6~5_uwh RHA* U:*TZ2  P8Xb=F29phذa3=ܓr*0z'(! U:s$!@`(t>.c]`!jܙgb{~`hc* Vv  M 7 ?s_ v 1{ZAp A *].#`R  31 *3FJU_~[gJJJh…i,ZHm.ʖ 䏀Ub$tc*1 PU" A%ps7p\2~Ŋt1tA@6v#QvJA@(&կU?{0aPf֭E4wu֍vyg:Jvglْ%˂,|%Q׮Y<#`*Z\ (@AޢҚ5M'1#QO ȌQ$F3F;}7/jnݺtS~ʥEJP i,LqEbhUA+6oF%hy^?3F2m#mAtEپh#2vY$!"\A@rAts)5.iLk61O$F[kFXd5k}'dH{-X N:$oiРAԬY39r$uޝLB{2ѣGK/Dx mz_Ts=:7ޠO?]_jGū>s) 1 cHUA 1rQ]#17cuq0lF:؆ DGKbdL[_|gre&OÇӀtguٙ0a51c4iCڵ!CШQ~Nz'O>μ?>tӈ#+Hp:au?ZDCtՁ`P0ϲ`!Ū0 K( 0ԭ^7Y%G9G% K0;UU*!0_?ZQ{̙C믶\jeTRR D۲e$S7T{ȶUV)rnj]v;+,͛7O3b)/bJZ{?Ga.Ĉ0[& N,ժye#4\KpdsNB1AƒA&A 4}t9a87< .y%ĉ)A'%@#q·/Df*]tRr.@kR>,~MYb1FMb*NoYLU<.\HX,X`}|FX'}vmnj3)z:@XՁK|vV0s0ʉ{n#q.-:ZEԨQB%: UHjU``` 1rlWJ\PB?ܶ}Gt)P6m~}W-8N4TTg';U)|c>m[omq1~Y e(# Zf)Wh҄lj.0Z [*:`a5_1<̅w j@ቌ?5nÇuW4 *_O^{-͘1`{F 'WB\0m%0= w|f6[qN)Bf曭ދ_A`d&&ڲ Z" |+y3{t?:cVU`jըYF̸Bb10ˊ_b[f:ObnF 1sXXŹ۵i\AT@8ȉ )EvkZjF ̌QX繼2<$Q>(3 L1rb tRH(hU:A/U:52%Fƻn"d)J k(D-%=dž3FP cٳg'qYti﯍$ꫯtT;vtNJGE~a+!?0F$71GR"tY~fZdn˶=F!@Ԩ@BB1$F@[yGkêEo!a1œȉ֔ѯ`!i6a"rPtq;α^᭷Jk׮mۖrHvI=LK,}Y;wf]w=s4gi3#0O`p`kf C8~-Ү+Wwܡ_CC_A`Į*9Ht_Ћ/RŌxuAߎ(LM ҅F$/&jפȌ[É@uda,U:W=F9UTV3 HbU q}NhW/p 15:&-/c?}Nb}ǶTTTϸ>SY_F& mׯ_8=`fU:ciqB"u -Aۢ_"7i"PZ[U`|A 86x>_#jo@/刊_SbA=^*l`"@0-]#@?c$`>s*ZCP /(:|'_l|!t+6Qb1~-Q63F,9~ͤϨIS{뭷V$4L`ƍG ;5*](ƅN!e0(b"~MYI,1aVwR|0|WUM p̌`R;:|c1-cҫg>1jϞ=o==n` LQ>R" zіsDbL0 jup(:T@; c*gƈձBVFfrQcF / SbV. <'̆Eh`#믩yaB'UEqB_cafWSzv"$a W8W㉾pis%%6ck! ix=~6x_#&cMbTA_0EQcHuFnr U:0E'7#{B::E &E4.B@?)[C:l%fެg\pR0P#?kLz-YLPȋa^ngO?pZ~s!|a'EGEh1ʶU\3DsD0FXk8hիoy8OF'A1ÈFNtj#`Z5#,(GZb=c1?j 3F*u5t҅^y4wqaήJSW[JG1Z^*qiߞhpRgE φ )uRgԹV*J򎉲A#GBF3p@+_`Fmy7/dcVH˖e[cTV#c1Gm1C-!5j(}5T#Բe!:`uҢ~Ua&LE0Նya)@%-h~ZabF"#"v O թY1 n}99ҕoybufms6t]xcMN/Ԫ<=D2Uct2x!bi6U:q3wJg=taB` 1 ?_ B;?d &S g}S>=0cZ)G)^bmP޿zƅD4lȜ7wňRwfCMWP8+%MO0C,qc1 S%"1]@i'Ctz8V<1RjkJLQRB17ČfB-L? F8uNq{K/ϟOyUjw/"LS Eȝ#BՀx\cp|N! QS-^Xe-Ӛ5Q.]BLQX.3z&f$1IHn  MJFҩEN/]t0|PsnJ]*12c^fuN]Q#̶n)@B@?Q+%5Ku oy Ycб1j֬12l1?tD}kʶrCqa_[8PT -3Fa;bEݚu=KHF1d?h}:tCuҁ٦6I:`Ib}7VćN c>ȴjcL8r2ȌQTƌ]wݕ?]_ȣ (>L2ĊdMg#X)Qlea)[a?cyAuƭD[[,Xa}pRfnq a|E*utL˜۴*Ma˗,Y̴wu0J ߴiN31F,e >ƉЧ6לvjR96E愠'H\ o\6ʔ6{t^{m;ypFཟ';Soxf ,$q86mBj Q1&)^=bO-WT!eiVO"D̼r;7iG`TL9f J S^ bWH"fuΜ3y[O!wO/8_ru0F69L1œ<\Bmǖi4 swU{(Ythp:_igK0>́#|9[GѝJ7+2JT?ƌZ#O4s9,YDb0Fk*1 1zGoԒZ~]7:!{ l6{X.L@IhC`Xe,Ƅ;sU h#{Dp[z~PEI4&G+F'[s(KnN tȭ*[nl%F,AbZTU vב#Gg0X8L0=蘰ڍ +#t1Zd{Jhy3ơЌ,t]Y|y3l0a]H<`;ҹT9\NM7|'.'3Ő-] _HlĨ߫==LZLyInUc |u 0FѾ0fݧ/NT.t4)i\Cy Kqޡ9rJ~EqЙ1fhfg„ ĉH@}vii֨Qao8}'Z ]x1)ԻK'pB2`56'B8 5®Wʹ+1cl:(/:v옲'/ zW_}EӪvwy'yMP{ >&?J2&ݞFSL9:ktl`(0+|KA3.0vu~72~|}=lme[TkraYB`rQvt#s|;<;v>Θܨ-3/;1NN`iM1|%FlaLvH@IDATUUcSm\ufwAcЁuNSN:yym~` Vlm/& ŕB>qQ>+Љr0Htܩ,O:񸙄wk0ϼipfC/lL LZ$F&Fs0Fgc&ߙm@El3 ʈnڤ9IV!'C`m$@?^[vx1Y!4HW%ͮ<M  j/tMhQ,I:fAϪot.xg*qЧ1F)ըQ #Y&+C?Ycx$+Rڅ 9+J;[ q8HV͙3Gz7ާ^xA~W[-Ӛ_Ls=N }冄UY庤%u^巪 nǔjZb6rB8KaßP+d*WU\&EbtK!37cŒ 1pS˖%v-ƷBI23mmovRVD(;B([|^3c}8 yu;Θ{ɨ! #!H LzՌR4%M͙?ELi#p?o "s l쏯` Ub{ܵsW<s0R$CVZJسsVʴmX)H}Q;PO6 qM7i?^^d/mwZg _h+6%Ee$\$F*ԲY>nvwH^KNW;cB,1>梅^ _xe`;yc*LqȜ0llm9qkyEcJ&c*dkXRΘ <%kg|OX27NMwY *uje)LFnW) c5&EfD X/.#^c\v~ "7ݫȍ*KKqm"ҡ?/ -ɡc `f{ S6(Ebe8~ŸË=<'WM?LgP(:ҽk4lذg/uD-M5=W h8sa/^=˔b+8Hk@7>Oh=E{"駟&p3zh F.BGD.tpS# c\,q|<M,0ifùb@6%F2q!@{ej9,fuMrkZ۪ߒ aʗd*CDk%k0Ilha:9bsbxsPpW=̟(cQtƈ)ozԑ]_~@?}&N氢q:aD`"M(x]O-{L]Xyd#I;l'<;E0-8O;w/35tLAm m |`nVF׭ĈO7Q-, ,gxq3deR"H](5@ uԉ\:6mгsj{̙C;vcΣ^:rH:+.]Pn0 +{ǩC{=ؤ.{1XaG3@ F u2"^xeos{0`M<}P{S] ;iflD+c)]<#JфK&d#:INXZfxG9垍~/@t0`mF-q5݂IלlKְ(WH@sncs|(JP#%-f=M??/}JxxR 6L|Ǯ!f,K1O'Q#LHȁxgȮ+ė˜]vzt{yMQm\7gN1ʧ:\Aݙ*T^^W/ 60hR  L͵ie9BÓVNB%9` |9D--tghK#5bΕ1Bx0<7|3ACa8Dc !=kՕ۱c^'XڶmA򊳗 &t^+#M0nF A'J +<.o/ܙWېx[#fg<~#+wsŀ#L|Ab˩EiY40VUGhb>|;t~t 1`6$sUU_ECqpǰ@|őy]v0&ڛZSqL 6CoWu{A5K#DGG#Rتns5̘3f.-LYW7(oc.D9(ő״f6#ďIҟ-~P{F_cV0Qx*_.]Jz#}+0F| E~WH%w|*gwoi_G q`q`æ20`0M%E0ò `Nqn$F7-,_koX[$R<Of 1FykU:ӊ ޫNA?+5Q{p3vrf5.~$M(2K>|^#Lnџͯ2!Pc!!2)WU:j}x}KzJK+iMj\AC Pj?(? cƪli$ AH2hٮ %fOL^,LU'Pmց 3շJA 1rGij϶41FɶZt@7X1!coKL 郟?G+Owd)2k:A0K0X6:suW<1&?`RX-)鎁qpW` uySI5~>01Q tTCKr`PL9FWuO>I;Wm1__>3i{7` ,^Z&`f OpO6V֪mma1Yb}Is^(e $־̚C1H d|xU< s("CXEyZݚmW;LkEe"ysL#,1BqLWjTepVݲ&z;E})1B9?&dNv ~@Vc+&#W s@Q<Ȫ9edQlDR~Ju`'Lޝ.wk1)5'60q3NqyB7-TeƘA {BČ& 0>PE^]',93Fl2 `hxQFe-#"X16/YҨvґGMlɥY-s$!svɻ29bDcOhķ4\671#&qS~%Fvu1R}f}BNtg| hԃumn1._8R5~!bfmi%]80ܴn:1k2Fga8nD=zk6[l}7l/sK'|g#j`%YgL|W:AGLgw8vGZh+f bg*GvvZYEPǨ_|n%5G01vƂ'#=ج bQm͈*[ =Qn"v;աJWKU"~->cČެYTq9Odf`yוxNRɠ~ #k>-a@W]Qby#% \OTEM,5=7|:Ot+[l-l%s]&(2F\6&Bx"mz~%ќ9ԑIb>!.1a??ӕB0F`Q ?K,2s?L9&e0.bsŚR `w:BRzho.H mHϪk`x`|\rUUrRfy qZKF$A?,V܁3F^3(HrE'5=\abXaaM96mʡӄ>R`uۙZȝ>1b5Ay[hGLq tL$. "xEScg$P3[TDază߃`bb^$F@oemIKTrk' JNG&jjYЮ1c%LaXbjߴN63c} jaLzy6\3|߬2n0r*371SQb|c3Feڠ[LKjg('UkqxJ7&ڂ5/Pa}* k)fpU4z/Ңb^,QF]bWI5נI4hcT44 DX((]z]ߵufšs>W{UzEOkTcRo*=wyF|}<-9jg^beJ-~Ae[D8$d0'ita V586b08,F!m^ZL=`p.N TB)kہ)jgKt0QR߫  o}s~l7Gw0v衇 i4nY7rU*K_^O(b uej9c4i FF#f}g|*n VBydGs Rf6: wVۻmAB^ e8˜П?2=1?1GVa=2aI#R7n n,הbWi@#`i 1`KJR>]~yJ]8K#/O7MX q1{?ExpmZJ0;ط#9B?m{ŗp?!_IHވn^pȡ#薯h]EȳN2Hgށ[1W\zivu+z`~2(S鴧(Ϸƍ`#a!4\gE8uSQUhw^Q]xQ;M'[+ucm]ˮ}q3<3x≉wBw簴1{m؀Q NW _~:X,Id9A\!ȸ0e`EVщY{-( ʠ 2Jūn†bby߅`#~Nw#8iY넩6 /bW/ AJQ'F e/U¿[15`rLP鷝wn37]#np/_r1Rqf%\Ҡ a_Y6on 9ۮ(f zBzgM:#C]es\*bD06ftOh2!^(F/MB/wvp-~)b2cSB?KÕ[JvwVyWcK[E8p^a5F7CsD$P-q\}/=%eWtaͫaW]uռ{!&v?,]˸Q45 *Chz(&0*,A@Z?|`|n17nj`գUvAĚ] v06N#푁сc$ÀƂG[4E[ワ&}y *F{a LhM'~賝vj*?>pbԞ{<ǽ.IӃzJW^Q߰ _ (A}Jb\s ш9BREiq^ƹ{ ݌8C#_U˅d,YL^q/<+*21>s~?};z.>pRj @_wvhb޶sϝW|;> h??huY(7s]\镦wkI$vZJqbMXk7h!| VŐ)AꛥJغk]% eNf/X5X"Ѯ9c9fB>yHQWƆXy`ͦ Pu)Ɔb=l1"/ LE?0_9D;!\<)Ī# I_Uiy׎wŜYNg Ox(3(((Niڽc.KA1Rrű|y+{2ۅӧ}t,s`d%d@xdzq6ݛS1DᙅSQT?4xi7|}wg DòYk̷Wzˮ_/;ө=wB8ꨔ*ad; C\ 'ʘb4Y(FR\aB|)Rsƥt7ܐtBَY1HPb.bŪ=5b]w2i `t|iз(`O}1DJ "Dň{Y]x(=U c `@ h"fp@8J|KX>~Ĕ>UT)*Lz,1Up}JtV6ߝtHBϗy`/v|jXT[rUfS2'?L/-'9H@ Wfs_)ic"ňR~]yij[U&&vXρ^W}hrpꆐN`Ux%?h[}h?iȫWh }aKㆳҍWtk.,>n~>ևgbXw{N%~Y/Jt;O??矝D,oRgH+FsL:/?ee^&1aX:oN+mYUÑuDS鮸5!(86Nx1#NIcc FĐ1Oْ *ąQہ '/x6&tbP ~{oo(|" Xe 3!@ ф"D&]'(| c21%kAkَ#W*oJ\R$EJbïᦩmZ?.%['EyYJ)Hn d:.u׵{71 lIPy1  @@3ԥ~TOHϕvꄨvDG}]vᢓi B>󱎾.$ƨ8ϣA(3/n!c1't)uǔu=GW 8J -m\ܡQH2ٶD8t\QsØnx1dY>+{n(ȏ;mr{)-2eh! Q1uBˮ?N^]3袋bн5=A%\j11<|H yKT!-HQH _ѺuNA#HiXn`EQm^?i/  Fs_sc0n!F8!"m[ ?&XMQuFD[?ٴk:죺Ssvk>3Ճz]L(^N`LbN,#s=8&m6D5=F yǙwѸs/X4#usY9q O{ sHL/ڮ$ M'uvk(uu@m+2y%ȡLjMbD_1BQ%.0j X28n|S+\)ci14^>dF⡀xA{֘bixp>I>{-x|.W'$&7ʢ'p|]Sզx:|ieW=t7k 7ܐxw/,Laԧ#F8lAܠ:UZBX7NU$XqC`0Z*^SSxPNг%@^hK CH#B_,Fbz`ɧ"Vd1?>׊Hs?uԈ_.oaiLzTFJHBW~`l0'F\2")얍e & D|/nR({-?)u}|j{);>X mrQax/Z!f|zb~Kz ^/}˄DɸǾ Ɠ<0"m{H+iӞ}uš/_ƙ@j<9B]NwFM;odG{\]Lj9Y'׍<yx+4Ss\{^OW h?X\t[oI*×~k_vhz榚 7:ሥ[Km%`r(7\J'3ad-_fcVvJ/2S8f,[iQ*9Mvڶ{$д|}EBG&hd\/Uׇ(Dq%9\>xHEXb$h[w:$- iM|]WOTbsO4cJ6ͺbz'g-OFEqNF%YK13\)_}!KrE(b4Ò|[VQyC|eU5 ey17l^" xweIUBS洪1FA~FMH?EODHL:(3ʑΕ~+R:V[(xH=b(Cs3Hu9#zul]ˮ=Ho{\wz[ߚo]Fm qDpxLhV\^T<$vR(dGZZcu~bCLa1 ӥ!`Y}&?SJ:;p5r&ʁhx]k^>NŹ5ۺ+F[1Yr>[Gc$aU)x@=tw`8t-m<)ae-ΈΙt %LL ʀ{- v 0@WA96ei0l0~#yW >ˆ'*jgy^ӳ kWݞwL$4GK["~D\5/Pϔ>֍yb޶&򈱝VuS̸mj̳۽D +\xi}f  ,/(o*O\hTPg3hרADC)'ؔN>9 ucftRP5zXV.31m4C^}!XR:SD_2pgШ2V^]OI1*ҹSe,z_VXvCvuvԧ<)v;ROA'pBs=m?cuuK첋a_9.j/;?Ivj[}53atcm+2%/Iv;ڸ[`^J'aS~dRE0!4-V6(2AB!nM vV[FvdRt.O1Rݫڠ-)FT%˂De!Wk[~|;^NK%b~n4Gq#> fY;k=FAGJ1ݖxW*1j udKk`xBp )8 פ9+x̩muGZSwؠ-ft$ēaodk/:?QI5˱ͳqb7$Z# lnh~[:z`M#-$Z/1=^rwʎ(h@s{uڼhq7eDD;cӆjR*F5<94:%}}~o6ڏO/~qRy +Ð=zȃ"R$"Gt޿{k¢IvAm0H(n<)7 h%͆,jg#SP.3MOD?RP xX}P[ [>/%a4J$ 2\SvxׇuD D18"E?T?aRbۄCbZ_.#36o*64'|8w׸w*Y^>##43#N*oRSWGsAN;  . K\fIQ'<y>e:o3ctv KuR*5%>E#<=G`uCCWN|13i|75=⫝#X+/{?^MsCaoyJ¿O _X0,%.adGRǀqқ&!yHzQ9C/?E/z}]zW?ߨ*w2y%^~鵯}mvc9 ?Aeia{8 M0%m0]WUw;M)Z>SJ %5 Qo`(xJͷtO67ǜ3 LvMdQKlR܋ x_ !sU;Sm1c$k,Žpt<",t2&BygR:ʦEk eӮ:!KKs)ti57]Xov:RM'Om]. !b /=騇W~e,,SrmRVT#_V"ˤ ||~їcYS(*U}C3j#o2r8~RK}_ yC똇c\S`}1)S7Q<8 OŀtSM`?<}7_@R*P4,s6T,W#^/  άτ;M1b٩h,`ܟ3AexXp["OcDR_C3ٷm0> wx8 c-g gQu„+lN=F(?X}_0.U|^ƋCFOHCҺUouew/m!*eyiJUN1z/I/8׿N;M=U),c"奓¹6@T㞲u!| c)h:*ْFDuMTJ)-CRgK=K*4U;:finʼPR:x 66iϪWbi)n+bA3xJ.*"y[ Z $˜;7]1JIڏbHkw1(ծЊ?WF+R^Eb%w[ň@xxFExLe&cY[; #yIBۊ$nf+"?\qO 0jԋ=I,㰉~b#A3ap o|{>(P,L1B1 = f\6!ʼnD9fL;@[JBU&;*\{b]TR]?RYYۚ"CĔ:lcwMfo@b`mgJ0(El/cBX"m23UKŨau=|nʊL!/*F1/LU~i2K2IVs |o$ƕ0y!}Rv]ov'gC n;2GSZ~?Fϝw=9}Ӧ~^BpwAJy ʑN1o|ٗ;%oCbiSEutFj#:|Cu'+F6oKwIEUcGӟNG?jzb2nsRekT4-yۆǚ~Y :9m`8`1+p)Fxmv y(Fƽ%qUv&ePFgW|Z ƹc }rA<nQ,sFY=y|~J }"|?Ϋv+Jk \H_e+ ڮcA%(kZSqe\~/\;찘=M&04zh\C'5"']$Z8}Pjj1b( E(B,D Tq~J0i{`Tiь{sqzA`dSyutĽ?W^c—+q8 EMV@()kT`SºIt5)UZ%ީ>Ob4膟Gkw{P`G^@͏ljt+zG*QqrZ;)N+F2B_i?枖{Ljt#<)Cà #Ѯh4xbs8Zcl'Sz7/>0>#;)DZ9؎'C}1M1^9q GblDsnW `)N.ӯ3)v"=^$^ z)NbT*'c4kP;y؁=y]V|r\7}0^g pWNEwX*Y3q jR ![y)Y[2*1faY2}; ^*vqr6TQ>Ϧ#8"MzV_vwvՙIO%LZu<„&[v`c9ż B=%e ffJVZϴ lu{yJ׭Γ(0VYJA<$l,a pKfKX/Qc9=F~O aK9ڙq QӔi8թE?\kvw:4l*oi8*/T%|+=wr:Oʽ¹RN#U7pS;k^(봮ZqKH>;GwCˇU^ݲIHsT\COo+ wmJ(Sy#ܠЂҒ.K9W2F/ Ѥ-oIfyυuqT{YvYg$?h{b$eᆦalQƚ1r<3Ssh)NγmyzoZuS4qP-?<\1"\V>"TQ _ƗC%_1EHa SI1:ӓ}!)=,[2[~Og7=I@;ށ ʇyHSn8i`G.~Ɗ|ŨclYvƼHiL|B/ /)*PO%sVG^2 aղPp̳.mJX1/l1Nzzh~+.{ECi6M`ltqL a1V^9u/Q}H~3z *9;%od.4>:y@;1K_UF ."nlb: Hc//a:Rb9:bG뎤Yrf&5|A.Lr]alO &ɾjY /DŨrIr 80aB1FHHXSt1BU zu*]toܲJMȱSLiF>B’S_CPxf\egc~tՅ.H,y%ap$?lcT!LQu-uoL1zЪCb=XPƤ)WvIx(˒2#1H_' І0X}>{p& E#^ |Q5_o0cKGM1\wc/p{ AVMå rDeW.GCh-p5WYP h_tVC+gҏ~y)m7408b~_C'6ir1Ѵ|k{@h,71#![JwYhe97xbi%fCu{B&X\2 '}vpfI%@7(j*>j#i}u3ELS=Й5swWy'm~cD:x40zO~$C?>&BY.6mL=d6&q;ňLJ[(d"|縙 'ܶEGa7Ϥ/=c&PBƶ #TTA@XO=!MO2|9,l𑎸X/wkqA~zɭ8JRո"CO)ݖ$0RH/P[{Y uRJE< -B/.E*u"'eJIS/NQSp1H?Y9YVoy2}PGz,lSɾW89O;/da;SJ˕N _ӎ:5vV\fc+JA|&eRUGƮB,Ek~7&6Y 8=.RuW01miWvΫCOo|!M'@z_^QR@`Ziyϸ߮8ҥ_rA z,yerZbdJA_HB;+sδg@B2'8\ $1ADeM/$ԁN!~+/Hzve|#Ov˕>Ãc)ZKު]QVd>kI1Z3p q" /XhdV3:gc9;+FAOS+^ǿZfMq8Sˀ6W t&3O%/ ;Nȇ G15_y#_vp$d;\|C/F\a:ye+oyVc+E9*%)]1Ї>3S |E9܃Nt{R#W4bSG:#I'7]w5}Mia(p7M k^GbK@iG?+R*S#\0-NbN+3 Cv)036l~iiX[1wG3딍Y9ӍQ(CȚ O_ zd&g%oP^!0w򃘽6,` Shf [E"@}0&.F`a^: 8 WKI}I  H0Q L{ՏZj#\V~_QoSiY.Lx b^19|!z'󛜂@֕St:8C.!CCO 31{?3pj)PF,UUh {d]KhX>#mL`Wƅ o;Oi7R)LF| @߲G/~^WxH4(vւj srv _Pe>ٜt?%Oֱ,k{3/r)6uRTW2{ ><^:@ k#ٌ/0|M{LXd|I5`,1L>-c^ǧbDAgL"[~ 'Q@ݺauuDYUxDd ٨D V8$JFfw$+R]:OU9NtөF12z-P[DQ?401 GT`$lMg]y?VQ?x02@+˃9I{Ǩ/O[fv-+f#SʤP5^V7"cc4gpüab¾H^WvO}F3cUǸ9ٖҡӨ" 4 HQ蕠jB?D1!shʩ銐Z㭸7oTC#ƻ7dHB*kKl)];<|w晭V:̢ӵ{LɡKf wo$OF;?Bp. Q•(,t)*Kc>6f`0(IP\qwwGXvĐYJ9~S|PETP*`J38PG])H7*iOMA=5l~_ou3'N;w]70;o6m#xej~ b鏈O |CWYJ2V`ѮbD;x] !3 oyJNAo$[i{_'NnhOޗҵx gl uAb[iW̑ݛ+ qhp8C# QM1 ρvZޘe49>d3 *gidP.srchlU!UiQ]1f xr.^r(F_Wmf}cz0YA Ik0!'ұnrgz!@^BʣŃĈYp.m8~sVed{IHU:; E9($ӆ1BPgZmM6~+G2Da>L)O xeTv ]1!E8pZgzjnv%g 0&{(5q[C1PvJ~oт.'SJՍygݶ~h4J>uoomEO&﷤bD+t>QaS6_x/W3` ݦMM<*Q o#vBj%NJG9h'xW2ut/.#H'ynNhӓ"}IyF?˜G=u$įp/=wM_H e_=}^ <ϤoN0+SDp$ĉH@0YtF:("(.~;ɎK7+Y dA!Ye*ل֙j n3f^1h=_[,'& BId1xO:@}>c49|{Dtaz.vggWͷ18i&6zk)"ت'`GFqGB#@gp+{*>zjh3t;J1_8N{hAsn{&[ƛ`m1|kmyy LpdNw(*O4w; ߾\aWfyN@xNr_ *TfWRU}꟠Mq)Vw8Hi/F d]FOJC G,~;@si})BR' c&Lz'_߿y`i1DS3&D1BW¿ŇKS\H֍ygHN4ZsSϑ3:JXFrO7ܘ%ovEDy@ٔSG@ċGaWJa |Y/3֑%Iib`.6O90(7oN @x"_/"PQM﷖=<wg 0eP20ii!F(F\Faxx #v*J͍θ*NM #'E#Ý9C<]b0Jk+Wfq)=YJysu,& ̀؏o8-#Co*ueg3_NtgƜ# t +PaxKVCuGX!aH\IY'qDRS9ym sqَ4;3PX4EAͷ{m;1z;6_tQCgC?v0Jᄱa$١-'V;v[20ڧ1'=Y074"X3sYoDz>U7*Y? MW}KM#3 NJf\,+uh9J%)JLh&n'ƮI[a|yR O]}PX/"o@Z]99F~:Oof a#ߑ8P{xp/*Q(*H_`hX>|Acte.&\=f{!lO".}Ўޑ} MgH^Ʃ^ 4m [ߢ Wxȋse(JE&YJ0~%z^$ϝz=F,D0xEG}x/ "m׾iЪum-%F ߬/euO uL1PGq[ʯs_ɌgQѨ}&dEKu PXVJTo|OO{zR(   q~n"h NCY]\UEh8F!t1rYuנ{ngzԞ_4yn5ޫQc$-S<}=1|k_vUzYF{:v:^1ln*F>u9tguR +u,#NIЕpmL@X3uޔz ¥IQȣنH, , KF늄زW ' dƵnЙ^ `"Ņ $*N9E`\7ͬo(Fo^,EJ@HU!c8QYխRW#.Nޔ1BĹ?qD1<tɅ _FV"}L_8hsx}Q?Ny.]h|Fr-Jv}12~S3>bd>8\2|&Z!8&=+)&/x֑ċse`֎ H}Eo™ /|Gy=Fq)zZ234jNJt9]}uN185Mi{*c,E63fb ί C@䃽*F{؃bJ1Iw6ݑξF2IsjMIE0xG8F[6hsH#t"df늑Q 10<vä3'&7 XD^$橉'|-fW2 ކt(1h =Fۮɇ/pXIX! RǙH QDHX$f2 l԰YF- ,X0 I9O,a`f}i<6[D8C">Lbnjq~twtieJtIxT|ޏ(ԕ'!vdtlbce-a=̳]RJK OĻa}ɘH"A gWЪrGsaEJK1C=z|(bq%_#˟nLj=be{qDbi9Q(*P!%MxU !$SXwF brHNwDb|EeeWƢ24xSyIʁK@DZ?tNs]1?89v#"SO7^WK1jחĉ#G>\Hwy`{WkLS.O4Bhg481Z}{2%GG88+#}/>{7c1+Ǔ_t6Y ^ts>S@4b#g Ƿ8_Wa*A /vxG.Q(F:>9GZ핉s샬WdW7x…o+FP 401bxhb\߉}t(/E<'Iqs.K$ 2XJYw~`# ΍5BFŜ7)= $!GP^9ҏ0 1[ ll΄F b +W3?(KP~'A a_66$ډCc ksK^э(FۗC|#=BhǴ$?jpP/ˣ!)`߶Oϡ@(A1~'cy.nQt2AcþYt׺fb4c0PY}n0m'!OoTxEQ^ Wٕ-;|a9bl 򙧤/I}7x(=Wc$B%@{qħؐ8xv# `Vܔ pew{ Eh8.BNἔ;nK^4&[zi/pFRv1$.gňJ)YsT2$$)/ % =*aFt*KgQ"P{ߛPZT~Gp%p30h%NS`%Я,#ꚨ 2R!)RG$ۚ%I1r<*_ʦ|#ٺtpcdJeeR'$z(T.0D=%2n, \ A8ƅ"QH+!xc:姧OCÌe'L=FflKT`86—HXS_u_WbNTpǫ9ŸPCFȪ2Uz+Eٙzs/ AwĬ<,?$4?g@F|5Kc/AOGlxiK1bt)Z)@ qJxC9@ݾfpC] /"Me}Ǩ WHv|-1+wKT3+{o=57鼕ec2S繁@xȬWw=;ݱ6xY3\OfhY0uð AN<8%hAW4}ݽƍyGG:7gNﶖ+F_'zc;8O|bݽ0 d&̄Q?#:j~!²~cv>( bQS;|7?N,*2! Nvc%v`a0 q)h b=Of™RJ#y^yEHt?n(5W xTטހD&Cg0x܌-r/p %"я+L:H0R47;Q޴EE6I9%Gg13i2$a5` ZN̅4QSZ9N! 몱M1*N+y! e8w:1~7q&}eT~# ʣđ\h3c01elKUJϕx+waIcΫ͊QA"wM zثcsp7Ws0 mӑ1jGtnCLT_'>VN\uu4/Ƌqdt΍)<2G=u)ݦKw/Ix B;mT!JُzQNpV_/Dg[N%~ aEײ Ųd{[S72!t.Oj)6yݯp&0v! 1J/-uqD,g1S;%,E'b4(o)=F}٠RDu.DG` @(GQ;xQ'{@uW0G0cDY=sȄimNUsXJAS/o]3CvUVe kRLF8 !ą:A)H4"ڍA߲!,(Թĉdq %C_%putfﺷc} 3C۽=FoLP'WF] /鴹Pw`! m7y#p\ Vd{\7<)?ix{%KbO1zf\[WeR_^M>pJ]5Vk ߗVsZ(E.=a=H3WgK~ӿ+c! Ώߑi9so5}qpK}P}'tNJƷb{EݭP"vZ14>>ƖvQqֽfJy5grEnQ\JG2?C{ꐳ+(*QLvkGkTW;ibb$~Kk_\5v#XvwO\]1gL99Ȣ ar+k:x͇N1E˭67=F *ĐRh;O he6v4b0jb9YY""(C7MwCU]pwέ*>uϽ>{ywUp=Ȇe_WsߟLFﴹw箅RɎp#,WrIqԁQ0t8u057RZt870g^LKSFx},z\YxK~![;MxcJ%YM3Fcg`s]E39蟾3py$tfM;,{>~:U"s5è1b.bt 6s[&whK^칼; gwq'xcUw>2;w# Gi5ۤe/?KIG@.L 2c^tD8L1a\ F(uB@!QWTVjz'!/⋉0b.:h1/3lt{wQb0?au"FjdzDB@R2'oIJ~#3)L2(7a#nuQ:EHuUcl:c4Jh+ɟ5e:~ Sރ2ZUNjI#g@t#՗bK,y=z']b= R j F|=;mۖ8 70hR1AXͽ@<9/gN;(u)Ezw5׽&٪t=e:V{6lA6c)0]o43֥%A0rŞ)7R)+7mahy=6o<ԡ9vs~6rpb=NNeSss9aD$SJv:ju܍!}|)Vxg-o #.T!2eq:@3O{ZxO,a9(\zU4H%m*~Cq_z 7"?. _]: Vm+F1}+92pwp*Fi˪O-7dE+1䘪6Èw-Wyo6*"녡tٳ.=ޞgh8oM/暪2; ItaD1xd1p9}MfV+\_kVC7hzO.WhڡiqU~-rvו9$'u=:Mn>σ ^K/|09ǵarEL ͌-B #EA4/ ]dAaRp\(EX(1G%ۼ., tb)0[G.PQ.|klR{/ALSj6em\|JM"zAP]sگH_o /( %IџܛRqSmRd"!y&0"#(Eu`6MRyrBa"F(YGV4>ɂy8= KDaEDuX]?oK>8_܉FӦ H0 ~2vDa[; I,%..6mO$k3[uWSijnƌ0:d9F( d+ S/|jl0Nd";ld*`G =7Wk<OF- ߜ1gvt27\) :7tG0Ux)9Ʊ:>Fn|WpWsk/P}Kc#.j8^+_M#Tu#atq/<d><bw! 4̸1^ J-2LjG2#tRD`cӨb1g 1v =7*( !m-]&w d yaDHqD_'0^)\\T8 3-SW4\ʗ\g6> # VŠ@=)zh1.pvcѿ}B/n78K˄E%mH(R.<~ <hTj ŇpBNe:}ҧ=&;ѵpm*QF>:, R9EՉŸO[wuLʼn-4`EP6Ļv|f2,`47u9\S'QM/\/TF-S_g7@Kt2pDHP^}Ȼy2~ /żDEA~,$m̺)"A'뤓r_=e7&9?M,VFĀsyj|s)-mշ񹜇"V*],EWᱤ naT5T_H1n:x J >ɹD9܊ 8x >}RHYF؋џsfD/r8FWy0-ǪE;~æE9;3&MHCVC7%gUDzfȳ:^Y.c }_̿?)Su*Ԯ׀-a<)*Hd {sm}׆ X\ѵ s*zr4 A'/]z#S4$(ƍ, Z[PPM#2` D0`$Μz)Uޢ)(N(1}aDԦ_}Ĉq!xGOaKdksr*x/VC`HnQs#^VSE.)2v^eJm(~~dYEdQQpD꜎oz1'ZF+\rTb~ eaܤEzMq8žY Eg0#hFp+/)Oz g\#Fb_^hb \ϥKF{ң»{!y5]-/ޅ>f1&3HYm)Suպ& .NAKJ犛\+k0Xga\}V49w m9Og1[C2(}>0_(xO.mؔx`;my Hq`x :ᔮ-wtY9YF!6q]IE㞳~|X~*:m3ll"^;odjl EیmreJt<л6M _Ix3 {,iT,ojGt*zކI4 @w[LiM |}KcuQ(aOA ֊q=BΚ֓#F(sD0?=QMh9HR}HKr={|r}_%Wz"s1l(M'_tH#0 f6]>tqqdn`j`}p$>!j (x8oFy冼Jr*G|T @$bE4e 4z, # EE/DcoW Ջ$aD?@qґ\\4bZW!*uX^"8Ǩp(9:Jt|추HלF`wk#^H[,bS]:i{GoL';Ա5}e^2%:4oP l,cӆ"^}p4j|>ѧ(2cwI GUV2(#0;o+Y*|eOh.vONۋD=φbϏ)ġ.)u:r.62F"){{@Iu8Ve0#9)U6GiF f o{X,,۲:Fk0-T #,k#*\}_:R{||x cQuY$I$ݳQ̎w =9-8ʗi1W@Nt(P3!4ll͢_,G\7(Tc䊲ydY?/S)fq,HĨj* Mt&1Tk~0N2ý@c+R]iK ,hG rQ~[709CFü)8MO jĨ,=*f(cn(UIg`hp3?wER!}gOL'wxtz\z gc{VC8#>9gd:6 2Pܫ<"EI+)N A|HT<ǣ.b}KKu,utHEacS;a8:f[칧@s:J_+U,p5K%a|#a Fn 02haD72OGI| =zoתcNW¯gQ6B(ſ韁VJ0 X0wlz[tf7O#u9d9w0E$KaR&e;.>e'~kLV*Ӵuvƈ ѩчoXաh"/;rGne/K4|!Mzޗ^RЇnW{9LO7ftG>Vbhz\QzT?ow?km#bF]< k??i+RUB:}IO R--ةx"-bL Ou٪Ke[GgDE>IƳ@N!efY }mFQI0Z ysAJ ۾)o4 e`DbVmqVY*bDvofཀྵW K2(Kp>meV #)i=z£[Ff`ZUW4Arz09F(\C5A;:&0DQ?x&sP&"HG0b,D|,W\ ,.Jsz#F{gvpK=45h/=/W7>Vb{hIL.띠k>/-E9}קᦷg!t:QF%֣ ZQ=o|bޜ^yQ_- LY*Ok"F9+,N?vLivWHeb6Q1ߨPS"G$<]#h,QG+u2=psLy8~xSrU_j2z'S>պ 9M}F{l"qT{T~m#"e0bw~LUYVS1FݮV0Q +daŠ着{u#͏o_ןτ1GtT _a/|7x}oDԓ8>-\rI'?) opsd=]| UzUz󞗾oauyJ[u"L}k}s . }k_k+:ycU>uO<H#3Ҵ3xR)p3S0:NXRfL="|(CU[hjL!0:S1j,da@Q&O! #>wT1`*b&]DžM}nMn褹Jowzၪ'Rb5(RymF8B`VTdX:גD+P80~RtjR8bH5Sg(F$2~`qJl `%kF.F_؋P= e98~F{ƿRJ_}櫽6wzFcl?}aD q Wb` UlhE3V$ьܠNF=e<_%3ൃ!b$zl h uT7_3a1W3pd G̻!AK.C℔L#3*N]?_a[:_G{qwx?nG2*u.~B#,\w a\'xwO?=]x|``haH=pg8.#n ǖg|' i Ejޓd[O}ԧ>Uޯ/_գKDn DEV $J7-]%| `ucҼyؽx`y`a1|/ENť"aK(rgHJ#& T:0 -t\1~j"~"Fwe7T>$|`2Go/L740Hcb\M?OsaD# F7 \}嗧^N3E}Q ~Ɣs0c1Ypo`S%|Q<`Iar%`lҤ}FdT ѧ#V/_3Ag{*"@{OhFQq>0з~,7*IEyێe1*b0؊OƲu[*)N32".q4=aBȻ(ϫWjZ(I̡"^W?JG*%mPc'~<[0람)|B>Ж TrֈD2eӋZ\ݵ_繃nCo)Dy/fmv͊ u>*I{{e" Ö_63*'~J+3io#ĔbO }\K>nUtz .ۻP#.S Èk.Lh1+ERu䥪*0A"0R2 !צ,b 2ЮL4sΞMB!^1,1F}G |< T1V/lJ] + \u8Cb&Ue(etҎpQ;ҊZbH|2e\GL a5<2ĀD0|ƊdLe1\OGW gk/]_(>B4<ԽaBYo N@m#zJ0=لRc {JE&u*]PuNG7o>~=N.K붴5Si" `8=jEazT_:zr2^u㫔x}wM6M7.4F2y0*"Fp섃c8IFxpA"ϼsi-}JqS!/uV{Pe@TBut1 x=O]2Jb%KOwKbF~| }1C73oQâWkߙ1c&^*pAQJ'}ʘQ 9u"x -6 È1$_3y{q mr[zx}xhl#C.NBiG=)E*E!X~N|hQ/2.Fn31z.vIu_*_R_r |~wD8ѹ ^錿IoۍK_G;Y :]ɹ{RP T}Ɨ%OXV6AnyDgZLsәu=i̴{F>CӡC9qa<~" )r)y_B~(;~\n0”=PJ7 #ZR)+LBC‡),f5^,kbŦZp ,uT#Fw-# 3UkxOw[7WʁyoZq,e81veLI @؊:87k1o].K|bIz\8CZaFQ߅ 3[WtƻRr%3 "bVtxbs t0@/iGyϩ{?Q9Z'U-aʉ炯+1HlfCHzvaCOhSϔBKʠB ?ۢUOSӫ*/NTT t;s;WGisH 靉"'eg Q#2;Q{Z(guY,?xGLgԷx0È4Ba C=c/Tms[o\Wy 1;(wH~>8sE Èv'8uwD8ы_b_ō8s[D¤qc * 8GY+б )^ѩSqJ}s'wup*!oO=)1bTJKذfX-PeԻkWݳf1ZWw bP*]0b<1wu/lٺѿͩaT}?>=qK=nAXFN{UGD?lq;+_O:6aRzÅofҟ0Ju@IDAT`' ~}]73|Z%rf f(c1,E0 1(?y'Ӟ"[D_lO[UfLQaU:E-n^l1!Bad yqOW eK E|iZÈqJjĈqnّ߳O8sF3 0`yX)HwͮK{rc0AQѳlLiX}GV번$r`_TK~>*hq6\{m"H CYxk\ʶ6asiLo{/5WPR"I);OhZ Jz˴KZ=)*zJueڿK!%UBtUiK}lsYPHUr])bUw) =:<` lq^RTcEE;c6x;y/"}&-o2\ʵ&aS?Qzww^Fm:1@w(1PrQ)+7|s "n'\1 7#FE1ƦakhWMU=.|_:2X=u$Z>8LDг[1"܋aDd*ȱ:X߽) R'y8>HWQ*j_~Яp ZbA3uc~- F [O?iOm#۪lԵHcoN{B:c}3F_g+ѱ7#m޼Ye_<޿ӛEay2ԔmyYgţ;:F! ha(T.0gtx=F+BuI9FbpS2, Oύ矒.#j;Fkx>?VcUvW[{-Sdȑ!᥎Qss5aT8?O&@˵#_]FWE֨.,L02XܨEǾQ)|Wq30u{Ke,E gJk9\IE+ èQ̅#XKQ]\T(U/R0ɞ{N_N)O?^sk-wf9ךhKk-]Ũꖢ->@Hs 3#0Blp96 IKuFqѸc) 4at1b*hGX mƧudʆmv)T99Pt (3O+72;A?(ȇ۴."F2T.FQ5v2id1$ "jcmF  _ #"P^qXwGh<-nҴ}M%;<~*}_Ng|ċ,PFz4eU ͦlLߞy0TF> #8tS:3D?߶΀a6ըgx+q[aW + G9\LJ(:m:舾a|A!ԧ҉Zb-Yz!Ȕ:lK~R8I}&Ve>!w{L'Xh0"-Eǘ_{GnAy{1 %Ϩ@N=T_$@Eu( Xp.kEJC,1cy,aw3x6|e?$PH'pBzsuʐd(2%3Pӑ{ _mH&no dAE8R{LYD{fo2(/PR{pa0!b䊛E҅6jRwDLܟ.Kbf 缄A"VSU;*uFڵ(mzJ[Sr2*q]RJnCОy[jz"kmw~y۵(]`0]Ȫ\bb:ы&HƷpg|=ƊB_F >/62՗UB^qg;R򠋱Y )R(z> %gK{aG8d,Sh3l U_LAR9ELGlP5F9<ʰ [L'6Y S2Z(]y6Fe|l4Qa7J lF\s346{[-|@Gj2Bş #g6H;OT }il|AڄAX-+ ނ:P9J}p𲣟| `ٜhZO[o 9b1ާ~R?sUΤFnǁl/S;K<pm`Y B ^\G͹ Qa\g~=| ]?`{`$];=²\7 ^?i@keoY+1T/G6K"Fǫ|-𑹳q}QaHl #c$eYs] Hu#0 (j} MUt^8 [𞾉QA(\й:{Nw;08F1'F'M<{)P:t]zW?h }|3v"٪|~o m5{o.ݦ}vz{.ךɼ~(#B~ޑYge3Jl +ʞH໷Gi56|nJJ7K[>9 ߂.!j ;Vsk?$t%F1Didžv |JJٱٗsw%m}u˽B1EE(bfhqUʿr/5{qu`ƥ;ҁ!p`v#WU!t!vs> idNQ0`ִUTӲj #Ku3|bQ*D1*׾= mo(UOa}b4D`f* 3,\~+g[mKcyEIɈugBE﹦' y_ +H>?ح"FȂ8^}tgaх"W|/(7 eEb=uq|Ld `P}t>V̕Tؗu|βh??0LΫ5N0,b@T:ɕ\*˿H_sّu-r7/|g{ &ݩlЊi)mͳh.U`_k8O' S}7aԞJʓņQF1&jUEUsΩ*.(YJf2\LnuWNS(d:f1.gFv^EIRG(Gd+b3ݣ"pEugTz4"bؿ;6lZBEXH9 \C#iI#Pݫ,U#}KdI }znSExhPQ)t0|'Z ^0Zh4mrTue}M'+6@ݼ85 %iFcy1sՍJ W[a.L[zӎLb[~SOw0\Q@id&lx16]j_k|hfP^Mi6gwI_E mK ^vR'B|SuV6~/ύ3pA8{`-bNG5e`jγ==*ҩA&w̙$7caDEѰgXa+NpmTz"ƪtvEsc:j_Y}[2}ȏ'ІƆ~d2d ~5>_2WU':g4ʬ`5G<_X0g.]K oD@}#EHuѯK'~k,xFOo.~>/ W})&5Hc+ۿ%bu7z-c \ pg?`veaĒ!kIO':o)IPG3 !IPl69v # SUehw#וw: Ϲ.S0]`G2׏uBVܪ^<;v ƘxL J3yAHt Dz(Jbn|D,uֺ"sV/h/J> 0}`4ᕏx tCiCc*y H$B~NPre9sH c0P?Gyt@,849Һ  3ClinIL`}qdtmɫwN goџ) v:(_?'ad^J{6߭FCt-qJqYs)ީgLwڌ[>q'*ZeYfEE>e_p9]eeFMi]mɝ3@e-U܁F00Gn\1gzwǞh#! ptg/g5WhG.>Cڿ~9̬I#ؼqxa6nQȓJQxCyk&]8xYmFߔ< ڝtxx@lr?BAMp^Ӱ9T -d": Fd=cdNƛ?诤מZ./[lg>$W.o1 V^<|7{4oA7Ho{ߜֱĸ9`mKgߴȇ>mU="N"]ds 2!4FQݦ?\wMQ<)"-<{CZXD@ў1ct+,)'(`A>70 ι̠܊)CH Ml`9JaDV/ֹƐ*HP1zMbuz{^?/z0PPPa;ǺU^cp:^*jϭ=ʘSXU2x#ғdW!̹ s| 0T:@}?;eR#+<>DI) ŀ2ftN[0/{ @F,@)\Ca$^mQ1P_(R|}U% I)N *#T/MfPa2 ZʋWw88W1ns:?Lwib [#q aЏ0*_:ז-SIǭ;ίw2{}UТƪQ N[*SV(b83Fݪ~|362g7\72'J(EaxǞVu`NNod #3J{G/%o9r$ =moPTS*6IGG"p|w+Ce-ȱ#6UCܑS2NǬ= _To<.3^h0P^Q),r=>=[}DWi[JÈ8웑, 4]xcwr#\BA_ѣϽ!J/ 0n(@4\5Gj#\ӿ]fW:oq=r%֧ﴋ3;J2- Tzk;+ZNg/䖟xDè;:::T !t $mg',%?ic\1Cܟ0^lE^n26ՁJu=^]Wx7aIE `tW^yeb؉m"D0`ÍB˚B(ingl(Gm2F-bd G:BSr0'c8 K<& -LQa F_kr1AiS\Y~V̤Gq@ڰzм(u3/)9U%CW-!^+ iun#}>uadM.;WYn! SDT fGy޾sBW6Tx} #~Q!|#YHC"C!P#0&r`X'ŕap2bW)E&À˫߸:L!J*]sz(5 FK!KûpqG1CaquV,7ڲyF'c2D8ѝ?:I]@_T:uت]?KojgiiP7c"%46.J t:f4ٴwW_Jϼ3ӚZR"a&54#F(K󋅼_}WӖmq20R #)X[)-T`%EX}^ja5,XÚXY#!"蝉@M0jLKZ n(Q#{҇~wbĝmb͜^Na9dHŸ=l z"t&F fiґKxthXɿChJ ~>}}=)C_rj䶗1w("F| Yz*/~ d77JEgPޢ~ʑFNBl \kދ#x!Fr>{?2&,^{}iFX|S451?aD:)hL'+<ǧ j'>! 0_;S=A6i[Q HFIE%:Prxu0˩̖̥= "F jw7O_iٟso# w^0DMa!W{Uy!_'h|U&mO;zzf0-L\~Z#u', ުu<Ѷm|l9`_L}mB@4[J9CMvg׺O~ Ou·WFEĨ{rcngRb$@Tz);5o*N0;Et͂P^T"F0V m'EHw1!{+mRW[}O,F|~w2xds69b%)}R15 C{WH )@_ +b7t(s,K]b)mk0m$b4aTDxP BZ5=ߪjb [t[=%9xaqz1}1. šRn[Fr0tB)z9/C bf>{A$tD34PNJF x,IT=>xAZ&Yu )KE m<,Rz`mfe&F.SaӪt\0}RLa]U71r5hFyU#ucߌd5 0bR8c9(,)AB3U;Wl щU0b<4OOR=`B}U<_n7ڹ6)6RHGhQ\Pd1GKW/kGSQQ| #zJ#W|$srQƱ1R}c݂fJ/em}3ph~/-1*R0" #\)1:my [uXX{A^T$ӵ #Y ձag}eݶko^:?)f *Z;ʆ䚢W^Rd`dG:@ލrU' q=Y#F!j)"v9!]p~s`oCyǶ6 ̍g9z\?珜^X*\7FkHck6WeXsYCaU ;ijP~Ď @ۯ@#fC͍[:ΉQ 6yP}3e˩L%|9:\YuB|!wk^F7#)bEHAFEY* wUÈѼMnZu ۄPGEP"ԏT1bdtadɾKaBx5)u[=ŰojS sxk;$o<.1/478HT \fD=rEx^3k|PoT@ApIgR /x dw5bm &">L5(@HtD%P`q>&"V%YϽǹ~nWzw9.+6~œ^Q1Ljv$$M (UtFMӃ[Rȝwz~v5b1ꓐʋ7Z,ot'Aq҆i?ף6FihhEXPU,*5\I +g{۸vpZ ϳzfgs=Rx$?;>uFYU#C< ?n4 h]ĸ {kiZADx*.4ѳǗ2ƶJWXo3=ce_;|e:B$vTH;~G~ad[?t\07J8Qjspނ'Zxa3'ʇ "FqjexQV\eɁ.[|a:G0zHs=DA49_V3( {-,G:݃Z_FuƠ-=>k%/%xf\dYMjY 6eia@?Q~ Rܢ#G[IV/^#EGQD}wVĨS* 9lF >9~t9y 1Rtܢ,1IݪeP4;@ ѝNM;-rSH|A ePx;FyNP勱޵)eھcLakO^C9;=9dȗdqCU>Lh#FI#5-*xgXzjJy>QP^\ HTZD|3jñ5`(/'w8'ͯ#Fkx?;z,+ISwcRH;Ƹ>!SsO|"}ނU6jmf[=m7Ka4#F2P:YF5BfY83b~czCTX۽і̛He o1aĞ<5خ7I^Ŋ:(%+ƍ"y~.b;'r}Ǚ JG,D'a)8u%o)N(TKHИzm2nd (A'ዬ}0YofF~m)Ob4Tc]U 'uty_ʖ<> A ԟ|KϽs+F3Aj~yWO"ȦۻL"r|79 Ϳ:qRUWc+0"}pv2z9ۂv>t?KEhK5H]'ۘ*#H $1n6r6COjE(H]z=[0qJ^ uYT}we^}rIؓAᬀ.~SFَ_is/zы;H@\1V10HzN'{?_˨dR?t)+\s)R^zKKnAhb^~bw̙6FF!Py),gIW\a8 ys1Ҫt]~A7Uխ[u䪒w/b7m5?T#U.ft"{# m2/!#-׍(Z^K2;`Q'ߞ2hcYqI#[ʄ< e}z$ Pƌ/XZՁ!o;{zّqx :H(1L[CQij(om&d;f{L1" *j] Nۼl;̸nBOEU2 G>b|غM;Q,GXWHEi=WGbf9۴5KYhon1#: ߁b=?HxZZx3=o/+F@V~ wf piUL/eߺljK3zf+={*Ԏp\IPpB{_,;6\ [;N Jwt>|(T_R !毻 5 vU,7g+FBXJ=m1ZSQ0 Œ|W(x p/S6n(  %8IQOy~G)4Hן3U@Uv"t0cFH?QGu1瞥:"6ʶŲhKOWN9n)ގ )F1Y|hNK;%`RohiE1vHO~2.)+F12HX0QUUܿÓ| Et][ۆ (zc˘"$(y*k7)-Iv`^ "!a‡^kҲk3Ys>YDjKx$ yQMxfz ]!<"T0A'hW@UQkR{CL@-P:c6ɱ-^Ӈo_^N"d# 1㧑H1t9TpX2zi^ -h`JYV}oOH ;\Q=J p3pD$BmǸLRl\8붔L|VJe@?G ^1T·ra,<h3Vl]XGHyǾ>S& 3BMd}!R; 7Bh"e\2֌HJJ'5)cm 0/tцҊMy#vHbmR!<Gה5oJ;=~Z Sx#QMmqg+ ZV49"Mei`q/çpZ;ȠXj5G$NN<4ZEH ]SY ĬB!`xZm~VbcbH7[@!"d,XH22DL{nte׀#1 GzB_wlfcy\Z}_ԁwHU⡤F#x`SPꢱzWb9?/<(iʵ]U7 /ɵu+tH)I }3N,u|eoLwJĕ0ӯ2vI,P}=)z? <ς0Gkj|wjP1X Ol=YZ ~~S~2AbGڅkk5 og>󙟥@7F7SFOY-gǢPYܫ׃U"f ?O7=wa@KXxHP49 pǨdxň'Tq.ň86 s.Jg3=0нD 0)1-e6d!C"BLjG-6\2Ks=Q<d'{h-oV?M{tP-~z*k^ՌsRӼ\_yǕ}:zͤH1b>d,Rˌ^Lj)Rbd{%ɘU+<EBI$K7YKiT"9{mZ$(F& cU*^`( |94cjNFŨ5S.s}B /:6:_N 㳙c:afqpE2>ЪkE8"-kzgO#xDc֖R{C |ӟbֹU[gx&OV=ii%v_77q҇cf#scV%TcdNVr.etsq1rè\ ˔)GWeDY1b%<>bR0Ѻ]1ڿx[TaԮ_KY]b<4RNxj,`&?XYeaX6Y' =FfN O_}n6ciN#?Ę^1sl2 wێ1l+~R۴Eh{ msC\Y-]G#='x!-:ñOy#ڞPCkxIZJcN-$k.D8@E1j1ŨӤ=;\8$2>6xAz#F1rJ*GˑqB#zK>vl>RVȣJ#~;yxF ;{dy&u1yuz- Kbh?HP`p+v-:K| $`NjRrOۥ@yiZ4."H__[L ݃6g*<ƩI~cmtg@#ň>X|d,@5bJ_ګft1^1tfSE6#)Oy06>_I+@8 &aygO_( C1|Z#R&}s-0[6VyCC(8U iA#?I˒E4&Y 1*FlZoQtFMQ`_3?'$z%QD_BXYXbt3OZRkߎ2>c;қlDEk)'y |Yx.^əd8 C(Fn!<&@2^4Nf,_y;T'1̯kx΍n-{,3*Q)RBAdD@9U'I<GxSJuL3[!PV8G}{3(k!%˿}`/9kڍ_QL1%8*xdThVp7S8>1vO NW C Mƚ<2c_4CC {*ooʂub#~-^4Bh#e**̷|Ib$!^L<~;w[`Է'/dq{l`bn!|m'ssBH+^|E\AuK[tOaRyOjEeQ$ K*+F^{n8AUQB' {R!]MePfRC|H5ɼO{DO+ kƏ@(ydLQ¹WǾ17Px ” 0:=ߟc"~w]wt%m71%hpNy2p\}NH:M3v8*DmE:)lWeƩOx>؍'Un|{tf<4v c }#A$c]b??N?#ԷhHMOS# =Kӥ׌chƋNѿQ)0/g3賽I>/WǷHi4'O1ZcT12{Q1:8Wefh@Q O5~'e.O9."{zdatpYMwj#cf$) D'<em ;wwqnQi\&߃7̫ 82pm92,%˵~ёA:[Jcd y`n(v]3{h nۖCiA1Z2^cU!"eO,NeeP=c꒙ HMѵs볗r|xaoLJk={CbcRV$9r) t廕!#}wQ趒yelG@!  !0^ ! ,Y>Y^[^wKTHPBp.c$y˧5j4Bډ>"4D*1 "уY?EG/Dy{\+Vy8):Ӟ>ךE~zlZ(M U!ͭ`frݵ1B~(m&NJ7O #p.#ඏ-~Yf^[Q{Č#Bi C B1>ĺ[)tM}By,ʐRǪt=cN,!tKá""@#vsGxt7;'/#Ud`ŴV"KN}C|>*z6 i?( 塑Ҧ<^SfPY62vbT@"jz8\Oh @ g_xH84_U#Yv[@r(zL(R48@$y|BLj~bD;I9Q^EEh#hcæ>şg_\s`f*X@ {R1*Fn#MzVk#\%Buai !|3K҉1ϯ&DՊb?rM§8)4Px!Diֆ9ѲJJ`aDQ!i6+F0{bd#̈!h]woI}YΣt`}e+@l o #Ζzb" &1@8_VAFh9E2}/<&3)^iS`šyKhmz'e ж%dW#)T>RxOr&hIQPH1B:kYB`^6?S bżo|#cEx<5`ާޓrIŎoSc`hc;\)F'n$႓.w q! >! 0ջVcpI!rovJ$(٘ǻJ0 @+-=d " zQ$ )^\8\ `ZSp=FP¹ g$%EE̻̓ᕀyS& 0I=5Y|^Thmml;~* ^ Ď2t't;˒ɐAWgwn%}o͸xQP7]yu_bzֳ§>׽.]2u8pk?sf]7n|*ĐAY=ffY=1 _WkֻqDsgЎAcL#vFktr5r c0w yy TQ([S=KT1*F3sKa?_Q(v^3c/yw"sbmW:,dkڨCPNWlng:J1CX%yG[_kĖvB1:$+VYi5_hO JxԌ@DTY.=!VCAJVMI}v,*q!ǞfZrYUHOTcvd{MM)FE+1C@`mf<=A0ˆu)Beo ԿQc4w *(FlT'qU+'e]:*NGr#:J-mĔL)x즭VG]p@uǛL!(hU,cz# >cRXZ̧#*# }pP-2D'7ay+Je{ӫt4 D $x|<ʄfxhwdcu ffOlAA]{)F{M1 -jrAJňox˂,a41}~ ZXyLpVO4%t̓|(j@ط?f|\4G/87Ő)Fw޵G×!q=x=Ƌ,4{=F6OȂfڂC^QL^DZd<ň]DE6  ۿh<6ƃ#U}$Bq4gi&c /LJqōS&O{10h{MCq!{2Ѹܹ?c$Ud 0c}.c#8 b1 gńrx| e]<*,W\qYlvDM/pUW}co{x_[L{[袋j #9?? ¿ۿ/˕/zыl/QiBy wL7L+ BQz,˄rmU@JQY>B n/e_*H{#~.cXRWX "MWiLcd^T 6SwH1{a-ΗBMR ʊ̿{1LݨW>f8$&,@8ߞLLmi j!F,K/^55Q!mKkm5/iRe=cǫO4> 6<7+Y_I JxPa,l[Io>`}sɘ$M ;{bԭ3'`dž0ǚG]pӲi[:}t!]b!D֥= c--Kb-6_J.P:QߓYp]we (Gx%4egђ )$OIcyiC.xc$\ F`pO\s S}caɏ2}㾯;sRS?'9&Q E@ݱy$b+D1od| o'h3 }iF n:G6Asn,f($PDourp@Gf&cm=&1z:s Ka6==͚ {3ULj6!IuD@Ic̰\KWFwU~z Z:5asb(FtwV]@^JAdMV co)PNpruh,Ky2NucX:XQR< )F(m?&gLGoĢᦛ2Kޯ>yH{)_jm1i,`A7pF܄0%~IIxBGM0W1>)yCݦ-<mϘ\7 0#)qnd<A}8)!J]3^Gc3-*F0Ce{ZeGVe] FH 9>oٗorMkyv4J1b>CL\;2 ȋ"ygX̲K6rِ{01hNOLtcj MJǀePblLX `<޼ح  >,` *@kv gB|AE`[Q[ imK^bD1$ R[a(F+LS {?,f Nm %8*F^b{o͝٬WU1)F{, E+xLl2+M,oLWJ!u@o^n{o1<νc(zvz mb%2x,ڡދp3Ýw֤V;!*B6.(U_)!qCh_5cSEq[< /1ۦ-·REhH9lvi[ q@#)F0s@h3K~Ic~N5GڎEЂ+gq24#+Ʊ_J\r$y~9XY^Mt5&y=l;!UF7G OG11M <ҷ(yE4-ˑn!V[L1jͪoNx$ ]]i/ش#¾V-ج:N||Vwj,=~cdEvܯj6""ދ!#-DtJ_xD)e;~jxZ;m9J,XkJ2yNYi7  Cmﶍ\WƶL1 Uf2W+6{ar¢/7.@*#b3|w zׄxkcHt^y<'g,aQrkWFtqpICaX8~K-#>Jߤ\ 6`%* Bf9!<|ϡCٻz+ uXơo:I[uvY1Ybh-!!ˑPE3QO!%\L * *Q1J}Zg0 o$BE,05F-g9F0KKQl 3[R~=ڡw,*f8^B,F*jLh?~$rJ>/b?Ώe_-e~XygQI\>,򈾆"0xBt-I|? @~1ʳGnJ@Y/Ƙ>!(xFXe{]pq{ҽBB-L}}1`AV+FEVPa36VJa iC1 2tMe%35bECUPƦ@T1Z옴-*=3B(`;N!0_de!~S* +폊Ko 6W_\(FXoEWi#!O 73Zi]F:sOVSh8Gn~m|~7|&M*`lHjy{Z<~aFC0{R!2 *ecU)VGǰr}H>=VR,</t"(|qsy1B1ty >C R>}*F,D:d8pxs;tϷ3 ۇj-[Nrz7)\\$av#~`@4xlʢgiv?$Q~X!}#b׾O~2-YIЛ +[Ŭ!,Qk`H)tסц!0~A5 A!⛗a9FbԒy*Wνa;Ʋ 8LRi!+i2 枰uDB`zgl>#I7E fddvpe=4{{skjȷ al^ac2qRtŐMh1iK/ު%r[Jq,TP DBŇhQ2uaOm.ctp-1]M8L\&+4>zίk x,i{ITA_v yhKA1}SzGd2ǎX ?^8#| BBP?|({=9V4EB \ifF֕ЇB ^u;KR?|m6vSBJzq"Ǘͷ~ !2: /z9Ë ;o 6DEiG\qBsm$@}dp;f%['@ZWa^j/m̙1[5ڃ򛁊bt#z ny)[ze =F9+%]REid ͶS:tOG+8ýeg L}m` Zٯ[+YAf1j{n;Hig?99FTb YdH6"V0L>c& 82t@e0>(t OTf# 1Ѣfk9- ,0t6XFۦ&-L-ֆ"BD}KRXBb=B8})& @!B)>>(8|c} ^DX-Ie~8b®]楱R Gߦ7׵eۗۦ"sˑ][½O$Q'ywX) U ʦ>$|36ixr~a5sTcVн#mGۣrb$I bS3 e=F1NNh-EYJ~3z7nb0Dž2GV{#B!}-y)de/i6uoD @, UiEFc‡ұhZ+Y2,>\ӎaFS*Y̛ NyX=-l0fF/R[YwOôώG1tچH׻䘮A(#cpG\f2`I@x $\ޕ:M1.UƜf ^{̳#S$~nSIAcC=pŋI N)5  "`<(ƌ*F<[V#I'7lä#[<2x1@1ByzK_~`K56y@;WҬ^{43]p{‡lcmC&Ly F+z̛2iI'N^ׄ&K9FmݵB/\G EiuaC7KF#ĢppU܀$D J(3IQV~TfH2;BL1Oв Fr!&'b$I7?n<6^ѻ ҴYs)FP=˷I{a gZmȒoBDҳGP:B > )D0m_K]4]\ݐ0HrW'9#?|%lZ31Cw34^=L tv$xЗ cY@hH YrWh@ȌWҼBw.<"aY -Wܗ83<ӡ~tg|#Ib mQ Q[p~ ld,HR0VJGGy6{ԋZdH'IR%HyG 7ZHxu-w001Ry6\vx}^=f$H)g0DbXmJj‡ㇷ\S(~0=;޵>FQ122T1GP*_hs11:mS9FA[;7c1Dգ7ʯk[m(5v=T^^eũ; KQw*s&7pG1W,lrפãPÂxpFMz.:)ĭƝ_5Mo -;E dM^/7,axEx;ߩW5\W]uU3'=Y#}[UP^FG#ڢ/+|`E͖͙( a*Ё"ai7XSv˓ރߠz6wMI"P7hN 1TѮxKNV_#p3mM̻k+-͋mג+@_)FEDO(}ZR~یH1c3ɘCdNh=zCz@%vVT?"VZ SʒWʗU5chb2z׻Q=$2ۿ{E?[Vx=u3<Ľ=F`TL@~^cn̈́F<DE =/fg =KSQ4RnI_LU*/-Lf鷯>&}޼,{0gUm4\ѡ k xDk Ѧ -SH#FOb&z½}IMe2F@@?/]%| ?%DLP%,4B' l{8RmXw'mE} QV'3~(/"D(d(F|w3|H$З0Ջl܇^ňfU=5T D1C,Dp7 ڿSccURd2!d5IGg 縗Q#\Q7@ok&LNno0\*̈́bOo$ȸz=BFCDnj}M̻FA\V웙6Qɷ 4S>A=ӻ_b+*1Z>@H=UPqwҦNPK ~pgy#ⱺG_wʥXd%']wAsԴ>Xj L>JiQ12J J3LLYE01ԳfQ/+FXz-HbPAxV=!04A]2_@ ;81Z-D "(Ǩ7[|Am=b01k?ރzRy,*Q1Ʀyyxc XpKepfͶc|m#oci`  0n {iqyitO}O= fv/}Pj%@Gkgh+|a *P_:/`M!];j7omh2tDQ35yPk|}]@U/(FWf wA'i*׺_XwGQf.?Fr(r^{4_﹔49F<wqCy8yƫau)((/KOe?>Ű:D> TG@B[b!QB4D(TK^41,zI߾ԣTNI#VUTL :<9cK2FeG@QE3y4HC3ybS4S#a楥2za"ς/#ZM E 3m9SX"tU=F`A 1[@-*٣[͡4H1Jqa=mp!]LKo[Ym!(E FJ"zL:-^AXpM[HY:@joU3Qv;0bҗU0TA0ficW xvU:/c" awVu4,yE \ }SH1cD&>ᵧLekJWTD[$dN0VO$H)3J?fҰM\:bdEVW1nկY-:\4=V,G1ηEg}$8yE-ba{ۜƆ-vb`8eRڢ#+ 6WpiKe q4/Y4_sD| 4(&(a^I{zfR[]҇R_)!i~~|(xDy,y ԣiY7#e>hȼ oj0{+EAOFʐrogcPѸ ^D47/-K>9/en]ʆI(0= }[t=F(FO%[sBT&T=3³5пH벞Hň1LG,usu Y;V9CIOyHHfeAv c$EzWFG:!m'_Ucuz}r=*FGO_քAx# #t|A,RR.Y?]:{8Z 3"@ӌ75)Xb ~yq/O.SR¨,l[-gflie؛4 l4S7[z$ 㼺yDvb޸^KZD @1BJla l۲8^bJK¥G c[t<}O1nѪIR@A26`m7W.֗7[>EUGqS/8ND8UGZ5h2ϡe ˨>i# ܇zR(FnVgҤVwV>c {.DUyN)T1Ɗmabeo4@#d=.\bIεsQK{;k(IrB%A4-?7݄w{  hhѪa(E6`_r12wǐ0Rj?n,]:FB隕 (C4yae FbUD'}=-R;dD}X]#}|-6r=*FGO_d=#Ybb.=!fK Xƕ82R 12I֠H1"\#GRܡbk^,|0v$iHGG˿ztREt@CB4O1b.csʗ%NЩ]E0CXrN=F܃!yxیbD&C87@{//p{w[pi!N=nymox`#ֵ3>,0_J90 iQ` (zf8r%ҋyFx| 70vXmrꞪ7 Yf7+6֣yc$MC*tqw<@ie` a\` 5J':IǨObbdcEtOGc40fr ^If_K!cD!gL,+$Y";!8ٓlnP: zBDgxY/ߗݨ0b'BH$.L ,w1KYvhS#opdzԂ}KxG6 ޼hW<87kvy^cYUF2 @(( -Y,WSɟ([2 ?R}xF}Ӽ빦y[7WB鰼q{I?0٘~bϲsNӧ): (J;."l=蕕04} pLT/XQ::BQbT> /Rt@Bp%-wOg~qҩ><#R"V`yW]H% VI#F\-UX28[k\t~1ZnrU:sC&cD #ͽF@qyQ~Jwǹ4AVoN ~ODY 4DXer6̸#Մ(P0jLh[#^/H핈zye)-g$|3^1}Ff9CB,Y ymJw>0-zJpr[{я#3:7<"L{zEw P ܍sCrW _q>boh؀Lq./?P":쳟 GzܸBIK]5yIø=n޼P:wsx4'MNǥ4ztz{rd9WUX*FODh{sD BDd/4տzQㇴi1 j)ɣx) -h*+7YWcˋt="O)oَLIнcoƕҚACo4 ;̛֌bD<Ũ^HV= g`|/F$|“FxP1:{%S5k* 8a; Wheڥ"0Xli 4'Msy\d&TN#c>yaT;Q'+r^jD|FXqM|0zg]y RSJ>ȔZ3go^Ċj eMe1/|e4s7رᤉl]T:c WȈ<9*Ee 4yl XLrO={5V'?9e}Hc"6R|7Uh(F*3]/ B[E0a!<5}=(cC3vZW^= _Aǫ1LyEiDL,]ᢝ2:t??b+AwyKv xC9>LQlzFLv^l6f`b?2:atKS38*Fz#Y^x.>ZQI:vkE\{*rtJyo|6q)&aoeay߼2bѼ-PTvbpO:"5FD#| ˈOҽewܐ~ʳ"I{CbG;9?~i!s7pB#4O=4)_ y|[f*QXj s9B j\n.N;DJsoR3yߊp,mfjL1d!ƣu9@[4V. O;iG{^kCx˫/"Z<% I_tW~" h{G&J;jh ZM_k;K8cd$Vg=1R潏0?ۘ]4F71zi=wDv#Ym#ojltxm;lʉ̈́4W1R4HaSMʫ#e61RzG7_w_={P1Z^,7zckl¹!LbeG_yb4e.1w#aRFDƗs=;* )a1r5D AI|5:f)rxb]r%a3̓!`MG?jӖxzj-jvG8\[}wbtߵV2fHb1nŖ0l70Iv!piiQ8.!7*m6olez`-"Nz&68iIᶱۢ5.a@3ߒlLɚ X+G !"~+7QG> Tbէg07I@R1B8Fn"ϏP輋|N-_`Ũ^dڴO+?q.Jz_y4g2gB-.Rhb޻\*Յ/6zl/W\@L}2DO+J{}x =ii] ?A!"!1`cZ2jp$ T7:@# }k#VEGdzc1cŧkKloo*݃}ղbY(]ss^Pۣ2Bܻd.#AL)FhoxΟh E+dIcȦDATVTPAed@#" 0 >P#C ȾF!&`CM]uNWusUN׭ĵ\o#JޯAtKu\=c20w-k\pf2&GQiRQ?jJ5Jiv S<^v_ǓwD$6(H-,y=s]}nWdO΢cFw Э~f#7Dhݼ|6qO}hN0sfj(~LH!*G3n>3F1JgIic_L6_Ec[S_ E7&(R*raBkl?OS6$=MKF#DzZT/ PJ2!}JEb;i0@ 趣QZm=39tH/F7@ ],yfN+)ѯvihP3xy%ƓuSS,O_()2v}KNgbR)1q)b[C%J*܊#w0Jl֝o&/ݥ+c3F4;W$-y>Nz,Y*F&OW+QĖyλPDuh)H-JE/ #}jG> N:7e)՟X8^U402nE}Nu>Lt]Ed498r~\Tlf3X9]+kǢgƞQ1 h.F 9͔1cӗ}1_q+; v1c.3Fxk~,-Ltb P/y"/5Db`D }_qCN%gNٌ_0@DJE׿ G$yJαM5H7]sx @[(u/|5lMgG3HuӤ=S +.O =d3x$ ѾNiȜu(i{w]L-M+g]guFd.XtGYؔxj\msћKȊI#-;[Sp8IW_o#kfF-x4Ev?6 $=sCI}#Hn1|Μxѝ2%m>[gEL,EϚ9#DK}Ӧ|6N;-zNNω v\oxe:^U+;"[G?_w]vJLM4;L) u })_o躈/c?y͏xЉ vtBiz~$gL[75.Jtq̝Y3.v333P'Œҏ|7>rcOߢso A]~kE%ít+It~RyGN:B7TZ#E(DžQXN:E&E$W]uU!̋/VGuT D.ѭ*ֽ.1Z`z2%?haiϹ30Zov0YsҺeӄ'(7*iYv>-bed袀nygՌSm>;}`D|{h%COKvW8WvMoR(~>~laZ9-y>ӏp߮qy:u:֨a;hyO{w4>=5<<fzN;枮ӟ=4kу]t:Σ}cOV{챇 Zcfe.Ҟss & \~|hE臦[niOeZ EkQE[6TGݷS0JeE4z_￿\;]XIw퀴-`vOR'7B|wcђ|9ǩ7+8=^v_u=gԯh{UPX||&V X|c/Џ\3Ft> Aq'Wۍ,LOV4`m;֏G"D\ϟN9藷cg>VsbL5\3>N3L~z?7D]ߏ_oKeZ ۣVoI{&oQ?X+Dqi|ˉˬ`.es]WRWϜqG%Y-sO~9~!EK;ʷo3Vn$}˕\c:H}uN]qz6橋vqm [x"vz+/Hn}D}6+Bô+EkGϥgvcF4WBDЀ=4PJTe/{oOY+z;$`43<ߚwǷ9x{.-"A]<- O>MV޽5%\qC>޷ew]z桮;袍:}zӹ%I꟧}i[t+=񶕶ZW-UDwD445 {lU~y/k/t~d ]ox|Eo 8uݟnxPocsӋY~?i6 yM]Af'Du>s=ђ;+1#MzSx3IMRE1I+z=~c=딴} t [\2o&V>]Kc1 [5׌NW;9QVTP[]өcX׭>maw+~#JCxϖQ.yt2[NZԫFU=n4WWѝJc"Ϡq{9Eή90xy ե牒ɬyGIӌ":)u"xC&>&.1Og葫88 Vγ$pOW_jkTK[}qHR"%鋡޼ٛ㰏#ӷԷp1:>jU2]5X}DuG*EBOZ7?30׫dA%:Yvhp\m[Q˴y,M-\ e-Pv(i     M oƈfhPt-jiFYh]}4̴A^})ksҋ4Yy啳@@@@$@-5fm&^$aɒ%]mp78BVcD/IHui$K:P"zy|w7wIIw0m?t䝉K;     30M[4eG/[ՉnyKsN:q?1tO<я~w_o<jvS7ճ>;~7 tҮ`}}1iF/…     J7'ZG-TGՓO>6^F/l}}r\׿uu[/. d… \i@CN;MW38#ԼMoj;2~'>9{7yT:b'pg?CK|8Z+VP1TB`BtߘW6 )A6@} 46<M#-      `` onN#M@@@@@|w >wyo8     F[A@@@@{00     Fޛ&}00p@@@@@7 |x'&      ```@@@@@; 7M#-      L$K-[LM4IM0!<t:jjԩjpi00 -]T*{@@@@@3$ZǨCAH0$}Q[$猉SU8.`ʔ)b./"X>w~& _ײPun[\m/\1I䱢E-n+J獎(tyɵ~vc!}ʧP\84J[Y>TQ'٧)~ B)-κMYa$QJ848 WLR:ylb`dKzznK7-Q9K OGΝIѱ/+VUI$g(Pa@@@@%B-?ВC     !Q{d( A@@@@C5" Q:H&G  Mg ;C) V;<<ErGj'v;;i0tg(0IRڲu^;?o޼rl4BO;N J+=F҄xiX!P2ȈՋYZBmA@wƽa5nVjyYM,qv@_hG;#JEgɃ;*004i1H ЎvF  θCwU(``Te'NTCCCTNǑA}(A@q' P UPX hE\,d$}!ƁK  ;ވ`PZ/Hv/`=G4-f7*B!@wƽqPߤɓ'CeRH24€g c}!'  D;ު`PJH.@m``T     R00" ]6MGA@@@@``$EVHV2(NRuJO?EYNm%O^mٖ{YU_eۿt9FD a588hyCi_*I֓)"_>ko>ٮgi;uOtٮgi;uOtkw>ٮgi;uOtkw>ٮgi;uOtkw>ٮgi;uOtkm>ٮgi;uOto/~ E4Kf%;-@%Z!O#Yfh(CHcmq0*HHm= KGs MGǗ̹LشcZu$}LrnzOeT]ǖKKG˭uul41(OI&˗ףY׸:U#agyfwjM6Q~w]vSe%ZSO:S>:::(iDil-mCCbB%s.?y66m>7v]4_n_4Ӽ&;6Sm.:U#>6riݬ*{HYڠAQ) 8DO?xPT4cDrJ! }.sEiflKCC#ͷ"eܶ984(v.͑K/4_23ocӦk}SKoE[O3˹mr뙾shsh>oSu=ck[.]./ʫGרYA4cbCL:5~SO=U:uԹ$ JBYETm4'984|+RmCCbB%s.?y66m>7v]4_n_4Ӽ&;6Sm.:U#>6riݬ*{tzGqQG{/ 遧BZLtB QgI;<^=FE3D>_4ֵFoEʸmsqhP\:#^h::dgR7oƦMޮ&+fs3}0}*eVzf?\\:_n=We/͎5nVEʱ4k>mW|Ds9.So1sG/@eMEݦ>Su_$ۺ|HHm= KGs MGǗ̹LشcZu$}LrnzOeT]ǖKKG˭u%H_fۯ<y .@-^X=裱G_~zmZnfm◻nqW^he+wPTE4*; ~ֵ9SmnzĀKG MGǗ̹LشcZu$}LrnzOeT]ǖKKG˭uUe'+΢1*J* 7P==FqM)ھԪCu 7ă'xջngjRdNjBM; (I k\ J6did[    "5nK&@@@@@b#FvhumKf34_1\?>?{yٺhFkS&NR3- ~Ys],2ugN vLVB}04f(PS|`3~%XB/_]|YTEƽl W/< 4BFҽ4LG- ~Ys],2ugNB׸ʅA` B鶪gg>ms2V4_1\?>?{yٺhB?71F7' =Ulڒ_Rی B ~/.LVb~s?KǶC ^܆+MLQxfFFF*yQ(~@@2~IL OCM?"F:.߹tr0=5.nt8!hꠈ`WBXlH/ @@H4$_lXֵ׵w.j[kUùV#:^E ŏ*bM!vZ,(!Ӑ|aY:^ז:.߹tmja`T Z8qJx)?5͆di lOCņe]{][ZO|ҩ*f!h(^rBGdS2~Im_`yB!ҼΎܳ u'\s鬠Z|+k\,PLeB}4EKFJWn9!Ӑ|)Gg{ۯ*O|)f^N*,49 ~\2~I9^s OC-2AI/:.߹tzֳA@@@@H6*B(Grp6@ ``FEH     ``TWϦIhʋ&:5RZ̥lnc M9lj1qh=KJKwɥ|Еǎ&SKk9hiSZߴenkݚQz&{.X A(i_Sn)[rũǩZBSk's[uL\:ZOR~k]r)%t%4ɡҚerN6Z6u'iڔ7m۾>lSy;=PO7pCg7)ʗ_KʳM,-]έɭ'M 98Z;srpcz:ҕ֗[K(+ُM ',sڷѲ/> MӦiܶ[5km{;kF;N;sL<r N hDtJJhcaCCɩ5mlOBӴ)o2mVukZd5/x~/?\]~S>+ԛfٷDZE4}*031mskritK U/N=N-Z;srpcz:ҕ֗[K(+ُM ',sڷѲ/> MӦiesjS9@c=VE}Z`A5@\QZFtJJhcaCCɩ5mlOBӴ)o2}5}n詧Rҗ.BwS,':jlPT-m0 5RZ̥lnc M9lj1qh=KJKwɥ|Еǎ&SKk9hiSZߴenk]~&3k֬;6N4sY};{xYHUIg(~ɦN՚>R Ri|lnk M9lj1qh=KJKwɥ|Еǎ&SKk9hiSZߴen[5k:: T쓟?3;L]uUyN[9hJ5~Q_$:t5RZ̥lnc M9lj1qh=KJKwɥ|Еǎ&SKk9hiSZߴen5k5#eԔ)SԄ C4C    !5n)`vfAhhuQӧOWO9ugWh3P_g|WU@@@@@Z30?袋ܵV[m6=BZ30zG+^ xN}H     %КٳfrTK@P)=+ʙ+ ?vhrl.\USվ_lhܹ*Zc=~ƈbя~8O}J}a:L`49j WUM 3W.\4*emYx}~u];kV'\y˯8 CK )r>Bϥ `Fžm<o} 1EV\15޹00 l^l;22R% [:]/Tޖ/ =+O|E&Ƕ"J-UƳNPq[s+"@1mPDR9%+ʙ+ ?vhrl.\USվ_֜*rW_˽ &hyLSSOcsEi#1㡤P}_P9sEz|MͅKZ]Jb_6ukך1:<SOEt z'NTCCCB~UCB}K;496.iu*}Yx)^j߯k?Wo;q=!D3)79 Q+j WUM 3W.\4*emYxk׸Y|anyw߭zktRA&@qIz!SKKI)T_W5&T\ч_CcsVҨؗgm^}j啕k=_~cH~ `hR¯jp=si&%.XQ/k: _MF/`'?kQ}kC7p/򕯘b@@@@@eZs+7ިKu]q3Ϟ=[[@)u14N@@@@>BmHgM7ݤ/^.u衇;?AuKS>sZkzMi~b@@@@5QC{z'{hn/ZH~j׏ne~aE>:K-YDs9oW4gj@@@@@h':ivf+髏mxttJisrh$cKKUHY֚U>xswꩧGeRxPDu7dV򖷌]wUm&{^f<`|Kϝ\l'y68bHmmiY9.^.]BlIfš%Jjdٙ9wz>Ic+͡ˡ[[//Wa#.{+UsڴiN|͝hƩ3.6lW9|hd+zW=+U7/v|L㈍C#޷նfܺzY~sR7w M%.+퓙fgܱr뙾ؖKK'6.F2fnMnocüMUF3H;S?9{Y#]o~'S%ig:H="(Qyt`Lc̈́ڠ<Ic+͡ˡ[[//٠kT}͛V7IJ e_H)g(==Gs\9!Q@٭:X2j`` Y\z4٩U7/H|L㈍C#޷նfܺzY~sR7w M%.+퓙fgܱr뙾ؖKK'6.F2fnMngQv% vi^z=ꎜo*-͝^{X둦BETfhDi.ܼ2J=l싅.>ͳ<F/om[Oʹu(wեndKJ7+].iWR'3̹c3}-.N[)m]dܚzIi?]5, y/O Y {wxGhUnS,.ߗI6ϝ\l'y68bHmmiY9.^.]BlIfš%Jjdٙ9wz>Ic+͡ˡ[[//헵MQ6z 9p嗫=3EÙ,XD_Kt!tw1Ǩh򗿌/&~їeuQGuܡiF}{)UF _u}L䶔nN>Y>jCsri?%r_]f"Itv%}2̜;Vn=WRpr餱H̭ɭHMӬ#/bDY&]pjGU4o[oKBVlZmg-߭[o=4.NS@׸ x (mw}.\>?}쳏:sՌ3pn :     C k\ rP;o.    P_^_V_@@@@@ 0*     T5eE*hbeܑ!+Ɗ^TmSSˌ_J״FZ Rm/R&CZH9U#eCJ䓶iKKG:^Sg ӧmi;Yʖc`TXϧ%)}$eCBWB3'n4ie?t}J_L~sGʆ'm.t>OYv$'pBV~}<ɗ˗ww u~w暮|ʳ˯]QOG\IʆfGn;zi>SN=N-3~)]ӆ6i1HfH4i"1TᏔ )]O6].-.xM}94LHgiu%]놔Z* 7ܠ<%(-=::jʊ4I3DQB/QS1Q eCBWB3#í3q25mlsǡo[iI/yNHِ5msґC)k[ڎ~6Kז'O JwGNqzꩧ>R64 s *lHJhí3q25mlsǡo[iI/yNHِ5msґC)k[ڎ~vV>[30gfϞV_}uju+Jfk!+Ə^TmSSˌ_J״FZ Rm/R&CZH9U#eCJ䓶iKKG:^Sg ӧmi;yY,ohΜ9{ɺ6T6:!+^m7ie?t}J_L~sGʆ'm.t>OYv$ZOG?DE,Xsmumݦ/v@yfb(}$eCBWB3'n4ie?t}J_L~sGʆ'm.t>OYv$sLP׸yk_kV Ydz!:_F,^"eCBWB3n4ie?t}J_L~sGʆ'm.t>OYv$Mm1Y_hcql{PV;l4@׸7>^@@@@@ .-^y17\j66A@@@@!M rqW*|jR[nRٯ~+.3!9r-]!zel@= zۚԩS߮6x%=zC)i>VKpR7ޗ:ϡS=j'.fΩͩeXdŶK]/\::dR7.uѴe?YmnfqgSxkn1c[{F3R3g)C;MYqIh&mIЩZN%L,sRW +M!Kݬl4mdOjjqġá!(o_"~6Ŏ5=?:j笳wyq 1^uT.\l͊GB3iK>Ntmϭ-gfm .-._tt\Eߥn}_]feiS'~Ug}W,H' FI~q]8IDAT)y\IMu>uf͚Չ^[gu:*u:b4K:KzE4Pr:Ν\l͊CB3iK>Ntmϭ-gfm .-._tt\Eߥn}_]feiS'~Ug}W,H' FI~.qd])}-_\}PxzGŋ?p\w4P:b4):Ν\l͊CB3iK>Ntmϭ-gfm .-._tt\Eߥn}_]feiS'~Ug}W,H' FI~q)}I&!XXeUk+J?1Rt;tfҖ .}58Q{q$۞[[42.u\Z\p踒Kݤ.͊FӦNdYO:%gSx+FqΜ9/HЧh\bu:.ub2 ͤ-i\:Ukpؓ\>J}8\j?84|q%s}I?]|uM,rWm]ms =.8t84$%5KϦq{BѿK;nag޼y뮻F/?M?Ig(}bӥnV\I[69tGťl{nmI?|7\l>phpiq¥J..u~HR7+.M:Y>fAz\>qphH0Jj۷5n?R[#f%zƈ -Y1t廄fҖ .}58Q{q$۞[[42.u\Z\p踒Kݤ.͊FӦNdYO:QM6`>i|MfwR3"āOq@Z30?[C7lH5ҭj]#{ɓմiO?\:(8    P^fHPG{g_;|;Dp@# f(,X>`u]we<ѴRv]O"/&ê}gSnj1k[J7˞YӶv]w[ms*u|UqU'.웡^f( zҤIGѻ V*zRӾ_vMU`cϦcֶn=ܧmsUl*,kN\B54Blz BO~1?|_lf=#4wcB}+=_vMU`cϦcֶn=ܧmsUl*,kN\B54B'ozۚ[/xf̘veu窵^5xӌ45K3EQqfFFFTuωL|eV6l1fmKf3}6ڮk<>nm>W϶2β%t_C/4f׸ok΂N`tt4uPD4XU |ˮzU`cϦcֶn=ܧmsUl*,kN\B54Bl =cDT+JI3Ft_vMU`cϦcֶn=ܧmsUl*,kN\B54Bl E{wEz(݋>OfR}f_kB{6ut>m~pm5~6WRg[UgY[u?efg=մhζnߙ>}z{CEj 4EE3D Ӿ_vMU`cϦcֶn=ܧmsUl*,kN\B54B'ozۚ[o=u饗^nS/w߭ZdIUӉPL>s[t?+Mү>/_|5YW탍=:fYRYrM?OjU㳭:q ͟d 5/W3gLZtڗ/){6ut>m~pm5~6WRg[UgY[u?e_aԯ~~_|q:m.~7wroCДA2>mmhߒ9isJjvnHʑruK{ࠊ!ϕh9WgS.iġ}ذpkals8t.+5s;6uLzKGQ^F̹ sEå.Nr˿뾫_M]\뛾4%|F&F:ڷdΩEzڦʶ^Ԥ.^zI'uNڙ2eJ!g^uTh9WeS.iġ}ذpkals8t.+5s;6uLzKGQ^F̹ sEå.Nr˿뾫_M]\뛾4%|F&F:ڷdΩEz.Y׸mèowyg;LQC]JivYA ۦ2e˩AZ.qh_66lh?\jp$\::KJhjƎMӦz,sivp僩/k}W-/z;M $ѶѦ-sj6鯫v5kҙSo<:#UZnj7O z;*L>R9Me˜QS\оml~ԵItt:Е:Mͥ(/Y\ӆRS'_Ru/ZM_v>HgmM[2"mn=_ImNۭ-^XEͩ78~o[uW-آjy(z(-QRe˜}QS\оml~ԵItt:Е:Mͥ(/Y\ӆRS'_Ru/ZM_v>HgmM[2"mn=_ImNۍ}/Oo/t}P5g6{PqTF̹:u95H%KƆMK][ dKGǡs ] Mرc\:Z2e5m..u|0u\%u]]roZEo)d|661Ѿ%sN-36TN0y[?W=#BLy_oLQaR2jlp -8/r6u.um58lm.%t%4fncǦiSosh=h9״anhInsuw˵鿫k}hhSG9H[V;ԯz/x=T48ԁ'KR,+Z7Ls2e>˩AZ.qh_66lh?\jp$\::KJhjƎMӦz,sivp僩/k}W-/z;M $ѶѦ-sj6鯍vk\Sm+N'@@@@C k\i'"h Ԙ@@@@@Fv ׺俛nN#0IN&?^mvfh     Fqw'~,lV0,mCC#~mnz66uxe]-v%Ffmi{\\::n\7i$%sƭyL.vzܾmܮVҶ>ns%X>-d|5OV{T8*]}c2y睧{oZk;Cs9jҥS[oufe;K/qWʒfFFF_Jvh:f4Z (7LorгѰ3!cCB3ÔxmW.Nki{\\:i \ˤ}O_}n߸|7IY7y&O5ED׳ӧO/x( #-q-RoT/2=4pX|pˡϡaٔ1pгѰ,4*m+.16@oKqsҾI'YTm/i?o7nFZ|LZ_1snz66uLiivJ[Uٕ#M$cGOݖMZ?o}<&i[;d=n6Jn٢:A]ۆ00*뮻n9t PSLQG}tCCC"ё1$aCC#->]&9M= :&m 4;TV-ӇJّ&p#?nK&j{Iyܾqn˭of%l+]/ľnwL6-C4Z\'z-)*]}.첮c掾_i=6::C#]}BOù>շ-u=gy&jܹ/y|&i^b`hi|s}Tv@@@@@00jx#<00gdUGh*r$Bo!!z%Fl7^AK)i @B%P[~@m:s_ODYA@  oX. q" %*_:  `wI }Sn׸=n+ކ83"0:::("84XH   |gO7oc ߆F000?Wff8BYMamrCCC6ѥA!'fA}SF o# ,\xs4SD"]^C0 9wr  |߰`&o8Ӵ!OKSǘ)TJY M~zv4€(EYA5FM=ճ5# aB @@@@@00gkF1„@= ``Tvk״b JByo}ɡZhbLYC54rC-4WkbO 7W ?%4M뺍Q][~AE@CNa&Fh1e1-}Ӷ_*-^Uˋ=y,dL_%4}v)X/B'\ʛ;8.G!&aC#Ękhdq>i[/[hqתŞ<o~Jh>/x xX˯\-E= Ha&FO\Ękhdq>i[/[hqתŞ<o~Jh>ҕiEL`tt4uPD4X!%aC#~@41,ơ?YtO}K?yܫj{y'髄M X\#:R694BKcbZMקmTZlǽj_{XȾJ)i܄m Њ5aĉjhh(c*!%aC#~@41,ơ?YtO}K?yܫj{y'髄ύ.Ϋ@qxi?_9lrh/Sb ͟,nܧ>mb ͟ƅ]ij~=ׇ}&Ɣ8XC'.O:~ o#x     L#aF<&0&J&H  +'PA]|UN ";jppPEsG#]9P:}k]y C _ ʑ@@ {)GNЮq1tZXHkT+E'=KZ{GJ߻6 ܪ@>u5?C5;4n3i`;hꠈ*`#.K(՗@>u.~^CsEiӌG#]>P/:}k]k10A6'NR=r:  G߻|,T_u׺Na6I`޼y^xa|ѠH3*x  ߯ <:}k]]B7B}0ڣSi*~u@%]YP:}/?CŌQ= C$p@FxsP_gp~g zA@@@@``     &Qރ0"$AV^UP(o{j:)uc %(J-Y˞QYb8ࠢw/QNmMMg|[0|,E sז8y` Q;#8bGmKMg|W0|,E sW8C ^T WDci(HktMWP|%@(|g׸ ՒhꠈoKj:磟)uc %(J-Y]ȅO``` ~(Q1mIMg|S0|,E sז8yC L8Q It-,~ |%@(|gvw:k8z>V= wPb4C/@955Eo:X%Ж] /8 +e+`lrp"݇KSJSY4=>}L%K(@Qm%Pq'P8NCm6l:ǧ۱Lh%K(@Qmܵ%΢^vl^h$ ٬ @@@@@  в8W't5k6mz^* %T"u;0uy=j޼y{キk-C@@@@@(JWy7tS d=Dջ=gߖX=K%k\J>e)[^{m_Ko…t^x:#;˖-g^y5uT6WO/,hfNA=dx ŵ~:SJ%n6kwȊ>dz췱pgJе+2L'OV-RZ|uW>nLu.ty:UVY%=t1;aڠr:$Kit)cM$4Ąs Y>M+meԸ9?m/LjKڠbrQn+ o.d#4B-$QEŋ㥾Υ#߉fh68r3Zn_$4CXwfuoAvٷk&ѝJQO#cen":P?x ΆRݠoΡ0& ~h.Yh3YPP 2Ώ}ѩ<펪ԓ.L\ NK$D! BjrHfpvYhp>4>n~6d+׿/o|C=jRԎ;F4 C?$"γC9Ot* E@gr T@ #Z7뼪q+]K[dITw*8@@@@jAչ++F4E#tO&--&L{J@@@@@fhhK`VPFF !70      v ~:@ ``.     F     Q}W't5k6mz^=Knf|@m%/yZ;Nuw;՞{m5Trs\|jjppPm& .H=&pg(z'ī^@_z[nQo{}gL>=+~ .Y`w}k'OW###]ttvaxC` 'w?N4|Se/DΟq=PglƝ袦sgv/[oyϣ/˝%8կ~ ><ԛǔD`_pv+쬴J߾>>8;яqLWQv;SLl;A"}{`i 8lw}q?` :;s z51]}| _D?"wVYeNq6ʦNvYlYxE X]w]gW|:uj/߉~ЗNb."]y:/ÝeAZOwcni:[lEW /]8Z%/iDZ}cߒ v|衇ѯq9JVDw5ux8hV1i#q饗~^hQgWtAe.{'ƳCK,R?묳wc=vk^gΜ١ dtMc],;/tQ_q7ͮrԓ5\ә4iR<71Bgrz/})u]>ۡ_` }%I}-sq_n f{~p҅u;ѭq?II e<44P[xDJt$=Aϖiw?n 賬_hTt\#<_IT4P!@}żVo'ύZS=kW]ܪ;Nyi_DڹOϪFhYE?&cQl+%vd{T4?pum}&^EeYU$5kR}g%'C10*Lc0>w?w^8ss_seϟ` %KL^0((rg]|\uqPu |kT)߿Y9{o]]P^ T2ur]5j 2VXaVqG<eEwΟmfw4vXBh,J9Vuߣ̋q?]0 <>p.}_ i:0W;.]d ./2V(kLy)S9\9o$'?kPV*\zYuD[fK=Iw+X&R F+J9TiH ֓*GaÆkXh˖-3׳S4PUJS^dҼ;vX.]*JR-Cj))NDe&'J問R QίTN1#BcN,Sp=H_]f6l0ew|Qr( 7ezrEJVmP7R*w)Tj~UP:fӧO^]3gΘu0z@y)\2F~Ve%lCYI U޴W !MSGM{(7_TeC-=2Q|˫_h\t9gnM ĉ Wn[n7`iΝ,'*SJj>6Xg[@1tʀ>G$%1 (Npu#ڞϖZݞVUBUUs%_ P PIX:=Bul@믿su7~# G 'rAN{< cěckԭ urhժUKHiyR}Mލw;nܸ](y唕8wY~`y:ﮛR3u]ϑ0kZk&MD+Fbb(cƌc}Ǒnp]{u"(0k,~{9*/+rըfŋ15F08qX'qxԽ\w_5Fgk-X >e% Ews5ʊ?0v5{ ?РњKI+lh5=$hܸq:{v r%OO4ЫKW^y%ڴiS sY4hPT~}aK˓СCfM6&L]x1IoFgϞ8Nj]   e,@ e|8u@@(Qq9   Q_@@?އ ME^z"T^x^W4h)`={5mZli?ݸq&M,u6lؐs6dGL8.]MҥKmĈ9۵kg_|_O^x[v]p߫W/۳gOߙ3g:oF쥗^u^m߾=^ @zFY @ ,Lve3f̰iӦѣ_{[oe&L۷oݮ\bo, Ȍ3&y؜Gh?gf{l߾}6tP?~OAa:tSq?֭[MW_}Aֽ{w>,/ @zFY @ Ծ(Il޽m>O쩧OL◩J8p[߾}}' :u2O?ٖ-[ĉ> kӦMziÆ Sy]zշEzwMO͛gj ;wίW=qR0*{9S$@7'G@Z (0ٳg|uЬY3ѣGLB`~[xu>'OKN\v$PJ.USv`AJ-yl$!/@`99"A'9P~7oB /=r3ح[lByZד,u} ^~eSdRPԤI"@R 0J l@ѩm?o۷yUT_U)B1-z3g9U ʓ Q PKBmvHu~pe;v޽T}nƍ6yd Ouѹsgk޼ܹ3տWz6mSN6olݺu'SUL &@`5!<@V˒V5ݪU+۶mݻwX6B8gU>^V}Fƍy} /QF8u~'u\ <͚5ҥKQPP˗oAFF<==5 axyj[d qQÇc>}! `Brr2~,^puX,0f5aÆ!88 ZФIڵ+7nMb֬Y())Ot{G=l"""""ZAYYƍiӦaٲeسgV\k׮!99/^;<W^EiiC˗駟CQٳ-Zh= QTT77nUh"6l|///'oO?ĉ]JLLDddp[ @jALDDDmUp!:%֯j:Tݺuq>Ĩ, w}'mtkB@@N8֭[moժ //NŲuE4oi^%;ҭ[7IG__65,B<77n -[D@@Zl#G8/"غu+틌ΩS#`ӦMԩf͚S/"+W{""""wɰpA? &`ܸq:u*HtiҤ z-L2RC/hժˏMDDDT!Ce$I+bرcqĉطooV@@fΜi|',X5zСCa2P^^%KK.Bv0|VիWR_&"""2ӧODpp0ڷoQwᆳxxx}2df͚ooo+_t7n`Æ hڴ)~aDFFk֬߁H$N'Z]יGqO< vxΝ999ukFY9s'Nॗ^wމ7bҥhݺ[l+W0c xyy cƌ֭[{nBDDD$z";Gq33P4mk׮1>CZ m۶ų>[/[L >Xh233o;J["!! DHHE0q]_öhS㏣qHKK6Щ$I۷@@^^SNaϞ= ޫn5jZn>W\Ao!"""H=M&•-ZI 2po^ LHUv DM}5j<< 駟m۶ҥK }Wؾ{|jFDDDG @ȰfΜ gEjjm۰~C {ӧQZZΝ;۵ݹs'ƍF!==wyCY|ѢExGvZL6 @Ha͛7۷o͛q"<<ϟǦM뫶е5l0?gϞEvT=[n֭['Ibcck<6n巜9s}ZlqU߄ 0gaʕh֬YBDDD$2  ==/2RRR.pBNĖ$IjDqE@$!--j8;FuY$aҤI7]?8֬Y7bΜ95 h\[RYYYӧ233zvU5ݏ?DDDu]H DDDDD$ """"" aBDDDDD0!"""""a0 @H DDDDD$ """"" U^T}>GZ4*b .}APdGBIya9n\0 @][Ƅ ]w5 mڴdB۶m]4KJJddBzz&c-ָHHQyCp|qluPaP=_VЯ™kEyϐe. dX%%%>|8^|E!&&uV5~d,[ {A@@@$W ~.Ҋa# _|~0& J.h</-,WO|l*zHD +..qa,]IIIؼy30uߑѣGQXX,z'O_|aÆᮻ‡~_~E  9%n \sMP}qy# @ȐdY;I+=6vX8qW׻;qw6tPL&cɒ%ҥ Ю];̟?>w*(b=&,7,alR~-+ETN\o "bBuiF5j`ݢo!C`֬Y+/Nq6l؀MFdd$<==f#=FˀXl̀lw$A.ްozsέ/^ѣǾ;̝;@ 0zhšEYTZ˵+ !-B#'O;}sac̙38q*K;qF,][˖-r f̘///xyya̘1غu+vލp-~ZSrc|}бALT*׿EGG;r7$o߾ەs%//nSg{U5 [|+Wyx0,KY_Nɼ MZGT-ϑO,RSSir۶mѣG 29OFii):w\'k~eEGuڵk1m4$$$M.M0B)7'd d{(%%)))v۴l^c$r#?ТE 3lƍv &`Μ9HKKʕ+ѬY anqMy7̀Ts9>}w5 իW#>>_7nsФIu2ŋѼysNAHOO/ǫ-\P%ICC矱qF]$wU^pa5k@$L4___!7CwbDx0BT-f@H(!3 @tKɀ.4R5"J|==̀]x"C3XBYdY >f߈l1!"l#\ r%t7a vyDv=J @H(ۻFa=pMel xUbBDB7 a_ AN!Q!"pSlg_!j@n0%R0!" 7]``2)XD`BDB-b]XBq QPr T2 6s ),UYDT\L:BPPΝk׮iZc0LE~~c0yVbEA vyDz#ӧѧO_ǼyбcGk0`^kݎ{b$&& 9(,CȀp!BEŻ$EaVN7- f͚K.aʕزe ,Y]vaܹ8y$yM^v$''={6<==zj!F$,e@L`Y,"h:SC=" @m>} O>"̜92:u 4@ǎoJqq1ၤ$i~%%%HLLDJJ MV ez ٳk.ػw/vŋퟛݻ}8smV]IMME~~>Ǝ[mӦMCbb":ecMa 9H,(ЉlZǎW_!** F\\Μ9:t͚5se U] "t"[ @m>} Ow0xg0zh?~5]ze@ 9Lb,Bf@"t{D۳g`Ĉ5j q!C\v  Çc!** .\ԩSqF9z(@DGGDݵd6 q75 )X$چEoBFPT^fR|fɓ'Nҥ ӑpf;iӦaժUj-ɜ9sлwo,[ 'OvxΪU@ <xj Xb7n,pY+`Ø5 nLɀLGTH=]T>٧B]tѬtjڴǕUW|}}gWޭ[7 8GQQ|||Ǯ_dᡇfL8qqqHJJ̙3uNY (XwU!FրX3 =" @jȑ#ذaكsΡy߿?^z%tnsŁРA=qqqhѢ&cAf>ҥ 5j䰽m۶eyyyv{gB̜9 6TGEE!..W6Fb[n&bAe/f@H0 c = l2|?~f2ZQ2խl6&[ã?r[aJǔ)SBff&hH%݌ۄnLɀxXrUo [ՕЕ = ~sz &L@^/w,Y8zD~0|p_ӧOdF׈q/!!Aq[Jd rYV k/%dHߔ{+fe #= @jمaΝѽ{wdgg0f5aÆ!88>:HOOwRPPj5d?VdYFrr2RRR___C;FHml2yGІ0M,^z~'\t }u{;DѰ:v#F`Νx7OƢ3g΄ NB бcG-III@RRڴit$&&"%%ӦM<:d @*j<:`[0%$#}SL,5EhmJJJ… K/.^wW^Eii&u F[oa={6vڅ>|{E׮]xbsssѽ{wogΜ{>|DTTe˖xWwHMME~~>Ǝ[mӦMCbb":QUYU2 `Dm[Q !;;O<]#OuHLLCrDD"""rѱcG|Wx'?F@@bbb^g];7I3g~tСNI쎿fHAL?~{hBY 5 36 $Z"K(PƅRJJ RRRihܢGFfͰyfRSRR_ngW`` ֮][};tyXɘ;[֭S޿_׶mm Xẁh&UsϽ8(-ǨQ_|ݴe2ŋѼysNMF,| S`IZsZ_ԩSVwhٲRVFF.QC%%Y.Xu@H_2 b S$k,f@왴;)//DŽ palڴ {yl߾]v!''Ǐ5\"}@ @p^5R WEbJ\/Qf@j駟G}|/_Fbb'N,X6mBXX̙ݻ7:~(5 &c Tk@?s +Qc^7̀aR ǎ$I裏G=&Ib߾}7oϟ b̘1Ø5 n@fĨM D  gϞ۽{w|'. J$M"tA}q!B";!"l cD||eHߔ6H@uIt{DX p  f;bʬ]xyU`BDBmx րLMZ)X̀aBDbp%t : / 2j D$m/ep!BS/M"$!J3 DX*Sq!Bb>)XDPEV"4BvF)XL=,Sʕ"tf߈hȷ~/_$IhѢuݻk5$"`뀨k@tmxI4)X"3s Ȟ={0eqի{1̚5 _1~xwqLRE~ԩS///aܹvZ_իXf z!t>>>ߏkת0L>& zl3b…E$:ȒrY!#ٱc"++ ={Dtt4BCCѱcG4k ,#//gΜAff&"44/F)bN>ҥK7nBBBpakOpq:>f͚]v矱eL6 ;vMF@AA{=@qq1O9,w̅SH)XSN"Gӑj8p "##xw0~x&YfҥKXrEO?+VoӵkW|G=z%K_~HKKÖ-[PUrr2_ٳgի MXmkbB Z)XDvL:(mۆ?M4C=H 6L8#).BfLC]sѬtjڴǕUb>|x닸8[n8p ߏ"]~C=0͘8q"␔NskF.DgEU] y# $a۶mHNNFRR6l؀6mO"##ѧOQéw|B|'S>!>N눋Cn]tAFmVf{(,,̙32QQQի3`I0 6_N/"22:tbXz- 8gƮ]Çc޽ڵ+/^lnn.w̙3 6 66fwPQRSScV|iӐ @lD&J,bY$:EDN @ uV$''c׮](++?ΝH HǎW_^'|?Alll-z~>{,b8 >`СuHdw5k@ *`dffݻmE8^ZvD~Fj~@&~cSsu(..FƍNW04' ڵkko`8.B:c(֭[u֩?߿Ư}[\0Pnb1B^_yNDzU2.DHdGX2l0L8cǎX?{%Il`E4ܐEa fIBzLn &N"JX:,DTn~ou 0 \w} i~TL~bBe M^z+rq'j<rgy$T%)ր蟚y3İ(m=x)4 @-[9sCp[]tBnqҥ6W`$d@,̅ΦHbpzJfT+BG$:t(^y=z@۶ma6;.ֽivHt T"tv""WS*Rjp!+ 7oԩS!2~GsǝWF&j8q+S /VQ!9[J`(?>v-[ 88X!Ck I0}AeUIދ/b֬Y >e$k!B) $ʕ=KTW4 @BCCqy-MDrZ @"$K GD4 @V\djqx"Ҋ)Xp)jMp!Bb &2LTqIET&5 &MbADDf̘@.XZ \Ŷ2 j^DlTהiDuv dmN&HѢE tܹ}xމp Y3!QO9 f@iݻWָ:i@&'yشnLdlJJ~GTW4!zJ67h!B6\PTM,&RfV΅|嗚_F߾}xbL4 -[1<"ʊԀj@`ӆWp ^+ E.X-[ܹs1w\r ּڵ'ےwthë`9(AWu а ':u~_~ԩႏLaiƶDFvmx=-EbYlH6.+V`ҤIxa6ZHF\ ݦ /k@tE$m,N"Rha}T,_L4 ={ij> '1. @<1; o9g13 .yfxxx`ƌ궆 3KOH,$UyͶm…u `,äԀ(<!R0qG"885~=k-E-g]j^.D?lKٶU=րUb"/^veۅ DH{ @E)gBDΰEѰaC^^^n[DD"""~DٴU|k@L] wm׮]h4"$VSvZF ooo8lW_vxtlDZmk\r!BAɌV"mvB>}4q @SvΤw:ŋC"Ҟ`Umëh"3BBuf{-& gϞq]w^qÇ.F'_Wm q  f"t",BwG}HHHP`ݺu߿?~i8:"րd%t[ @H0 r T"J~0~x/N:aÆ 8<֭[4{i!*mx]^B CvǫZTd}yEf?qw& ffYmܸ111xw1gc6l`>XPUN9Y(r%9] 17BT4 @0f/ӟłӟ޽{^bhuaÆxWpСC>|"Ҏ1Ij+^w0چ ``9mĚGQtRYYYXh`ԩHJJ·~AAAZ \ȶ /Pq1vmx"t]bBٯ^J 'N@DD<<<ԩV:`֬YXlC#"W U2 $ $IUj@9M4hЬY34lP]Zj~A+U @`!j@mk@Hv 5)7H?I'N?yظq#JKKQ\\kN+ٶ12 mx 9gGbD"t" ?0mۆ?ݻ͚5C˖-_`Z \Ȑ5 mx=,My% IDATe h":Q?3xgԟnj}!-- fcƌAXXC#"WrR)=O,B'dY"t"'tߏ_a+9qcր )'WHBDT9m^.DS\Sk@,5 ̀d@ IN< OOOg&w %Ib',"Q #Mr҆׶怴͢smފsdȐ!$I2do>D|ڴDuc.X6w'-N21!׳f@? //O.I?h5<"r҆(w @WB`Pfn~Ӆ.hxЦMzrG*DSu IrVB'a  fۆ"&kpرD6~! !'XBYsCPf=yia^^ƏچSlZ2/mxS3È)4 @뇓'Ojqh"Ғ`ٶ,pJe\HIo"-- IIIZ$E րgS!:5Ӛ 1mxyiS2acҤI5ka6Ǖ;ǏbxD*Uj@`r{N8] S ͛7G-йsja,"Zb)Xr!B}e뀐PVBBD4 @ݫaHkN`Yn,)X֯3:`B'0Y5S2 \HI SX(+. D؆S9yDuE$??K.ň#pw###pUƩSC^z!BR6!Wsz$.h2+77Fnn.:wl5k;w#"W1XjMFCb"ε[ ]yYioCaa!=֭[UVc$aܸqؾ}C#"Wrֆ׍Wf@t &2L&J @*i2kΝxꩧУGGED.W 7I0)Xj 5q.DuE.QUAA(Nj^ R +[*WB/力.< @u}Umpw  t2[`n`)X\D! fQ^D Ms"55˖-C~~>9998q"j$pDD$2 յlD3 $m :x)4 @~Kii|]"2'wZwhk 8mˋ@r!gSeDuE|w /_t<_ЈHe@_K[I p!BLV`9Y ݝoaH||<.\jE DR>xBRe Pyt,7|I[ [a xgؠ$IE߾}ѷo_QC#"Ad l.|UN'"rY ڮd8p <z%D Awπ8 @̒k[-2I(e rӅED4ib!"Qc t5 .X$)XlKTI.XΝCTTZj___uֈƹsMڵ SNEpp0|}}ѩS'L>?<ǜ9spu&gw@5 n%tN#I$;; B~~>uƍGڵës=k׮aҥ N>7x۷o_֭[~6lz+Vˑ?X߂H;N3 n|!X, "2L͉Nj@r1m2f@T χlѣG@"<<=M+B||<>m#GĐ!CouZ`7o{QF*`>}:1|pc'e@_Ys̀` r gã*Ə,'aAv[uUTZԶ*R?WkպTť-j.P*aJd29?f6̙ܟʕdΙ'z89e  7}ҤIp |GQt >N=Tٻw5͛+}ŋIJJbÆ !_$.ËԀ @DN9U: . !!!GGt욚hll$33ڮ]p:ݖ6L:;wzB`iz.S"4"!S'H~"D%6m_ﶭgyӧ0[f  ^+//=77Op:g@0z>zI4MFk":K#B!t9?~p8;4i&۷s%^|\~5v!"V"Hπx>K H @1M['Jn&ƏO||< L0n #ƹKyy96mbȑ~KIIa޼y 455^l\z饡a%2 ~$t @DgjfN KdϞ=̝;*N:$ w}իy4i +`?eϞ=ٳǷbp={6~:K,UVqg3|=/d@"fPFɀMNBKrur9;m۶ms_}5#ꫯPkײvNۆ )6m7ofŊ,_dV\a 6-"5 Aj@D%n ۶m[n|̚5e˖0$H"uɀxDd5Q{jLe/w't͝B:Հdee'..H Q  KH_@?U/u]:y'NȲex.S[VVƟg-[ȄAUa:˛y'"X4@p0y?%,UUX,==p曌9MXjU-_\ !j7D~Ļ9?E]' {?|;_znwo~nK"D> EÎ "eG wBNȁ8BgQW,EaNba^įfTd 6L !6PHVD (hv%uBB <&`D`!Ѿ:: SD:2H HؑDi(@ jU @!4`2 Կ?D 4"DJQ Vex% +IDJU J"t9DBQ 4" C]`I6M0+B!0@ꫯhjj{(B \ZTe@=BH @e,!t @6nرc4hӧOg۶mTUU1uT׿54!D% 2 ]u^ @DQ{?,X,i YYY 4u14!Du%}@D(HDix;k"z'D%{8SOկ~m'Ν;u"Gz2!-}]Ѕt @v… nɡ"#BB H IDE膎}@t @l=۶#GO3|F&@5 22+ t΀Dx+ŸiN}Gqq1'x"O>$v&MBUU.=&$hy|ES ]2 B: ƍc˖-dffrw?+W2yd>ЄA-bA4" ?STvԡ]\#f'NdRXX1l$6oqu=#Ҝi]1(@B 4 ^̚5Ka!B@SUJ>V-Ë. "4:${;tBW ]Y t @N'S3B/,!D ";je"u  "T͍*5uoB]//Dҭ1roI9I 5 "4Mw섮zƊkE"_WoRSS~?Q*Xn^~i.ɀrwBF b^9tʀ|w}\pz^ŨDcRnM2M.w'tO#Bi ȹ'.AqX!JD$B;TM9ҩb%YbO= z^$h.YW( C1g@\R"F, Gf… 2m˗0:!D 4" ?҈PS0yR.%Sr7~'%P$Ro=ij`vWLJNrNzV3-"E{n,*u1w%7!aÆqX!4"> .*[`4DUL=5 EKb`;+3 .AIdA‚/q Cy -1v(B>pJ+S૯O;vjvj#f͚w9p#F //C駟ЄAm6O~j&P/O} B: wy'#F`޽[[n-[駟RZZe]Є42 3 M2wπ?1رg`0uI'qrw14!DE[D'SD9Ԁ{ R$"B١k"t @nfk׮ _ K,g}֭eΝ$''seh_4f@ Hxq=Dx;;O2u"t @8a_|s=G\\/3<'zjyٷo6maB@}@"FP2 R^i V C2 B: Æ F4.]UW]͛s뭷G}DRR,YYg! f@"FPj@_ex%"ěQ\ZHv*DѥݫW^yzz 6DDa?7|}݇&jyfJ_xbذaC(+DZTMqd@\O5 r( @d@P!•М.Hg@H[ίGr+›/^Ҵ _tɀ<[^1bg}6?x';6Mz 1..Gy_~pbcc,v-[Fjjj-ZĢEz5V!•ie8CG2 EVPULVi}h֯_;V__h.HCCsmp9u 1sվ{e̘1x̙3.=M8$$$|5k>}z%D$ $"Hx> "Լd@";v`ƌ:(z̙3?_~o۶SN9%c?~<֭վ|{:tȷtÇIOOb^ybuT^^N~~~ BDͳ4 HW[,E!P `qjfKB8Xl_=#F`<|gACNN/EEE\|ݶ1|p֬YҥK4i&۷s%|\~ aVH. H IAz ID97 $X,(iZ#<#<3ZU ~!5 V HxМ(`Eŭ IDATM@_zg}/! @,X. ~7~N̞=O?%KPRRªU8쳙?~-Df@ H\{@dOѡ`nbTT5B DiӦyfVXINNkaʕ!!`d@DiN+@nE85 =yBF6g>F:K2 "zM5 x#'D-4_ooD衶 !r3 6O"油0go@$N'd@ttRRRRxCUUBm0 d,jM.Հڦ% O+WrYgqx!6-2 )X HPmjH;DdhrH9E OdXV=-Љթ)oKzA3 ZAEr&!%y衇:p;1Iz>G&=sN?M @L >LZf ̞= &0d`[:N Z'!U‡d@D(5{綴[. vBQ hlldϞ=FBDG ^954&5 "dф`VYhG l B9뜘Ң+`4bD2 es ʀDZݑ{E:Հ!Hf@\.Lɀ g `Qs"5zf ]t @N'ׯ?9]tv7x &gu`N.R3 v;911~I`D@QZ:wՎL > ̙3+oRUU@bb"K.e͚5z Mjfע*S"`!wT|яs.Z%&7S=tB.~;}] zshDHDm6N.ttStۧ},9Y HQQ|ӦM{怒yvZVk!!W gVRN+2 rzj* Fc}$7K 鵪*RM&?l%qReǽUj@]( gqO=뤧sד_kFkkk$P[U2DVú:d@VA$LPd@D4WؒA[5d\ }@h*X111\xlذ |I9… ǐAPj HF{'UU4.q?ɀGsiTn$̞k '{os3nY$wq9Sɀ2mmm7dΝ1tP=$'*;UV{?^i<^Ztc{IԾWNΏrzO2 +WrezB㲹(}ec0 aDСܴ?rVZf/v+3ՉG]]2 ?~ 5t~ IS)XK=1,Yvm%ЉjWvѷu7rtݕd@B}7#<9h@½H w殮ɝrQq,B\}ա:B/&o1)p1_}Eț&c g vF}F&wwȤUL<Sow h92Bqyef*y[_ٹyrbb~ɀŮwVƤ7&aJ350:Dx;_~VO|x$g9&hƁP$犞/.a~2mCsH9nw4r¦Hط OElf_Pmn/cȊ! ^ve;K=1]]Tw#k0#)~N8_MM2dw2W 6v-LdRNNϐ 荢Vfu5sߺ8)e]zW'$" Bcz= дq7G:os3:K̴X6c3{U $xYǷW|bR4Ny@Db))!dⵌь-~t4٭{bDgUW%Ŕbbږi$VoQEc#kJJX_QA~l,O|7 C2 O0?Lڙia<19}HnmR/+Cn<_IBZ 0)ڻr8_Rެb2|RIgܺq`b "cyfΝKjj*̜9 6to֭r)$&&Ǎ7ވfaBաRh)&lMdk%f@{,[rno%hdIt ԯhH8j|9g'3ۆwᥘcz )S+67ؘ1>J/}q76p\t(B!$W>,\s gʕFKIIIK$"Mը{ ( $MMbɼ04WAV+[brqq|?5CTccC6hH/Wg)Y]BV2~8+}u,7Mֆߵa͆d͜Q7iuԽ_Gƿ4˲EQ0 ЅhF[[[<9ijjB4|Anf."jkk_n6iii ύQ\\o?˖-#5-bѢEjvq#.e_ ''3i$2~)KfШhic|獍\.̊ NIavr2yapt%5 QRʞ(",&>˴^ ouijF>UR5;%_̜Fv*/VPIӓ. IёQQpyi[~=ׯZ}}N.6㏙;wnݻw/cƌ!>>n_λΝ;9S/imm%!!5k0}>&B^[yoNd1q1EcFnlNNIΡCL~^"7(YUBK -c ;H2 bzb42b|f''dz1^ee>VA~|4)R&ر3f41`dznݺ^훛 @~~>'''lWީX4}Dj `ktÃm<7ޏ2L K^KjSt1O>%vp,#Aޒ rOr-lr,.\/3<}ڸq#~; ֭cܸq݊M͛Yb˗/'99k+W4rl{l* f?uwF`;;O`00b͞Ę'D4ޯdU y1kkDy,mF`& W2#))ltԼ5%YwTYe##pW.$DV^ի9s MӨ\G=71 g8y?Üֻ@ΦNuy ANOVOfc\?DhjqQb%%k<˙NKb#{axNw9$`u1>eL^JVPV5,3CV !dͤ(8$" BDM JV`m#ijOeY=6\|qUSl@Ԥ$榦[O!c,=V6+j@N˸ яw}Go(:ʥimlZlt 6~LɉA6:R*UUQ/IاǒsE xs$jSB BDGý+d?T=7Y|V+l6\FgՙS= fZ,LHHsh -5 6X^l ' Z:/gZm6. 0S JuSaёIe>RJ[Iig1ɤ/N።k}%afJVp#AU `ƶV|!H$ IDAT8)9_s¤Db°_M wKC w8yKz?/RDsmVϠXNXmPfY,̰XH6;z5%?SڦsE=Iz;N@]BtE#E/*bczP+F>/)qV+=f|/9pWJTlH̀hF[5=X3+=˙.E1EGMoK [V+{=\F#'&'sUn.9baPlCiWSr%#oLu8)g "DHheu#~T<cyOvŞFF$~Iɜ"MM ڦRb(yo3)pN tCdOf퍍lmh`ʧ :8#5C0balBB- ,rgچ2rH~1Q_`R\6 ѕ BLS5߬hei;!mSy6i))),fvJ t*ձRL C{&2 N'(YSN慙];SZDM`Y[;aMM85 ,<9)) T=4wj)} n aBBg:2* NMWW*!H44@]k ~ӅKuriTKCSۿvTT8UͥrSU ޖՅ;f³f11^ᴤ$nDL솦A[C?ܿ|{֛vݯ۔fV|uHm&gמ=}&t4BJ6pB\CrPP Ű)<*NW 1^ 8d0qL QL <ÝN8`Dcic\PyEo'TCH;~HƴR>{pLcn.r-mϐWۏ-*:c  @̫~EB(()t{Gޣ5~iީ@yVt7hJg+)Oy Rq\9=M(=ϰmnNgiMMwp9Ô3J0ƍ8ol{88Hp1fy^!Gܗnm ??L}ee*/ԯf=6ca39̏i!43IiEӣ<|T`+zi`;aَ$ P;ޅ{;}ǯ=wYW:mZ;*Ѽo~^P=G @t (4:t8 w} ųs1~{^00xFP*ce\# Ic226s,9I(CǺ7tP6%3N/L }{`l׸(y0hq2KR;]֫m}Շ*?>ڦ†BvLM(lkæ&>nl&J .IJtSH;DX_jN 5})-8/ 2$4څ3PCq%x>$Ǎڎ0cޣx^>ӧO{"4zmra3ANb f3`=M#}K0kCF*+qCv.cSp1.E1( G\`RcNHM q*vYWfGQ[FEaFR?TNIIUz95*^i)l!L&leECQP4Ust-m-DoEN7d;cG,+?ܼ[;hq`Ts5h&`2D?h]K"tG⇊)yŬPcΐUr_yTNJL,楥qZJ (84Fŋx0 &`&GG1McGBU\ppڵ¹̛w.Y~ۚ=l+ƶm|Z)TM%a"fY^]dM  wqk؟+U5MMW[uulmhipVz:)(T¬FT׫8xAZk!L&:UL}gt (À#kp؏i.CJ\A\Ўm 14{inMlaՊKӘ`Nr2(s+n9@$NfSI=-u4"``'D4D0(pbx1x)w@rppi틈d'fsޘ8oyQ\<|`3o ̄ L˛ԜLʞ 9'io2ͩAR1 AɀhF-[^?*o(1=7lUUYW[lyii<6z4礧3$n|=6~$NJd[8?#j<`;N1~ @ZpTZEҔ$?yi2o!pqk+o԰qj,n2ffFu-GO\.XL Z3k1 ?y fST=p$" 0o y==g`LwVKo &z .z{}Aɮ] XKwơC91uvbQos qpÔ>VJLv ֍#y-Vmmg<2j?dPX[^R-W|`6S2/Y+J]KÈ r5"J继fr ;+d t\y%0mڱ57ǻWʛ:VuԸ?oڷ?}'\ ԸṉHAѽR7Msg P)s:v0z0c?r`̆*6TVOm 񜓑Aj8Vg4~H%YGJ}QM:Dr.jZj8t *lGtV̥\m2&\pAxiX]>y2'puJO䂓9N]vhNmշۿA10$eG1h6`DFn1X^AK }c#oINL,{'D22{p&QsnN]Vf?UUaiaڿr2q\|Sa9U'JJ5QXFicMT4UP\bDNbI$0`$99$`;l7OF*zH"D>x{u\p;+r?c|Y4MVRX[>zWn) ߒϨQLɨQG 5.5O5 E}<;_2]z*V^䕪*H>t(礧otgJbZ|!ڪRp18).0Q6!}E`l$XhF]kNET*:&<-[9e)C 2zzð#fԓDdsTWË/χ'`༌ VpnF|K>,|g%fe#ށJ!LwKaNxY4)Sӳ.ܽoxk@c2dsOmK-?s0͎f߾K%uCS2$e?D1(B~eQ! dro9Twkkyjjp*gܸq\(=Mi*k9BBjޮ!m^'EHaV[m o@dx>*lNfH Qg3$y) NL%ܤ\_=NJQ`tû3#w^Ȃbtf ŬAm4I!惃pP'f&AA N(H.}nR.Fñ? 6PZJ TYM3$4퍍<_Q˕T;LNLQv6}XJi?:Z\?ζL|u" 2T𥷎`EkSPā;_͎799$^[NH;ߕ.8\#d9^4::u$/c1~1ɯA&k$!j;T,s5L'r 0~<\{m9^Pd "Tk**__g@I+Nc߉}df9gb s'(ջ3LW&O;LR"GR n|6vEau~>ffm^:qAAե ]KU˛h'<+;LGY^KIwT V vl3s80r0 ջIЙ.XXg7|Ή]!80u@r2|,_&-!]^: D7B@S @=crk9YYddY^/tޚ̒LNtW~SM7r,_<=+^Lg5oɣ|GYYUVӓ7t֐F{gVK(BgzE.[53FC"w^9ΖEQPCnt ʰa]h$!kWxIu;|X "˗=<`(e\h,W2Mk̈́oM1?AރydܐA7K7˲.ϼ?.VRjNɩ2NLA4Ioj #xt= j V[ lN1spAW<1.s~WvoIړ?v Cqps[8R]-zOk@Wv[`7MEL@ ֹ:PK5L z=\}55}%Z$.+-_@yqAg8)ϩqy'.&"Y:w5T %e}xnM-33y?# >>|DŽ:mLmD+_G9>@Oo{4:%џaȧt蠮/2s&}-{DDƌaԮ[Zz%Bdr46ѲT4]4ZgLz?S`.\E )T/ ͅq8p}6uӺ %xvo7/06X}9fs#Z釷m7šV:GyHwo )\PCqpNce^݃'c/KоMWsή2v.6V]k zl$!/|YyF,P_}:v$!ud/&yz2(}iwBn q²s?؅tZs6RrkyR(}ڗ8d( oΝ=Lػ7 N?߷zǧsںU@MNC_VTn͜aFg89o+3^M`C>[#F<$^Q\ii|NL !@۟ɔTPb)bKۙ;H2ke2ʬeXsCѪAZ(1Mt4g((q͈Qot^zyw}g :Mq(dj*3.X'"LM6verve@)oh_BBFFƩQ:N984{QF߾ƺfcGqqV"p)zPRgP&h9Gƨ+֑#_J?tg;?N5O kox#5_`n.WAƀFy=JF(9nW`aI!OwUPaVqnggR='T]w׹/ 9l۰o5ŋBZ),VM VƦc~,طt;hNxo#m]~>l۷ }jؘ:U8P \"VIJ2rh{>eK kc Bf奎 _<4ImY {N )jȰa֥K@ȓGH{3 [GtY 'k/veefZ**חzdLP/x6z_/ `>ff̭1 d9H\#3/.-qVI2\U]gP0΀^G}]{~|]oTX+ؙi[[zI:xA<@r:ޙθ^i- E6不fZ6ッ{w.t Sixݴn Xi<vg)/,'LJ(0ڀ~ߪZ_ZSRZN݂aU/3^*W4K9C6Vװh|:VNsit/:Ζ-lM{9l&E br\u)&$8~b(C3ʸ3U[7lQCPR::(|pxiRU}Æ`Wi)O9RÊ2NzLx=-֠e.F D"h=fP[>^|QSOr]A䪫_PH{##Ogbo}A*KnɱX;T* wX];Յh :LZ6RoKŚm%8B m];VLp֠c,v s?qש\vҨK F]J޸iOj^VîWo;tHrlܨ~oxu#$. zJ ngKQ[Kn[ l` /f1}!D 5j9 7\w؆ j 'uv-1$W\Ͽ˖%BIOG;R Zsw1AURf^OK t ""x(*p7 Fr"Tl ^>Ʊ=ЛbrN4"MMQ*tvC~Ox":j- iu?EtG:>ѹhpFXjSf^>~jl,GVSVl G9Wp]?.} =Z{1-UaTÇÐ!B av8RT|is9vCU'pdQw` -]VBIZ-↭Pz*,+[BytZ~蘭+_.P[K Pǒ7KTQYy?#o77fp_d3P5:/= Ldo)&߱txgW^EPvEjؽ[E:NÇ 7paEQ8TQ|8={7$55^{df+ht:EMe\V7N2>#vA;~W@^_ RRNR$(auI1ya0ۛu}2|OA:/lkbS8äFP-6gRp4K֍/ P?^H~I\6l"`eWmiGau !@ 2E /ݝ7ԩS:t(-@|IY~=^'7wԉSffhzevVG4o-Gcs7p2nLЍFuz||eej ٿ_Gx]ҽ;tnݺ]Ԝ_ny-577>crh(Zѩ[@VEV"gD/%Ы/,uݣaXuspE˳ٴAߗ3t#</-|=tF ܢ& `4qww׷4aaaL&b֮]? wq=˖-"iՏ#[W$?@oz}eys|II&1iҤzU֪$Zp+WxC",KNuQġ(|G0[7 Gw2lEDUW~lA=Zs;rD]l,%D;t2,L]y- ]A( _9'z{(}xZzV17s?",Y[7СpC~>ˎf<\07*s?QO46W+~>3> r#[ 6l`ذa*DnxGqssc1ǏgذaL>ѣGj1ZW15Ƌnܹğ[vZ@qw#~[c/$nQaw6΢ 1p-s@g,\v*(P;Wb|ȵZYz_͠#Km'w'tr]MP=fa;juЫW/رcǏwX,ٳ[nj.Dۤhpw;( ioqÄBGq-bP^QCsgD/F^{]v8qB $5tHJR|X;Ah(U1XN_KLϏ}ZWg|M[ŖoC)hj&BXT v{V>_6Eau~>$\~!Dhz'V ^|>ve˖CΝeĈ,Ygy9ŋ)++c„ 7ђ TfolVŦ@2ed41/ƴ/P \pwJ;xqSPץ NOktSl**^mn5Tn@Ru ْRѺk鿥?H}ѨBN3kf `uϪSf>`͖L.kxa~!CpSB:nkz믿K.{>3vŬYU5k,UW]ԩSIKK7 !!#G6_!DʴՙVs~OS^OAÁy dØt:ٓ.E d4mƞ\e%dgaIIen::I^1:eEcy_``ͅ[[cieugQoU |d ypswBi֛]Q!?S"s&ȍ{;\Bh6@`ԨQ|̚5F\\ ,`ԩ5ߟk~fϞL3__$ސHErM@Btu6'O4NX,xL,UT0?Y_XȔp^Bbݮ$'j`ZZ.NO/ I۪sWyzek4)qíW\{'baaf&_LW;g$uf'HHHW!CȚB4GcsvY*O*gߨ}8^.eE itn@) olJ ke-hT޽^v6InnͰy9 9L4bޡΔ^ӃI}|ԭYEPQAοq50el%cU /EQX_XȂtҾe76xk3GCZ;D!Dbxtav`ɰYX"$Nѡ厃l4p9Kee5)]%%< 11x6ZϞTBLL2r;n;@nF.]ʜ,u}azY};}_V8pjqd0Az(wĴG{-HIf׺3ky OJrnZCP&= [Byhy$!Z x%x{17en;ϥ>Z 7fjj4GY99Ǐ%>>.I :XNXHHrzӛ ZZfSW5WRSk.*R+^rv ~!==u_) lo +V-]SɏLhT;ҹzY4*!`fF3H>;D&!Bã=(FQ9!B&q\䏲2:xt4uG3L^yD+/ûwx}"J6; pnnؒs=3"(+S,-y=7W]`T 1h|3[gбZarx 7SW(&LU'' !!wyhnV>&{D!_XEw"[qk 4˗/g˖-g,OSNL:5kp5믿Ό3x뭷2e W]u=&L@mO!BC, ??s+..fڵL<>Xls7|fӦM#--!G!BJLLf1pz=c}wˋe ]B!h} <<}aaalڴFZQ> +,,d׮]] "r9ȫjeXM6t3.B!DE(ZemڴI2db4PWJKKks8ٳN:)J޽K*g6\ WFr%(k֬iSەӧ+=zP<==heĉkݿxyy)߮4CEcz饗Fի}rۆ;w*GVɤKy뭷jc6رC3fL&%..Nyx.ʳ>$$$(FQ-ZTgن?PS<<<]*ovSFQCP{6i$Kz!vG}Ď;ezVllٲ &ЧO233yw(--e֭4?3gΤ^{hoߎ^oD4."Z-111۷}r[իW3zh 7ߌBQ^y@u[ȠA;,Z1cưb @wkBll,;v$&&׳hѢZrl,Xi?~< lܸŋ+㏻Ol9k۶mS4;fK.fB믊j/99YP&O7m4SIMMu[vheoVF :V ֯H Uƍwrrۆ'|Rh4k;F*"ǻ5TN8(oFQ>Z{l˕@e5?ydK)((hAh帹q:3elBzzz3N\. 7CtB=HJJr/r>|8ݺucٲe.h<7n/dܹ(Rk̗oҥdgg3k,p8ɱn ?,, N`xFy\tӧSVVw}A%޽nݺUcAسgOsTK4EQ8qAAAk4hwvuL:ͮ:9mڵk!55.ooo|}}ʱn;nBCC2e {%55?3sLF6!Ƕejr^ŴՌ233 VW/##UMO?%##9Z”gzcZp+2|?O?Trۆdl67x#sy),,dҥrې6ou]G~i^x@oe 9t:U r^WfTQQ{Eې,>,uc6R^^δiӘ;w.7x#  /ȱnCN8^ |@`` +Wd֬Y2}t9mXCmEEK弮 h4:3E뗕_?˗/w :h~iXFyPu&MTcIX`[n%..c⋤sA"""5p:0i$݆5F,Kc6=PiFu6U5U}Љ֫kbVZEXXܪ]]ff&kY+|OZZ)))`6X,;v9mDgshhhUY eX~6mGMyy9{mXCmxx8v, r^WfԿK/%22k'.nof۶m|\r%u7n+Wq"n:0a+.P޽Ybsٳ';vdŊL2cL8 :t( Ǻg׮]$''gӧ ǻ-6l̛7͛'_Jo믿桇s|o[/'Ӄ>[oѣ>>`Pٳg+vF99ޭONj|?W}wkZرcr 9|+]v󳡽!B!!B!HB!B!B!HB!B!B!HB!B!h.bbb,[@]V:ZX!kHBVJk۰aFUns1sLL&^W#0k,*++]B!TBRK.q?f͚5,Y#F(zWVVXqHKK#<<ܥ]TTDhh(K_[!; BF̘1{UcRXXȆ njCQQQBWK!ځǀjyyӓ#G_$** M7DAAA+LJn>f#FԺOrӣGL&&11ӥKF#Æ رc5̸qh4ҡC&MDqqqr#F`ӦMP!Dpk !pƀ,Yƃ>H^^sofȐ!lٲ$''o裏pBc/^]wŨQ3geee̛7/ݻwӱc3eΝX,ƍ|̘1EQ={6G`…̘1|̙wͺuX,$$$`Z9s&aaawQTT5 (l޼믿|YB4!h'qIrr2ހ:0|TTTsNZ<''O?)--e̙L:;;䢋._fgKRRgJNN&))hƫJrr25{qٿ?))),_/{gjFll,".$]0a3|\|~Qb5k(**[n!77׹iZ.b~糾n^^ >>kQ}#G`ժUTTTU{rB!!hǪé:ԹjHrr2Æ #$$ƶfrrrg|?̇~HPPFޫ5k鉅.XBюt:iikɒ%*v@@ V/^{oիW3sLfϞ֭[t -AAAgB%D!Du` pQz٨uիz⩧b˖- 2/:=zݻ7 !83%Dcv55j>>>lZk\ŀ0 رPRRR>zBbXj߹s'.Q D!ډ\wۛyq-BPPǏ/>=<<9r$k׮oz[3f0qDvfcŸ1nܸe׬Y_~ƁB!!h#4[9v_]eҤIDDD+ꫯRYYITTW\qw}9_fܸqU^cԨQ#==D~3ff͛wޯ+hIL!'A=8q"/K_{ܹk>|wwwBw2D!Dj {uV+o&O?!h"B!piB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!HB!B!B!?7_wգIENDB`PyNN-0.10.0/doc/images/step_source.png000066400000000000000000000523211415343567000175020ustar00rootroot00000000000000PNG  IHDR ,;-:sBIT|d pHYsaa?i IDATxy\ fDXYZe;dnS},Ži~5i|Nby0RRL% 7@qQe{fx?1ts3p\lvݎH!):@DDDDD(HQB""""""FDDDDDD )4 """""Rh@DDDDD(HQB""""""FDDDDDD )4 """""Rh@DDDDD(HQB""""""FDDDDDD )4 """""Rh@DDDDD(HQBR6-믿.GDDDDX];JMsqկsdHI1~ӹdp^|M]8nۭ.]l̯;F-FDD+=DHJ''O^:5%KG_Ώ~PW8_D0iDDD fφkmkZ܊Q eqq "Y@DD~f̀hE濟zʴŕJ0 ]D<8EFY1i|T?vrs+)bcciժJ"((FhѢlmܸ͛@pp0 … T,".]YLv:>3tG^ԯo6#LO(Oxx8&Lۛx>۷ӺuknLɓٻw/+WzØ1fv࣏ys+s_u3{EDG=kvƎ ٮ;{,33|ݛkgZ>+6ҥ RYeՕ8GXXbUT!((r1f̘,A_~4j(Ŋ^ziHfwî]&t^mj،x  {СCۗxb{1ƍȑ#3KJJ 888sTTBYDmd6 85}i35i۷""mրvRSSsupuĉ2dO='Oweȑ ou7ҫW//@DDUD]¿e6j˗CXUy&MLر; ֭UV6>>PINNwW_}E\\͛7 ǀB%nє/_>W}sg3fmqou?-[@D3?jb֬YRJ~*V*TԩSթWWb]+))-",>}F[@zVWTeQDݹMX"{=5b߾}>|j|ueMǕڵkÏ?HΝ3t۷o{x""eZwd3'duUES&nU8G/B֭|I湌 ([, `[nfΜ9?>h.\@.] p;gObE>Ը1mwqwn3۷uL0ǏSN.]ʆ 1cŊ˼vrF… 9y$aaaDFFإjÆ DFFm6ڵ+&L ]sرcZ."nonx/Ԇ"=n2tՈgRO&|T_ 9l$jJ?5 "";[=Uڵ aF3KDĝ)q?? wWCٲVW$9i 5ksDDܙHkiw Vuf4{Yʗ4,qo ""Eķ0r$~~WͨCby;_Fڵ-M?8|جkHJz\9_&dgX1^RPգjլ}o??_EE ?ތ+fu5""yspÁY2o6 1fQ2Plǀ鱈ڼ65h .vݻ>9co`QԨ[oj RA 4z@Dh4"7ǎOlm[͑dje=LnWcf𫯬DD$o4""Ns |_o~3˕3|}<֯oF4~B$Z7:q?)dui r90k6{<[5eJٴ< H?K??lGٲVW)y[q/)HQu  7ߘy=O<mwjC0<(oDD$6}v6nȮ]8~86rQV-~ի笗'ac330mt+Z]HÅ ;ph9z(~!g/^ҥK3gtv{矧~sqY̙0}i[: {CVW'RE֙Gwఞ3ԨQ?vڱtRRRRHJJѣtRx >cWΰaUFbcciժJ"((FhѢ'''NHHAAA4hЀӧD\ѣ0jWBo{apWp_[]H9l HӦM6l۷ϲ6F222X|9&Mbƍ(#(Oxx8O>$SJ Ν;[.?0W_dzͬYr|nO'֞/ ""g@{;E u]@>#n>#7oN5jwv޽{9t},^{q1r{%NJiܸq!U,R8N^eK ~2*X]GxqI""5 ' ,`޼yP^=zADD!!!y~NNjjjĉ2dH۶m˺u8z(9>ʕ+iӦM\GxDDDZE ۊfayJzջ!"kfwAVW#"rcN_o1o<ϟϾ}lhт=zйsgr>PINNTR%gϦO>_9Moɸq1bu_Gŝ\?e cg"22 81$DD\Yv7o-")) ???.^{=ʪUrum "44Bb2W_Ѷm[-[Fv;k,?>]L> -"no_НonzTR$RRRr}oŊݻw^QF۷ÇSZ󉉉òe߿?:uiqs}n~rh&"];3¹?9^De8}sOyG\2 ⯿bĈN}nݺ'd **eҰaׯ{hтs:.nZ :- "GxqKDĕ9e ֥K/7o_~%))))S]ңG5k藼GyoPN.]Jll,3f̠ 5kbE++w K@VW#"VO2W_}NJ.ѣfڕ|‡@h(s(krHpp05kdŊ⡭84"V:}zテխHD\ر0yku5""Y9m# .гgO "VJKΝM׫oQ:ws""iE/zz"n_ᮻHD\]wAZfqZyYjo6'Otˈ9SGHNt1 SRDD$+vD$w -Ftжm[;g=Hf[pVW#"bEY9VW""`ٳp0?`uE)X{[n4mڔSjU]WƍDŽ5KqfE$́^vII1S&q(8'O^}Yyi$7Sl6Ss# l{òecvՈ;:W_+-l `>HH0GbqT{||e߿rr vm \9 ֘1cnz`\Ct4̞!"h:͜ GCDAvӵrn&a٠re{fCp0Tdex4"rŸqcXE%"_?M_B۶VW#.]_m`Vk;g_8k4S U3U[K@Yތ~ju5"v_W7"wGwi}  HFZv3Js˄ x)Yd;s ~!ÇwT)"nm&>NUǰ٠x3׼R%+q}'N_&0}{5j@fffÆP.X[qHڵ9|0tڕ|0ǮWiii]Eh"V/2 F@'(&h8ʩSÆY]4+?1oedE>h0?qX`ر___j׮Mj(]4vSN￳sNҨS=zp hӦ/[]x޽aFI'.l+`BoB, cЦY$.5 qqq,]7ɓ'([,aaaw}oߞ 8 8ɓfZ0cՈ'ڸLbt)t33m;u]HGbVWٴ@đ (ػ^,"hvU Zeu5"+-̈́?2ӝvݡeK¤X{7|!"c;t) Ξw1ǟz 7^(HxGa+$66VZQT)hԨ-OB xyyxBT!CLOADD<]׮ ִiVW"\G@d52Ҍrřb NE g„ x{{Ç{Ϙ1cHNNfxq/6!D"R87{ Moecx'M瞃^*UL5 u] ʡ:uʑOW`b TBPPʕc̘19>M61iҤ͢ƍ];fP3G~,+ɿ}LK , vS94TT:gȧΗ{r!KYx1=ƍcȑYMNN^cTZբ(Y~ƍ3iDD ӝwB.0qiO*N22NKͿVW&5 =z`ٲe\x z)zA֭ n:ngĉ 2$m۶eݺu=z@^u>C%Xv-ZG}ŋADDDާ v;wYw "bz`ljDr3j:xEpܹs9vs`ܹSre֭[֭D:?6-[޽;l߾0'O)QDꋎf,_\CnjzppNݺ#VFՈ\L=x֌(|-QDɓ'Ϙ7o.SN%44=zУGWUr9QRJ~*m"` kVƌCʕy衇HHHȑ#nmݦ0'BfުF4#)Y"%gִif/? *6f̛7F&Mشi^駟f߿jժe9s&gƍ4mڔ-[nݺ>ӧ OJ~\mЈXM/"$1`};LfB_~a̘1,[ '.[z#F0nܸ{衇'11bŊaN8ѣG}G۶m =z IDATbŬFDm Oo_*.t`h s2o<ϟΝ;ѣS_}nݚ &pqԩҥKٰa3f̠k֬Y{v4nܘ'|ҩuJq,X`z+|hLkyuxio/K-_n~/ 8Q_|@fͨV#G˼|tgZt)/˗/g{C[LW曐d̙fUxvY:,Yyzj._Lpp0ݻwG4hQ/4KER߶{,F߿|X"bw߅_6?~^N\@rr2%KcǎуVZ?9 7 hvmC8Bh6r{9j(?F2&NbsOOҳgO|=íy""j3vZFa0;քU]<Uf>JI1ը_}_p|! oau5l97JżVW""rc~~f6]Dml>^~/ ѣf0hՈܜmB|< VW$j*xqr q9sL^DD$wl6x}VW#jNڴ>'ۯ2/SjDDrF 7ԩVW#3Q̟VW$IS @Sf6oM\Dp} ۶AVW$%h-[LH)Z4"D3gX[O>~3!"0d,YQT)8ŋfXO -SǬy-R\ ,iLMF)Xpc.)Xr0t(߯EĽ]l> ۷CRVW$hnhڷ7 ZR)[@$o ;xօyˣͥKpp ?ExX(]*Ԭiu""qmUjDYֿ;3~4gQ`HQ@TYҸՈ;XL0"#F\H(HQ = [B VW$YȠjUXFMYP,5__1sw4+W6r$?g+|U """'UgiX=K!97njĕh Vh es@^fiiР `H i6UWE~ f[੧Hv& ԯou544KDD޳'|9xJv-%KZ]"H@tߩ,[mX]XaXfTHhDDDKsgkKhVW$)#<#xת\GшUV*U 5jĢE]w%z-RJ<T-""^7y۷7#!|buEŔ~S><~ VTT'<< &M||<r]ZZ?86mbԩS'OeΞ=Kʕ-z"""̓eHfҀxT=qu@xx饗2e 2e ׯgÆ 4jԨ* \t%JDDD1#! Y*Y]8ŋPçZ]KXX+VJ*Q\9ƌõk׮]$%%q=0p@ nݺ]ֺ7 ""z!!52_gy}8~^J]xtٻw/o߾ߟŋc1n8F:: ?&**ڴi/b[q{M5j@˖nuUgĉfzuw6Sv;ooov;'NdȐ!o۶-֭رc3J """UDDøq0vYjքiv-&"y6GoeԩSKˌ3߿uw{dɒKvMFFFqqqg{n"""8G¨Q'P  > ".nz̺+Fܑ .0j(.\ɓ' #222DžqqqDFFi&hݺ5o653"""xwÛo‚P*  <VW&Wf-ZX]# Τ"""wR """YaN3 dI 5a;F ӑL8)* VW#@@DDD\ ӆw˖-4mڔɓ'3x`Ԯ] *0$$$p]ws1eʔ\6"LJf_~lڴ?N>رc8<DDDDD\8BCC rql߾Ɗ+R AAA+W1c(88JRR_9x{݋}%22uxbƍ˗y뭷VvÅ V"yZ67PDxLINN7y??_ϕ)W'NdȐ!>˗~ԭ[7>sDEEQlY6lXHBD|1φ {e8kԤIt9v5jO?СCDEEe^7l0fϞMBBU۷uL0ǏSN.]ʆ 1cŊSCq_W`ef x, gfDGGs)֭ˊ+, lYv0bҥ5 2k,˜;wY,cCSDs%߮ 2ǎZqI~G{paJ+au9 ]>™3|kKq8Y"""9,ex,_OG}ŋٵk>WRSS$$$%JдiSbcc< ˋv9J¥Edžր*пFk֬a֬Y|>}:kJ*Evӧ-[,T>}Xx1 wqQQQmۖ5kЬY\=O?ħ~_kEDDMZF6oFl6uϕѣGm6j׮ͳ>K ^:Kns)8֭[oA76m8}e.\ɓ>ӧӥKl:BLL >>> 80󜯯/cĈ矙{\Ott4vbɒ%|7QD 3.kQ^dСC-[6O1uTF8BCC rql߾ܹsDFF2b*VEDnF# "@>N:Eҥuon$%%s7ر+8>)z3yl'tK"ЙCfl^1/_ܹsYj)))N{d|}}ٳiӦ`|zޫW//@DD60wDg.CвV y`iDD<HFFWfܹ,YsQ|y~iGTf;%_AѬY3z|~tt6",Τۗ6Z]UX] uO?1w\,XѣG3/r=sf@HHH}Z?/_ŋ},ԤIt9v5jO?СCDEEe^7l0fϞMBBUV[o[o| bŊ<䓅6ă(H~yټ4""+߿ԭ[AQR%"""/IOOf9\={6/2 4tVXA3lϪ #=#o/M\yy9p@: R|yZlsחI&1iҤ^eDz8Ҥͦ.X" Qj5jwG_0!e|ND)H~ix2v԰aCyVZE6mXp!۷wJMM%22J(AӦM}Wo߾@50`GqzKDKEē9Yoooyf͚ѣG?>[vKeѧOLB^6m޴mۖ 6H֯_ONx޽;-~{F@D$4"";?ݺu[nN}-[pB&OիkfС7 !SNͲPM6>z:u;ׯMs}xtH 3gC,F/u ;wf̘1W_}0;GEEѴiXGԬY/\@۶mIJJb͚5ԨQiuJѡ6"_j+>PGrHȰSG&Mҥ ÇرcԨQO?CyݰaØ={6 TZ=z?ҷo_~W~K,I  uנ" ٳ=z4ќ:uubŊ, ll{l6fΜmo]DEDDKU;K#@ /^Dz{3L4I&],#"pJ=Rgkά8C~nVxSqaƌ9IiDDK# *@,X*UCo_k.HLL_eŊ4k֌nE:=dJMM%22J(AӦMսOf/_@ZjE\\jϧ""6*K!_SvJN/b\t)k/Nxx8/'yӇŋ+pwE۶mYf ͚5}<رCRlY>CZh֭[Yjϥ.X"_j+B# ^MС)))lݺxN8@ٲe aÆ9ٲe .d <^zQvmʆ {oLL 6m"&&;&d3w\/G# "_6`kPgpH,???5kvQgLJf_~1?3s/TRf(W]veΜ9QX1, "_Z.B]<淣8BCC rql߾6h ƍsEbHP,/-BWq$))l篜KLL-Zw} _\:;|7PDo4"BDcHrr2_Y|{SRR}wxfm,{qC^6/K@4/1ߟlSRR2{]KBˆZ]!҆W# H@sfueĐ{Eu(_|n.uW)X T~}ùs粜߼y3ջc۶mن7oL@@[DD W"ΐm۶:M9JΝs:uh׮ZH4K\C=6l6եwiԨQlnI&t҅Çs1jԨ~ʡCʼnذa̞=V ҴiS}Yvڕn7pZ"""9 i%ΐ2sLGgfDGGs)֭ˊ+h޼y56[ʕ+2dӦM#99&M0{l~""Ryټ4".ADfG,vm9vCL09yR{o{ JțoI [""".Ɔ`k8GD>}R|yiժqqq?[nT^x8s挓JU(3x> <رCf.$oѢ[nf͚7瞣rݛUc}V\ɶm2wEq6-BWnO.p@bbbشi111t]믿ܹsoxŋykذ!< sΥ_~N]DDZWqRJ\rtڕe˖v>:t@||c/F@%(3xOT\\\6nܘ/gϞ88D<=ĉsΎ(QDD$Wl63.-iեHwaùd+SRRֿz޼y̜9HjԨq{E """O'""rEo/pFCKβwZ]x ֭UV6>>Ps -)));G6m?~|FD!7RlW1?w1Q]}s@ePYAQkbHp\oe#vVcjm%jjQ؊#NY mX/t:,3g`~73fSBЉO@? n /rワZjURR\sb̘1HKK #=0># LuSIߦN-  BddAŹs "$& LooNq ( 8;;㧟~Q}RoqaR{έu߿XXXɓF˝G@RĉtRh."6mڤ722hii nݺk";;[3; Z2SA:>ȣG_]trre28m41??_ԩSEAbDAH$:mڴim۴gvW"zE>d::>#=>!"""""1Љ """""2.@h1PCC֭[WWWbĉ8}"#;s Ah]xQסP(`oo\H>7ƌ`ƍP(ptt HMMm!vzo}#**ǧ>C^^1j(aȐ!Jѷ>za6"4(9rW݋Yf!++ 'O6uzdd+WĸqbK/Guu5pU\xNXYYbcc1d̙3H$: ݻwcP*Xf Z]֘o!}R)RRRb}0~)rrr0|3صkq5 Jzaǐ,D"o߮׋Æ 'MdزDD"9r~˗/e2x=MӢD"$#hhhzz%Ka^Ο?/666jT*hmm-.^X;[=q)XHKK|MML*^CNNM(FSSSۏ99s]>}:q!cIϐI=GZBEE~mׯX5558~xz---xq}X% ' 6 #GDaa&wҷ>7pb˗/vvvZSp~WSE&tR666 B~~f[qq1/nܸq|1S%2Z{_yƌ\.Gtt4jjj>̟(xp mV=m5 (--N5VRRbDR)J%f͚'''\v 2e Ο?___@5SQQF^ R ӧ·r93u---HOOGbb"\3gΠO>8|83.@ PWWTl! ̙R1c`HOOCg53`ee~R)3 6 -- 0sXb&M%KAV}=s)XACCN^z/OOO̝;YYYEQS2lllVO}}=kYz5A@ff&0_ٳѿi=}...mj=O}}=BCCرc1bv[gў>pbRfٳGkhh޽{1qD0;22ؕ+WpQhbaaa8v4LT*̟?(Ro-IIIZOJJL&ٳ3GCC[ P(ü477#<<8|0&Lf?>SٍIKxx8;^HMMťK@SGF[[[`(((={ JÇxZ???+WDuu5mۆ#//`]v%%%HNNƼy4$ZHJJŠ+T*sa߾}|`IO^}UqIc̙:g}UVaǎ mKŋ0쳄a>۷oh<4/ SN:-2;v&Lrhii)7t^vM1c(DGGG1""B?L5=+=(HDD" Ν;~—_~)1BJ_P>*++ňKd /[l'444m\""soFTddd`Z`8::BEXZZ3} CQQ\\\:vUU $,]Ԩc+.@zh$&&ԩ_Fee%Ξ=kΝ*ODdnx i۷oCl߾;wСC!{pww-^y|["<<'OFNN֯_J;wb͚5HIIѼv߾}B֭[QSS$2dHCV߿8z(!"+W"%%Ѩ֭[l2dffj5f̘FEEE8~8cرE fϞV""\---J='ǣ'p$'''bbbo 99Y%K`ላݻͥڽ3JBaa! ߿?z-l۶ * 2L+߻wb(((۷yiG_Q)XDDԩk0~x@DDfW(..ddd .Dyy #++q>|¢-ӧO,>R,>y&o߾'NZ.// ""_G@T* ((j(++ky :.+899AP 11QnOLDS,""T>} zYECrk ?SN!&&p4Z-NNNJDDOOOdGuFեѣ1zhlذ999}6m겼233  MMMطo,,,7##^ODDH$h[[}/Zزe mۆcʔ)XlY,[ aaa(**^u/ ~GHOO1 IĮ`ȑX`6olԱ?s$$$ƍJF\""A͛Z؈> ~!DD]G@hV-!JEIENDB`PyNN-0.10.0/doc/images/tmp.png000066400000000000000000001006321415343567000157460ustar00rootroot00000000000000PNG  IHDR ,;-:sBIT|d pHYsaa?i IDATxw\U WLܣđ{Ά rWO-s{ Ms/Ds "{.wq|hҤ ^xL2/_?(]"""""%VZ_4QF@ؾ};Ke4kL4e( 㑒ܹpQ{7V\WW\ZH@5j___xyym۶~Qv'-[6ԨQGѳDDDDD.˥`yyyo߾hԨ|||p!ի(QpU@ѢEiH"صk&ƃÇ@;%&9sӌ(DL:u~ ={D@@ƎM6ƍ0a£ݿ#Gg3gG?7W^@@@-[oLdjՀka8^68}}^{s-ZA0>H+n(Fw۷oGƍ-zӧQb ^^=!** zjt;wDxoΝ{n?ϴcccQP!Gܹܧp!pP:m1o5CQww#c1>ii`U\ϷE1%Jٳ44+WXbwHG ^^E[V^J$Wr 2AZr{V?1ZQJxzzر'%%!22]vUDj3% f(kJ5,]*%Kjz*J΍A0>HKNqqq|/,, hٲiӦXx1޽-BBB:uKq."pE@IƸqV}` O SrR2AZs^zҥ {̞=F۶mQdI5N07oDÆ 1k,|ТE 4oܠ߀(crRoJN ]XAA@j*P:m s»uS=2VPX֬N{ tҵkWDEEaҤI:t(6oތ,Y&Ñ+W.1sŀzjzOdފ@@ٲ괷l7/+U LYl2՛G2Ň:1>\ ヴ ȸq[n1} Uկ_v½{p5ORku/ez.괧([oT&'su+xpA0>H.`Y,n@;Gq9i4?k@WP" %`M9FcùC =0!rB+V/ *΂)EK;J (S H]a|9۷剒Zӯ@4mWMuO7P=2a|^90 )8`ߐ޽^ 8L~eu@  0!r2AA@Z7wH{&yJպ:푱ҫ~eO2 A0>H/L@ȃ2Үzmn T/^d;wdDڧ&[li s'& DNd6]dvqc#l <|iN{ @FGb|9"'v-P O~! ӯ~arT.N{{ IÆO~dIGal{wϿ:qRRdzޛo>}77/;v J Mrn2AzcB$bc՟~U:?zm1➽=͕ sb|9"'*zmn]EH$/N{Ip 2AzcBEZ.&'$DJ)ط/c1> 8u ڷgi+eO:{>ق|dヌ ^^@&굹?PL"&O.[P:u 0AuFヌ [l ̩^u'$D*ͫN{p%2AF`B_͜meÇ@DKIƸwWzͥKw@06ǽ{5 0!rp6ȰuVܖ!cǀ/"w.!@2y5s`|92 "*si VOOfM$c+@ɒ@" sa|92 "!y3ge\lOQ7սsk`|92"kpN Q.Dbb2meJ   #1!r`2mnON+W#l2bm|DGTƇc|92"*>]KǏ@I ٳfdV9y0> :^F*,~e͐ѣT^XkGŁme5˫W 1!rP@l@fgHx;:16l׌3eM2S sd4& D*4h@ɒ 9Yg}&Gf<}–!!R(݌AFcB՟^*O8$`&OW0>)>>0>\"mK76I;wߣヱd"j i*{B!r" =-I$,GF^^@r s9EDq/NRm>X^݋^<<2~T s(<=鯿dwZ-SAY9`T>u*jڽX~*TAj|TĥG@ϟwwtbccx믿ZYRHE2ݠ埱tIreu4J2e'v]H3'и*@XFJ J9۷3F%mdǤI<4Ğ={pIpssCQreԫW5t,0n8)S>{&Mz{ŊӴoDO ?ri''ӯAiϜ݁m%+߀`R%Md,ܽ+7yZR`@`B_!CcQQssS9(}2Usg mMR5~:f̘ ŋٳ#_|HMM۷(Uzw}~SV/f>___t]Ӿsw/0{m?/-K"$ɇɗ_:T>]Ou| 0Pn zz9 f*@.@O?qθ5d+LJnSRHII}))){!ȦM2uk>gSU %K"6"kP{횶}q56-< *~&ׯC}>yVǎ2ft`bd+GJr=sG㓺TK@v؁ŋO?/^/^%K?TjԨ|}}其m":::={^^^AѢE1zh$''k7BC5]m'O>>@Ѣߖy|г'Э[ɟ ј޽9!!R:o)[29c? dpt0>WӔ3glkZo>knV]vسgZx3f ((~)n݊z!&&/__~%/_Enݺ?~\L*7.DJ$JLJ)9}Z>T]VZCa7gΜN:SN-ZA0afΜgs}=z`3gu=f^={v@n-ǿDطySq@.ժi6R͇Z6Ӭ0l[o%Kj7WR{b@n0NZ&3g!C,{<ȴuk`JO3?W*UbLT={6.]ٳgc̙(]4w=zw*W["l\D IJ͛7-:.= %/_ %*k5ʔΝ-_ce֭R"O{S W[bt`H?U0>d09!j2p@ 8/_Kbĉ8q"jԨ=z[n6޽{ϣPBE%GL p(`KA8KMV{o-e~-|F.]dWQdj>E௿ѣ%sϜrl_f- )+Ve5}'UEq믭,eTIFSRG3d'ŋ㣏>Çq)|W{.>*U8tɺc14I@ҪTƌgb:t(N>d:U^C.]cdWVr' i*U e˖}"11QuQQQ4i͛7c8xӪJ. `ڵ_#))Z"-=x h|Ē'Jі_ }BcTgպ=mX `zq5 mWQ;>{|,F)IH_V%//<ǂ ТE /^Æ C\\FGɸq[n1}gt.]+Vq=ܽ{5) rPA7l(WxUoY?[%I|?T9mn >åZ~[ iʩ0>#}Y)>݁^r4I@vZt ~~~۷/>bΝ8<Əowe,"W*UM))] xRVU嚔bg& ͚5 PvmXbڴiOkVeR"@Ktjd)wش1IL峲#GW$>ݥ6mm-Naǂd]YDzb|T"#29.U~/_Ɩ-[ЧOfKS,(,LJj_T$ Z\ m^zI] 3fצ |}m+o'Ѳx3GxjXvW~YvYDzb|2sS51bZ 2Q*k' D@l#c4hn9rb߬,$DwVTu ,!۷Om2wߗ;+6b||ƇȊQ|qӴ Ν;ѷo_^:UjժV'rh M%3߲l9߶v 8xPۀma]dںw'Y*#cUrhL2 6ʕ+q˗U@(P@9mdqw-]^=۶T@XV-&.wsPQ{LP:r~ wOAX̍g|< s,Q~}\rؾ}3_V'rxrj@.ʩ]3֭ڴ֯^yEhUiR;>e E nVlz٠>G֍U%1ʪɧ,IHH@Ϟ=!*6 RLu֮]NAT_;JL6oڴ8.kٲƒ'ǎC-o0˕ sjw GempNY=>Wkc}Y/qe.T27)˶2nn@Pq;n JsM;|XԬiS,6`$gh{GcTƇsQ,?|ضϓ>4K@OM6aɸyV!rJaa2OY R(r Ց#bSpq4Sʕo VD=wnu}Z~8&8a Ƈm9c|!Ӱ86%Kb9r$ *///ɓy䁏ϣW(4T6˙S\ {tO '\u}Z@2",F7^ܦ6 tӺpAa""ONX@ :d1>=#GZL@fۓW0aJ(ZjMD-`_k*[/n#"dfg dmm;Gez'#Ç;tnkӿ?0utu1 0>,Ў#Nj/"\kٳo`ݺupwt"q#̦`}ɒըL';7k$ 2AUv7*O THsf`|hǑV-;ԭNH]eIIIx7|=%8XN="Ez@S+۷9֯ٳ^FsSSՏӴ5mל$|Yca=Ƈ5>VzNr\e[Ν;j)%%6h_Gm=p<[Lc!}O\&hmFȎm䱽o)z?·XΜ2qi|78q Ç#..7o|(+ٹS}H i׀ݱiѣ={6ԩ???,XB iux" (􃌞(ٺHfON'Tj [=q~lpM IDATXzmf>ɓϫۦ%??^֯uP&u0>T<0{WH]-B=zta,JE#^ ^xA6-Ѻ5'(MZƎU6^SfnW"eӯ@lƍSmƇc|<^=:phX6I=% |VM9夭܌.Zr0dmZ"[6ORߕl*I? KOW#pSO#WuLd|؆.gʕ%Aڳ #b*"\_x.@O/_ 6֘'з/p&dnP"P>dzBZzqℬawxZ@rk+`|؏gwwڳGH]% &MB||՟}6&MV7VpGTɽ9Lr'L_٤HR>jLJ9'O7:&o_+rzvgZdǻuK֛5:fҺ5Pk1>3K/ɵӰj ;zH>|~)͋{bXd ߏ >áC={`˫/سzzI#@jư DTG$9ԍ۷e#4=I_X:TƇsȓVMFȱhRf͚YM9x24q6wyU ٳh%9\X>>-0>,*Ѹ$ W!cd$<<7F޼yڵkcʕϼoϞ=xW兢EbذaHHH0*֬VɟV b#F6+W0Zo̖ngE2{v{b=EVڴa|harh~8uJcuTM@nݺfs D-#GL4 ?4h*D&M)S`_ѩS'zN`zcG폥")XvѲEvQe En;&Mы+Ȃcg'Zb|~}ٿjAQ o"EЪU+ȡuZ .СC1e5j (۷Pti 8[lAf2-[Ī-W϶6 ̚L et,l,rĥO0M M L8 -|9Ck9k| 4l(k2 8d9UG@:vpt~~~۷/á4n֬YPcǎܽ{7ݾܹsٳz ootkefy}Ie(K8"R}s:"7oO'Zh6tHNY0>0ϕ7dݻ,jdbɒ%xװd4oŋLj#pa5p#$$%J ,ѣG?_HNNFڵ|lPF q d䆸cG}Lͫل0#*V^{͹Q:$]s&bצZykz aG@Ru!˨=w֭֯_k׮a̙PM:uƍyv¥KЯ_? 0k֬AV0~x|wU@ѢEiH"r}%ײq<]cuH{1 ;&b_.*mtO,|Tzu#Iq:;7ylYÇGeLѰf|8>Ƈm*TPEӴ V1x`ر/^w}\r믿F +Xܖ(HLLݻuƎo۷ŋѲeKL6Q@kVr 2g%M,] TGXL/N{TɑC.zcG)g 5SSzMl^|;uEtiS[V;;SnƇsx I@Xx-Q>,\m۶(ؿş߱crmٳgr傛=5NٵkWܿԪ\[gܹsgڿE!88x 8ؘ@P( rd:S`lNvirzMBv MS|8l٤X2=ґ \ou#W7ߔ??WP:T/bҥXl?Wzaq+W| o)RPX1;w~O=-\0eMSLSҺz*+fq?D`` <hdlBL嗍9=%4ӣ(K}['OՂus|g ղ;:G$..+WҥKw^xG(mxzmgj׮hĠL2oZQ[:WR8x :ٴ!)) $9KeQtɒ@9mRs"Pmj饗dۼy$$惟|bhҕ+zzwl$܎~/es̻w%>>7Ƈe=>eڷV&MruUCݻXhZjŋ> 0|p:t'O_|aua.]~KS'55(PjժEӦMxbMSmѢEHHHfdXCWO@^}`zđpe {5k&g)vQ;>vႼ: & yԽxQ^!>:uQ ݓ3>HGF2 k*IR5iҤ /_ׯ#00M6V0tPcĈ)0`Y&Ñ+W.1sŀzjzN(0P*,qME]s :tf̐Dđ\uyU3AxQ*ͫ^Zɓ;W6s$-1>0US+WTѹsg̙Sf)Sʕ+HLLDdddUׯ]v޽{v~'xyycrVGJe͛Jtbv 6l0'OZ݋r횼1E/m|*H ޽ʆQLO"/QBg& ['Oa917=mzU*D>صqS!C9>I'b|I+e.1RS|A9aC 5h5'(H?g^ P9 DΒ=3fuȗ^u2j(mJeFKZ|ܿ/I jb9 @V bo2,ڡwI ycq* e{OuMĞ=6,\oQÜ;l)P6>n1nx\exsx9Y3 1!\&'ў=͟Le M2Ց?/}5jy9׮; fgu=άYclmtn5GG^1>D_ǬY@G :d\*& LiJFC֭y\*_kWv Txqԋ2wϘ>$$ӵ~WP3A5@8c| L.Oa }2> 1W}HE & (9-'vKsȗO2b@  XMDQdөd缁#emΜ9.MGs}JEÁ?ԦoXX9?W)Xis'T(SFM, &k5p2:V #}mD OQ54B:]ܭ[[>q~ |=ТP>̬?Zhv{- t|~OMѲ%1>F||$XSݓ inb)ugϓKesq=&N&9EW(cH9a*b] DF苣0Ňe4(V̾_ѣgp(RDRO*==& ;^{MvP7ؾgaԨ't|P=QPe=ѸqSK))RY3XhOLJS(Bʗr~ ܸLѼ9РDz#s~*?`tO& UdGv7j{W6m1iP(\}^c8K'O&h{kIeZ_}%7'j{%KSΆQ2p޸>& PI[<<$I $D_BI얗9=G_\ߗRuhs [p^&ĸJ|.,OΞAo: GY#>>\6~tAnݾ-k6xCv,mThƃ1m֔0]G@ϥk~}mK2UHs9jcw6?[wpNq4e @2_d=^m6m)Xٲw\sL'X[ߗ# ICCeΟ?Q>_Ww$+Gܲܶm2NMɍ'*۶=c|׮#G@zR}{㺘f6l^x(YRǏ^yE "5֕)6&6VrHzڵ[>@Qy*Z0괩6Wzm:-Νei]RS%>d#b|XGww%/bBOqcϖ X\KNZ EFWz00s#Ol ̙xyߞ RMS MGe>}9L.#sStG~sJNcBzҹZ*^\ٴIyqq24ޫcFLV UCbP_/qߗQJipblr`Nl_[ǎСR1>3@36޸& ;U/2!!O)#7zBejY'm:6m3bqmmLXQo;"77X+5U)~aͥhAA+ ƇqYʺ{Z&Sҩh͜oy׮~]vM XMOEhdU+S'A>/1; ;Clx୷iwH|$&J|a|Yxq`25rѽq-L@HT%UT2MR_?u½{5@C..Uzm,܍ UWBf{ /,O7lsqq22{挌>69S|4n,kA~:޸& x_Aww % R[;bԜ{\ -s_yEn"&N!+9#{%j/5ݿ5#W.)ܴ<=x Ԫ%eUkC0>С2{ Tw䈬0Z<ž{WZؚ$'={OxSMi#5uD)e_xA*e]$/F.RpaaH>L=;nY%>r֮ƌ&L9SQQ޼ Hi@@Dh::Ƈq[|֫0oѽq~L@Hu rec_׌=;]$7!!F m& ˖MnZ.́H`lѧ]=paG\}PZRqNbE1+P@~9#7&vM3a|؇a=g KF9|^]B`~YW0 ի}>Y|W @l5T['DsTqxѢBVJPM*WVOVM֠1>c|0>qpwf̐RÇ'O>.ٳ3Tẇ5Z~0` 9v~"(OF֭ok~.ilB䩥3}5:y:y^m0>̷8o|Iuw@2F̹p *>8{qzy6n IweG bb\@Lq#-_{g3O.Ja|9g l.{T&19%rL@HU莒4o.}۸QF@vjGS.Sӧ{}dCdp-2W^u:ɺ_9N"U;&SZn^oh 2A0>2#/v>P%Eq(}Yb$<<7F޼yڵkcʕO_3_Z2 Yh-=:Z-Sȹit1>Ƈ7ѐ)SdSJ ĀмysL4 8}4bbbys=I&=bŊUpF"k*2%Lli2*2yVM*qv-jt/K' .\{gCbʔ)ݻwסgIQ$idM@9j'YD+c|9/ݲW);liNUYKڳf͂(;v,ݻP̌)ܽ{W.XI2A0>Ƈm<<-e_X`|yX8z4P&P о=RM{N@(Q>>>(X Fn"rYxyyEѣl@ϝӉD_JT IDATZu'TYVu c|9 ɶ۷ˈȭ[_˾1 JTÆ? g Fr)XQQQD~0rHT^k֬㑜'>zoѤITZ XjƏgb U=ɚ2-1:dC}9sʾd JDEvGNԭYǟ˓G-*_ ˦O5nlf)I@E,zoΜ9o7oĴi0j(x{{ΝD=z1g >u5{̺u{3;Ypiuɓ5e +&da|hCninSRΝ ^k md#hg4;v@c SӧObŊȕ+߿nݞLv튍7"22>œ9suLEPWz|eE݃vmCrZ <5SKժ2D $0>Ƈ<<%+3;87ܪlrulڴɢk>>>X"Ν;DdK1ƍѺuk[mڴɰ{#F?LƢ \ӌwV}vڈFLL ʔ)W\L[>"""""KWҥ ~{T@U >>%`pssC-4 s[mM4IZj ݻ믿>uat ݻwGrp}]{QF """""4k@l/+V͛7#G>0… 9r$s9\ d%& v`BFQs"`ȗ3~j"&-y-n;.C0dxeҠפEQ_提/W>Lm1*w)>^;6|vbPAb*re˥AI/<9<؁ !!)cPAl2|rfgOQ>yeUǤ{ap`chGX"Vw^˫cd& v`BzvZ/i37`N9^=vXe-njWzikqV8vZ/w[WKj-??HOL@}3-FTqnݿ:b=Xq%TjqH]oGE͐ =6h%+c_>,{k8.??HoF{{fȗ+ºuh=Lc:NŝByvkysņf7ʷ ͎E;ppy4lߜg; yg))K-kֿ.%뜿u|9a[m(UX㦤uoc_˰*^ux sx 0Z̝<=h?95tC`t Art=>ϝZkpξ;٠dt_Az(By<9<\j VBBkl燻;,X`gϟwwtbcc59ѳͥo"%5[{o<=4-WǑGt΃;h%S+\q|4/Vu±t22K% qqq7nΜ95juǍŋ?Ew2nk% KvXi*6JBn_ƞQ*o)#w\㍥o0>d\*)V]'OVZ{O|a~O"}#lވUVJ*Fwƺ憀e]>16Foʎ+|{#{ͥo2> 9C'N`՘5kfϞ5kɓ6={v., l]ڢ( 㑒bs?1?r>ퟆZPsb)?>}S148s ya??a(:۶mһwo%_|[_Szl۶([nÇe8q/"Ə-[ZuCᥗ^ѻwL߿j*lܸ5:Cܹ%J<V"5Nsj+ߋ\rݥ 8 3Į~RNp{cO=C7 ŬCg?rN9/// 8-ӧ1k,ox& ٽ{74hAa̙j3~ǂv HeVn] X?u56 >m&> I 21??門(`[nY(QQ?p*>ޭ.3;+[sE ;2q'^Сrc|ïm~@x sx GpexrJmEqΟ?ϵ$\|Kar3JG}_?mQFw$$^(4oݱZ;`8{q9<9<#s$55[lo ???tC59޵kpi$''?^\\3 CDD ,5f~ K:,AlM"EsmO\Ν;+}2k,eРART Kex;/TǸ+`tW^c2Fwe쾴[q㮌1?17|ctW\dtO@1c(+VT???eС777e͚5v_tGɌM o+|J͚5y*ٳgWJ.{f & dJeWS*kcra$QN+2aC/}xT\=btWd t+ /28\r]vٳ'5kOOODGGbŊXz5:tpRl5tP̍rUիWǰaPHt HIIyb4U%&'p-Qۻs[o%RLd',5FW,ca Y5_KEe ٲdK $K5T[~={>syݼ}9wf byr~~8rS¦O>k&q ?"[|Ǒ;ť Dwg6qO,(@\z=¢Ep-?,‹% 9'AEw:N&LҲ?qc*n}  :;jSK+ăUb=@ǣ^555?{!&&(L:ǏGQQiXsu zC|ǩ3vθWoy~~O~BSg>bUw_2"bZ/DϺPq FOYY.\={χP(ě7oj7ozjDEE!::999ؾ};&MTsrr`9rի֭[nݺsԔL=/.<믯G8tlґ8Z}b˅׷Πq2qo\ܚ~ |GsԜ믯oA{㐏P/nD***ϱcddd $$CYYY_KKKu%bFBHHzjdff>CV]YlsئWj a(^︙~JZ148荈~@@|Ԇ-t[`q?KOqq1`1v q۹sgݿ{dee1===W_s?}@H g3j"sG0W\wzˇLL1(28W-7z1ZfwÔï`1|G!S^z$''#;;Eٶm[_ƍիv؁'~p{\riiiڿk޽PS5E>L\)A8hjIfmqpw]kwz1`[<~V;yy"41&¸1q/#4hw$ݨMăH&to`9s&&&5kq\xQY`eeUgϞ(((d(~zJ9CW@ӎOKe-n.$_@}R~x5fwzg8ޢ o> oyoFNN+=RRR,,022^RbdfxaF6;otu`plJKünֶ|McaclƑ{GSodg`eF;oz4ru ao ;w+DQQ44^اYuaa_ ppp@HH3ʃWVς!(7ʡ+ûamCZ `橙Tĺa;ΰokoO}ܢ\ Nς@뇯; 8vpYc3\QH-6133C~~>_D(J{}HB_#88aaa 㥞Q -/ ; $'; 5^B&Č3~<41GXؘ88GnF( wGă`Z|ǩq  ydٲeؼyNz##jOJOOӅ5AqAw޼*|? _8PGnF_a8p8.318vp;NѢa 67G|MNQ9 F W Wzǁ DiӦر#lmmabb*mذγXZZ"""[+[EEEA[[m۶  cډi;.Դ%sߟť`W+ov Nuz~{b5Ws݃ǧ׷r _Q~TB9 ͛qmɓæM<>JKK%mNNNÇ%m/^aoo_*HKEo#\plkQ `㈍(bs"s/]ZrX82w!n`\9q%?̀f@b߹rӦMɑN'O b׮]HII H>}0yd$&&B__`aɒ%Rϩgͱf /¸NгYODAIA-'q-CCn;&1(#DAIV6b58Vh"X L=>[ĴSoQAGuhD6R֭[ooo89rXx1rrr=m)p)76l дiS\p|Tq ; Ls2Xry R_bV.n35qd7`@?Z@;(KhP5K̀ruorr2b1b1PVV&sl+\QF@VVq…joNHޭbTrBbV"V]Ysu{75Cqނ\,88u.Y __%6K`،87ob|ǩsQQXjׇPAf@ -Z%K$+KDjj* H XZbAX Y<&[2܏SNߏ )&Xe [bR%Qa4ܾs#7z7y}aŸu8u>vw};ܠf@ ~~~طo7oA'ODbb"ӑ8q/FѲeK8pI>3 yUV6e2խˑ_쀚 -XP_L`J|,i}j$f%"!.KKlUVPcE /@@3 Gf#445aoo 4k ͛7GΝUVA__HLLbOTD} "S#xɛ7"b~u;p:Y3,8Rwm,?XKCKۿ.9EI͸+z!v;ܡT1EEEK>ڷoݻCSA.]l2ھw;vã?MVgffI&άH;w5%sߞ;no+92zl DA]EHrkX=+̪iť =%<'_s̅ ?\c%*W@/KpsMO@ j(xDѿ;BCCw۷oȑ#q6l(| lW3p;ↈ 1So;#b˺1- 1\v A՛ʛ% !+>\Jf@¬~êU0e\p-[Ă5[%cĈꫯ*=44~Mpv IDATUw Hug *VlZ,VF/S'bմ/ceJ|ױ*j+Շhia6\{z "]{z ] Nݔ?]"fr!bڴnडOOODFFٳg| PV;ֵ̀@}z~+bbeLE:Xd8 aW,^q'q>ZAI&=/*+Zq3&q>Z(LDf|Q(7 fҿ5; 3C۶mSgAoݺ G&PFɤks,:U?A{/=3Or`瘝 qa}3?댯B|#=?2?:K($==FFFU+޹6&O ___=z ,@xx8:ˬh>4*2)9)Xp^9pAqA8b\ [hj`~$g'csSkbKhoО8 G]E{œ'{VpaǺa膃uqt QHaaakTUXo۝ &K.ř3gK,_FwssqoBd:6鈵ŦMM A2xNLsGgLwաIl7p qj,9;SOsGgLwΠ~[cȽ#|ǩdxSG'L>8 D3 GaB!Iw8|Vexk2Rᛞߗ恮]Ҽ2ƇG2zKZyvĹ0T4 S=ģ=ocSӒ^k#LPW46F}GC _8heeU파=*==`ll\c7o<~ʪ3 @yg[3"=#Z{gG̳\v =qq:z+,Xbܡqs>/Dlz,"&Gf#(dC3 G=zj{j"Unp%AYZ.?VYڨ j! ^vfaϝ=GnF}4j6!CnO|_lΰO>|QzB=v9A}1z[!wCb sh֬; o?++@ԩSy&fϞ]gMmf@*t5 Q DϺ`fuɸǧbb׉cqNw7XYߑ*~SOĮ]6eհ+o7n3z֯i.07_"k(?2k Xh233ѦMܹO<%-\vBJJ LLLw ͛ضmLLL?YfES <F[fX4; o_LL2q0\X4!|Gdg`̾1hjQTn7>:6%L{D>efԍ2?]CD 9; !!3@]Ex\jT\ #n8,G=S9  =o2eT\IG'bEu=Ju$T*etg(;.\1c4GPl4"hBcyqcw;|923*>< \rCCpŹa#RL<2`zs.9Q#\x| 5DGN3vٱIPC"5:RTeKXoFJNʧ"|u+ć`ؽ~L|s}s\vů1p@<}Z,.-Ƅp0 ;SG:?&8mI5'O2\@sGQQhD(?>}N9996m4i 2qqquXHcB&p L muzaiNQv!0r>Nuv,"m}fzorZS _aa8z(8cG:;s}s\t EEػNeK =GK(rC3 G X~=ܰa`ȑz{5jBBBիW#33666x?1Ku)zqe4nA}^v;w+dy×R?Z#3 aJ N!G-0p@싗ũY8v~ R7P,4"bDZuIڊׯ{ݿ?8Jڲꫯ޹#33߄o {_@T1 64[ɯY&.c~1~Ls&ו%LBR‡Qs=+1O'X,f73eׂCK+l x>Pv۱/}w R 3r!bڴi6 xzz"22Ϟ={ニ;v...8vJJJ4`L:׀PMcv? _ &~t띌;cfI]'!3fͤ kjB~c =-茮]&']h: P}w֭.]<4h@|羖STTѶmjۻw/Ə/w@MHHy'>T}>ESeѧOYP~gt 4i׮]<999033jpuuX~/^#F޽*B!y07n9mڴΝ;q @wڅ(51|9oB!0`]/ 88ڵ+N8!|kEfyNaW^صk >!B"!B!o9!B!DfhRK`ll ---ϟ;Co޼ŋaggƍC `Νn{=١Aĉŋj B  Ѷm[lڴ.#1119s&:ulƍCRRRm>OBBѦMhkkC__Þ={lKA`ܹF˥K }DGGWVjZquuejjjl, ׏+WԑdqkժeܜmܸX5nܘYZZ2HTi[qsvvflĉ8j*Y-"%ؘ͞=e˖1CCC%Q}(SN1;;;tR6l8c˖-lGA+---:w\9rEq3g۳gONj/$cmqlݺu"fffc2RYFFc7ns2c ͞>}*i;<8mݺUVPP}'LtttXvvvR]JJJ*%%%1MMM6aIPVV,--0ظq㘭-aQ>n'AZ?>SSScyyyW\8c<%#HӦMٸq㪴k׎J~>y$8>}v8{n'2geez!ꃼmѬM6>˗*g 2B5|* b_eWy жm[Tjٳ'֭[|"gϐ=zTygϞ\okee@@ucȀŋx֯_3g`>PVVYfaԩԩSFɓѰaCB 26> u-==FFFU+dxJJJtH>VPWW>ՑسgҰl2T;w.n PUUņ 0m4TǓ'Op…jQNprrȑ#a``] k`ii)AZ(,,FvMMMD9U>PXXuuj_GCCHݿ~-I& w}aϞ=9s&B!&MD^| ooox{{C__mFS߾}ѷo_ϣGtEr[4P*EEErI}BDj_H=F:P}]vh׮`„ >|8̙qQ}(5k;!ڴi9r1 266u$ROTL}Vӡ555ɶeeeUDxՑň#k044ՇKJJB@@f͚T %%EEED￑M5B*iѢD"޼y#AZ֭bzYfhҤ bbb<]6uUqb1Ց***=>|'N} Vq@ PbϞ=X,Zn-yDGG055 ёڐz[ > k׮Uo߾<&#exg̘]{˖-z7tWYmT)33JH$bVVV2ƨ>Ջ/ѣGٱc$G2 ֪U+v1 MFOuǭ[3fMk "-[X~:;C7nd>>>lƌ8|||es'R333ɝHX׮]܉Wr'ҀɝHW\#`ٌ8<*P}(1cưC%KڷoA/2> TFT#glԨQlٲel֭lΜ9LKKKڠH-3###zΞ=w,RZj8c1@d6|p7nܪ1X홆377gdvH@Pi[o>3CCCȑ#lKA*T!cT#fÆ wL__f͚'GUVjc1ٝE!BQft:!B!DfhB!BB!Bd B!!B!DfhB!BBpww)18pQPP cE1 !DB@ 82,^^^^Ғq0o<,_2;.!(:!!({VyΝ8wv]7c jjj^G#RSSadd$c>&O,cB!(3gb(5_ ''/_h,B!$%%֭ƍajj mmm 6 O>X,7o---|ήOƀ]]]=STT3gֶsfѱcGhii_~{.fff 2dJ'%%FFF hѢƏׯ_WW\ANNN~BOBٽ{7JKK1g|WƸqпDFFbѢEHJJƍ#((Hopp0aggիW͛7󃵵5вewfH$U ̙3ʕ+aooٳg#((3gīWzjxxx << 0|p HMMɓ' ]]]1w^QF}쯕B!J;37==IIIhР W\BB (Pž={555 SN&MvaŊزe;ܿ޹2WRR߿O5k )) ڕ>y&&&HLLDJJ :cJ^_~r֭[ݻGB:B9;;KЫW/dQ.3s琛 WWWxBի.^|@:C >$|c@Æ  ߛ/^xvBj B>?[hQm{u III!CiӦΝCVVVR>6)Ν@UxmybBWt !RQQU{Ňv CC*۩!}}}ccckǎٳg兕+Wh֬dA{BB3mڴ4iC ۷$''SNRO?Hl СԏO!ʈN"R4O5.VX*躊ݻC]]111RyyyUXXX@ @$Ujq۷T3BB!H 4`eeWWWɓ'8y$qwaÆXdpu IDATrc̙pqq9JKK UUU8::Vܹs~Bj d8{,nۚ?߰fy0`<<<>x8::"55͛7Q岴?gϞAKK 8}d,Źs%RǤU!"eb;v .]*cXv-=z B]B!^Xt)|}}QPP 㖔`iA!RD3 B!?)Rަ!aIENDB`PyNN-0.10.0/doc/import_export.txt000066400000000000000000000042131415343567000166450ustar00rootroot00000000000000============================================= Importing from and exporting to other formats ============================================= Other formats for representing spiking network models are also available. PyNN currently supports NeuroML_, NineML_ and SONATA_. NeuroML ------- See section on :doc:`backends/NeuroML`. NineML ------ See section on :doc:`nineml`. .. _sec-sonata: SONATA ------ SONATA_ is a data format for representing/storing data-driven spiking neuronal network models, experimental protocols (injecting spikes, currents) and simulation outputs. In the network representation, all connections are represented explicity, as in PyNN's :class:`FromFileConnector` and :class:`FromListConnector`. A PyNN model/simulation script can be exported in SONATA format using: .. code-block:: python from pyNN.network import Network from pyNN.serialization import export_to_sonata sim.setup() ... # create populations, projections, etc. ... # add populations and projections to a Network net = Network(pop1, pop2, ...., prj1, prj2, ...) export_to_sonata(net, "sonata_output_dir") A SONATA model/simulation can be read and executed through PyNN provided the cell types used in the model are compatible with PyNN, i.e. they must be point neurons. (SONATA also supports biophysically/morphologically detailed neuron models). .. code-block:: python from pyNN.serialization import import_from_sonata, load_sonata_simulation_plan import pyNN.neuron as sim simulation_plan = load_sonata_simulation_plan("simulation_config.json") simulation_plan.setup(sim) net = import_from_sonata("circuit_config.json", sim) simulation_plan.execute(net) Simulation results from such a simulation are stored in the SONATA outputs format. Support for this format will soon be added to Neo_, but for the time being you can read the results as follows: .. code-block:: python from pyNN.serialization.sonata import SonataIO data = SonataIO("sonata_output_dir").read() .. _NeuroML: http://neuroml.org .. _NineML: http://nineml.net .. _SONATA: https://github.com/AllenInstitute/sonata .. _Neo: http://neuralensemble.org/neo PyNN-0.10.0/doc/index.txt000066400000000000000000000021021415343567000150340ustar00rootroot00000000000000=================== PyNN: documentation =================== .. toctree:: :maxdepth: 1 introduction installation quickstart building_networks injecting_current recording data_handling simulation_control parameters random_numbers backends parallel units import_export examples publications contributors release_notes .. note:: This is the documentation for version |release|. Earlier versions: - `version 0.8`_ - `version 0.7 and earlier`_ .. add 'logging' after 'units' once configure_logging() implemented. .. add 'descriptions' after logging Developers' Guide ================= .. toctree:: :maxdepth: 2 developers_guide API reference ============= .. toctree:: :maxdepth: 2 api_reference Old documents ============= .. toctree:: :maxdepth: 1 standardmodels Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` .. _`version 0.8`: http://neuralensemble.org/docs/PyNN/0.8/ .. _`version 0.7 and earlier`: http://neuralensemble.org/docs/PyNN/0.7/PyNN-0.10.0/doc/injecting_current.txt000066400000000000000000000033031415343567000174450ustar00rootroot00000000000000================= Injecting current ================= .. testsetup:: from pyNN.mock import * setup() population = Population(30, IF_cond_exp()) Current waveforms are represented in PyNN by :class:`CurrentSource` classes. There are four built-in source types, and it is straightforward to implement your own. There are two ways to inject a current waveform into the cells of a :class:`Population`, :class:`PopulationView` or :class:`Assembly`: either the :meth:`inject_into()` method of the :class:`CurrentSource` or the :meth:`inject()` method of the :class:`Population`, :class:`Assembly`, etc. .. doctest:: >>> pulse = DCSource(amplitude=0.5, start=20.0, stop=80.0) >>> pulse.inject_into(population[3:7]) .. .. plot:: pyplots/dc_source.py .. image:: images/dc_source.png .. doctest:: >>> sine = ACSource(start=50.0, stop=450.0, amplitude=1.0, offset=1.0, ... frequency=10.0, phase=180.0) >>> population.inject(sine) .. .. plot:: pyplots/ac_source.py .. image:: images/ac_source.png .. doctest:: >>> steps = StepCurrentSource(times=[50.0, 110.0, 150.0, 210.0], ... amplitudes=[0.4, 0.6, -0.2, 0.2]) >>> steps.inject_into(population[(6,11,27)]) .. .. plot:: pyplots/step_source.py .. image:: images/step_source.png .. doctest:: >>> noise = NoisyCurrentSource(mean=1.5, stdev=1.0, start=50.0, stop=450.0, dt=1.0) >>> population.inject(noise) .. .. plot:: pyplots/noise_source.py .. image:: images/noise_source.png For a full description of all the built-in current source classes, see the :doc:`API reference `. .. todo:: write "implementing-your-own-current-source" (e.g., implement "chirp") PyNN-0.10.0/doc/installation.txt000066400000000000000000000141771415343567000164450ustar00rootroot00000000000000============ Installation ============ The following instructions are for Linux and Mac OS X. It should be possible to install and run PyNN on Windows, but this has not been tested. Installing PyNN requires: * Python (version 2.7, 3.6+) * a recent version of the NumPy_ package * the lazyarray_ package * the Neo_ package (>= 0.8.0) * at least one of the supported simulators: e.g. NEURON, NEST, or Brian 2. Optional dependencies are: * mpi4py_ (if you wish to run distributed simulations using MPI) * either Jinja2_ or Cheetah_ (templating engines) * the CSA_ library * h5py (to use the SONATA format) Installing PyNN =============== .. note:: if using NEURON or NEST, it is easiest if you install NEURON/NEST *before* you install PyNN (see below). The easiest way to get PyNN is to use pip_:: $ pip install pyNN If you would prefer to install manually, :doc:`download the latest source distribution `, then run the setup script, e.g.:: $ tar xzf PyNN-0.10.0.tar.gz $ cd PyNN-0.10.0 $ python setup.py install This will install it to your Python :file:`site-packages` directory, and may require root privileges. We strongly recommend, however, that you use a virtualenv_ or a Conda_ environment. We assume you have already installed the simulator(s) you wish to use it with. If this is not the case, see below for installation instructions. Test it using something like the following:: >>> import pyNN.nest as sim >>> sim.setup() >>> sim.end() (This assumes you have NEST installed). .. warning:: If you get a warning "Unable to install NEST extensions. Certain models may not be available" then ensure the program :command:`nest-config` is on your system PATH. If you still get this message even after adding the directory containing :command:`nest-config` to the PATH, try ``pip uninstall PyNN``, then re-install with ``pip install --no-binary :all: PyNN`` With NEURON as the simulator, make sure you install NEURON *before* you install PyNN. The PyNN installation will then compile PyNN-specific membrane mechanisms, which are loaded when importing the :mod:`neuron` module:: >>> import pyNN.neuron as sim NEURON -- Release 7.4 (1370:16a7055d4a86) 2015-11-09 Duke, Yale, and the BlueBrain Project -- Copyright 1984-2015 See http://www.neuron.yale.edu/neuron/credits loading membrane mechanisms from /home/docker/dev/PyNN/pyNN/neuron/nmodl/x86_64/.libs/libnrnmech.so Additional mechanisms from files adexp.mod alphaisyn.mod alphasyn.mod expisyn.mod gap.mod gsfa_grr.mod hh_traub.mod izhikevich.mod netstim2.mod refrac.mod reset.mod stdwa_guetig.mod stdwa_softlimits.mod stdwa_songabbott.mod stdwa_symm.mod stdwa_vogels2011.mod tmgsyn.mod tmisyn.mod tsodyksmarkram.mod vecstim.mod If you installed PyNN before installing NEURON, or if you update your PyNN installation, you will need to manually run :command:`nrnivmodl` in the :file:`pyNN/neuron/nmodl` directory. Installing NEURON ================= Download the sources for NEURON 7.4 or later, in ``.tar.gz`` format, from ``_. Also download Interviews from the same location. Compile Interviews and NEURON according to the instructions given at ``_, except that when you run :command:`configure`, add the options :option:`--with-nrnpython` and, optionally, :option:`--with-paranrn`, i.e.:: $ ./configure --prefix=`pwd` --with-nrnpython --with-paranrn $ make $ make install Make sure that you add the Interviews and NEURON :file:`bin` directories to your path. Test that the Python support has been enabled by running:: $ nrniv -python NEURON -- Release 7.4 (1370:16a7055d4a86) 2015-11-09 Duke, Yale, and the BlueBrain Project -- Copyright 1984-2015 See http://www.neuron.yale.edu/neuron/credits >>> import hoc >>> import nrn Now you can compile and install NEURON as a Python package:: $ cd src/nrnpython $ python setup.py install Now test everything worked:: $ python >>> import neuron NEURON -- Release 7.4 (1370:16a7055d4a86) 2015-11-09 Duke, Yale, and the BlueBrain Project -- Copyright 1984-2015 See http://www.neuron.yale.edu/neuron/credits If you run into problems, check out the `NEURON Forum`_. Installing NEST and PyNEST ========================== NEST 2.18.0 can be downloaded from ``_. Earlier versions of NEST may not work with this version of PyNN. The full installation instructions are available in the file INSTALL, which you can find in the NEST source package, or at ``_. Now try it out:: $ cd ~ $ python >>> import nest -- N E S T -- Copyright (C) 2004 The NEST Initiative Version: v2.20.0 ... >>> nest.Models() (u'ac_generator', u'aeif_cond_alpha', u'aeif_cond_alpha_RK5', u'aeif_cond_alpha_multisynapse', ... Check that ``'aeif_cond_alpha'`` is in the list of models. If it is not, you may need to install a newer version of the `GNU Scientific Library`_ and then recompile NEST. Installing Brian2 ================= Instructions for downloading and installing `Brian 2`_ are available from ``_. Note that this version of PyNN works with Brian 2. If you need to use Brian 1, try PyNN 0.9.6. .. _PyNN: http://neuralensemble.org/PyNN .. _NumPy: http://www.numpy.org/ .. _lazyarray: https://pypi.python.org/pypi/lazyarray .. _CSA: https://software.incf.org/software/csa/ .. _Jinja2: http://jinja.pocoo.org/ .. _Cheetah: http://www.cheetahtemplate.org/ .. _mpi4py: http://mpi4py.scipy.org/ .. _pip: http://www.pip-installer.org/ .. _Brian 2: http://briansimulator.org/ .. _`PyNN download page`: https://neuralensemble.org/trac/PyNN/wiki/Download .. _`distutils`: http://docs.python.org/2/install/index.html .. _`GNU Scientific Library`: http://www.gnu.org/software/gsl/ .. _`NEURON Forum`: http://www.neuron.yale.edu/phpBB/index.php .. _Neo: http://neuralensemble.org/neo .. _virtualenv: https://virtualenv.readthedocs.org/ .. _Conda: http://conda.pydata.org PyNN-0.10.0/doc/introduction.txt000066400000000000000000000064161415343567000164620ustar00rootroot00000000000000============ Introduction ============ PyNN_ (pronounced 'pine') is a simulator-independent language for building neuronal network models. In other words, you can write the code for a model once, using the PyNN API and the Python_ programming language, and then run it without modification on any simulator that PyNN supports (currently NEURON_, NEST_ and `Brian 2`_) as well as on certain neuromorphic hardware systems. The PyNN API aims to support modelling at a high-level of abstraction (populations of neurons, layers, columns and the connections between them) while still allowing access to the details of individual neurons and synapses when required. PyNN provides a library of standard neuron, synapse and synaptic plasticity models, which have been verified to work the same on the different supported simulators. PyNN also provides a set of commonly-used connectivity algorithms (e.g. all-to-all, random, distance-dependent, small-world) but makes it easy to provide your own connectivity in a simulator-independent way, either using the Connection Set Algebra (`Djurfeldt, 2012`_) or by writing your own Python code. Even if you don't wish to run simulations on multiple simulators, you may benefit from writing your simulation code using PyNN's powerful, high-level interface. In this case, you can use any neuron or synapse model supported by your simulator, and are not restricted to the standard models. PyNN transparently supports distributed simulations (using MPI) where the underlying simulator does. It is straightforward to port an existing model from a Python-supporting simulator to PyNN, since this can be done incrementally, replacing one piece of simulator-specific code at a time with the PyNN equivalent, and testing that the model behaviour is unchanged at each step. :doc:`Download` the current stable release of the library (0.10.0) or get the development version from the `Git repository`_ . Licence ------- The code is released under the CeCILL_ licence. (This is equivalent to and compatible with the GPL). Citing PyNN ----------- If you publish work using or mentioning PyNN, we would appreciate it if you would cite the following paper: Davison AP, Brüderle D, Eppler JM, Kremkow J, Muller E, Pecevski DA, Perrinet L and Yger P (2009) PyNN: a common interface for neuronal network simulators. Front. Neuroinform. 2:11 `doi:10.3389/neuro.11.011.2008 `_. Questions/Bugs/Enhancements --------------------------- If you find a bug in PyNN, or wish to request a new feature, please go the `PyNN issue tracker`_, click on "New Issue", and fill in the form. If you have questions or comments about PyNN, please post a message on the `NeuralEnsemble Google group`_. .. _PyNN: http://neuralensemble.org/PyNN/ .. _Python: http://www.python.org/ .. _CeCILL: http://www.cecill.info/ .. _NEURON: http://www.neuron.yale.edu/neuron/ .. _NEST: http://www.nest-initiative.org/?page=Software .. _Brian 2: http://briansimulator.org/ .. _`Git repository`: https://github.com/NeuralEnsemble/PyNN .. _`PyNN issue tracker`: https://github.com/NeuralEnsemble/PyNN/issues .. _`NeuralEnsemble Google group`: http://groups.google.com/group/neuralensemble .. _`Djurfeldt, 2012`: http://dx.doi.org/10.1007/s12021-012-9146-1 PyNN-0.10.0/doc/logging.txt000066400000000000000000000044431415343567000153650ustar00rootroot00000000000000======= Logging ======= When developing a complex model with a long simulation time, it is unlikely that everything will work correctly the first time, and a fair amount of debugging will be necessary. Such debugging can often be helped by having a log file containing information about the progress of the simulation. Equally, it is useful to print information to the screen about the progress of the simulation, but here we generally do not want such a fine grain of detail as in the log file. For both debugging and status updates it is possible to use the ``print`` statement. In general, however, it is better to use Python's :mod:`logging` module, as this allows you to both print to the screen and write to a file with the same command, and to independently control the level of detail written to each destination. More concretely, if you are using ``print`` statements for debugging, you will have to find and remove all these statements once debugging is complete, whereas if using :mod:`logging` you only have to change one configuration option at one point in your code. Configuration ============= The :mod:`logging` module allows almost unlimited flexibility in configuring logging. For the common use case in simulations - sending status updates to the screen and logging at a higher-level of detail to file, PyNN provides a shortcut function:: >>> from pyNN.utility import configure_logging >>> configure_logging(console='HEADER', logging='INFO', filename="log.txt", with_color=True) This will print a high-level overview of the progress of your simulation to the console, in colour, and a more detailed report to :file:`log.txt`. To get more detail, use ``'DEBUG'`` instead of ``'INFO'``, for less detail, use ``'WARNING'``. Adding logging to your own code =============================== Using logging in your own code is very simple. At the top of each file, put something like:: import logging logger = logging.getLogger("MySimulation") then add statements like:: logger.header("Creating the network....") logger.info("Creating population A") logger.debug("Trying to figure out why this isn't working. p = %s" % p) logger.warning("This could be a problem.") at appropriate points in your code. .. todo:: implement this, and give an example of what the log file looks like. PyNN-0.10.0/doc/make.bat000066400000000000000000000106331415343567000146010ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyNN.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyNN.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end PyNN-0.10.0/doc/neurons.txt000066400000000000000000000374221415343567000154330ustar00rootroot00000000000000========================== Building networks: neurons ========================== .. testsetup:: from pyNN.mock import * from pyNN.utility import init_logging from pyNN.random import NumpyRNG log = init_logging("doctest_neurons.log", debug=True) .. _section-cell-types: Cell types ========== In PyNN, the system of equations that defines a neuronal model is encapsulated in a :class:`CellType` class. PyNN provides a library of "standard" cell types (see :doc:`standardmodels`) which work the same across all backend simulators - an example is the :class:`IF_cond_exp` model - an integrate-and-fire (I&F) neuron with conductance-based, exponential-decay synapses. For any given simulator, it is also possible to wrap a native model - a NEST or NEURON model, for example - in a :class:`CellType` class so that it works in PyNN (see documentation for individual :doc:`backends`). It should be noted that these "cell types" are *mathematical* cell types. Two or more different *biological* cell types may be represented by the same mathematical cell type, but with different parameterizations. For example, in the thalamocortical model of `Destexhe (2009)`_, thalamocortical relay neurons and cortical neurons are both modelled with the adaptive exponential I&F neuron model (AdExp): .. testcode:: :hide: log.info("========== create cell types =========") .. doctest:: >>> refractory_period = RandomDistribution('uniform', [2.0, 3.0], rng=NumpyRNG(seed=4242)) >>> ctx_parameters = { ... 'cm': 0.25, 'tau_m': 20.0, 'v_rest': -60, 'v_thresh': -50, 'tau_refrac': refractory_period, ... 'v_reset': -60, 'v_spike': -50.0, 'a': 1.0, 'b': 0.005, 'tau_w': 600, 'delta_T': 2.5, ... 'tau_syn_E': 5.0, 'e_rev_E': 0.0, 'tau_syn_I': 10.0, 'e_rev_I': -80 } >>> tc_parameters = ctx_parameters.copy() >>> tc_parameters.update({'a': 20.0, 'b': 0.0}) >>> thalamocortical_type = EIF_cond_exp_isfa_ista(**tc_parameters) >>> cortical_type = EIF_cond_exp_isfa_ista(**ctx_parameters) .. todo: check details + parameters of Destexhe model (see :doc:`parameters` for more on specifying parameter values). To see the list of parameter names for a given cell type, use the :meth:`get_parameter_names()` method: .. doctest:: >>> IF_cond_exp.get_parameter_names() ['tau_refrac', 'cm', 'tau_syn_E', 'v_rest', 'tau_syn_I', 'tau_m', 'e_rev_E', 'i_offset', 'e_rev_I', 'v_thresh', 'v_reset'] while the default values for the parameters are in the :attr:`default_parameters` attribute: .. doctest:: >>> print(IF_cond_exp.default_parameters) {'tau_refrac': 0.1, 'cm': 1.0, 'tau_syn_E': 5.0, 'v_rest': -65.0, 'tau_syn_I': 5.0, 'tau_m': 20.0, 'e_rev_E': 0.0, 'i_offset': 0.0, 'e_rev_I': -70.0, 'v_thresh': -50.0, 'v_reset': -65.0} Note that what we have created here are *neuron type* objects. These can be regarded as templates, from which we will construct the actual neurons in our network. Populations =========== Since PyNN is designed for modelling networks containing many neurons, the default level of abstraction in PyNN is not the single neuron but a population of neurons of a given type, represented by the :class:`Population` class: .. testcode:: :hide: log.info("========== create populations =========") .. doctest:: >>> tc_cells = Population(100, thalamocortical_type) >>> ctx_cells = Population(500, cortical_type) To create a :class:`Population`, we need to specify at minimum the number of neurons and the cell type. Three additional arguments may optionally be specified: * the spatial structure of the population; * initial values for the neuron state variables; * a label. .. testcode:: :hide: log.info("========== create populations with structure =========") .. doctest:: >>> from pyNN.space import Grid2D, RandomStructure, Sphere >>> tc_cells = Population(100, thalamocortical_type, ... structure=RandomStructure(boundary=Sphere(radius=200.0)), ... initial_values={'v': -70.0}, ... label="Thalamocortical neurons") >>> from pyNN.random import RandomDistribution >>> v_init = RandomDistribution('uniform', (-70.0, -60.0)) >>> ctx_cells = Population(500, cortical_type, ... structure=Grid2D(dx=10.0, dy=10.0), ... initial_values={'v': v_init}, ... label="Cortical neurons") (see :doc:`space` for more detail on spatial structure and :doc:`parameters` for more on specifying initial values.) For backwards compatibility and for ease of transitioning from other simulator languages, the :func:`create` function is available as an alias for :class:`Population`. The following two lines are equivalent:: >>> cells = create(my_cell_type, n=100) >>> cells = Population(100, my_cell_type) (Note the different argument order). Views ===== It is common to work with only a subset of the neurons in a :class:`Population` - to modify their parameters, make connections or record from them. Any subset of neurons in a population may be addressed using the usual Python indexing and slicing notation, for example: .. doctest:: >>> id = ctx_cells[47] # the 48th neuron in a Population >>> view = ctx_cells[:80] # the first eighty neurons >>> view = ctx_cells[::2] # every second neuron >>> view = ctx_cells[45, 91, 7] # a specific set of neurons It is also possible to address a random sample of neurons within a population using the :meth:`sample()` method: .. doctest:: >>> view = ctx_cells.sample(50, rng=NumpyRNG(seed=6538)) # select 50 neurons at random In the first of these examples, the object that is returned is an :class:`ID` object, representing a single neuron. :class:`ID` objects are discussed below. In all of these examples except the first, the object that is returned is a :class:`PopulationView`. A :class:`PopulationView` holds references to a subset of neurons in a :class:`Population`, which means that any changes in the view are also reflected in the real population (and vice versa). :class:`PopulationView` objects behave in most ways as real :class:`Population` objects; notably, they can be used in a :class:`Projection` (see :doc:`connections`) and combined with other :class:`Population` or :class:`PopulationView` objects to create an :class:`Assembly`. The :attr:`parent` attribute of a :class:`PopulationView` has a reference to the :class:`Population` that is being viewed, and the :attr:`mask` attribute contains the indices of the neurons that are in the view. .. doctest:: >>> view.parent.label 'Cortical neurons' >>> view.mask array([150, 181, 53, 149, 496, 499, 240, 444, 13, 100, 28, 19, 101, 122, 143, 486, 467, 492, 406, 90, 136, 173, 8, 341, 5, 348, 188, 63, 129, 416, 307, 298, 60, 180, 382, 47, 484, 370, 223, 147, 72, 32, 261, 193, 249, 212, 58, 87, 86, 456]) Assemblies ========== As discussed above, a :class:`Population` is a homogeneous collection of neurons, in the sense that all neurons have the same cell type. An :class:`Assembly` is an aggregate of :class:`Population` and :class:`PopulationView` objects, and as such can represent a heterogeneous collection of neurons, of multiple cell types. An :class:`Assembly` can be created by adding together :class:`Population` and :class:`PopulationView` objects: .. doctest:: >>> all_cells = tc_cells + ctx_cells >>> cells_for_plotting = tc_cells[:10] + ctx_cells[:50] or by using the :class:`Assembly` constructor: .. doctest:: >>> all_cells = Assembly(tc_cells, ctx_cells) An assembly behaves in most ways like a :class:`Population`, e.g. for setting and retrieving parameters, specifying which neurons to record from, etc. It can also be specified as the source or target of a :class:`Projection`. In this case, all the neurons in the component populations are treated as identical for the purposes of the connection algorithm (note that if the post-synaptic receptor type is specified (with the :attr:`receptor_type` argument), an Exception will be raised if not all component neuron types possess that receptor type). Individual populations within an :class:`Assembly` may be accessed via their labels, e.g.: .. doctest:: >>> all_cells.get_population("Thalamocortical neurons") Population(100, EIF_cond_exp_isfa_ista(), structure=RandomStructure(origin=(0.0, 0.0, 0.0), boundary=Sphere(radius=200.0), rng=NumpyRNG(seed=None)), label='Thalamocortical neurons') Iterating over an assembly returns individual IDs, ordered by population. Similarly, the :attr:`size` attribute of an :class:`Assembly` gives the total number of neurons it contains. To iterate over or count populations, use the :attr:`populations` attribute: .. doctest:: >>> for p in all_cells.populations: ... print("%-23s %4d %s" % (p.label, p.size, p.celltype.__class__.__name__)) Thalamocortical neurons 100 EIF_cond_exp_isfa_ista Cortical neurons 500 EIF_cond_exp_isfa_ista Inspecting and modifying parameter values and initial conditions ================================================================ Although both parameter values and initial conditions may be specified when creating a :class:`Population` (and this is generally the most efficient place to do it), it is also possible to modify them later. The :meth:`get()` method of :class:`Population`, :class:`PopulationView` and :class:`Assembly` returns the current value(s) of one or more parameters: .. doctest:: >>> ctx_cells.get('tau_m') 20.0 >>> all_cells[0:10].get('v_reset') -60.0 If the parameter is homogeneous across the group, a single number will be returned, otherwise :meth:`get()` will return a NumPy array containing the parameter values for all neurons: .. doctest:: >>> ctx_cells.get('tau_refrac') array([ 2.64655001, 2.15914942, 2.53500179, ... It is also possible to ask for multiple parameters at once, in which case a list of values in the same order as the list of parameter names will be returned. .. doctest:: >>> ctx_cells.get(['tau_m', 'cm']) [20.0, 0.25] When running a distributed simulation using MPI_, :meth:`get()` will by default return values for only those neurons that exist on the current MPI node. To get the values for all neurons, use ``get(parameter_name, gather=True)``. To modify parameter values, use the :class:`set()` method. To set the same value for all neurons, pass a single number as the parameter value: .. doctest:: >>> ctx_cells.set(a=2.0, b=0.2) To set different values for different neurons there are several options - see :doc:`parameters` for more details. To modify the initial values of model variables, use the :meth:`initialize()` method: .. doctest:: >>> ctx_cells.initialize(v=RandomDistribution('normal', (-65.0, 2.0)), ... w=0.0) The default initial values may be inspected as follows: .. doctest:: >>> ctx_cells.celltype.default_initial_values {'gsyn_exc': 0.0, 'gsyn_inh': 0.0, 'w': 0.0, 'v': -70.6} Injecting current into neurons ============================== Static or time-varying currents may be injected into neurons using either the :meth:`inject_into()` method of the :class:`CurrentSource`: .. doctest:: >>> pulse = DCSource(amplitude=0.5, start=20.0, stop=80.0) >>> pulse.inject_into(tc_cells) or the :meth:`inject()` method of the :class:`Population`, :class:`PopulationView` or :class:`Assembly`: .. doctest:: >>> import numpy >>> times = numpy.arange(0.0, 100.0, 1.0) >>> amplitudes = 0.1*numpy.sin(times*numpy.pi/100.0) >>> sine_wave = StepCurrentSource(times=times, amplitudes=amplitudes) >>> ctx_cells[80:90].inject(sine_wave) See :doc:`injecting_current` for more about injecting currents. Recording variables and retrieving recorded data ================================================ Just as each cell type has a well-defined set of parameters (whose values are constant over time), so it has a well-defined set of state variables, such as the membrane potential, whose values change over the course of a simulation. The :attr:`recordable` attribute of a :class:`CellType` class contains a list of these variables, as well as the 'spikes' variable, which is used to record the times of action potentials: .. doctest:: >>> ctx_cells.celltype.recordable ['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh'] The :meth:`record()` method specifies which variables should be recorded: .. doctest:: >>> all_cells.record('spikes') >>> ctx_cells.sample(10).record(('v', 'w')) #, sampling_interval=0.2) Note that the sampling interval must be an integer multiple of the simulation time step (except for simulators which allow use of variable time-step integration methods). .. todo:: discuss specifying filename in record() At the end of a simulation, the recorded data can be retrieved using the :meth:`get_data()` method: .. doctest:: >>> t = run(0.2) >>> data_block = all_cells.get_data() or written to file using :meth:`write_data()`: .. doctest:: >>> from neo.io import NeoHdf5IO >>> h5file = NeoHdf5IO("my_data.h5") >>> ctx_cells.write_data(h5file) >>> h5file.close() .. testcleanup:: import os if os.path.exists("my_data.h5"): os.remove("my_data.h5") :meth:`get_data()` returns a Neo_ :class:`Block` object. For more information on Neo see the documentation at http://packages.python.org/neo. Here, it will suffice to note that a :class:`Block` is the top-level container, and contains one or more :class:`Segments`. Each :class:`Segment` is a container for data that share a common time basis, and can contain lists of :class:`AnalogSignal` and :class:`SpikeTrain` objects. These data objects inherit from NumPy array, and so can be treated in further processing (analysis, visualization, etc.) in exactly the same way as plain arrays, but in addition they carry metadata about units, sampling interval, etc. :meth:`write_data()` also makes use of Neo, and allows writing to any of the several output file formats supported by Neo. Note that as a short-cut, you can just give a filename to :meth:`write_data()`; the output format will then be determined based on the filename extension ('.h5' for HDF5, '.txt' for ASCII, etc.) if possible, otherwise the default file format (determined by the value of ``pyNN.recording.DEFAULT_FILE_FORMAT``) will be used. For more details, see :doc:`data_handling`. Working with individual neurons =============================== Although it is usually more convenient and more efficient to work with populations of neurons, it is occasionally convienient to work with individual neurons, represented as an :class:`ID` object: .. doctest:: >>> tc_cells[47] 709 For the simulator backends shipped with PyNN, the :class:`ID` class is a subclass of :class:`int`, and in the case of NEURON and NEST matches the global ID (gid) used internally by the simulator. There is no requirement that IDs be integers, however, nor that they have the same value across different simulators. The :attr:`parent` attribute contains a reference to the parent :class:`population`: .. doctest:: >>> a_cell = tc_cells[47] >>> a_cell.parent.label 'Thalamocortical neurons' To recover the index of a neuron within its parent given the :class:`ID`, use :meth:`Population.id_to_index()`, e.g.: .. doctest:: >>> tc_cells.id_to_index(a_cell) 47 The ``ID`` object allows direct access to the parameters of individual neurons, e.g.: .. doctest:: >>> a_cell.tau_m 20.0 To change several parameters at once for a single neuron, use the :meth:`set_parameters()` method: .. doctest:: >>> a_cell.set_parameters(tau_m=10.0, cm=0.5) >>> a_cell.tau_m 10.0 >>> a_cell.cm 0.5 .. _`Destexhe (2009)`: http://cns.iaf.cnrs-gif.fr/abstracts/TCX2008.html .. _MPI: http://en.wikipedia.org/wiki/Message_Passing_Interface .. _Neo: http://neuralensemble.org/neo/ PyNN-0.10.0/doc/nineml.txt000066400000000000000000000075771415343567000152340ustar00rootroot00000000000000PyNN and NineML =============== NineML_ is a declarative XML-based language for describing neuronal network models. One of its key features is the separation into an "abstraction layer", which provides the mathematical details of the model components (neurons, synapses, etc.), and a "user layer", which describes how the components are put together to form a network model. PyNN can work with NineML in two ways: * at the user layer level - exporting an entire PyNN model as XML or importing a NineML XML file into PyNN and running simulations with the resulting model; * at the abstraction layer level - constructing PyNN ``CellType`` classes from NineML neuron/synapse components (and, in the future, PyNN dynamic synapse classes from NineML synaptic plasticity components). .. note:: most of what is described below does not work yet, or is not properly tested - this document is currently a statement of intent, which will gradually turn into proper documentation as things get implemented/tested] Exporting a PyNN model as XML ============================= Using ``pyNN.nineml`` as the simulator backend will cause the model to be written to XML rather than simulated. This requires specifying an output file name in the ``setup()`` call:: import pyNN.nineml as sim sim.setup(filename="my_model.xml") ... sim.end() Writing to file takes place when ``sim.end()`` is called. Note that any PyNN calls related to recording or saving data are ignored. It is possible that in future this information could be saved in a simulation experiment description format such as SEDML_ Using a NineML abstraction layer model in a NEST or NEURON simulation ===================================================================== This requires code-generation support for the backend simulator. For NEST, this will be built-in to some future release; for NEURON, the nineml2nmodl package is required [currently available in the NineML svn, will be released on PyPI at some point]. Brian support is also envisaged. :: import pyNN.neuron as sim celltype_cls = sim.nineml_celltype( name="my_neuron_type", neuron_model="iaf.xml", synapse_models={ "AMPA": "coba_syn.xml", "NMDA": "nmda_syn.xml", "GABAA": "coba_syn.xml" }) parameters = { 'iaf.cm': 1.0, 'iaf.gl': 50.0, 'iaf.taurefrac': 5.0, 'iaf.vrest': -65.0, 'iaf.vreset': -65.0, 'iaf.vthresh': -50.0, 'AMPA.tau': 2.0, 'GABAA.tau': 5.0, 'AMPA.vrev': 0.0, 'GABAA.vrev': -70.0, 'NMDA.taur': 3.0, 'NMDA.taud': 40.0, 'NMDA.gmax': 1.2, 'NMDA.E': 0.0, 'NMDA.gamma': 0.062, 'NMDA.mgconc': 1.2, 'NMDA.beta': 3.57 } cells = sim.Population(100, celltype_cls, parameters) cells.record(("iaf.v", "AMPA.g", "NMDA.g", "GABAA.v")) Simulating an entire NineML model with NEST or NEURON ===================================================== Again, this requires code-generation support for the backend simulator, or for PyNN to be able to recognize the components as equivalent to existing PyNN standard models. .. todo:: For example, the PyNN Git repository could contain NineML representations of the standard models; these representations would then each have a URL including version information, and if these URLs are used in the user layer description, PyNN can safely use a standard model rather than doing code generation. :: import pyNN.neuron as sim network = sim.NineMLNetwork("my_network_model.xml") # user layer description network.populations["excitatory cells"].record("spikes") sim.run(1000.0) network.populations["excitatory_cells"].write("spikes") sim.end() .. _NineML: http://nineml.incf.org .. _SEDML: http://sedml.org/PyNN-0.10.0/doc/parallel.txt000066400000000000000000000104431415343567000155300ustar00rootroot00000000000000============================ Running parallel simulations ============================ Where the underlying simulator supports distributed simulations, in which the computations are spread over multiple processors using MPI (this is the case for NEURON and NEST), PyNN also supports this. To run a distributed simulation on eight nodes, the command will be something like:: $ mpirun -np 8 -machinefile ~/mpi_hosts python myscript.py Depending on the implementation of MPI you have, ``mpirun`` could be replaced by ``mpiexec`` or another command, and the options may also be somewhat different. For NEURON only, you can also run distributed simulations using ``nrniv`` instead of the ``python`` executable:: $ mpirun -np 8 -machinefile ~/mpi_hosts nrniv -python -mpi myscript.py Additional requirements ----------------------- First, make sure you have compiled the simulators you wish to use with MPI enabled. There is usually a configure flag called something like "``--with-mpi``" to do this, but see the installation documentation for each simulator for details. If you wish to use the default "gather" feature (see below), which automatically gathers output data from all the nodes to the master node (the one on which you launched the simulation), you will need to install the :mod:`mpi4py` module (see ``_ for downloads and documentation). Installation is usually very straightforward, although, if you have more than one MPI implementation installed on your system (e.g. OpenMPI and MPICH2), you must be sure to build :mod:`mpi4py` with the same MPI implementation that you used to build the simulator. Code modifications ------------------ In most cases, no modifications to your code should be necessary to run in parallel. PyNN, and the simulator, take care of distributing the computations between nodes. Furthermore, the default settings should give results that are independent of the number of processors used, even when using random numbers. Gathering data to the master node --------------------------------- The various methods of the :class:`Population` and :class:`Assembly` classes that deal with accessing recorded data or writing it to disk, such as :meth:`get_data`, :meth:`write_data`, etc., have an optional argument *gather*, which is ``True`` by default. If *gather* is ``True``, then data generated on other nodes is sent to the master node. This means that, for example, :meth:`write_data` will create only a single file, on the filesystem of the master node. If *gather* is ``False``, each node will write a file on its local filesystem. This option is often desirable if you wish to do distributed post-processing of the data. (Don't worry, by the way, if you are using a shared filesystem such as NFS. If *gather* is ``False`` then the MPI rank is appended to the filename, so there is no chance of conflict between the different nodes). Random number generators ------------------------ In general, we expect that our results should not depend on the number of processors used to produce them. If our simulations use random numbers in setting-up or running the network, this means that each object that uses random numbers should receive the same sequence independent of which node it is on or how many nodes there are. PyNN achieves this by ensuring the generator seed is the same on all nodes, and then generating as many random numbers as would be used in the single-processor case and throwing away those that are not needed. This obviously has a potential impact on performance, and so it is possible to turn it off by passing *parallel_safe=False* as argument when creating the random number generator, e.g.:: >>> from pyNN.random import NumpyRNG >>> rng = NumpyRNG(seed=249856, parallel_safe=False) Now, PyNN will ensure the seed is different on each node, and will generate only as many numbers as are actually needed on each node. Note that the above applies only to the random number generators provided by the :mod:`pyNN.random` module, not to the native RNGs used internally by each simulator. This means that, for example, you should prefer :class:`SpikeSourceArray` (for which you can generate Poisson spike times using a parallel-safe RNG) to :class:`SpikeSourcePoisson`, which uses the simulator's internal RNG, if you care about being independent of the number of processors. PyNN-0.10.0/doc/parameters.txt000066400000000000000000000171621415343567000161040ustar00rootroot00000000000000=================================== Model parameters and initial values =================================== As was discussed in :doc:`building_networks`, PyNN deals with neurons, and with the synaptic connections between them, principally at the level of groups: with :class:`Population` and :class:`Assembly` for neurons and :class:`Projection` for connections. Setting the parameters of neurons and connections is also done principally at the group level, either when creating the group, or after creation using the :meth:`set()` method. Sometimes, all the neurons in a :class:`Population` or all the connections in a :class:`Projection` should have the same value. Other times, different individual cells or connections should have different parameter values. To handle both of these situations, parameter values may be of four different types: * a single number - sets the same value for all cells in the :class:`Population` or connections in the :class:`Projection` * a :class:`RandomDistribution` object (see :doc:`random_numbers`) - each item in the group will have the parameter set to a value drawn from the distribution * a list or 1D NumPy array - of the same size as the :class:`Population` or the number of connections in the :class:`Projection` * a function - for a :class:`Population` or :class:`Assembly` the function should take a single integer argument, and will be called with the index of every neuron in the :class:`Population` to return the parameter value for that neuron. For a :class:`Projection`, the function should take two integer arguments, and for every connection will be called with the indices of the pre- and post-synaptic neurons. Examples ======== .. testsetup:: from pyNN.mock import Population, IF_cond_exp, HH_cond_exp, SpikeSourcePoisson, IF_cond_alpha Setting the same value for all neurons in a population ------------------------------------------------------ .. doctest:: >>> p = Population(5, IF_cond_exp(tau_m=15.0)) or, equivalently: .. doctest:: >>> p = Population(5, IF_cond_exp()) >>> p.set(tau_m=15.0) .. note: for some backend simulators it is more efficient to set parameters on :class:`population` creation, rather than using the :meth:`set()` method. To set values for a subset of the population, use a view: .. doctest:: >>> p[0,2,4].set(tau_m=10.0) >>> p.get('tau_m') array([ 10., 15., 10., 15., 10.]) Setting parameters to random values ----------------------------------- .. doctest:: >>> from pyNN.random import RandomDistribution, NumpyRNG >>> gbar_na_distr = RandomDistribution('normal', (20.0, 2.0), rng=NumpyRNG(seed=85524)) >>> p = Population(7, HH_cond_exp(gbar_Na=gbar_na_distr)) >>> p.get('gbar_Na') array([ 20.03132455, 20.09777627, 16.97079318, 17.44786923, 19.4928947 , 20.80321881, 19.97246906]) >>> p[0].gbar_Na 20.031324546935146 Setting parameters from an array -------------------------------- .. doctest:: >>> import numpy as np >>> p = Population(6, SpikeSourcePoisson(rate=np.linspace(10.0, 20.0, num=6))) >>> p.get('rate') array([ 10., 12., 14., 16., 18., 20.]) The array of course has to have the same size as the population:: >>> p = Population(6, SpikeSourcePoisson(rate=np.linspace(10.0, 20.0, num=7))) ValueError Using a function to calculate parameter values ---------------------------------------------- .. doctest:: >>> from numpy import sin, pi >>> p = Population(8, IF_cond_exp(i_offset=lambda i: sin(i*pi/8))) >>> p.get('i_offset') array([ 0. , 0.38268343, 0.70710678, 0.92387953, 1. , 0.92387953, 0.70710678, 0.38268343]) Setting parameters as a function of spatial position ---------------------------------------------------- .. doctest:: >>> from pyNN.space import Grid2D >>> grid = Grid2D(dx=10.0, dy=10.0) >>> p = Population(16, IF_cond_alpha(), structure=grid) >>> def f_v_thresh(pos): ... x, y, z = pos.T ... return -50 + 0.5*x - 0.2*y >>> p.set(v_thresh=lambda i: f_v_thresh(p.position_generator(i))) >>> p.get('v_thresh').reshape((4,4)) array([[-50., -52., -54., -56.], [-45., -47., -49., -51.], [-40., -42., -44., -46.], [-35., -37., -39., -41.]]) .. todo: Another example, using Space For more on spatial structure, see :doc:`space`. Using multiple parameter types ------------------------------ It is perfectly possible to use multiple different types of parameter value at the same time: .. doctest:: >>> n = 1000 >>> parameters = { ... 'tau_m': RandomDistribution('uniform', (10.0, 15.0)), ... 'cm': 0.85, ... 'v_rest': lambda i: np.cos(i*pi*10/n), ... 'v_reset': np.linspace(-75.0, -65.0, num=n)} >>> p = Population(n, IF_cond_alpha(**parameters)) >>> p.set(v_thresh=lambda i: -65 + i/n, tau_refrac=5.0) .. todo:: in the above, give current source examples, and Projection examples Time series and array-valued parameters ======================================= For certain neuron models (:class:`SpikeSourceArray`, :class:`GIF_cond_exp`) and current sources, the individual parameter values are not single numbers (with physical units), but arrays, e.g.: .. code-block:: python celltype = SpikeSourceArray(np.array([5.0, 15.0, 45.0, 99.0])) to set the same spike times for the entire population. To set different spike times for each cell in the population requires an array of arrays. To avoid ambiguities in this situation, the inner arrays should be wrapped by the :class:`Sequence` class, e.g.: .. code-block:: python celltype = SpikeSourceArray([Sequence([5.0, 15.0, 45.0, 99.0]), Sequence([2.0, 5.3, 18.9]), Sequence([17.8, 88.2, 100.1]) ]) Such an array-of-Sequences can also be provided by a generator function, e.g.: .. code-block:: python number = int(2 * simtime * input_rate / 1000.0) def generate_spike_times(i): gen = lambda: Sequence(numpy.add.accumulate(numpy.random.exponential(1000.0 / input_rate, size=number))) if hasattr(i, "__len__"): return [gen() for j in i] else: return gen() celltype = SpikeSourceArray(spike_times=generate_spike_times) As a generalization of :class:`Sequence`, some models require array-valued parameters, expressed as tuples or :class:`ArrayParameter` instances, e.g.: .. code-block:: python cell_type = GIF_cond_exp( ... # this parameter has the same value in all neurons in the population tau_gamma=(1.0, 10.0, 100.0), # Time constants for spike-frequency adaptation in ms. # the following parameter has different values for each neuron a_eta=[(0.1, 0.1, 0.1), # Post-spike increments for spike-triggered current in nA (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)] ...) .. note:: The reason for defining :class:`Sequence` and :class:`ArrayParameter` rather than just using a plain NumPy array is to avoid the ambiguity of "is a given array a single parameter value (e.g. a spike train for one cell) or an array of parameter values (e.g. one number per cell)?". Setting initial values ====================== .. todo:: complete .. note:: For most neuron types, the default initial value for the membrane potential is the same as the default value for the resting membrane potential parameter. However, be aware that changing the value of the resting membrane potential will *not* automatically change the initial value. PyNN-0.10.0/doc/publications.txt000066400000000000000000000156771415343567000164460ustar00rootroot00000000000000============================================= Publications about, relating to or using PyNN ============================================= * Schmuker, Michael, Pfeil, Thomas and Nawrot, Martin Paul (2014) **A neuromorphic network for generic multivariate data classification** Proceedings of the National Academy of Sciences 111: 2081-2086. doi: 10.1073/pnas.1303053111 `[link] `_ * Kaplan, BA, Khoei, MA, Lansner, A, & Perrinet, LU (2014) **Signature of an anticipatory response in area VI as modeled by a probabilistic model and a spiking neural network.** In: Neural Networks (IJCNN), 2014 International Joint Conference on (pp. 3205-3212). Beijing, China. IEEE. doi: 10.1109/IJCNN.2014.6889847 `[link] `_ * Djurfeldt M., Davison A.P. and Eppler J.M. (2014) **Efficient generation of connectivity in neuronal networks from simulator-independent descriptions.** Frontiers in Neuroinformatics 8:43: doi: 10.3389/fninf.2014.00043 `[link] `_ * Antolík J. and Davison A.P. (2013) **Integrated workflows for spiking neuronal network simulations.** Frontiers in Neuroinformatics 7:34: 10.3389/fninf.2013.00034 `[link] `_ * Pfeil, Thomas, Grübl, Andreas, Jeltsch, Sebastian, Müller, Eric, Müller, Paul, Petrovici, Mihai A., Schmuker, Michael, Brüderle, Daniel, Schemmel, Johannes and Meier, Karlheinz (2013) **Six networks on a universal neuromorphic computing substrate.** Frontiers in Neuroscience 7:11 doi: 10.3389/fnins.2013.00011 `[link] `_ * Kaplan BA, Lansner A, Masson GS and Perrinet LU (2013) **Anisotropic connectivity implements motion-based prediction in a spiking neural network.** Front. Comput. Neurosci. 7:112. doi: 10.3389/fncom.2013.00112 `[link] <>`_ * Brüderle D., Petrovici M.A., Vogginger B., Ehrlich M., Pfeil T., Millner S., Grübl A., Wendt K., Müller E., Schwartz M.O., Husmann de Oliveira D., Jeltsch S., Fieres J., Schilling M., Müller P., Breitwieser O., Petkov V., Muller L., Davison A.P., Krishnamurthy P., Kremkow J., Lundqvist M., Muller E., Partzsch J., Scholze S., Zühl L., Mayr C., Destexhe A., Diesmann M., Potjans T.C., Lansner A., Schüffny R., Schemmel J., Meier K. (2011) **A Comprehensive Workflow for General-Purpose Neural Modeling with Highly Configurable Neuromorphic Hardware Systems.** Biological Cybernetics 104: 263-296. doi: 10.1007/s00422-011-0435-9 `[link] `_ * Galluppi, Francesco, Rast, Alexander, Davies, Sergio and Furber, Steve (2010) **A general-purpose model translation system for a universal neural chip.** Neural Information Processing. Theory and Algorithms; Lecture Notes in Computer Science vol 6443, pp58-65 `[link] `_ * J. Nageswaran, N. Dutt, J. L. Krichmar, A. Nicolau, A. V. Veidenbaum (2009) **A configurable simulation environment for the efficient simulation of large-scale spiking neural networks on graphics processors.** Neural Networks 22:5-6, doi:10.1016/j.neunet.2009.06.028. `[link] `_ * Davison AP, Hines M and Muller E (2009) **Trends in programming languages for neuroscience simulations.** Front. Neurosci. doi:10.3389/neuro.01.036.2009. `[link] `_ * Davison AP, Brüderle D, Eppler J, Kremkow J, Muller E, Pecevski D, Perrinet L and Yger P (2009) **PyNN: a common interface for neuronal network simulators.** Front. Neuroinform. 2:11. doi:10.3389/neuro.11.011.2008. `[link] `_ * Brüderle D, Muller E, Davison A, Muller E, Schemmel J and Meier K (2009) **Establishing a novel modeling tool: a python-based interface for a neuromorphic hardware system.** Front. Neuroinform. 3:17. doi:10.3389/neuro.11.017.2009. `[link] `_ * Bednar JA (2009) **Topographica: building and analyzing map-level simulations from Python, C/C++, MATLAB, NEST, or NEURON components.** Front. Neuroinform. 3:8. doi:10.3389/neuro.11.008.2009. `[link] `_ * Goodman D and Brette R (2008) **Brian: a simulator for spiking neural networks in Python.** Front. Neuroinform. 2:5. doi:10.3389/neuro.11.005.2008. `[link] `_ * Pecevski D, Natschläger T and Schuch K (2009) **PCSIM: a parallel simulation environment for neural circuits fully integrated with Python.** Front. Neuroinform. 3:11. doi:10.3389/neuro.11.011.2009. `[link] `_ * Ray S and Bhalla US (2008) **PyMOOSE: interoperable scripting in Python for MOOSE.** Front. Neuroinform. 2:6. doi:10.3389/neuro.11.006.2008. `[link] `_ * Sharon Crook, R Angus Silver and Padraig Gleeson (2009) **Describing and exchanging models of neurons and neuronal networks with NeuroML.** BMC Neuroscience, 10(Suppl 1):L1doi:10.1186/1471-2202-10-S1-L1. `[link] `_ * D. Brüderle, A. Grübl, K. Meier, E. Muller and J. Schemmel (2007) **A Software Framework for Tuning the Dynamics of Neuromorphic Silicon Towards Biology.** LNCS 4507. doi:10.1007/978-3-540-73007-1. `[link] `_ * B. Kaplan, D. Brüderle, J. Schemmel and K. Meier (2009) **High-Conductance States on a Neuromorphic Hardware System.** Proceedings of IJCNN 2009. `[link] `_ * D. Brüderle (2009) **Neuroscientific Modeling with a Mixed-Signal VLSI Hardware System.** Doctoral Dissertation, Kirchhoff-Institute for Physics, University of Heidelberg. `[link] `_ * A. Davison, P. Yger, J. Kremkow, L. Perrinet and E. Muller (2007) **PyNN: towards a universal neural simulator API in Python.** BMC Neuroscience 2007, 8(Suppl 2):P2. doi:10.1186/1471-2202-8-S2-P2. `[link] `_ * E. Muller, A. P. Davison, T. Brizzi, D. Bruederle, M. J. Eppler, J. Kremkow, D. Pecevski, L. Perrinet, M. Schmuker and P. Yger (2009) **NeuralEnsemble.Org: Unifying neural simulators in Python to ease the model complexity bottleneck.** Frontiers in Neuroinformatics Conference Abstract: Neuroinformatics 2009. doi: 10.3389/conf.neuro.11.2009.08.104. `[link] `_ PyNN-0.10.0/doc/pyNN_icon.ico000066400000000000000000000004761415343567000155700ustar00rootroot00000000000000(( cQY63/)MJ]]kl{{ؒۤy 343~22633L6#2tc224#2,B2#4J32(.~PyNN-0.10.0/doc/pyNN_logo.png000066400000000000000000000105171415343567000156070ustar00rootroot00000000000000PNG  IHDRKyqLsBIT|dtEXtSoftwarewww.inkscape.org<IDATxyy73}_;;̮ZI\$U`1`1F0dc62T9NBV 6b %,.hծv_`Ң=ggV_~o~aaTDrMv\e%-tyB;Qr@ZJi]{;p^sRF-Vjpsŵs;Q,[KHDo%buJ,)lt<>B*r8qh,h}Wt2sMSB*2sD h:9MDe _rN&YK.su6\#7rF)MH]e!!1U;%Jl9MF]eY9J-Μa#3kdU-sUN[&}f3uEq&e4$m M>L]e9[FMXf3uT(Zi̜nr6u9MEdYY+o"k]:ʲI9E~.!@K0#r,^> )Zo2#r,Z/9EqȂw'_Y}}cۢ'l5T(WWFv#s*˲sE8 +\Q-57}xb<'A*j4*r,J#TD "9 ?TTS@Tb!,\&琊#bNg-: ב~%\ *Q1-+<"Qq'Cʿ.>Bh>b'Ȱ. A*NUBXWJ'ɵ>2Se!r@u,'10Eۢbt|dYX\Wz6"1vE:w622)-b-<[o57ȱcۙ\xШڹAgL&4.=#*!8ݱF *Mt8Zm,Cd!!%sFU4P*f?%'6VjYHE%<X SuU]GΨǃ+Zǯd.gklmZEp {{_] ]#(6 Gjc=Utf ?E^skN%6o)*L&3ws-x]WG]_GK!S:;ۃw|>q$MZ;sc4f췛4 >(d `sxcF/7|)R> Y;Ӫ4YIC] +49Eb/v$H34IDXi+vZpGxH HE+;RKV.w_lոBJc&r{c}X`6 %s!ckvDY]MfkGGHEB#r'lD#a%"d+kcp'5j_EԽJZoK.^-b$14/ ~=!Y֝9R zLwo_nb{RS@ AiVC *Nǜ\;w* aM5bw6uڹ[TY[x̻Ļ>G *Ngs͌xz.'m}"=߽ί7:)SPMG]ecD۽?MKmTLe ;y [4u21-'G.UPYNak{jV1Tj'U0Y.1^ྍo~slXO*J1O.Q2a6b"Jt]QU5Y&'7Y+H5,ism=gj a#w\YzT~(䚾1xTuD .ǜsuKh~,/=]LiE#}fz"73dr>xhDL/70vfL>E1^-ireg G3Y;7lTiQլXۘ'1h^hѮȇdžqvS̞YBHG蘭D*#Ռ\60d;;,c%4L,Sth̾LL{">//VS/%3JE "^;ÇEK9´A%80ƐM6FV:~*-zDiZ}A-}Kffb粉8fj.2|\,A*p1q9hZXYȴ(dBvRw{? YD;B+f B- @.=g4R2xB*g,5OĔ%|hgR‰ڎvLKB"iϧsGpi;%ȏ,(K8q@F ğ۲}2Jt|e83F4cpwNe \\:^>R/ej:;mELQ#or^4sd;E;F3*;G/"٫eϐη{z䝼Pu0(KеhWJ1_,~ۻvF.a~XO|+T&_kTZ1{ǺW9"YHE$ ^97{ߏvE SBnYHE l toܗtUg]+*2x^=o8dཁҶhmٲ|`e O_ބ {Ug^]YXG“mb\}>6L)#zħ}sZFϻȕOz6f>i_g_gUKOO2=,FL'?[{W˔ B|g񿐊NwWuYKZ?/FAͿRp z uO{|UndY콊Ư9~YHE-/lƻO_ecd [z*抠' ^aӸT_ 3|Rp܂So \YbZ\cb ,E1K ӿj#W;[pz5Fc`|+)K '3n|Kf&;JG|(ʹD_(lk̆$mWk]ZNVRRZ#ӻn8W̉HvEV3 sJ^I1 Pz5Io-dKJÇip>rVpFS ֹ\{cggҢ1%P#WLEtD}ZDU&a~=-R~r~Ң}'iddư2m1$hwl&1u,8_v!\qHG.^(y*# "RWAJLb=Oh?o˗U_ pfld[(H/<\)po\qL7.a3ZT{$ߞ/km-ad6J";&J`.iCdr {;Y I>>|:cɨѺZ3"+l/k_h7 KC).r a#p gqt&Y 9:췉?sCw6B E-ʿ_h#6OO.]M9Gld,StpJN)Y:8%Kd,"+W0Y"ca2-dX׉K%Hu\6BaD "OVJSBh!?HEIENDB`PyNN-0.10.0/doc/pyplots/000077500000000000000000000000001415343567000147035ustar00rootroot00000000000000PyNN-0.10.0/doc/pyplots/ac_source.py000066400000000000000000000012671415343567000172260ustar00rootroot00000000000000""" """ from plot_helper import plot_current_source import pyNN.neuron as sim sim.setup() population = sim.Population(10, sim.IF_cond_exp(tau_m=10.0)) population[0:1].record_v() sine = sim.ACSource(start=50.0, stop=450.0, amplitude=1.0, offset=1.0, frequency=10.0, phase=180.0) population.inject(sine) sine._record() sim.run(500.0) t, i_inj = sine._get_data() v = population.get_data().segments[0].analogsignals[0] plot_current_source(t, i_inj, v, v_range=(-66, -49), v_ticks=(-65, -60, -55, -50), i_range=(-0.1, 2.1), i_ticks=(0.0, 0.5, 1.0, 1.5), t_range=(0, 500)) PyNN-0.10.0/doc/pyplots/continuous_time_spiking.py000066400000000000000000000025341415343567000222310ustar00rootroot00000000000000import numpy as np from pyNN.nest import * import matplotlib.pyplot as plt def test_sim(on_or_off_grid, sim_time): setup(timestep=1.0, min_delay=1.0, max_delay=1.0, spike_precision=on_or_off_grid) src = Population(1, SpikeSourceArray(spike_times=[0.5])) cm = 250.0 tau_m = 10.0 tau_syn_E = 1.0 weight = cm / tau_m * np.power(tau_syn_E / tau_m, -tau_m / (tau_m - tau_syn_E)) * 20.5 nrn = Population(1, IF_curr_exp(cm=cm, tau_m=tau_m, tau_syn_E=tau_syn_E, tau_refrac=2.0, v_thresh=20.0, v_rest=0.0, v_reset=0.0, i_offset=0.0)) nrn.initialize(v=0.0) prj = Projection(src, nrn, OneToOneConnector(), StaticSynapse(weight=weight)) nrn.record('v') run(sim_time) return nrn.get_data().segments[0].analogsignals[0] sim_time = 10.0 off = test_sim('off_grid', sim_time) on = test_sim('on_grid', sim_time) def plot_data(pos, on, off, ylim, with_legend=False): ax = plt.subplot(1, 2, pos) ax.plot(off.times, off, color='0.7', linewidth=7, label='off-grid') ax.plot(on.times, on, 'k', label='on-grid') ax.set_ylim(*ylim) ax.set_xlim(0, 9) ax.set_xlabel('time [ms]') ax.set_ylabel('V [mV]') if with_legend: plt.legend() plot_data(1, on, off, (-0.5, 21), with_legend=True) plot_data(2, on, off, (-0.05, 2.1)) plt.show() PyNN-0.10.0/doc/pyplots/dc_source.py000066400000000000000000000011531415343567000172230ustar00rootroot00000000000000""" """ from plot_helper import plot_current_source import pyNN.neuron as sim sim.setup() population = sim.Population(10, sim.IF_cond_exp(tau_m=10.0)) population[3:4].record_v() pulse = sim.DCSource(amplitude=0.5, start=20.0, stop=80.0) pulse.inject_into(population[3:7]) pulse._record() sim.run(100.0) t, i_inj = pulse._get_data() v = population.get_data().segments[0].analogsignals[0] plot_current_source(t, i_inj, v, v_range=(-65.5, -59.5), v_ticks=(-65, -64, -63, -62, -61, -60), i_range=(-0.1, 0.55), i_ticks=(0.0, 0.2, 0.4)) PyNN-0.10.0/doc/pyplots/neo_example.py000066400000000000000000000041361415343567000175550ustar00rootroot00000000000000import pyNN.neuron as sim # can of course replace `neuron` with `nest`, `brian`, etc. import matplotlib.pyplot as plt import numpy as np sim.setup(timestep=0.01) p_in = sim.Population(10, sim.SpikeSourcePoisson(rate=10.0), label="input") p_out = sim.Population(10, sim.EIF_cond_exp_isfa_ista(), label="AdExp neurons") syn = sim.StaticSynapse(weight=0.05) random = sim.FixedProbabilityConnector(p_connect=0.5) connections = sim.Projection(p_in, p_out, random, syn, receptor_type='excitatory') p_in.record('spikes') p_out.record('spikes') # record spikes from all neurons p_out[0:2].record(['v', 'w', 'gsyn_exc']) # record other variables from first two neurons sim.run(500.0) spikes_in = p_in.get_data() data_out = p_out.get_data() fig_settings = { 'lines.linewidth': 0.5, 'axes.linewidth': 0.5, 'axes.labelsize': 'small', 'legend.fontsize': 'small', 'font.size': 8 } plt.rcParams.update(fig_settings) plt.figure(1, figsize=(6, 8)) def plot_spiketrains(segment): for spiketrain in segment.spiketrains: y = np.ones_like(spiketrain) * spiketrain.annotations['source_id'] plt.plot(spiketrain, y, '.') plt.ylabel(segment.name) plt.setp(plt.gca().get_xticklabels(), visible=False) def plot_signal(signal, index, colour='b'): label = "Neuron %d" % signal.annotations['source_ids'][index] plt.plot(signal.times, signal[:, index], colour, label=label) plt.ylabel("%s (%s)" % (signal.name, signal.units._dimensionality.string)) plt.setp(plt.gca().get_xticklabels(), visible=False) plt.legend() n_panels = sum(a.shape[1] for a in data_out.segments[0].analogsignals) + 2 plt.subplot(n_panels, 1, 1) plot_spiketrains(spikes_in.segments[0]) plt.subplot(n_panels, 1, 2) plot_spiketrains(data_out.segments[0]) panel = 3 for array in data_out.segments[0].analogsignals: for i in range(array.shape[1]): plt.subplot(n_panels, 1, panel) plot_signal(array, i, colour='bg'[panel % 2]) panel += 1 plt.xlabel("time (%s)" % array.times.units._dimensionality.string) plt.setp(plt.gca().get_xticklabels(), visible=True) plt.show() PyNN-0.10.0/doc/pyplots/noise_source.py000066400000000000000000000012541415343567000177540ustar00rootroot00000000000000""" """ from plot_helper import plot_current_source import pyNN.neuron as sim sim.setup() population = sim.Population(30, sim.IF_cond_exp(tau_m=10.0)) population[0:1].record_v() noise = sim.NoisyCurrentSource(mean=1.5, stdev=1.0, start=50.0, stop=450.0, dt=1.0) population.inject(noise) noise._record() sim.run(500.0) t, i_inj = noise._get_data() v = population.get_data().segments[0].analogsignals[0] plot_current_source(t, i_inj, v, v_range=(-66, -48), v_ticks=(-65, -60, -55, -50), i_range=(-3, 5), i_ticks=range(-2, 6, 2), t_range=(0, 500)) PyNN-0.10.0/doc/pyplots/plot_current_source.py000066400000000000000000000024571415343567000213650ustar00rootroot00000000000000""" Helper function for drawing current-source plots """ def plot_current_source(t, i_inj, v): """ Plot voltage and current traces """ fig = plt.figure(figsize=(8, 3)) fig.dpi = 120 ax = fig.add_axes((0.1, 0.4, 0.85, 0.5), frameon=False) ax.plot(t, v, 'b') ax.set_ylim(-65.5, -59.5) ax.set_ylabel('V (mV)') ax.xaxis.set_visible(False) ax.yaxis.set_ticks_position('left') ax.yaxis.set_ticks((-65, -64, -63, -62, -61, -60)) # add the left axis line back in xmin, xmax = ax.get_xaxis().get_view_interval() ymin, ymax = ax.get_yaxis().get_view_interval() ax.add_artist(plt.Line2D((xmin, xmin), (ymin, ymax), color='black', linewidth=3)) ax = fig.add_axes((0.1, 0.14, 0.85, 0.25), frameon=False) ax.plot(t, i_inj, 'g') ax.set_ylim(-0.1, 0.55) ax.set_ylabel('I (nA)') ax.set_xlabel('Time (ms)') ax.yaxis.tick_left() ax.xaxis.tick_bottom() ax.yaxis.set_ticks((0.0, 0.2, 0.4)) # add the bottom and left axis lines back in xmin, xmax = ax.get_xaxis().get_view_interval() ymin, ymax = ax.get_yaxis().get_view_interval() ax.add_artist(plt.Line2D((xmin, xmax), (ymin, ymin), color='black', linewidth=3)) ax.add_artist(plt.Line2D((xmin, xmin), (ymin, ymax), color='black', linewidth=3)) fig.show() return fig PyNN-0.10.0/doc/pyplots/plot_helper.py000066400000000000000000000031011415343567000175650ustar00rootroot00000000000000""" Helper function for drawing current-source plots """ import matplotlib.pyplot as plt def plot_current_source(t, i_inj, v, i_range=None, v_range=None, i_ticks=None, v_ticks=None, t_range=None): """ Plot voltage and current traces """ fig = plt.figure(figsize=(8, 3)) fig.dpi = 120 ax = fig.add_axes((0.1, 0.4, 0.85, 0.5), frameon=False) ax.plot(t, v, 'b') if v_range: ax.set_ylim(*v_range) ax.set_ylabel('V (mV)') ax.xaxis.set_visible(False) ax.yaxis.set_ticks_position('left') if v_ticks: ax.yaxis.set_ticks(v_ticks) if t_range: ax.set_xlim(*t_range) # add the left axis line back in xmin, xmax = ax.get_xaxis().get_view_interval() ymin, ymax = ax.get_yaxis().get_view_interval() ax.add_artist(plt.Line2D((xmin, xmin), (ymin, ymax), color='black', linewidth=3)) ax = fig.add_axes((0.1, 0.14, 0.85, 0.25), frameon=False) ax.plot(t, i_inj, 'g') if i_range: ax.set_ylim(*i_range) ax.set_ylabel('I (nA)') ax.set_xlabel('Time (ms)') ax.yaxis.tick_left() ax.xaxis.tick_bottom() if i_ticks: ax.yaxis.set_ticks(i_ticks) if t_range: ax.set_xlim(*t_range) # add the bottom and left axis lines back in xmin, xmax = ax.get_xaxis().get_view_interval() ymin, ymax = ax.get_yaxis().get_view_interval() ax.add_artist(plt.Line2D((xmin, xmax), (ymin, ymin), color='black', linewidth=3)) ax.add_artist(plt.Line2D((xmin, xmin), (ymin, ymax), color='black', linewidth=3)) plt.savefig("tmp.png") return fig PyNN-0.10.0/doc/pyplots/reset_example.py000066400000000000000000000014331415343567000201130ustar00rootroot00000000000000import pyNN.neuron as sim # can of course replace `nest` with `neuron`, `brian`, etc. import matplotlib.pyplot as plt from quantities import nA sim.setup() cell = sim.Population(1, sim.HH_cond_exp()) step_current = sim.DCSource(start=20.0, stop=80.0) step_current.inject_into(cell) cell.record('v') for amp in (-0.2, -0.1, 0.0, 0.1, 0.2): step_current.amplitude = amp sim.run(100.0) sim.reset(annotations={"amplitude": amp * nA}) data = cell.get_data() sim.end() for segment in data.segments: vm = segment.analogsignals[0] plt.plot(vm.times, vm, label=str(segment.annotations["amplitude"])) plt.legend(loc="upper left") plt.xlabel("Time (%s)" % vm.times.units._dimensionality) plt.ylabel("Membrane potential (%s)" % vm.units._dimensionality) plt.show() PyNN-0.10.0/doc/pyplots/step_source.py000066400000000000000000000013361415343567000176130ustar00rootroot00000000000000""" """ from plot_helper import plot_current_source import pyNN.neuron as sim sim.setup() population = sim.Population(30, sim.IF_cond_exp(tau_m=10.0)) population[27:28].record_v() steps = sim.StepCurrentSource(times=[50.0, 110.0, 150.0, 210.0], amplitudes=[0.4, 0.6, -0.2, 0.2]) steps.inject_into(population[(6, 11, 27)]) steps._record() sim.run(250.0) t, i_inj = steps._get_data() v = population.get_data().segments[0].analogsignals[0] plot_current_source(t, i_inj, v, #v_range=(-66, -49), v_ticks=(-66, -64, -62, -60), i_range=(-0.3, 0.7), i_ticks=(-0.2, 0.0, 0.2, 0.4, 0.6), t_range=(0, 250)) PyNN-0.10.0/doc/quickstart.txt000066400000000000000000000000561415343567000161250ustar00rootroot00000000000000========== Quickstart ========== to write... PyNN-0.10.0/doc/random_numbers.txt000066400000000000000000000150551415343567000167530ustar00rootroot00000000000000============== Random numbers ============== There are four considerations for random number generation and consumption in PyNN: **Reproducibility**: When comparing simulations with different backends, we may wish to ensure that all backends use the same sequence of random numbers so that the only differences between simulations arise from the numerics of the simulators. **Performance**: All simulators have their own built-in facilities for random number generation, and it may be faster to use these than to use random numbers generated by PyNN. **Distributed simulations**: When distributing simulations across multiple processors using MPI, we may wish to ensure that the sequence of random numbers is independent of the number of computation nodes. **Quality**: Different models have different requirements for the quality of the (pseudo-)random number generator used. For models that are not strongly dependent on this, we may wish to use a generator that is faster but has lower-quality. For models that are highly sensitive, a slower but higher-quality generator may be desired. Because of these considerations, PyNN aims to provide a great deal of flexibility in specifying random number generation for those who need it, while hiding the details entirely for those who do not. :class:`RNG` classes ==================== All functions and methods in the PyNN API that can make use of random numbers have an optional *rng* argument, which should be an instance of a subclass of :class:`pyNN.random.AbstractRNG`. PyNN provides three such sub-classes: :class:`~pyNN.random.NumpyRNG`: Uses the :class:`numpy.random.RandomState` class (Mersenne Twister). :class:`~pyNN.random.GSLRNG`: Uses the `GNU Scientific Library random number generators`_. :class:`~pyNN.random.NativeRNG`: Signals that the simulator's own built-in RNG should be used. If you wish to use your own random number generator, it is reasonably straightforward to do so: see :doc:`reference/random` in the API reference. .. note:: If the *rng* argument is not supplied (or is `None`), then the method or function creates a new :class:`~pyNN.random.NumpyRNG` for its own use. All :class:`RNG` classes accept a *seed* argument, and a *parallel_safe* argument. The latter is `True` by default, and ensures that the simulation results will **not** depend on the number of MPI nodes in a distributed simulation. This independence can be computationally costly, however, so it is possible to set *parallel_safe=False*, accepting that the results will be dependent on the number of nodes, in order to get better performace. .. note:: if you do not provide a seed, PyNN will provide one for you, the same each time. This means that running the same simulation several times will use identical random numbers each time, so if you want to have different random numbers on different runs, you must provide your own seed and change it (or randomly generate it) each time. .. note:: *parallel_safe* may or may not have any effect when using a :class:`~pyNN.random.NativeRNG`, depending on the simulator. The :meth:`next` method ----------------------- Apart from the constructor, :class:`RNG` classes have only one important method: :meth:`next`, which returns a NumPy array containing random numbers from the requested distribution: .. testsetup:: from pyNN.random import NumpyRNG, GSLRNG, RandomDistribution .. doctest:: >>> rng = NumpyRNG(seed=824756) >>> rng.next(5, 'normal', {'mu': 1.0, 'sigma': 0.2}) array([ 0.65866423, 0.87500017, 0.90755753, 0.93793779, 0.94839735]) >>> rng = GSLRNG(seed=824756, type='ranlxd2') # RANLUX algorithm of Luescher >>> rng.next(5, 'normal', {'mu': 1.0, 'sigma': 0.2}) array([ 0.61104097, 0.83086026, 0.87072741, 0.7513628 , 1.12875371]) In versions of PyNN prior to 0.8, distribution names and parameterisations were not standardized: e.g. :class:`GSLRNG` needed 'gaussian' rather than 'normal'. As of PyNN 0.8, the following standardized names are used: ========================== ==================== =============================================== Name Parameters Comments -------------------------- -------------------- ----------------------------------------------- binomial n, p gamma k, theta exponential beta lognormal mu, sigma normal mu, sigma normal_clipped mu, sigma, low, high Values outside (low, high) are redrawn normal_clipped_to_boundary mu, sigma, low, high Values below/above low/high are set to low/high poisson lambda uniform low, high uniform_int low, high vonmises mu, kappa ========================== ==================== =============================================== The :class:`~pyNN.random.RandomDistribution` class ================================================== The :class:`~pyNN.random.RandomDistribution` class encapsulates a choice of random number generator and a choice of distribution, so that its :meth:`next` method requires only the number of values required as argument: .. doctest:: >>> gamma = RandomDistribution('gamma', (2.0, 0.3), rng=NumpyRNG(seed=72386)) >>> gamma.next(5) array([ 0.4325809 , 0.12952503, 1.58510406, 0.81182457, 0.07577787]) You can alternatively provide parameter names as keyword arguments, e.g.: .. doctest:: >>> gamma = RandomDistribution('gamma', k=2.0, theta=0.3, rng=NumpyRNG(seed=72386)) Note that :meth:`~pyNN.random.RandomDistribution.next` called without any arguments returns a single number, not an array: .. doctest:: >>> gamma.next() 0.52020946027308368 >>> gamma.next(1) array([ 0.4863944]) .. note:: the apparent difference in precision between the single number and the array is not real: NumPy only *displays* a limited number of digits but the numbers in the array have full precision. SpikeSourcePoisson ================== The :class:`SpikeSourcePoisson` model is currently an exception to the situation outlined above. It ought to take an `rng` argument, but at present this is not supported, and the model uses the global random number generator seed, as set in the :func:`setup()` function. .. _`GNU Scientific Library random number generators`: http://pygsl.sourceforge.net/reference/pygsl/module-pygsl.rng.htmlPyNN-0.10.0/doc/recording.txt000066400000000000000000000050601415343567000157070ustar00rootroot00000000000000==================================== Recording spikes and state variables ==================================== It is possible to record the times of action potentials, and the values of state variables, of any neuron in the network. Recording state variables of dynamic synapse models is not yet supported. The classes :class:`Population`, :class:`PopulationView` and :class:`Assembly` all have a :meth:`record` method, which takes either a single variable name or a list/tuple of such names, and which sets up recording of the requested variables for all neurons in the population: .. testsetup:: import pyNN.mock as sim sim.setup() population = sim.Population(20, sim.EIF_cond_alpha_isfa_ista()) p2 = sim.Population(1, sim.IF_cond_exp()) assembly = population + p2 .. doctest:: >>> population.record(['v', 'spikes']) # record membrane potential and spikes from all neurons in the population >>> assembly.record('spikes') # record spikes from all neurons in multiple populations To record from only a subset of the neurons in a :class:`Population`, we create a temporary :class:`PopulationView` using indexing or the :meth:`sample` method and call :meth:`record` on this view: .. doctest:: >>> population.sample(10).record('v') # record membrane potential from 10 neurons chosen at random >>> population[[0,1,2]].record(['v', 'gsyn_exc', 'gsyn_inh']) # record several variables from specific neurons To find out what variable names are available for a given neuron model, inspect the :attr:`recordable` attribute of the population's :attr:`celltype` attribute: .. doctest:: >>> population.celltype EIF_cond_alpha_isfa_ista() >>> population.celltype.recordable ['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh'] By default, variables are recorded at every time step. It is possible to record at a lower frequency using the :attr:`sampling_interval` argument, e.g.: .. doctest:: >>> population.record(None) # reset recording for this population >>> population.record('v', sampling_interval=1.0) You should ensure that the sampling interval is an integer multiple of the simulation time step. Other values may work, but have not been tested. An alternative syntax is available, using the top-level :func:`record()` function: .. doctest:: >>> record(['v', 'spikes'], population, filename="output_data.pkl", sampling_interval=1.0) This avoids having to call :func:`population.write_data()` at the end of the simulation; the data will be automatically written to the specified filename. PyNN-0.10.0/doc/reference/000077500000000000000000000000001415343567000151275ustar00rootroot00000000000000PyNN-0.10.0/doc/reference/connectors.txt000066400000000000000000000014071415343567000200470ustar00rootroot00000000000000========== Connectors ========== .. module:: pyNN.connectors Base class ========== .. autoclass:: Connector :members: :undoc-members: Built-in connectors =================== .. autoclass:: AllToAllConnector .. autoclass:: OneToOneConnector .. autoclass:: FixedProbabilityConnector .. autoclass:: FromListConnector .. autoclass:: FromFileConnector .. autoclass:: ArrayConnector .. autoclass:: FixedNumberPreConnector .. autoclass:: FixedNumberPostConnector .. autoclass:: FixedTotalNumberConnector .. autoclass:: DistanceDependentProbabilityConnector .. autoclass:: IndexBasedProbabilityConnector .. autoclass:: DisplacementDependentProbabilityConnector .. autoclass:: SmallWorldConnector .. autoclass:: CSAConnector .. autoclass:: CloneConnector PyNN-0.10.0/doc/reference/electrodes.txt000066400000000000000000000014001415343567000200140ustar00rootroot00000000000000=============== Current sources =============== .. currentmodule:: pyNN.neuron.standardmodels.electrodes .. autoclass:: DCSource :members: :show-inheritance: .. automethod:: inject_into .. automethod:: get_parameters .. automethod:: set_parameters .. autoclass:: ACSource :members: :show-inheritance: .. automethod:: inject_into .. automethod:: get_parameters .. automethod:: set_parameters .. autoclass:: StepCurrentSource :members: :show-inheritance: .. automethod:: inject_into .. automethod:: get_parameters .. automethod:: set_parameters .. autoclass:: NoisyCurrentSource :members: :show-inheritance: .. automethod:: inject_into .. automethod:: get_parameters .. automethod:: set_parameters PyNN-0.10.0/doc/reference/neuronmodels.txt000066400000000000000000000075731415343567000204160ustar00rootroot00000000000000============= Neuron models ============= .. currentmodule:: pyNN.standardmodels.cells PyNN provides a library of neuron models that have been standardized so as to give the same results (within certain limits of numerical accuracy) on different backends. Each model is represented by a "cell type" class. It is also possible to use simulator-specific neuron models, which we call "native" cell types. Of course, such models will only work with one specific backend simulator. .. note:: the development version has some support for specifying cell types using the NineML_ and NeuroML_ formats, but this is not yet available in the current release. Standard cell types =================== * Plain integrate-and-fire models: * :class:`IF_curr_exp` * :class:`IF_curr_alpha` * :class:`IF_cond_exp` * :class:`IF_cond_alpha` * Integrate-and-fire with adaptation: * :class:`IF_cond_exp_gsfa_grr` * :class:`EIF_cond_alpha_isfa_ista` * :class:`EIF_cond_exp_isfa_ista` * :class:`Izhikevich` * Hodgkin-Huxley model * :class:`HH_cond_exp` * Spike sources (input neurons) * :class:`SpikeSourcePoisson` * :class:`SpikeSourceArray` * :class:`SpikeSourceInhGamma` Base class ---------- All standard cell types inherit from the following base class, and have the same methods, as listed below. .. autoclass:: pyNN.standardmodels.StandardCellType :show-inheritance: .. automethod:: get_schema .. automethod:: get_parameter_names .. automethod:: get_native_names .. automethod:: has_parameter .. automethod:: translate .. automethod:: reverse_translate .. automethod:: simple_parameters .. automethod:: scaled_parameters .. automethod:: computed_parameters .. automethod:: describe Simple integrate-and-fire neurons --------------------------------- .. autoclass:: IF_cond_exp :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: IF_cond_alpha :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: IF_curr_exp :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: IF_curr_alpha :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based Integrate-and-fire neurons with adaptation ------------------------------------------ .. autoclass:: Izhikevich :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: EIF_cond_exp_isfa_ista :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: EIF_cond_alpha_isfa_ista :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: IF_cond_exp_gsfa_grr :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based Spike sources ------------- .. autoclass:: SpikeSourcePoisson :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: SpikeSourceArray :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based .. autoclass:: SpikeSourceInhGamma :members: :undoc-members: :show-inheritance: .. autoattribute:: injectable .. autoattribute:: conductance_based Native cell types ================= .. todo:: WRITE THIS PART Utility functions ================= .. autofunction:: pyNN.neuron.list_standard_models .. _NineML: http://nineml.incf.org .. _NeuroML: http://www.neuroml.org PyNN-0.10.0/doc/reference/parameters.txt000066400000000000000000000164151415343567000200420ustar00rootroot00000000000000================== Parameter handling ================== .. testsetup:: import numpy from lazyarray import larray .. module:: pyNN.parameters .. note:: these classes are not part of the PyNN API. They should not be used in PyNN scripts, they are intended for implementing backends. You are not required to use them when implementing your own backend, however, as long as your backend conforms to the API. The main abstractions in PyNN are the population of neurons, and the set of connections (a 'projection') between two populations. Setting the parameters of individual neuron and synapse models, therefore, mainly takes place at the level of populations and projections. .. note:: it is also possible to set the parameters of neurons and synapses individually, but this is generally less efficient. Any model parameter in PyNN can be expressed as: * a single value - all neurons in a population or synapses in a projection get the same value * a :class:`RandomDistribution` object - each element gets a value drawn from the random distribution * a list/array of values of the same size as the population/projection * a mapping function, where a mapping function accepts a either a single argument ``i`` (for a population) or two arguments ``(i, j)`` (for a projection) and returns a single value. A "single value" is usually a single number, but for some parameters (e.g. for spike times) it may be a list/array of numbers. To handle all these possibilities in a uniform way, and at the same time allow for efficient parallelization, in the 'common' implementation of the PyNN API all parameter values are converted into :class:`LazyArray` objects, and the set of parameters for a model is contained in a :class:`dict`-like object, :class:`ParameterSpace`. The :class:`LazyArray` class ---------------------------- :class:`LazyArray` is a PyNN-specific sub-class of a more general class, :class:`larray`, and most of its functionality comes from the parent class. Full documentation for :class:`larray` is available in the lazyarray_ package, but we give here a quick overview. :class:`LazyArray` has three important features in the context of PyNN: 1. any operations on the array (potentially including array construction) are not performed immediately, but are delayed until evaluation is specifically requested. 2. evaluation of only parts of the array is possible, which means that in a parallel simulation with MPI, all processes have the same :class:`LazyArray` for a parameter, but on a given process, only the part of the array which is needed for the neurons/synapses that exist on that process need be evaluated. 3. single often all neurons in a population or synapses in a projection have the same value for a given parameter, a :class:`LazyArray` created from a single value evaluates to that value: the full array is never created unless this is requested. For example, suppose we have two parameters, `tau_m`, which is constant, and `v_thresh` which varies according to the position of the neuron in the population. .. doctest:: >>> from pyNN.parameters import LazyArray >>> tau_m = 2 * LazyArray(10.0, shape=(20,)) >>> v_thresh = -55 + LazyArray(lambda i: 0.1*i, shape=(20,)) If we evaluate `tau_m` we get a full, homogeneous array: .. doctest:: >>> tau_m.evaluate() array([ 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20., 20.]) but we could also have asked just for the single number, in which case the full array would never be created: .. doctest:: >>> tau_m.evaluate(simplify=True) 20.0 Similarly, we can evaluate `v_thresh` to get a normal NumPy array: .. doctest:: >>> v_thresh.evaluate() array([-55. , -54.9, -54.8, -54.7, -54.6, -54.5, -54.4, -54.3, -54.2, -54.1, -54. , -53.9, -53.8, -53.7, -53.6, -53.5, -53.4, -53.3, -53.2, -53.1]) but we can also take, for example, only every fifth value, in which case the operation "add -55" only gets performed for those elements. .. doctest:: >>> v_thresh[::5] array([-55. , -54.5, -54. , -53.5]) In this example the operation is very fast, but with slower operations (e.g. distance calculations) and large arrays, the time savings can be considerable (see `lazyarray performance`_). In summary, by using :class:`LazyArray`, we can pass parameters around in an optimised way without having to worry about exactly what form the parameter value takes, hence avoiding a lot of logic at multiple points in the code. Reference ~~~~~~~~~ .. autoclass:: pyNN.parameters.LazyArray :members: :undoc-members: :inherited-members: :show-inheritance: .. automethod:: pyNN.parameters.LazyArray.__getitem__ The :class:`ParameterSpace` class --------------------------------- :class:`ParameterSpace` is a dict-like class that contains :class:`LazyArray` objects. In addition to the usual :class:`dict` methods, it has several methods that allow operations on all the lazy arrays within it at once. For example: .. doctest:: >>> from pyNN.parameters import ParameterSpace >>> ps = ParameterSpace({'a': [2, 3, 5, 8], 'b': 7, 'c': lambda i: 3*i+2}, shape=(4,)) >>> ps['c'] at ...> shape=(4,) dtype=None, operations=[]> # doctest: +ELLIPSIS >>> ps.evaluate() >>> ps['c'] array([ 2, 5, 8, 11]) the :meth:`evaluate()` method also accepts a mask, in order to evaluate only part of the lazy arrays: .. doctest:: >>> ps = ParameterSpace({'a': [2, 3, 5, 8, 13], 'b': 7, 'c': lambda i: 3*i+2}, shape=(5,)) >>> ps.evaluate(mask=[1, 3, 4]) >>> ps.as_dict() {'a': array([ 3, 8, 13]), 'c': array([ 5, 11, 14]), 'b': array([7, 7, 7])} An example with two-dimensional arrays: .. doctest:: >>> ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]], ... 'b': 7, ... 'c': lambda i, j: 3*i-2*j}, shape=(2, 5)) >>> ps2d.evaluate(mask=(slice(None), [1, 3, 4])) >>> print(ps2d['a']) [[ 3 8 13] [ 34 89 144]] >>> print(ps2d['c']) [[-2 -6 -8] [ 1 -3 -5]] There are also several methods to allow iterating over the parameter space in different ways. A :class:`ParameterSpace` can be viewed as both a :class:`dict` contaning arrays and as an array of dicts. Iterating over a parameter space gives the latter view: .. doctest:: >>> for D in ps: ... print(D) ... {'a': 3, 'c': 5, 'b': 7} {'a': 8, 'c': 11, 'b': 7} {'a': 13, 'c': 14, 'b': 7} unlike for a :class:`dict`, where iterating over it gives the keys. :meth:`items()` works as for a normal :class:`dict`: .. doctest:: >>> for key, value in ps.items(): ... print(key, "=", value) a = [ 3 8 13] c = [ 5 11 14] b = [7 7 7] Reference ~~~~~~~~~ .. autoclass:: ParameterSpace :members: :undoc-members: .. automethod:: __getitem__ .. automethod:: __iter__ The :class:`Sequence` class --------------------------- .. autoclass:: Sequence :members: :undoc-members: .. automethod:: __mul__ .. automethod:: __div__ .. _lazyarray: https://lazyarray.readthedocs.org/ .. _`lazyarray performance`: https://lazyarray.readthedocs.org/en/latest/performance.htmlPyNN-0.10.0/doc/reference/plasticitymodels.txt000066400000000000000000000046121415343567000212640ustar00rootroot00000000000000============== Synapse models ============== .. currentmodule:: pyNN.standardmodels.synapses The synaptic connection between two neurons is represented as a "synapse type" class. Note that synaptic attributes that belong solely to the post-synaptic neuron, such as the decay of the post-synaptic conductance, are part of the cell type model. The "synapse type" models the synaptic delay, the synaptic weight, and any dynamic behaviour of the synaptic weight, i.e. synaptic plasticity. As for cell types, PyNN has a library of "standard" synapse types that should give the same behaviour on different simulators, and also supports the use of "native" synapse types, limited to a single simulator. Standard synapse types ====================== Base class ---------- All standard cell types inherit from the following base class, and have the same methods, as listed below. .. autoclass:: pyNN.standardmodels.StandardSynapseType :members: :undoc-members: :inherited-members: :show-inheritance: Static/fixed synapses --------------------- .. autoclass:: StaticSynapse :members: :undoc-members: :show-inheritance: Short-term plasticity mechanisms -------------------------------- .. autoclass:: TsodyksMarkramSynapse :members: :undoc-members: :show-inheritance: Long-term plasticity mechanisms ------------------------------- .. autoclass:: STDPMechanism :members: :undoc-members: :show-inheritance: Weight-dependence components ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: pyNN.standardmodels.STDPWeightDependence :members: :undoc-members: :show-inheritance: .. automethod:: describe .. autoclass:: AdditiveWeightDependence :members: :undoc-members: :show-inheritance: .. autoclass:: MultiplicativeWeightDependence :members: :undoc-members: :show-inheritance: .. autoclass:: AdditivePotentiationMultiplicativeDepression :members: :undoc-members: :show-inheritance: .. autoclass:: GutigWeightDependence :members: :undoc-members: :show-inheritance: Timing-dependence components ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. autoclass:: pyNN.standardmodels.STDPTimingDependence :members: :undoc-members: :show-inheritance: .. autoclass:: SpikePairRule :members: :undoc-members: :show-inheritance: .. automethod:: describe Native plasticity models ======================== NineML plasticity models ======================== PyNN-0.10.0/doc/reference/populations.txt000066400000000000000000000044011415343567000202440ustar00rootroot00000000000000================================= Populations, Views and Assemblies ================================= .. currentmodule:: pyNN.neuron Populations =========== .. testsetup:: import pyNN.mock as sim sim.setup() p = sim.Population(10, sim.IF_cond_exp()) pv = p[::2] .. autoclass:: Population :members: :undoc-members: :inherited-members: :show-inheritance: .. attribute:: size The total number of neurons in the Population. .. attribute:: label A label for the Population. .. attribute:: celltype The type of neurons making up the Population. .. attribute:: initial_values A dict containing the initial values of the state variables. .. attribute:: all_cells An array containing the cell ids of all neurons in the Population (all MPI nodes) .. automethod:: __add__ .. automethod:: __getitem__ .. automethod:: __iter__ .. automethod:: __len__ .. autofunction:: pyNN.neuron.create Views (sub-populations) ======================= .. autoclass:: PopulationView :members: :undoc-members: :inherited-members: :show-inheritance: .. attribute:: size The total number of neurons in the Population. .. attribute:: label A label for the Population. .. attribute:: celltype The type of neurons making up the Population. .. attribute:: initial_values A dict containing the initial values of the state variables. .. attribute:: parent A reference to the parent Population (that this is a view of). .. attribute:: mask The selector mask that was used to create this view. .. attribute:: all_cells An array containing the cell ids of all neurons in the Population (all MPI nodes). .. attribute:: local_cells An array containing the cell ids of those neurons in the Population that exist on the local MPI node. .. automethod:: __add__ .. automethod:: __getitem__ .. automethod:: __iter__ .. automethod:: __len__ Assemblies ========== .. autoclass:: pyNN.neuron.Assembly :members: :undoc-members: :inherited-members: :show-inheritance: .. automethod:: __add__ .. automethod:: __iadd__ .. automethod:: __getitem__ .. automethod:: __iter__ .. automethod:: __len__ PyNN-0.10.0/doc/reference/projections.txt000066400000000000000000000020011415343567000202200ustar00rootroot00000000000000=========== Projections =========== .. currentmodule:: pyNN.neuron .. autoclass:: Projection :members: :undoc-members: :inherited-members: :show-inheritance: .. attribute:: pre The pre-synaptic Population, PopulationView or Assembly. .. attribute:: post The post-synaptic Population, PopulationView or Assembly. .. attribute:: source A string specifying which attribute of the presynaptic cell signals action potentials. .. attribute:: target Name of the postsynaptic mechanism type (e.g. 'excitatory', 'NMDA'). .. attribute:: label A label for the Projection. .. attribute:: rng The RNG object that was used by the Connector. .. attribute:: synapse_dynamics The SynapseDynamics object that was used to specify the synaptic plasticity mechanism, or `None` if the synapses are static. .. automethod:: __len__ .. automethod:: __getitem__ .. automethod:: __iter__ .. autofunction:: connectPyNN-0.10.0/doc/reference/random.txt000066400000000000000000000012371415343567000171530ustar00rootroot00000000000000============== Random numbers ============== .. testsetup:: * from pyNN.random import NumpyRNG, RandomDistribution .. module:: pyNN.random .. autoclass:: RandomDistribution :members: :undoc-members: .. autoclass:: NumpyRNG :members: :undoc-members: :inherited-members: :show-inheritance: .. autoclass:: GSLRNG :members: :undoc-members: :inherited-members: :show-inheritance: .. autoclass:: NativeRNG :members: :undoc-members: :inherited-members: :show-inheritance: Adapting a different random number generator to work with PyNN -------------------------------------------------------------- .. todo:: write thisPyNN-0.10.0/doc/reference/simulationcontrol.txt000066400000000000000000000011551415343567000214570ustar00rootroot00000000000000================== Simulation control ================== .. autofunction:: pyNN.neuron.setup .. autofunction:: pyNN.neuron.run .. autofunction:: pyNN.neuron.run_until .. autofunction:: pyNN.neuron.reset .. autofunction:: pyNN.neuron.end .. autofunction:: pyNN.neuron.get_time_step .. autofunction:: pyNN.neuron.get_current_time .. autofunction:: pyNN.neuron.get_min_delay .. autofunction:: pyNN.neuron.get_max_delay .. autofunction:: pyNN.neuron.num_processes .. autofunction:: pyNN.neuron.rank .. autofunction:: pyNN.neuron.set .. autofunction:: pyNN.neuron.initialize .. autofunction:: pyNN.neuron.record PyNN-0.10.0/doc/reference/space.txt000066400000000000000000000021641415343567000167660ustar00rootroot00000000000000================= Spatial structure ================= .. module:: pyNN.space Structure classes ----------------- :class:`Structure` classes all inherit from the following base class, and inherit its methods: .. autoclass:: BaseStructure :members: :undoc-members: :inherited-members: :show-inheritance: .. autoclass:: Line :members: :undoc-members: :show-inheritance: .. autoclass:: Grid2D :members: :undoc-members: :show-inheritance: .. autoclass:: Grid3D :members: :undoc-members: :show-inheritance: .. autoclass:: RandomStructure :members: :undoc-members: :show-inheritance: Shape classes ------------- .. autoclass:: Cuboid :members: :undoc-members: :inherited-members: :show-inheritance: .. autoclass:: Sphere :members: :undoc-members: :inherited-members: :show-inheritance: The Space class --------------- .. autoclass:: Space :members: :undoc-members: :inherited-members: Implementing your own Shape --------------------------- .. todo:: write this Implementing your own Structure ------------------------------- .. todo:: write this PyNN-0.10.0/doc/reference/utility.txt000066400000000000000000000013021415343567000173670ustar00rootroot00000000000000============================= Utility classes and functions ============================= .. autofunction:: pyNN.utility.init_logging .. autofunction:: pyNN.utility.get_simulator .. autoclass:: pyNN.utility.Timer :members: :undoc-members: .. autoclass:: pyNN.utility.ProgressBar :members: :undoc-members: .. autofunction:: pyNN.utility.notify .. autofunction:: pyNN.utility.save_population .. autofunction:: pyNN.utility.load_population Basic plotting ============== .. autoclass:: pyNN.utility.plotting.Figure :members: :undoc-members: .. autoclass:: pyNN.utility.plotting.Panel :members: :undoc-members: .. autofunction:: pyNN.utility.plotting.comparison_plot PyNN-0.10.0/doc/release_notes.txt000066400000000000000000000010041415343567000165550ustar00rootroot00000000000000============= Release notes ============= .. toctree:: :maxdepth: 1 releases/0.10.0.txt releases/0.9.6.txt releases/0.9.5.txt releases/0.9.4.txt releases/0.9.3.txt releases/0.9.2.txt releases/0.9.1.txt releases/0.9.0.txt releases/0.8.3.txt releases/0.8.2.txt releases/0.8.1.txt releases/0.8.0.txt releases/0.8.0-rc-1.txt releases/0.8-beta-2.txt releases/0.8-beta-1.txt releases/0.8-alpha-2.txt releases/0.8-alpha-1.txt releases/0.7.txt releases/0.6.txt PyNN-0.10.0/doc/releases/000077500000000000000000000000001415343567000147745ustar00rootroot00000000000000PyNN-0.10.0/doc/releases/0.10.0.txt000066400000000000000000000007351415343567000162560ustar00rootroot00000000000000======================== PyNN 0.10.0 release notes ======================== December 1st 2021 Welcome to PyNN 0.10.0! NEST 3.1 -------- PyNN now supports the latest version of NEST_. Requires Neo 0.10.0 or later Bug fixes --------- A `small number of bugs`_ have been fixed. .. _`small number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=milestone%3A0.10.0+is%3Aclosed .. _NEST: https://www.nest-simulator.org .. _`Brian 2`: https://brian2.readthedocs.ioPyNN-0.10.0/doc/releases/0.6.txt000066400000000000000000000110141415343567000160350ustar00rootroot00000000000000====================== PyNN 0.6 release notes ====================== 14th February 2010 Welcome to PyNN 0.6! There have been three major changes to the API in this version. * Spikes, membrane potential and synaptic conductances can now be saved to file in various binary formats. To do this, pass a PyNN :class:`File` object to :meth:`Population.print_X()`, instead of a filename. There are various types of PyNN :class:`File` object, defined in the :mod:`recording.files module`, e.g., :class:`StandardTextFile`, :class:`PickleFile`, :class:`NumpyBinaryFile`, :class:`HDF5ArrayFile`. * Added a :func:`reset()` function and made the behaviour of :func:`setup()` consistent across simulators. :func:`reset()` sets the simulation time to zero and sets membrane potentials to their initial values, but does not change the network structure. :func:`setup()` destroys any previously defined network. * The possibility of expressing distance-dependent weights and delays was extended to the :class:`AllToAllConnector` and :class:`FixedProbabilityConnector` classes. To reduce the number of arguments to the constructors, the arguments affecting the spatial topology (periodic boundary conditions, etc.) were moved to a new :class:`Space` class, so that only a single :class:`Space` instance need be passed to the :class:`Connector` constructor. Details ======= * Switched to using the point process-based AdExp mechanism in NEURON. * Factored out most of the commonality between the :class:`Recorder` classes of each backend into a parent class :class:`recording.Recorder`, and tidied up the :mod:`recording` module. * Added an attribute :attr:`conductance_based` to :class:`StandardCellType`, to make the determination of synapse type for a given cell more robust. * PyNN now uses a named logger, which makes it easier to control logging levels when using PyNN within a larger application. * implemented gather for :meth:`Projection.saveConnections()` * Added a test script (:file:`test_mpi.py`) to check whether serial and distributed simulations give the same results * Added a :meth:`size()` method to :class:`Projection`, to give the total number of connections across all nodes (unlike :meth:`__len__()`, which gives only the connections on the local node * Speeded up :func:`record()` by a huge factor (from 10 s for 12000 cells to less than 0.1 s) by removing an unecessary conditional path (since all IDs now have an attribute "local") * `synapse_type` is now passed to the :class:`ConnectionManager` constructor, not to the :meth:`connect()` method, since (a) it is fixed for a given connection manager, (b) it is needed in other methods than just :meth:`connect()`; fixed weight unit conversion in :mod:`brian` module. * Updated connection handling in :mod:`nest` module to work with NEST version 1.9.8498. Will not now work with previous NEST versions * The :mod:`neuron` back-end now supports having both static and Tsodyks-Markram synapses on the same neuron (previously, the T-M synapses replaced the static synapses) - in agreement with :mod:`nest` and common sense. Thanks to Bartosz Telenczuk for reporting this. * Added a `compatible_output` mode for the :meth:`saveConnections()` method. True by default, it allows connections to be reloaded from a file. If False, then the raw connections are stored, which makes for easier postprocessing. * Added an :class:`ACSource` current source to the :mod:`nest` module. * Fixed Hoc build directory problem in :file:`setup.py` - see ticket:147 * :meth:`Population.get_v()` and the other "`get`" methods now return cell indices (starting from 0) rather than cell IDs. This behaviour now matches that of :meth:`Population.print_v()`, etc. See ticket:119 if you think this is a bad idea. * Moved the base :class:`Connector` class from :mod:`common` to :mod:`connectors`. Put the :meth:`distances` function inside a :class:`Space` class, to allow more convenient specification of topology parameters. * :meth:`Projection.setWeights()` and :meth:`setDelays()` now accept a 2D array argument (ref ticket:136), to be symmetric with :meth:`getWeights()` and :meth:`getDelays()`. For distributed simulations, each node only takes the values it needs from the array. * :class:`FixedProbabilityConnector` is now more strict, and checks that :attr:`p_connect` is less than 1 (see ticket:148). This makes no difference to the behaviour, but could act as a check for errors in user code. * Fixed problem with changing :class:`SpikeSourcePoisson` rate during a simulation (see ticket:152) PyNN-0.10.0/doc/releases/0.7.txt000066400000000000000000000247441415343567000160540ustar00rootroot00000000000000====================== PyNN 0.7 release notes ====================== 4th February 2011 This release sees a major extension of the API with the addition of the :class:`PopulationView` and :class:`Assembly` classes, which aim to make building large, structured networks much simpler and cleaner. A :class:`PopulationView` allows a sub-set of the neurons from a :class:`Population` to be encapsulated in an object. We call it a "view", rather than a "sub-population", to emphasize the fact that the neurons are not copied: they are the same neurons as in the parent :class:`Population`, and any operations on either view or parent (setting parameter values, recording, etc.) will be reflected in the other. An :class:`Assembly` is a list of :class:`Population` and/or :class:`PopulationView` objects, enabling multiple cell types to be encapsulated in a single object. :class:`PopulationView` and :class:`Assembly` objects behave in most ways like :class:`Population`: you can record them, connect them using a :class:`Projection`, you can have views of views... The "low-level API" (rechristened "procedural API") has been reimplemented in in terms of :class:`Population` and :class:`Projection`. For example, :func:`create()` now returns a :class:`Population` object rather than a list of IDs, and :func:`connect()` returns a :class:`Projection` object. This change should be almost invisible to the user, since :class:`Population` now behaves very much like a list of IDs (can be sliced, joined, etc.). There has been a major change to cell addressing: :class:`Population`\s now always store cells in a one-dimensional array, which means cells no longer have an address but just an index. To specify the spatial structure of a :class:`Population`, pass a :class:`Structure` object to the constructor, e.g.:: p = Population((12,10), IF_cond_exp) is now:: p = Population(120, IF_cond_exp, structure=Grid2D(1.2)) although the former syntax still works, for backwards compatibility. The reasons for doing this are: 1. we can now have more interesting structures than just grids 2. efficiency (less juggling addresses, flattening) 3. simplicity (less juggling addresses, less code). The API for setting initial values has changed: this is now done via the :func:`initialize()` function or the :meth:`Population.initialize()` method, rather than by having `v_init` and similar parameters for cell models. Other API changes ================= - simplification of the :meth:`record_X()` methods.With the addition of the :class:`PopulationView` class, the selection logic implemented by the `record_from` and `rng` arguments duplicated that in :meth:`Population.__getitem__()` and :meth:`Population.sample()`, and so these arguments have been removed, and the :meth:`record_X()` methods now record all neurons within a :class:`Population`, :class:`PopulationView` or :class:`Assembly`. Examples of syntax changes:: pop.record_v([pop[0], pop[17]]) --> pop[(0, 17)].record_v() pop.record(10, rng=rng) --> pop.sample(10, rng).record() - enhanced :meth:`describe()` methods: can now use Jinja2 or Cheetah templating engines to produce much nicer, better formatted network descriptions. - connections and neuron positions can now be saved to various binary formats as well as to text files. - added some new connectors: :class:`SmallWorldConnector` and :class:`CSAConnector` (CSA = Connection Set Algebra) - native neuron and synapse models are now supported using a :class:`NativeModelType` subclass, rather than specified as strings. This simplifies the code internally and increases the range of PyNN functionality that can be used with native models (e.g. you can now record any variable from a native NEST or NEURON model). For NEST, there is a class factory :func:`native_cell_type()`, for NEURON the :class:`NativeModelType` subclasses have to be written by hand. Backend changes =============== - the NEST backend has been updated to work with NEST version 2.0.0. - the Brian backend has seen extensive work on performance and on bringing it to feature parity with the other backends. Details ======= * Where :meth:`Population.initial_values` contains arrays, these arrays now consistently contain only enough values for local cells. Before, there was some inconsistency about how this was handled. Still need more tests to be sure it's really working as expected. * Allow override of `default_maxstep` for NEURON backend as setup paramter. This is for the case that the user wants to add network connections across nodes after simulation start time. * Discovered that when using NEST with mpi4py, you must ``import nest`` first and let it do the MPI initialization. The only time this seems to be a problem with PyNN is if a user imports :mod:`pyNN.random` before :mod:`pyNN.nest`. It would be nice to handle this more gracefully, but for now I've just added a test that NEST and mpi4py agree on the rank, and a hopefully useful error message. * Added a new :func:`setup()` option for :mod:`pyNN.nest`: `recording_precision`. By default, `recording_precision` is 3 for on-grid and 15 for off-grid. * Partially fixed the :mod:`pyNN.nest` implementation of :class:`TsodyksMarkramMechanism` (cf ticket:172). The 'tsodyks_synapse' model has a 'tau_psc' parameter, which should be set to the same value as the decay time constant of the post-synaptic current (which is a parameter of the neuron model). I consider this only a partial fix, because if 'tau_syn_E' or 'tau_syn_I' is changed after the creation of the Projection, 'tau_psc' will not be updated to match (unlike in the :mod:`pyNN.neuron` implementation. I'm also not sure how well it will work with native neuron models. * reverted :mod:`pyNN.nest` to reading/resetting the current time from the kernel rather than keeping track of it within PyNN. NEST warns that this is dangerous, but all the tests pass, so let's wait and see. * In :class:`HH_cond_exp`, conductances are now in µS, as for all other conductances in PyNN, instead of nS. * NEURON now supports Tsodyks-Markram synapses for current-based exponential synapses (before it was only for conductance-based). * NEURON backend now supports the :class:`IF_cond_exp_gsfa_grr` model. * Added a :meth:`sample()` method to :class:`Population`, which returns a :class:`PopulationView` of a random sample of the neurons in the parent population. * Added the :class:`EIF_cond_exp/alpha_isfa/ista` and :class:`HH_cond_exp` standard models in Brian. * Added a `gather` option to the :meth:`Population.get()` method. * :func:`brian.setup()` now accepts a number of additional arguments in `extra_params`, For example, ``extra_params={'useweave': True}`` will lead to inline C++ code generation * Wrote a first draft of a developers' guide. * Considerably extended the :class:`core.LazyArray` class, as a basis for a possible rewrite of the `connectors` module. * The :mod:`random` module now uses :mod:`mpi4py` to determine the MPI rank and `num_processes`, rather than receiving these as arguments to the RNG constructor (see ticket:164). * Many fixes and performance enhancements for the :mod:`brian` module, which now supports synaptic plasticity. * No more GSL warning every time! Just raise an Exception if we attempt to use GSLRNG and pygsl is not available. * Added some more flexibility to :func:`init_logging`: ``logfile=None`` -> stderr, format includes size & rank, user can override log-level * NEST :file:`__init__.py` changed to query NEST for filling ``NEST_SYNAPSE_TYPES``. * Started to move synapse dynamics related stuff out of :class:`Projection` and into the synapse dynamics-related classes, where it belongs. * Added a new "spike_precision" option to :func:`nest.setup()` (see http://neuralensemble.org/trac/PyNN/wiki/SimulatorSpecificOptions) * Updated the NEST backend to work with version 2.0.0 * Rewrote the test suite, making a much cleaner distinction between unit tests, which now make heavy use of mock objects to better-isolate components, and system tests. Test suite now runs with nose (https://nose.readthedocs.org/en/latest/), in order to facilitate continuous integration testing. * Changed the format of connection files, as written by :meth:`saveConnections()` and read by :class:`FromFileConnector`: files no longer contain the population label. Connections can now also be written to :class:`NumpyBinaryFile` or :class:`PickleFile` objects, instead of just text files. Same for :meth:`Population.save_positions()`. * Added CSAConnector, which wraps the Connection Set Algebra for use by PyNN. Requires the csa package: https://pypi.python.org/pypi/csa/ * Enhanced distance expressions by allowing expressions such as ``(d[0] < 0.1) & (d[1] < 0.2)``. Complex forms can therefore now be drawn, such as squares, ellipses, and so on. * Added an `n_connections` flag to the :class:`DistanceDependentProbabiblityConnector` in order to be able to constrain the total number of connections. Can be useful for normalizations. * Added a simple :class:`SmallWorldConnector`. Cells are connected within a certain degree *d*. Then, all the connections are rewired with a probability given by a rewiring parameter and new targets are uniformly selected among all the possible targets. * Added a method to save cell positions to file. * Added a progress bar to connectors. Now, a `verbose` flag allows to display or not a progress bar indicating the percentage of connections established. * New implementation of the connector classes, with much improved performance and scaling with MPI, and extension of distance-dependent weights and delays to all connectors. In addition, a `safe` flag has been added to all connectors: on by default, a user can turn it off to avoid tests on weights and delays. * Added the ability to set the `atol` and `rtol` parameters of NEURON's cvode solver in the `extra_params` argument of :func:`setup()` (patch from Johannes Partzsch). * Made :mod:`pyNN.nest`'s handling of the refractory period consistent with the other backends. Made the default refractory period 0.1 ms rather than 0.0 ms, since NEST appears not to handle zero refractory period. * Moved standard model (cells and synapses) machinery, the :class:`Space` class, and :class:`Error` classes out of :mod:`common` into their own modules. Release 0.7.1 ============= .. date? This bug-fix release added copyright statements to all files, together with some minor bug fixes. Release 0.7.2 ============= Release 0.7.3 ============= Release 0.7.4 ============= Release 0.7.5 ============= PyNN-0.10.0/doc/releases/0.8-alpha-1.txt000066400000000000000000000342701415343567000172710ustar00rootroot00000000000000============================== PyNN 0.8 alpha 1 release notes ============================== July 31st 2012 Welcome to the first alpha release of PyNN 0.8! This is the first time there has been an alpha or beta release of PyNN. In the past it hasn't seemed necessary, at first because few people were using PyNN for their research and those that were understood well that PyNN was in an early stage of development, more recently because most of the changes were either extensions to the API or due to internal refactoring. For PyNN 0.8 we have taken the opportunity to make significant, backward-incompatible changes to the API. The aim was fourfold: * to simplify the API, making it more consistent and easier to remember; * to make the API more powerful, so more complex models can be expressed with less code; * to allow a number of internal simplifications so it is easier for new developers to contribute; * to prepare for planned future extensions, notably support for multi-compartmental models. Since there have been so many changes, it seemed prudent to have a number of development releases before the final release of 0.8.0, to get as much testing from users as possible. There may be more alpha releases, and there will be at least one beta release. This alpha release of PyNN is *not* intended for use in your research. If you have existing PyNN scripts, please install PyNN 0.8 alpha separately to your current PyNN installation (for example using virtualenv_) and update your scripts, as outlined below, in a separate branch of your version control repository. If you find a bug, or if PyNN 0.8 alpha gives different results to PyNN 0.7, please let us know using the `bug tracker`_ or on the `mailing list`_. .. include suggested installation, using virtualenv and pip? .. warning:: The first alpha release only supports NEURON and NEST. Support for Brian, PCSIM, NeuroML and MOOSE will be restored/added before the final 0.8.0 release. Creating populations ==================== In previous versions of PyNN, the :class:`Population` constructor was called with the population size, a :class:`BaseCellType` sub-class such as :class:`IF_cond_exp` and a dictionary of parameter values. For example:: p = Population(1000, IF_cond_exp, {'tau_m'=12.0, 'cm': 0.8}) # PyNN 0.7 This dictionary was passed to the cell-type class constructor within the :class:`Population` constructor to create a cell-type instance. The reason for doing this was that in early versions of PyNN, use of native NEST models was supported by passing a string, the model name, as the cell-type argument. Since PyNN 0.7, however, native models have been supported with the :class:`NativeCellType` class, and passing a string is no longer allowed. It makes more sense, therefore, for the cell-type instance to be created by the user, and to pass a cell-type instance, rather than a cell-type class, to the :class:`Population` constructor. There is also a second change: specification of parameters for cell-type classes is now done via keyword arguments rather than a single parameter dictionary. This is for consistency with current sources and synaptic plasticity models, which already use keyword arguments. The example above should be rewritten as:: p = Population(1000, IF_cond_exp(tau_m=12.0, cm=0.8)) # PyNN 0.8 or:: p = Population(1000, IF_cond_exp(**{'tau_m'=12.0, 'cm': 0.8})) # PyNN 0.8 or:: cell_type = IF_cond_exp(tau_m=12.0, cm=0.8) # PyNN 0.8 p = Population(1000, cell_type) The first form, with a separate parameter dictionary, is still supported for the time being, but is deprecated and may be removed in future versions. Specifying heterogeneous parameter values ========================================= In previous versions of PyNN, the :class:`Population` constructor supported setting parameters to either homogeneous values (all cells in the population have the same value) or random values. After construction, it was possible to change parameters using the :meth:`Population.set`, :meth:`Population.tset` (for *topographic* set - parameters were set by using an array of the same size as the population) and :meth:`Population.rset` (for *random* set) methods. In PyNN 0.8, setting parameters is simpler and more consistent, in that both when constructing a cell type for use in the :class:`Population` constructor (see above) and in the :meth:`Population.set` method, parameter values can be any of the following: * a single number - sets the same value for all cells in the :class:`Population`; * a :class:`RandomDistribution` object - for each cell, sets a different random value drawn from the distribution; * a list or 1D NumPy array of the same size as the :class:`Population`; * a function that takes a single integer argument; this function will be called with the index of every cell in the :class:`Population` to return the parameter value for that cell. See :doc:`../parameters` for more details and examples. The call signature of the :meth:`Population.set` method has also been changed; now parameters should be specified as keyword arguments. For example, instead of:: p.set("tau_m": 20.0) # PyNN 0.7 use:: p.set(tau_m=20.0) # PyNN 0.8 and instead of:: p.set({"tau_m": 20.0, "v_rest": -65}) # PyNN 0.7 use:: p.set(tau_m=20.0, v_rest=-65) # PyNN 0.8 Now that :meth:`Population.set` accepts random distributions and arrays as arguments, the :meth:`Population.tset` and :meth:`Population.rset` methods are superfluous. As of version 0.8, their use is deprecated and they will probably be removed in the next version of PyNN. Their use can be replaced as follows:: p.tset("i_offset", arr) # PyNN 0.7 p.set(i_offset=arr) # PyNN 0.8 p.rset("tau_m": rand_distr) # PyNN 0.7 p.set(tau_m=rand_distr) # PyNN 0.8 Setting spike times ------------------- Where a single parameter value is already an array, e.g. spike times, this should be wrapped by a :class:`Sequence` object. For example, to generate a different Poisson spike train for every neuron in a population of :class:`SpikeSourceArray`\s:: def generate_spike_times(i_range): return [Sequence(numpy.add.accumulate(numpy.random.exponential(10.0, size=10))) for i in i_range] p = sim.Population(30, sim.SpikeSourceArray(spike_times=generate_spike_times)) Recording ========= Previous versions of PyNN had three methods for recording from populations of neurons: :meth:`record`, :meth:`record_v` and :meth:`record_gsyn`, for recording spikes, membrane potentials, and synaptic conductances, respectively. There was no official way to record any other state variables, for example the *w* variable from the adaptive-exponential integrate-and-fire model, or when using native, non-standard models, although there were workarounds. In PyNN 0.8, we have replaced these three methods with a single :meth:`record` method, which takes the variable to record as its first argument, e.g.:: p.record() # PyNN 0.7 p.record_v() p.record_gsyn() becomes:: p.record('spikes') # PyNN 0.8 p.record('v') p.record(['gsyn_exc', 'gsyn_inh']) Note that (1) you can now choose to record the excitatory and inhibitory synaptic conductances separately, (2) you can give a list of variables to record, so, for example, you can record all the variables for the :class:`EIF_cond_exp_isfa_ista` model in a single command using:: p.record(['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh']) # PyNN 0.8 Note that the :meth:`record_v` and :meth:`record_gsyn` methods still exist, but their use is deprecated, and they will be removed in the next version of PyNN. See :doc:`../recording` for more details. Retrieving recorded data ======================== Perhaps the biggest change in PyNN 0.8 is that handling of recorded data, whether retrieval as Python objects or saving to file, now uses the Neo_ package, which provides a common Python object model for neurophysiology data (whether real or simulated). Using Neo provides several advantages: * data objects contain essential metadata, such as units, sampling interval, etc.; * data can be saved to any of the file formats supported by Neo, including HDF5 and Matlab files; * it is easier to handle data when running multiple simulations with the same network (calling :meth:`reset` between each one); * it is possible to save multiple signals to a single file; * better interoperability with other Python packages using Neo (for data analysis, etc.). Note that Neo is based on NumPy, and most Neo data objects sub-class the NumPy :class:`ndarray` class, so much of your data handling code should work exactly the same as before. See :doc:`../data_handling` for more details. Creating connections ==================== In previous versions of PyNN, synaptic weights and delays were specified on creation of the :class:`Connector` object. If the synaptic weight had its own dynamics (whether short-term or spike-timing-dependent plasticity), the parameters for this were specified on creation of a :class:`SynapseDynamics` object. In other words, specification of synaptic parameters was split across two different classes. :class:`SynapseDynamics` was also rather complex, and could have both a "fast" (for short-term synaptic depression and facilitation) and "slow" (for long-term plasticity) component, although most simulator backends did not support specifying both fast and slow components at the same time. In PyNN 0.8, all synaptic parameters including weights and delays are given as arguments to a :class:`SynapseType` sub-class such as :class:`StaticSynapse` or :class:`TsodyksMarkramSynapse`. For example, instead of:: prj = Projection(p1, p2, AllToAllConnector(weights=0.05, delays=0.5)) # PyNN 0.7 you should now write:: prj = Projection(p1, p2, AllToAllConnector(), StaticSynapse(weight=0.05, delay=0.5)) # PyNN 0.8 and instead of:: params = {'U': 0.04, 'tau_rec': 100.0, 'tau_facil': 1000.0} facilitating = SynapseDynamics(fast=TsodyksMarkramMechanism(**params)) # PyNN 0.7 prj = Projection(p1, p2, FixedProbabilityConnector(p_connect=0.1, weights=0.01), synapse_dynamics=facilitating) the following:: params = {'U': 0.04, 'tau_rec': 100.0, 'tau_facil': 1000.0, 'weight': 0.01} facilitating = TsodyksMarkramSynapse(**params)) # PyNN 0.8 prj = Projection(p1, p2, FixedProbabilityConnector(p_connect=0.1), synapse_type=facilitating) Note that *"weights"* and *"delays"* are now *"weight"* and *"delay"*. In addition, the *"method"* argument to :class:`Projection` is now called *"connector"*, and the *"target"* argument is now *"receptor_type"*. The *"rng"* argument has been moved from :class:`Projection` to :class:`Connector`, and the *"space"* argument of :class:`Connector` has been moved to :class:`Projection`. The ability to specify both short-term and long-term plasticity for a given connection type, in a simulator-independent way, has been removed, although in practice only the NEURON backend supported this. This functionality will be reintroduced in PyNN 0.9. If you need this in the meantime, a workaround for the NEURON backend is to use a :class:`NativeSynapseType` mechanism - ask on the `mailing list`_ for guidance. Specifying heterogeneous synapse parameters =========================================== As for neuron parameters, synapse parameter values can now be any of the following: * a single number - sets the same value for all connections in the :class:`Projection`; * a :class:`RandomDistribution` object - for each connection, sets a different random value drawn from the distribution; * a list or 1D NumPy array of the same size as the :class:`Projection` (although this is not very useful for random networks, whose size may not be known in advance); * a function that takes a single float argument; this function will be called with the *distance* between the pre- and post-synaptic cell to return the parameter value for that cell. Accessing, setting and saving properties of synaptic connections ================================================================ In older versions of PyNN, the :class:`Projection` class had a bunch of methods for working with synaptic parameters: :meth:`getWeights`, :meth:`setWeights`, :meth:`randomizeWeights`, :meth:`printWeights`, :meth:`getDelays`, :meth:`setDelays`, :meth:`randomizeDelays`, :meth:`printDelays`, :meth:`getSynapseDynamics`, :meth:`setSynapseDynamics`, :meth:`randomizeSynapseDynamics`, and :meth:`saveConnections`. These have been replace by three methods: :meth:`get`, :meth:`set` and :meth:`save`. The original methods still exist, but their use is deprecated and they will be removed in the next version of PyNN. You should update your code as follows:: prj.getWeights(format='list') # PyNN 0.7 prj.get('weight', format='list', with_address=False) # PyNN 0.8 prj.randomizeDelays(rand_distr) # PyNN 0.7 prj.set(delay=rand_distr) # PyNN 0.8 prj.setSynapseDynamics('tau_rec', 50.0) # PyNN 0.7 prj.set(tau_rec=50.0) # PyNN 0.8 prj.printWeights('exc_weights.txt', format='array') # PyNN 0.7 prj.save('weight', 'exc_weights.txt', format='array') # PyNN 0.8 prj.saveConnections('exc_conn.txt') # PyNN 0.7 prj.save('all', 'exc_conn.txt', format='list') # PyNN 0.8 Also note that all three new methods can operate on several parameters at a time:: weights, delays = prj.getWeights('array'), prj.getDelays('array') # PyNN 0.7 weights, delays = prj.get(['weight', 'delay'], format='array') # PyNN 0.8 prj.randomizeWeights(rand_distr); prj.setDelays(0.4) # PyNN 0.7 prj.set(weight=rand_distr, delay=0.4) # PyNN 0.8 Python compatibility ==================== With an eye towards future support for Python 3, we have decided to drop support for Python versions 2.5 and earlier in PyNN 0.8. .. _virtualenv: http://www.virtualenv.org/ .. _`bug tracker`: https://github.com/NeuralEnsemble/PyNN/issues/ .. _`mailing list`: http://groups.google.com/group/neuralensemble .. _Neo: http://neuralensemble.org/neoPyNN-0.10.0/doc/releases/0.8-alpha-2.txt000066400000000000000000000013261415343567000172660ustar00rootroot00000000000000============================== PyNN 0.8 alpha 2 release notes ============================== May 24th 2013 Welcome to the second alpha release of PyNN 0.8! For full information about what's new in PyNN 0.8, see the :doc:`0.8-alpha-1`. This second alpha is mostly just a bug-fix release, although we have added a new class, :class:`CloneConnector` (thanks to Tom Close), which takes the connection matrix from an existing :class:`Projection` and uses it to create a new :class:`Projection`, with the option of changing the weights, delays, receptor type, etc. The other big change for developers is that we have switched from Subversion to Git. PyNN development now takes place at https://github.com/NeuralEnsemble/PyNN/ PyNN-0.10.0/doc/releases/0.8-beta-1.txt000066400000000000000000000121751415343567000171170ustar00rootroot00000000000000============================== PyNN 0.8 beta 1 release notes ============================== November 15th 2013 Welcome to the first beta release of PyNN 0.8! For full information about what's new in PyNN 0.8, see the :doc:`0.8-alpha-1`. Brian backend ------------- The main new feature in this beta release is the reappearance of the Brian_ backend, updated to work with the 0.8 API. You will need version 1.4 of Brian. There are still some rough edges, but we encourage anyone who has used Brian with PyNN 0.7 to try updating your scripts now, and give it a try. New and improved connectors --------------------------- The library of :class:`Connector` classes has been extended. The :class:`DistanceDependentProbabilityConnector` (DDPC) has been generalized, resulting in the :class:`IndexBasedProbabilityConnector`, with which the connection probability can be specified as any function of the indices *i* and *j* of the pre- and post-synaptic neurons within their populations. In addition, the distance expression for the DDPC can now be a callable object (such as a function) as well as a string expression. The :class:`ArrayConnector` allows connections to be specified as an explicit boolean matrix, with shape (*m*, *n*) where *m* is the size of the presynaptic population and *n* that of the postsynaptic population. The :class:`CSAConnector` has been updated to work with the 0.8 API. The :class:`FromListConnector` and :class:`FromFileConnector` now support specifying any synaptic parameter (e.g. parameters of the synaptic plasticity rule), not just weight and delay. API changes ----------- The :func:`set()` function now matches the :meth:`Population.set()` method, i.e. it takes one or more parameter name/value pairs as keyword arguments. Two new functions for advancing a simulation have been added: :func:`run_for()` and :func:`run_until()`. :func:`run_for()` is just an alias for :func:`run()`. :func:`run_until()` allows you to specify the absolute time at which a simulation should stop, rather than the increment of time. In addition, it is now possible to specify a call-back function that should be called at intervals during a run, e.g.:: >>> def report_time(t): ... print("The time is %g" % t) ... return t + 100.0 >>> run_until(300.0, callbacks=[report_time]) The time is 0 The time is 100 The time is 200 The time is 300 One potential use of this feature is to record synaptic weights during a simulation with synaptic plasticity. We have changed the parameterization of STDP models. The `A_plus` and `A_minus` parameters have been moved from the weight-dependence components to the timing-dependence components, since effectively they describe the shape of the STDP curve independently of how the weight change depends on the current weight. Simple plotting --------------- We have added a small library to make it simple to produce simple plots of data recorded from a PyNN simulation. This is not intended for publication-quality or highly-customized plots, but for basic visualization. For example:: from pyNN.utility.plotting import Figure, Panel ... population.record('spikes') population[0:2].record(('v', 'gsyn_exc')) ... data = population.get_data().segments[0] vm = data.filter(name="v")[0] gsyn = data.filter(name="gsyn_exc")[0] Figure( Panel(vm, ylabel="Membrane potential (mV)"), Panel(gsyn, ylabel="Synaptic conductance (uS)"), Panel(data.spiketrains, xlabel="Time (ms)", xticks=True) ).save("simulation_results.png") .. image:: ../images/release_0.8b1_example.png :width: 600px :align: center :alt: Image generated using the Figure and Panel classes from pyNN.utility.plotting Gap junctions ------------- The NEURON backend now supports gap junctions. This is not yet an official part of the PyNN API, since any official feature must be supported by at least two backends, but could be very useful to modellers using NEURON. Other changes ------------- The default precision for the NEST_ backend has been changed to "off_grid". This reflects the PyNN philosophy that defaults should prioritize accuracy and compatibility over performance. (We think performance is very important, it's just that any decision to risk compromising accurary or interoperability should be made deliberately by the end user.) The Izhikevich neuron model is now available for the NEURON, NEST and Brian backends, although there are still some problems with injecting current when using the NEURON backend. A whole bunch of bugs have been fixed: see the `issue tracker`_. For developers -------------- We are now taking advantage of the integration of GitHub with TravisCI_, to automatically run the suite of unit tests whenever changes are pushed to GitHub. Note that this does not run the system tests or any other tests that require installation of a simulator backend. .. _Brian: http://http://briansimulator.org .. _NEST: http://www.nest-initiative.org/ .. _TravisCI: https://travis-ci.org/NeuralEnsemble/PyNN .. _`issue tracker`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+closed%3A2013-05-24..2013-11-15+milestone%3A0.8.0PyNN-0.10.0/doc/releases/0.8-beta-2.txt000066400000000000000000000076171415343567000171250ustar00rootroot00000000000000============================= PyNN 0.8 beta 2 release notes ============================= January 6th 2015 Welcome to the second beta release of PyNN 0.8! For full information about what's new in PyNN 0.8, see the :doc:`0.8-alpha-1` and :doc:`0.8-beta-1`. NEST 2.4 -------- The main new feature in this release is support for NEST_ 2.4. Previous versions of NEST are no longer supported. Python 3 -------- With the rewrite of PyNEST in NEST 2.4, PyNN now has two backend simulators (NEURON_ being the other) that support Python 3. There was really no longer any excuse not to add Python 3 support to PyNN, which turned out to be very straightforward. Standardization of random distributions --------------------------------------- Since its earliest versions PyNN has supported swapping in and out different random number generators, but until now there has been no standardization of these RNGs; for example the GSL_ random number library uses "gaussian" where NumPy_ uses "normal". This limited the usefulness of this feature, especially for the :class:`NativeRNG` class, which signals that random number generation should be passed down to the simulator backend rather than being done at the Python level. This has now, finally, been fixed. The names of random number distributions and of their parameters have now been standardized, based for the most part on the nomenclature used by Wikipedia_. A quick example: .. code-block:: python from pyNN.random import NumpyRNG, GSLRNG, RandomDistribution rd1 = RandomDistribution('normal' mu=0.5, sigma=0.1, rng=NumpyRNG(seed=922843)) rd2 = RandomDistribution('normal' mu=0.5, sigma=0.1, rng=GSLRNG(seed=426482)) API changes ----------- * :meth:`Population.record()` now has an optional `sampling_interval` argument, allowing recording at intervals larger than the integration time step. * :class:`FixedNumberPostConnector` now has an option `with_replacement`, which controls how the post-synaptic population is sampled, and affects the incidence of multiple connections between pairs of neurons ("multapses"). * The default value of the `min_delay` argument to :func:`setup()` is now "auto", which means that the simulator should calculate the minimal synaptic delay itself. This change can lead to large speedups for NEST and NEURON code. Other changes ------------- * Reimplemented Izhikevich model for NEURON to allow current injection. * :class:`Connector`\s that can generate multiple connections between a given pair of neurons ("multapses") now work properly with the pyNN.nest backend. * Added a version of :class:`CSAConnector` for the NEST backend that passes down the CSA object to PyNEST's :func:`CGConnect()` function. This greatly speeds up :class:`CSAConnector` with NEST. * Added some new example scripts, deleted some of the more trivial, repetitive examples, and merged the several variants of the "VAbenchmarks" example into a single script. * When data blocks from different MPI nodes are merged, the spike trains are now by default sorted by neuron ID. If this sorting proves to be too time-consuming we can in future expose sort/don't sort as an option. * Added :class:`IF_cond_exp_gsfa_grr` standard model (integrate and fire neuron with spike frequency adaption and relative refractory period) to Brian_ backend, and fixed broken :class:`HH_cond_exp` model. * Improvements to callback handling. * `Assorted bug fixes`_ .. _Wikipedia: http://en.wikipedia.org/wiki/List_of_probability_distributions .. _GSL: https://www.gnu.org/software/gsl/manual/html_node/Random-Number-Generation.html .. _NumPy: http://docs.scipy.org/doc/numpy/reference/routines.random.html .. _Brian: http://http://briansimulator.org .. _NEST: http://www.nest-initiative.org/ .. _NEURON: http://www.neuron.yale.edu/neuron/ .. _`Assorted bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+closed%3A2013-11-15..2015-01-06+milestone%3A0.8.0PyNN-0.10.0/doc/releases/0.8.0-rc-1.txt000066400000000000000000000013651415343567000167450ustar00rootroot00000000000000============================================ PyNN 0.8.0 release candidate 1 release notes ============================================ August 19th 2015 Welcome to the first release candidate of PyNN 0.8.0! For full information about what's new in PyNN 0.8, see the :doc:`0.8-alpha-1`, :doc:`0.8-beta-1` and :doc:`0.8-beta-2` NEST 2.6 -------- The main new feature in this release is support for NEST_ 2.6. Previous versions of NEST are no longer supported. Other changes ------------- * Travis CI now runs system tests as well as unit tests. * `Assorted bug fixes`_ .. _NEST: http://www.nest-initiative.org/ .. _`Assorted bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+closed%3A2015-01-07..2015-08-19+milestone%3A0.8.0PyNN-0.10.0/doc/releases/0.8.0.txt000066400000000000000000000472311415343567000162070ustar00rootroot00000000000000======================== PyNN 0.8.0 release notes ======================== October 5th 2015 Welcome to the final release of PyNN 0.8.0! For PyNN 0.8 we have taken the opportunity to make significant, backward-incompatible changes to the API. The aim was fourfold: * to simplify the API, making it more consistent and easier to remember; * to make the API more powerful, so more complex models can be expressed with less code; * to allow a number of internal simplifications so it is easier for new developers to contribute; * to prepare for planned future extensions, notably support for multi-compartmental models. We summarize here the main changes between versions 0.7 and 0.8 of the API. Creating populations ==================== In previous versions of PyNN, the :class:`Population` constructor was called with the population size, a :class:`BaseCellType` sub-class such as :class:`IF_cond_exp` and a dictionary of parameter values. For example:: p = Population(1000, IF_cond_exp, {'tau_m': 12.0, 'cm': 0.8}) # PyNN 0.7 This dictionary was passed to the cell-type class constructor within the :class:`Population` constructor to create a cell-type instance. The reason for doing this was that in early versions of PyNN, use of native NEST models was supported by passing a string, the model name, as the cell-type argument. Since PyNN 0.7, however, native models have been supported with the :class:`NativeCellType` class, and passing a string is no longer allowed. It makes more sense, therefore, for the cell-type instance to be created by the user, and to pass a cell-type instance, rather than a cell-type class, to the :class:`Population` constructor. There is also a second change: specification of parameters for cell-type classes is now done via keyword arguments rather than a single parameter dictionary. This is for consistency with current sources and synaptic plasticity models, which already use keyword arguments. The example above should be rewritten as:: p = Population(1000, IF_cond_exp(tau_m=12.0, cm=0.8)) # PyNN 0.8 or:: p = Population(1000, IF_cond_exp(**{'tau_m': 12.0, 'cm': 0.8})) # PyNN 0.8 or:: cell_type = IF_cond_exp(tau_m=12.0, cm=0.8) # PyNN 0.8 p = Population(1000, cell_type) The first form, with a separate parameter dictionary, is still supported for the time being, but is deprecated and may be removed in future versions. Specifying heterogeneous parameter values ========================================= In previous versions of PyNN, the :class:`Population` constructor supported setting parameters to either homogeneous values (all cells in the population have the same value) or random values. After construction, it was possible to change parameters using the :meth:`Population.set`, :meth:`Population.tset` (for *topographic* set - parameters were set by using an array of the same size as the population) and :meth:`Population.rset` (for *random* set) methods. In PyNN 0.8, setting parameters is simpler and more consistent, in that both when constructing a cell type for use in the :class:`Population` constructor (see above) and in the :meth:`Population.set` method, parameter values can be any of the following: * a single number - sets the same value for all cells in the :class:`Population`; * a :class:`RandomDistribution` object - for each cell, sets a different random value drawn from the distribution; * a list or 1D NumPy array of the same size as the :class:`Population`; * a function that takes a single integer argument; this function will be called with the index of every cell in the :class:`Population` to return the parameter value for that cell. See :doc:`../parameters` for more details and examples. The call signature of the :meth:`Population.set` method has also been changed; now parameters should be specified as keyword arguments. For example, instead of:: p.set({"tau_m": 20.0}) # PyNN 0.7 use:: p.set(tau_m=20.0) # PyNN 0.8 and instead of:: p.set({"tau_m": 20.0, "v_rest": -65}) # PyNN 0.7 use:: p.set(tau_m=20.0, v_rest=-65) # PyNN 0.8 Now that :meth:`Population.set` accepts random distributions and arrays as arguments, the :meth:`Population.tset` and :meth:`Population.rset` methods are superfluous. As of version 0.8, their use is deprecated and they will be removed in the next version of PyNN. Their use can be replaced as follows:: p.tset("i_offset", arr) # PyNN 0.7 p.set(i_offset=arr) # PyNN 0.8 p.rset("tau_m", rand_distr) # PyNN 0.7 p.set(tau_m=rand_distr) # PyNN 0.8 Setting spike times ------------------- Where a single parameter value is already an array, e.g. spike times, this should be wrapped by a :class:`Sequence` object. For example, to generate a different Poisson spike train for every neuron in a population of :class:`SpikeSourceArray`\s:: def generate_spike_times(i_range): return [Sequence(numpy.add.accumulate(numpy.random.exponential(10.0, size=10))) for i in i_range] p = sim.Population(30, sim.SpikeSourceArray(spike_times=generate_spike_times)) Standardization of random distributions --------------------------------------- Since its earliest versions PyNN has supported swapping in and out different random number generators, but until now there has been no standardization of these RNGs; for example the GSL_ random number library uses "gaussian" where NumPy_ uses "normal". This limited the usefulness of this feature, especially for the :class:`NativeRNG` class, which signals that random number generation should be passed down to the simulator backend rather than being performed at the Python level. This has now been fixed. The names of random number distributions and of their parameters have now been standardized, based for the most part on the nomenclature used by Wikipedia_. A quick example: .. code-block:: python from pyNN.random import NumpyRNG, GSLRNG, RandomDistribution rd1 = RandomDistribution('normal' mu=0.5, sigma=0.1, rng=NumpyRNG(seed=922843)) rd2 = RandomDistribution('normal' mu=0.5, sigma=0.1, rng=GSLRNG(seed=426482)) Recording ========= Previous versions of PyNN had three methods for recording from populations of neurons: :meth:`record`, :meth:`record_v` and :meth:`record_gsyn`, for recording spikes, membrane potentials, and synaptic conductances, respectively. There was no official way to record any other state variables, for example the *w* variable from the adaptive-exponential integrate-and-fire model, or when using native, non-standard models, although there were workarounds. In PyNN 0.8, we have replaced these three methods with a single :meth:`record` method, which takes the variable to record as its first argument, e.g.:: p.record() # PyNN 0.7 p.record_v() p.record_gsyn() becomes:: p.record('spikes') # PyNN 0.8 p.record('v') p.record(['gsyn_exc', 'gsyn_inh']) Note that (1) you can now choose to record the excitatory and inhibitory synaptic conductances separately, (2) you can give a list of variables to record. For example, you can record all the variables for the :class:`EIF_cond_exp_isfa_ista` model in a single command using:: p.record(['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh']) # PyNN 0.8 Note that the :meth:`record_v` and :meth:`record_gsyn` methods still exist, but their use is deprecated, and they will be removed in the next version of PyNN. A further change is that :meth:`Population.record()` has an optional `sampling_interval` argument, allowing recording at intervals larger than the integration time step. See :doc:`../recording` for more details. Retrieving recorded data ======================== Perhaps the biggest change in PyNN 0.8 is that handling of recorded data, whether retrieval as Python objects or saving to file, now uses the Neo_ package, which provides a common Python object model for neurophysiology data (whether real or simulated). Using Neo provides several advantages: * data objects contain essential metadata, such as units, sampling interval, etc.; * data can be saved to any of the file formats supported by Neo, including HDF5 and Matlab files; * it is easier to handle data when running multiple simulations with the same network (calling :meth:`reset` between each one); * it is possible to save multiple signals to a single file; * better interoperability with other Python packages using Neo (for data analysis, etc.). Note that Neo is based on NumPy, and most Neo data objects sub-class the NumPy :class:`ndarray` class, so much of your data handling code should work exactly the same as before. See :doc:`../data_handling` for more details. Creating connections ==================== In previous versions of PyNN, synaptic weights and delays were specified on creation of the :class:`Connector` object. If the synaptic weight had its own dynamics (whether short-term or spike-timing-dependent plasticity), the parameters for this were specified on creation of a :class:`SynapseDynamics` object. In other words, specification of synaptic parameters was split across two different classes. :class:`SynapseDynamics` was also rather complex, and could have both a "fast" (for short-term synaptic depression and facilitation) and "slow" (for long-term plasticity) component, although most simulator backends did not support specifying both fast and slow components at the same time. In PyNN 0.8, all synaptic parameters including weights and delays are given as arguments to a :class:`SynapseType` sub-class such as :class:`StaticSynapse` or :class:`TsodyksMarkramSynapse`. For example, instead of:: prj = Projection(p1, p2, AllToAllConnector(weights=0.05, delays=0.5)) # PyNN 0.7 you should now write:: prj = Projection(p1, p2, AllToAllConnector(), StaticSynapse(weight=0.05, delay=0.5)) # PyNN 0.8 and instead of:: params = {'U': 0.04, 'tau_rec': 100.0, 'tau_facil': 1000.0} facilitating = SynapseDynamics(fast=TsodyksMarkramMechanism(**params)) # PyNN 0.7 prj = Projection(p1, p2, FixedProbabilityConnector(p_connect=0.1, weights=0.01), synapse_dynamics=facilitating) the following:: params = {'U': 0.04, 'tau_rec': 100.0, 'tau_facil': 1000.0, 'weight': 0.01} facilitating = TsodyksMarkramSynapse(**params) # PyNN 0.8 prj = Projection(p1, p2, FixedProbabilityConnector(p_connect=0.1), synapse_type=facilitating) Note that *"weights"* and *"delays"* are now *"weight"* and *"delay"*. In addition, the *"method"* argument to :class:`Projection` is now called *"connector"*, and the *"target"* argument is now *"receptor_type"*. The *"rng"* argument has been moved from :class:`Projection` to :class:`Connector`, and the *"space"* argument of :class:`Connector` has been moved to :class:`Projection`. The ability to specify both short-term and long-term plasticity for a given connection type, in a simulator-independent way, has been removed, although in practice only the NEURON backend supported this. This functionality will be reintroduced in PyNN 0.9. If you need this in the meantime, a workaround for the NEURON backend is to use a :class:`NativeSynapseType` mechanism - ask on the `mailing list`_ for guidance. Finally, the parameterization of STDP models has been modified. The `A_plus` and `A_minus` parameters have been moved from the weight-dependence components to the timing-dependence components, since effectively they describe the shape of the STDP curve independently of how the weight change depends on the current weight. Specifying heterogeneous synapse parameters =========================================== As for neuron parameters, synapse parameter values can now be any of the following: * a single number - sets the same value for all connections in the :class:`Projection`; * a :class:`RandomDistribution` object - for each connection, sets a different random value drawn from the distribution; * a list or 1D NumPy array of the same size as the :class:`Projection` (although this is not very useful for random networks, whose size may not be known in advance); * a function that takes a single float argument; this function will be called with the *distance* between the pre- and post-synaptic cell to return the parameter value for that cell. Accessing, setting and saving properties of synaptic connections ================================================================ In older versions of PyNN, the :class:`Projection` class had a bunch of methods for working with synaptic parameters: :meth:`getWeights`, :meth:`setWeights`, :meth:`randomizeWeights`, :meth:`printWeights`, :meth:`getDelays`, :meth:`setDelays`, :meth:`randomizeDelays`, :meth:`printDelays`, :meth:`getSynapseDynamics`, :meth:`setSynapseDynamics`, :meth:`randomizeSynapseDynamics`, and :meth:`saveConnections`. These have been replace by three methods: :meth:`get`, :meth:`set` and :meth:`save`. The original methods still exist, but their use is deprecated and they will be removed in the next version of PyNN. You should update your code as follows:: prj.getWeights(format='list') # PyNN 0.7 prj.get('weight', format='list', with_address=False) # PyNN 0.8 prj.randomizeDelays(rand_distr) # PyNN 0.7 prj.set(delay=rand_distr) # PyNN 0.8 prj.setSynapseDynamics('tau_rec', 50.0) # PyNN 0.7 prj.set(tau_rec=50.0) # PyNN 0.8 prj.printWeights('exc_weights.txt', format='array') # PyNN 0.7 prj.save('weight', 'exc_weights.txt', format='array') # PyNN 0.8 prj.saveConnections('exc_conn.txt') # PyNN 0.7 prj.save('all', 'exc_conn.txt', format='list') # PyNN 0.8 Also note that all three new methods can operate on several parameters at a time:: weights, delays = prj.getWeights('array'), prj.getDelays('array') # PyNN 0.7 weights, delays = prj.get(['weight', 'delay'], format='array') # PyNN 0.8 prj.randomizeWeights(rand_distr); prj.setDelays(0.4) # PyNN 0.7 prj.set(weight=rand_distr, delay=0.4) # PyNN 0.8 New and improved connectors =========================== The library of :class:`Connector` classes has been extended. The :class:`DistanceDependentProbabilityConnector` (DDPC) has been generalized, resulting in the :class:`IndexBasedProbabilityConnector`, with which the connection probability can be specified as any function of the indices *i* and *j* of the pre- and post-synaptic neurons within their populations. In addition, the distance expression for the DDPC can now be a callable object (such as a function) as well as a string expression. The :class:`ArrayConnector` allows connections to be specified as an explicit boolean matrix, with shape (*m*, *n*) where *m* is the size of the presynaptic population and *n* that of the postsynaptic population. The :class:`CloneConnector` takes the connection matrix from an existing :class:`Projection` and uses it to create a new :class:`Projection`, with the option of changing the weights, delays, receptor type, etc. The :class:`FromListConnector` and :class:`FromFileConnector` now support specifying any synaptic parameter (e.g. parameters of the synaptic plasticity rule), not just weight and delay. The :class:`FixedNumberPostConnector` now has an option `with_replacement`, which controls how the post-synaptic population is sampled, and affects the incidence of multiple connections between pairs of neurons ("multapses"). We have added a version of :class:`CSAConnector` for the NEST backend that passes down the CSA object to PyNEST's :func:`CGConnect()` function. This greatly speeds up :class:`CSAConnector` with NEST. Simulation control ================== Two new functions for advancing a simulation have been added: :func:`run_for()` and :func:`run_until()`. :func:`run_for()` is just an alias for :func:`run()`. :func:`run_until()` allows you to specify the absolute time at which a simulation should stop, rather than the increment of time. In addition, it is now possible to specify a call-back function that should be called at intervals during a run, e.g.:: >>> def report_time(t): ... print("The time is %g" % t) ... return t + 100.0 >>> run_until(300.0, callbacks=[report_time]) The time is 0 The time is 100 The time is 200 The time is 300 One potential use of this feature is to record synaptic weights during a simulation with synaptic plasticity. The default value of the `min_delay` argument to :func:`setup()` is now "auto", which means that the simulator should calculate the minimal synaptic delay itself. This change can lead to large speedups for NEST and NEURON code. Simple plotting =============== We have added a small library to make it simple to produce simple plots of data recorded from a PyNN simulation. This is not intended for publication-quality or highly-customized plots, but for basic visualization. For example:: from pyNN.utility.plotting import Figure, Panel ... population.record('spikes') population[0:2].record(('v', 'gsyn_exc')) ... data = population.get_data().segments[0] vm = data.filter(name="v")[0] gsyn = data.filter(name="gsyn_exc")[0] Figure( Panel(vm, ylabel="Membrane potential (mV)"), Panel(gsyn, ylabel="Synaptic conductance (uS)"), Panel(data.spiketrains, xlabel="Time (ms)", xticks=True) ).save("simulation_results.png") .. image:: ../images/release_0.8b1_example.png :width: 600px :align: center :alt: Image generated using the Figure and Panel classes from pyNN.utility.plotting Supported backends ================== PyNN 0.8.0 is compatible with NEST versions 2.6 to 2.8, NEURON versions 7.3 to 7.4, and Brian 1.4. Support for Brian 2 is planned for a future release. Support for the PCSIM simulator has been dropped since the simulator appears to be no longer actively developed. The default precision for the NEST_ backend has been changed to "off_grid". This reflects the PyNN philosophy that defaults should prioritize accuracy and compatibility over performance. (We think performance is very important, it's just that any decision to risk compromising accuracy or interoperability should be made deliberately by the end user.) The Izhikevich neuron model is now available for all backends. Python compatibility ==================== Support for Python 3 has been added (versions 3.3+). Support for Python versions 2.5 and earlier has been dropped. Changes for developers ====================== Other than the internal refactoring, the main change for developers is that we have switched from Subversion to Git. PyNN development now takes place at https://github.com/NeuralEnsemble/PyNN/ We are now taking advantage of the integration of GitHub with TravisCI_, to automatically run the test suite whenever changes are pushed to GitHub. .. _virtualenv: http://www.virtualenv.org/ .. _`bug tracker`: https://github.com/NeuralEnsemble/PyNN/issues/ .. _`mailing list`: http://groups.google.com/group/neuralensemble .. _Neo: http://neuralensemble.org/neo .. _Brian: http://http://briansimulator.org .. _NEST: http://www.nest-initiative.org/ .. _TravisCI: https://travis-ci.org/NeuralEnsemble/PyNN .. _`issue tracker`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+closed%3A2013-05-24..2013-11-15+milestone%3A0.8.0 .. _Wikipedia: http://en.wikipedia.org/wiki/List_of_probability_distributions .. _GSL: https://www.gnu.org/software/gsl/manual/html_node/Random-Number-Generation.html .. _NumPy: http://docs.scipy.org/doc/numpy/reference/routines.random.html .. _Brian: http://http://briansimulator.org .. _NEST: http://www.nest-initiative.org/ .. _NEURON: http://www.neuron.yale.edu/neuron/ .. _`Assorted bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+milestone%3A0.8.0 PyNN-0.10.0/doc/releases/0.8.1.txt000066400000000000000000000006671415343567000162120ustar00rootroot00000000000000======================== PyNN 0.8.1 release notes ======================== 25th May 2016 Welcome to PyNN 0.8.1! NEST 2.10 --------- This release introduces support for NEST_ 2.10. Previous versions of NEST are no longer supported. Other changes ------------- * `Assorted bug fixes`_ .. _NEST: http://www.nest-simulator.org/ .. _`Assorted bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+milestone%3A0.8.1PyNN-0.10.0/doc/releases/0.8.2.txt000066400000000000000000000025121415343567000162020ustar00rootroot00000000000000======================== PyNN 0.8.2 release notes ======================== 6th December 2016 Welcome to PyNN 0.8.2! New spike sources ----------------- Two new spike source models were added, with implementations for the NEST and NEURON backends: :class:`SpikeSourceGamma` (spikes follow a gamma process) and :class:`SpikeSourcePoissonRefractory` (inter-spike intervals are drawn from an exponential distribution as for a Poisson process, but there is a fixed refractory period after each spike during which no spike can occur). Other changes ------------- * Changed the :func:`save_positions()` format from ``id x y z`` to ``index x y z`` to make it simulator independent. * Added histograms to the :mod:`utility.plotting` module. * Added a `multiple_synapses` flag to :func:`Projection.get(..., format="array")` to control how synaptic parameters are combined when there are multiple connections between pairs of neurons. Until now, parameters were summed, which made sense for weights but not for delays. We have adopted the Brian_ approach of adding an argument ``multiple_synapses`` which is one of ``{'last', 'first', 'sum', 'min', 'max'}``. The default is ``sum``. * `Assorted bug fixes`_ .. _Brian: http://briansimulator.org .. _`Assorted bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+milestone%3A0.8.2PyNN-0.10.0/doc/releases/0.8.3.txt000066400000000000000000000014021415343567000162000ustar00rootroot00000000000000======================== PyNN 0.8.3 release notes ======================== 8th March 2017 Welcome to PyNN 0.8.3! NeuroML 2 --------- The :mod:`neuroml` module has been completely rewritten, and updated from NeuroML v1 to v2. This module works like other PyNN "backends", i.e. ``import pyNN.neuroml as sim``... but instead of running a simulation, it exports the network to an XML file in NeuroML format. NEST 2.12 --------- This release introduces support for NEST_ 2.12. Previous versions of NEST are no longer supported. Other changes ------------- * `A couple of bug fixes`_ .. _Brian: http://briansimulator.org .. _NEST: http://nest-simulator.org .. _`A couple of bug fixes`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aclosed+milestone%3A0.8.3PyNN-0.10.0/doc/releases/0.9.0.txt000066400000000000000000000016231415343567000162030ustar00rootroot00000000000000======================== PyNN 0.9.0 release notes ======================== April 12th 2017 Welcome to PyNN 0.9.0! This version of PyNN adopts the new, simplified Neo_ object model, first released as Neo 0.5.0, for the data structures returned by :class:`Population.get_data()`. For more information on the new Neo API, see the `release notes`_. The main difference for a PyNN user is that the :class:`AnalogSignalArray` class has been renamed to :class:`AnalogSignal`, and similarly the :attr:`Segment.analogsignalarrays` attribute is now called :attr:`Segment.analogsignals`. In addition, a `number of bugs`_ with current injection for the :mod:`pyNN.brian` module have been fixed. .. _Neo: http://neuralensemble.org/neo .. _`release notes`: http://neo.readthedocs.io/en/0.5.0/releases/0.5.0.html .. _`number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.1.txt000066400000000000000000000022411415343567000162010ustar00rootroot00000000000000======================== PyNN 0.9.1 release notes ======================== May 4th 2017 Welcome to PyNN 0.9.1! Stochastic synapses ------------------- This release adds three new standard synapse models, available for the NEST and NEURON simulators. They are: * :class:`SimpleStochasticSynapse` - each spike is transmitted with a probability `p`. * :class:`StochasticTsodyksMarkramSynapse` - synapse exhibiting facilitation and depression, implemented using the model of Tsodyks, Markram et al., in its stochastic version. * :class:`MultiQuantalSynapse` - synapse exhibiting facilitation and depression with multiple quantal release sites. There are some new example scripts which demonstrate use of the synapse models - see :doc:`../examples/stochastic_synapses` and :doc:`../examples/stochastic_deterministic_comparison`. Note that the new models require building a NEST extension; this is done automatically during installation (when running :command:`pip install` or :command:`setup.py install`). Bug fixes --------- A `number of bugs`_ have been fixed. .. _`number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aissue+milestone%3A0.9.1+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.2.txt000066400000000000000000000027111415343567000162040ustar00rootroot00000000000000======================== PyNN 0.9.2 release notes ======================== November 22nd 2017 Welcome to PyNN 0.9.2! Recording injected currents --------------------------- It is now possible to record the injected current from :class:`CurrentSource` objects in PyNN, for example: .. code-block:: python noise = sim.NoisyCurrentSource(mean=0.5, stdev=0.2, start=50.0, stop=450.0, dt=1.0) noise.record() sim.run(500.0) signal = noise.get_data() The returned signal object is a Neo :class:`AnalogSignal`. Python 2.6 ---------- As of this version, PyNN no longer supports Python 2.6. NEST 2.14.0 and NEURON 7.5 -------------------------- PyNN 0.9.1 now supports the latest versions of NEST and NEURON. NEURON 7.4 is also still supported. NEST 2.12.0 should still work in most circumstances, but current recording (see above) requires a more recent version. native_electrode_type --------------------- It has been possible for some time to use native (NEST-specific) neuron and synapse models with :mod:`pyNN.nest`. It is now also possible to use native current generator models, e.g.: .. code-block:: python noise = sim.native_electrode_type('noise_generator')(mean=500.0, std=200.0, start=50.0, stop=450.0, dt=1.0) Bug fixes --------- A `number of bugs`_ have been fixed. .. _`number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aissue+milestone%3A0.9.2+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.3.txt000066400000000000000000000062731415343567000162140ustar00rootroot00000000000000======================== PyNN 0.9.3 release notes ======================== December 4th 2018 Welcome to PyNN 0.9.3! NEST 2.16.0 ----------- PyNN 0.9.3 now supports the latest version of NEST. Array-valued parameters ----------------------- The generalized integrate-and-fire model (:class:`GIF_cond_exp`) was added in version 0.8.3. This model has multiple mechanisms, each with multiple time constants, e.g. `tau_eta1`, `tau_eta2`, `tau_eta3`. To simplify parameterisation of such models, we now allow array-valued parameters, specified as a tuple, e.g. instead of: .. code-block:: python GIF_cond_exp( ... tau_eta1=1.0, tau_eta2=10.0, tau_eta3=100.0 ... ) we now write: .. code-block:: python GIF_cond_exp( ... tau_eta=(1.0, 10.0, 100.0) ) As for other parameter types, we can also specify inhomogeneous values across a population using lists of tuples, or generator functions. Project governance and code of conduct -------------------------------------- In an attempt to follow best practices in the governance of open source software projects, we have adopted some :doc:`rules and guidelines ` concerning the rights and obligations of contributors and of maintainers, and of how we decide who will be a maintainer. This includes a code of conduct for contributors and maintainers, aimed at fostering an open and welcoming environment. Simplified use of random number generators ------------------------------------------ Previously, a random number generator with `parallel_safe=False` would always draw a reduced number of values when run with >1 MPI processes, according to the number of processes, unless the `mask_local` parameter was set to False. Now, a mask must be explicitly provided if you want to draw a reduced number of values (i.e. only those values consumed on that node). If provided, the `mask` parameter (renamed from `mask_local`) should be a boolean or integer NumPy array, indicating that only a subset of the random numbers should be returned. Example:: rng.next(5, mask=np.array([True, False, True, False, True])) or:: rng.next(5, mask=np.array([0, 2, 4])) will each return only three values. If the rng is "parallel safe", an array of `n` values will be drawn from the rng, and the mask applied. If the rng is not parallel safe, the contents of the mask are disregarded, only its size (for an integer mask) or the number of True values (for a boolean mask) is used in determining how many values to draw. Support for NEURON "ARTIFICIAL_CELL" models ------------------------------------------- When using the NEURON simulator through PyNN, it is now possible to use "ARTIFICIAL_CELL" models, such as :class:`IntFire1`, :class:`IntFire2` and :class:`IntFire4`: .. testcode:: nativemodel from pyNN.neuron import setup, Population, IntFire1 setup() p1 = Population(10, IntFire1(tau=10.0, refrac=2.5)) p1.record('m') Bug fixes and performance improvements -------------------------------------- A `number of bugs`_ have been fixed, and some performance optimizations have been made. .. _`number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=is%3Aissue+milestone%3A0.9.3+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.4.txt000066400000000000000000000021161415343567000162050ustar00rootroot00000000000000======================== PyNN 0.9.4 release notes ======================== March 22nd 2019 Welcome to PyNN 0.9.4! SONATA ------- SONATA_ is a data format for representing/storing data-driven spiking neuronal network models, experimental protocols (injecting spikes, currents) and simulation outputs. In the network representation, all connections are represented explicity, as in PyNN’s :class:`FromFileConnector` and :class:`FromListConnector`. A PyNN model/simulation script can be exported in SONATA format, and a SONATA model/simulation can be read and executed through PyNN provided the cell types used in the model are compatible with PyNN, i.e. they must be point neurons. For more information on working with the SONATA format, see :ref:`sec-sonata`. Bug fixes and performance improvements -------------------------------------- A `small number of bugs`_ have been fixed, and the documentation clarified in a few places. .. _SONATA: https://github.com/AllenInstitute/sonata .. _`small number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=milestone%3A0.9.4+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.5.txt000066400000000000000000000005541415343567000162120ustar00rootroot00000000000000======================== PyNN 0.9.5 release notes ======================== December 5th 2019 Welcome to PyNN 0.9.5! NEST 2.18.0 ----------- PyNN now supports the latest version of NEST. Bug fixes --------- A `small number of bugs`_ have been fixed. .. _`small number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=milestone%3A0.9.5+is%3Aclosed PyNN-0.10.0/doc/releases/0.9.6.txt000066400000000000000000000014511415343567000162100ustar00rootroot00000000000000======================== PyNN 0.9.6 release notes ======================== December 17th 2020 Welcome to PyNN 0.9.6! NEST 2.20.0 ----------- PyNN now supports the latest version of NEST_. Beta support for Brian 2 ------------------------ PyNN now supports running simulations with `Brian 2`_. A few small bugs remain, so we regard this as a preview release for wider testing. End of support for Python 2.7 and Brian 1 ----------------------------------------- This is the last release with support for Python 2.7 (and therefore, for Brian 1). Bug fixes --------- A `small number of bugs`_ have been fixed. .. _`small number of bugs`: https://github.com/NeuralEnsemble/PyNN/issues?q=milestone%3A0.9.6+is%3Aclosed .. _NEST: https://www.nest-simulator.org .. _`Brian 2`: https://brian2.readthedocs.ioPyNN-0.10.0/doc/roadmap.txt000066400000000000000000000034541415343567000153630ustar00rootroot00000000000000======= Roadmap ======= NineML/NeuroML model definitions -------------------------------- Multi-compartmental models -------------------------- NineML/NeuroML export --------------------- API simplification ------------------ Multi-simulator models with MUSIC --------------------------------- .. code-block:: python from pyNN import music music.setup({“neuron”: 10, “nest”: 20}) sim1, sim2 = music.get_simulators() sim1.setup(timestep=0.025) sim2.setup(timestep=0.1) cell_parameters = {”tau_m”: 12.0, ”cm”: 0.8, ”v_thresh”: -50.0, ”v_reset”: -65.0} pE = sim1.Population((100,100), sim.IF_cond_exp, cell_parameters, label=”excitatory neurons”) pI = sim2.Population((50,50), sim.IF_cond_exp, cell_parameters, label=”inhibitory neurons”) all = pE + pI DDPC = sim.DistanceDependentProbabilityConnector connector = DDPC(”exp(-d**2/400.0)”, weights=0.05, delays=”0.5+0.01d”) e2e = sim1.Projection(pE, pE, connector, target=”excitatory”) e2i = music.Projection(pE, pI, connector, target=”excitatory”) i2i = sim2.Projection(pI, pI, connector, target=”inhibitory”) music.run(1000.0) The concept here is that PyNN takes over the rôle of the *music* executable, `music.setup()` launches the requested number of MPI processes for each simulator, and then the same script runs on each of these processes. On the processes running `sim1`, `sim1` is the real backend module, as in a normal, non-MUSIC PyNN script, while `sim2` is a no-op proxy object. Vice versa on processes running `sim2`. For connections between different simulators, a `music.Projection` instance is needed, which takes care of defining the MUSIC ports. PyNN-0.10.0/doc/simulation_control.txt000066400000000000000000000066431415343567000176670ustar00rootroot00000000000000================== Simulation control ================== Initialising the simulator ========================== .. testsetup:: from pyNN.mock import * Before using any other functions or classes from PyNN, the user must call the :func:`setup()` function:: >>> setup() :func:`setup()` takes various optional arguments: setting the simulation timestep (there is currently no support in the API for variable timestep methods although native simulator code can be used to select this option where the simulator supports it) and setting the minimum and maximum synaptic delays, e.g.:: >>> setup(timestep=0.1, min_delay=0.1, max_delay=10.0) Calling :func:`setup()` a second time resets the simulator entirely, destroying any network that may have been created in the meantime. .. todo:: add links to documentation on simulator-specific options to setup() Getting information about the simulation state ============================================== Several functions are available for obtaining information about the simulation state: * :func:`get_current_time` - the time within the simulation * :func:`get_time_step` - the integration time step * :func:`get_min_delay` - the minimum allowed synaptic delay * :func:`get_max_delay` - the maximum allowed synaptic delay * :func:`num_processes` - the number of MPI processes * :func:`rank` - the MPI rank of the current node Running a simulation ==================== The :func:`run()` function advances the simulation for a given number of milliseconds, e.g.:: >>> run(1000.0) You can also use :func:`run_for()`, which is an alias for :func:`run()`. The :func:`run_until()` function advances the simulation until a given future time point, e.g.:: >>> run_until(1001.0) >>> get_current_time() 1001.0 Performing operations during a run ---------------------------------- You may wish to perform some calculation, or show some information, during a run. One way to do this is to break the simulation into steps, and perform the operation at the end of each step, e.g.:: >>> for i in range(4): ... run_until(100.0*i) ... print("The time is %g" % (100*i,)) The time is 0 The time is 100 The time is 200 The time is 300 Alternatively, PyNN can take care of breaking the simulation into steps for you. :func:`run()` and :func:`run_until()` each accept an optional list of callbacks functions. Each callback should accept the current time as an argument, and return the next time it wishes to be called. >>> def report_time(t): ... print("The time is %g" % t) ... return t + 100.0 >>> run_until(300.0, callbacks=[report_time]) The time is 0 The time is 100 The time is 200 The time is 300 300.0 For simple cases, this requires a bit more code, but it is potentially much more powerful, especially if you have complex or multiple callbacks. Repeating a simulation ====================== If you wish to reset network time to zero to run a new simulation with the same network (with different parameter values, perhaps), use the :func:`reset()` function. Note that this does not change the network structure, nor the choice of which neurons to record (from previous :meth:`record()` calls). Finishing up ============ Just as a simulation must be begun with a call to ``setup()``, it should be ended with a call to ``end()``. This is not always necessary, but it is safest to always use it. PyNN-0.10.0/doc/space.txt000066400000000000000000000074441415343567000150360ustar00rootroot00000000000000======================================================== Representing spatial structure and calculating distances ======================================================== .. currentmodule: pyNN.space The :mod:`space` module contains classes for specifying the locations of neurons in space and for calculating the distances between them. Neuron positions can be defined either manually, using the :attr:`positions` attribute of a :class:`Population` or using a :class:`Structure` instance which is passed to the :class:`Population` constructor. A number of different structures are available in :mod:`space`. It is simple to define your own :class:`Structure` sub-class if you need something that is not already provided. The simplest structure is a grid, whether 1D, 2D or 3D, e.g.: .. testsetup:: from pyNN.random import NumpyRNG .. doctest:: :options: +NORMALIZE_WHITESPACE >>> from pyNN.space import * >>> line = Line(dx=100.0, x0=0.0, y=200.0, z=500.0) >>> line.generate_positions(7) array([[ 0., 100., 200., 300., 400., 500., 600.], [ 200., 200., 200., 200., 200., 200., 200.], [ 500., 500., 500., 500., 500., 500., 500.]]) >>> grid = Grid2D(aspect_ratio=3, dx=10.0, dy=25.0, z=-3.0) >>> grid.generate_positions(3) array([[ 0., 10., 20.], [ 0., 0., 0.], [ -3., -3., -3.]]) >>> grid.generate_positions(12) array([[ 0., 0., 10., 10., 20., 20., 30., 30., 40., 40., 50., 50.], [ 0., 25., 0., 25., 0., 25., 0., 25., 0., 25., 0., 25.], [ -3., -3., -3., -3., -3., -3., -3., -3., -3., -3., -3., -3.]]) Here we have specified an *x*:*y* ratio of 3, so if we ask the grid to generate positions for 3 neurons, we get a 3x1 grid, 12 neurons a 6x2 grid, 27 neurons 9x3, etc. BY default, grid positions are filled sequentially, iterating first over the *z* dimension, then *y*, then *x*, but we can also fill the grid randomly: .. doctest:: >>> rgrid = Grid2D(aspect_ratio=1, dx=10.0, dy=10.0, fill_order='random', rng=NumpyRNG(seed=13886)) >>> rgrid.generate_positions(9) array([[ 10., 10., 10., 0., 0., 0., 20., 20., 20.], [ 0., 10., 20., 10., 20., 0., 0., 10., 20.], [ 0., 0., 0., 0., 0., 0., 0., 0., 0.]]) The :mod:`space` module also provides the :class:`RandomStructure` class, which distributes neurons randomly and uniformly within a given volume: .. doctest:: :options: +NORMALIZE_WHITESPACE >>> glomerulus = RandomStructure(boundary=Sphere(radius=200.0), rng=NumpyRNG(seed=34534)) >>> glomerulus.generate_positions(5) array([[ -19.78455022, 33.21412264, -79.4314059 , 143.39033263, -63.18242977], [ 56.17281502, -23.15159309, 131.89071845, -73.73583484, -8.86422999], [ -78.88348228, -3.97408513, -95.03056844, 45.13969087, -111.67070498]]) The volume classes currently available are :class:`Sphere` and :class:`Cuboid`. Defining your own :class:`Structure` classes is straightforward, just inherit from :class:`BaseStructure` and implement a :meth:`generate_positions` method:: class MyStructure(BaseStructure): parameter_names = ("spam", "eggs") def __init__(self, spam=3, eggs=1): ... def generate_positions(self, n): ... # must return a 3xn numpy array To define your own :class:`Shape` class for use with :class:`RandomStructure`, subclass :class:`Shape` and implement a :meth:`sample` method:: class Tetrahedron(Shape): def __init__(self, side_length): ... def sample(self, n, rng): ... # return a nx3 numpy array. .. note:: rotation of structures is currently missing, but is planned for a future release. PyNN-0.10.0/doc/standardmodels.txt000066400000000000000000000222121415343567000167350ustar00rootroot00000000000000=============== Standard models =============== Standard models are neuron models that are available in at least two of the simulation engines supported by PyNN. PyNN performs automatic translation of parameter names, types and units. Only a handful of models are currently available, but the list will be expanded in future releases. To obtain a list of all the standard models available in a given simulator, use the ``list_standard_models()`` function, e.g.: .. code-block:: python >>> from pyNN import neuron >>> neuron.list_standard_models() ['IF_cond_alpha', 'IF_curr_exp', 'IF_cond_exp', 'EIF_cond_exp_isfa_ista', 'SpikeSourceArray', 'HH_cond_exp', 'IF_cond_exp_gsfa_grr', 'IF_facets_hardware1', 'SpikeSourcePoisson', 'EIF_cond_alpha_isfa_ista', 'IF_curr_alpha'] Neurons ======= IF_curr_alpha ------------- Leaky integrate and fire model with fixed threshold and alpha-function-shaped post-synaptic current. Availability: NEST, NEURON, Brian ============== ============= ===== ======================================== Name Default value Units Description ============== ============= ===== ======================================== ``v_rest`` -65.0 mV Resting membrane potential ``cm`` 1.0 nF Capacity of the membrane ``tau_m`` 20.0 ms Membrane time constant ``tau_refrac`` 0.1 ms Duration of refractory period ``tau_syn_E`` 0.5 ms Rise time of the excitatory synaptic alpha function ``tau_syn_I`` 0.5 ms Rise time of the inhibitory synaptic alpha function ``i_offset`` 0.0 nA Offset current ``v_reset`` -65.0 mV Reset potential after a spike ``v_thresh`` -50.0 mV Spike threshold ============== ============= ===== ======================================== IF_curr_exp ----------- Leaky integrate and fire model with fixed threshold and decaying-exponential post-synaptic current. (Separate synaptic currents for excitatory and inhibitory synapses. Availability: NEST, NEURON, Brian ============== ============= ===== ========================================= Name Default value Units Description ============== ============= ===== ========================================= ``v_rest`` -65.0 mV Resting membrane potential ``cm`` 1.0 nF Capacity of the membrane ``tau_m`` 20.0 ms Membrane time constant ``tau_refrac`` 0.1 ms Duration of refractory period ``tau_syn_E`` 5.0 ms Decay time of excitatory synaptic current ``tau_syn_I`` 5.0 ms Decay time of inhibitory synaptic current ``i_offset`` 0.0 nA Offset current ``v_reset`` -65.0 mV Reset potential after a spike ``v_thresh`` -50.0 mV Spike threshold ============== ============= ===== ========================================= IF_cond_alpha _____________ Leaky integrate and fire model with fixed threshold and alpha-function-shaped post-synaptic conductance. Availability: NEST, NEURON, Brian ============== ============= ===== =================================================== Name Default value Units Description ============== ============= ===== =================================================== ``v_rest`` -65.0 mV Resting membrane potential ``cm`` 1.0 nF Capacity of the membrane ``tau_m`` 20.0 ms Membrane time constant ``tau_refrac`` 0.1 ms Duration of refractory period ``tau_syn_E`` 0.3 ms Rise time of the excitatory synaptic alpha function ``tau_syn_I`` 0.5 ms Rise time of the inhibitory synaptic alpha function ``e_rev_E`` 0.0 mV Reversal potential for excitatory input ``e_rev_I`` -70.0 mV Reversal potential for inhibitory input ``v_thresh`` -50.0 mV Spike threshold ``v_reset`` -65.0 mV Reset potential after a spike ``i_offset`` 0.0 nA Offset current ============== ============= ===== =================================================== IF_cond_exp ----------- Leaky integrate and fire model with fixed threshold and decaying-exponential post-synaptic conductance. Availability: NEST, NEURON, Brian ============== ============= ===== =================================================== Name Default value Units Description ============== ============= ===== =================================================== ``v_rest`` -65.0 mV Resting membrane potential ``cm`` 1.0 nF Capacity of the membrane ``tau_m`` 20.0 ms Membrane time constant ``tau_refrac`` 0.1 ms Duration of refractory period ``tau_syn_E`` 5.0 ms Decay time of the excitatory synaptic conductance ``tau_syn_I`` 5.0 ms Decay time of the inhibitory synaptic conductance ``e_rev_E`` 0.0 mV Reversal potential for excitatory input ``e_rev_I`` -70.0 mV Reversal potential for inhibitory input ``v_thresh`` -50.0 mV Spike threshold ``v_reset`` -65.0 mV Reset potential after a spike ``i_offset`` 0.0 nA Offset current ============== ============= ===== =================================================== HH_cond_exp ----------- Single-compartment Hodgkin-Huxley-type neuron with transient sodium and delayed-rectifier potassium currents using the ion channel models from Traub. Availability: NEST, NEURON, Brian ============== ============= ===== =================================================== Name Default value Units Description ============== ============= ===== =================================================== ``gbar_Na`` 20.0 uS ``gbar_K`` 6.0 uS ``g_leak`` 0.01 uS ``cm`` 0.2 nF ``v_offset`` -63.0 mV ``e_rev_Na`` 50.0 mV ``e_rev_K`` -90.0 mV ``e_rev_leak`` -65.0 mV ``e_rev_E`` 0.0 mV ``e_rev_I`` -80.0 mV ``tau_syn_E`` 0.2 ms ``tau_syn_I`` 2.0 ms ``i_offset`` 0.0 nA ============== ============= ===== =================================================== EIF_cond_alpha_isfa_ista ------------------------ Adaptive exponential integrate and fire neuron according to Brette R and Gerstner W (2005) Adaptive Exponential Integrate-and-Fire Model as an Effective Description of Neuronal Activity. J Neurophysiol 94:3637-3642 Availability: NEST, NEURON, Brian ============== ============= ===== =================================================== Name Default value Units Description ============== ============= ===== =================================================== ``cm`` 0.281 nF Capacity of the membrane ``tau_refrac`` 0.1 ms Duration of refractory period ``v_spike`` -40.0 mV Spike detection threshold ``v_reset`` -70.6 mV Reset value for membrane potential after a spike ``v_rest`` -70.6 mV Resting membrane potential (Leak reversal potential) ``tau_m`` 9.3667 ms Membrane time constant ``i_offset`` 0.0 nA Offset current ``a`` 4.0 nS Subthreshold adaptation conductance ``b`` 0.0805 nA Spike-triggered adaptation ``delta_T`` 2.0 mV Slope factor ``tau_w`` 144.0 ms Adaptation time constant ``v_thresh`` -50.4 mV Spike initiation threshold ``e_rev_E`` 0.0 mV Excitatory reversal potential ``tau_syn_E`` 5.0 ms Rise time of excitatory synaptic conductance (alpha function) ``e_rev_I`` -80.0 mV Inhibitory reversal potential ``tau_syn_I`` 5.0 ms Rise time of the inhibitory synaptic conductance (alpha function) ============== ============= ===== =================================================== Spike sources ============= SpikeSourcePoisson ------------------ Spike source, generating spikes according to a Poisson process. Availability: NEST, NEURON, Brian ============ ============= ====== ========================== Name Default value Units Description ============ ============= ====== ========================== ``rate`` 0.0 s^`-1` Mean spike frequency ``start`` 0.0 ms Start time ``duration`` 10^9 ms Duration of spike sequence ============ ============= ====== ========================== SpikeSourceArray ---------------- Spike source generating spikes at the times given in the ``spike_times`` array. Availability: NEST, NEURON, Brian =============== ============= ====== ========================== Name Default value Units Description =============== ============= ====== ========================== ``spike_times`` ``[]`` ms list or numpy array containing spike times =============== ============= ====== ========================== PyNN-0.10.0/doc/testdocs.py000066400000000000000000000076671415343567000154130ustar00rootroot00000000000000#!/usr/bin/env python """ Script to run doctests. """ import doctest import sys import os from optparse import OptionParser optionflags = doctest.IGNORE_EXCEPTION_DETAIL + doctest.NORMALIZE_WHITESPACE optionflags = doctest.NORMALIZE_WHITESPACE class MyOutputChecker(doctest.OutputChecker): """ Modification of doctest.OutputChecker to work better with the PyNN users' manual: * Often, we don't want to have the output that is printed by Python in the manual, as it just takes up space without adding any useful information. """ def __init__(self, strict): self.strict = strict def check_output(self, want, got, optionflags): if self.strict: return doctest.OutputChecker.check_output(self, want, got, optionflags) else: if want == '': return True else: try: long(want) and long(got) # where the output is an id return True except ValueError: try: if round(float(want), 8) == round(float(got), 8): return True else: return doctest.OutputChecker.check_output(self, want, got, optionflags) except ValueError: return doctest.OutputChecker.check_output(self, want, got, optionflags) def mytestfile(filename, globs, optionflags, strict=False): parser = doctest.DocTestParser() if globs is None: globs = {} else: globs = globs.copy() name = os.path.basename(filename) runner = doctest.DocTestRunner(checker=MyOutputChecker(strict=strict), optionflags=optionflags) # Read the file, convert it to a test, and run it. s = open(filename).read() test = parser.get_doctest(s, globs, name, filename, 0) runner.run(test) runner.summarize() return runner.failures, runner.tries def print_script(filename, simulator): parser = doctest.DocTestParser() s = open(filename).read() script = "".join([ex.source for ex in parser.get_examples(s) if "+SKIP" not in ex.source]) print("from pyNN.%s import *\nsetup(max_delay=10.0, debug=True)\n%s" % (simulator, script)) def remove_data_files(): import glob for pattern in ("*.dat", "*.npz", "*.h5", "*.conn", "logfile"): for filename in glob.glob(pattern): os.remove(filename) # ============================================================================== if __name__ == "__main__": # Process command line parser = OptionParser(usage="usage: %prog [options] FILE") parser.add_option("-s", "--simulator", dest="simulator", type="choice", choices=('nest', 'neuron', 'brian'), help="run doctests with SIMULATOR", metavar="SIMULATOR", default='nest') parser.add_option("--strict", action="store_true", dest="strict", default=False, help="Use the original doctest output checker, not the more lax local version.") parser.add_option("-p", "--print", action="store_true", default=False, dest="dump", help="Just print out the script extracted from the document, don't run the test.") if 'nrniv' in sys.argv[0]: (options, args) = parser.parse_args(sys.argv[5:]) else: (options, args) = parser.parse_args() if len(args) == 1: docfile = args[0] else: parser.print_help() sys.exit(1) # Run test if options.dump: print_script(docfile, options.simulator) else: exec("from pyNN.%s import *" % options.simulator) setup(max_delay=10.0, debug=True) if options.simulator == "neuron": create(IF_curr_alpha) # this is to use up ID 0, making the IDs agree with NEST. mytestfile(docfile, globs=globals(), optionflags=optionflags, strict=options.strict) remove_data_files() sys.exit(0) PyNN-0.10.0/doc/units.txt000066400000000000000000000012561415343567000151000ustar00rootroot00000000000000===== Units ===== PyNN does not at present support explicit specification of the units of physical quantities (parameters and initial values). Instead, the following convention is used: ================= ===== Physical quantity Units ================= ===== time ms voltage mV current nA conductance µS capacitance nF firing rate /s phase/angle deg ================= ===== Synaptic weights are in microsiemens or nanoamps, depending on whether the post-synaptic mechanism implements a change in conductance or current. Distances are typically in microns, but any distance scale can be used, provided it is used consistently. PyNN-0.10.0/examples/000077500000000000000000000000001415343567000142425ustar00rootroot00000000000000PyNN-0.10.0/examples/HH_cond_exp2.py000066400000000000000000000036231415343567000170600ustar00rootroot00000000000000""" A single-compartment Hodgkin-Huxley neuron with exponential, conductance-based synapses, fed by a current injection. Run as: $ python HH_cond_exp2.py where is 'neuron', 'nest', etc Andrew Davison, UNIC, CNRS March 2010 """ from pyNN.utility import get_script_args make_plot = True simulator_name = get_script_args(1)[0] exec("from pyNN.%s import *" % simulator_name) setup(timestep=0.001, min_delay=0.1) cellparams = { 'gbar_Na': 20.0, 'gbar_K': 6.0, 'g_leak': 0.01, 'cm': 0.2, 'v_offset': -63.0, 'e_rev_Na': 50.0, 'e_rev_K': -90.0, 'e_rev_leak': -65.0, 'e_rev_E': 0.0, 'e_rev_I': -80.0, 'tau_syn_E': 0.2, 'tau_syn_I': 2.0, 'i_offset': 1.0, } hhcell = create(HH_cond_exp(**cellparams)) initialize(hhcell, v=-64.0) record('v', hhcell, "Results/HH_cond_exp2_%s.pkl" % simulator_name) var_names = { 'neuron': {'m': "seg.m_hh_traub", 'h': "seg.h_hh_traub", 'n': "seg.n_hh_traub"}, 'brian': {'m': 'm', 'h': 'h', 'n': 'n'}, } if simulator_name in var_names: hhcell.can_record = lambda x: True # hack for native_name in var_names[simulator_name].values(): hhcell.record(native_name) hhcell.celltype.units[native_name] = '' run(20.0) if make_plot: import matplotlib.pyplot as plt #pylab.rcParams['interactive'] = True plt.ion() data = hhcell.get_data() signal_names = [s.name for s in data.segments[0].analogsignals] vm = data.segments[0].analogsignals[signal_names.index('v')] plt.plot(vm.times, vm) plt.xlabel("time (ms)") plt.ylabel("Vm (mV)") if simulator_name in var_names: plt.figure(2) for var_name, native_name in var_names[simulator_name].items(): signal = data.segments[0].analogsignals[signal_names.index(native_name)] plt.plot(signal.times, signal, label=var_name) plt.xlabel("time (ms)") plt.legend() end() PyNN-0.10.0/examples/Izhikevich.py000066400000000000000000000043241415343567000167140ustar00rootroot00000000000000""" A selection of Izhikevich neurons. Run as: $ python Izhikevich.py where is 'neuron', 'nest', etc. """ from numpy import arange from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(timestep=0.01, min_delay=1.0) # === Build and instrument the network ======================================= neurons = sim.Population(3, sim.Izhikevich(a=0.02, b=0.2, c=-65, d=6, i_offset=[0.014, 0.0, 0.0])) spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=arange(10.0, 51, 1))) connection = sim.Projection(spike_source, neurons[1:2], sim.OneToOneConnector(), sim.StaticSynapse(weight=3.0, delay=1.0), receptor_type='excitatory'), electrode = sim.DCSource(start=2.0, stop=92.0, amplitude=0.014) electrode.inject_into(neurons[2:3]) neurons.record(['v']) # , 'u']) neurons.initialize(v=-70.0, u=-14.0) # === Run the simulation ===================================================== sim.run(100.0) # === Save the results, optionally plot a figure ============================= filename = normalized_filename("Results", "Izhikevich", "pkl", options.simulator, sim.num_processes()) neurons.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") data = neurons.get_data().segments[0] v = data.filter(name="v")[0] #u = data.filter(name="u")[0] Figure( Panel(v, ylabel="Membrane potential (mV)", xticks=True, xlabel="Time (ms)", yticks=True), #Panel(u, ylabel="u variable (units?)"), annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/Potjans2014/000077500000000000000000000000001415343567000161675ustar00rootroot00000000000000PyNN-0.10.0/examples/Potjans2014/README.txt000066400000000000000000000073471415343567000177000ustar00rootroot00000000000000/* * README.txt * */ Cortical microcircuit simulation: PyNN version This is an implementation of the multi-layer microcircuit model of early sensory cortex published by Potjans and Diesmann (2014) The cell-type specific cortical microcircuit: relating structure and activity in a full-scale spiking network model. Cerebral Cortex 24 (3): 785-806, doi:10.1093/cercor/bhs358. It has only been tested with the NEST back-end. Files: - network_params.py Script containing model parameters - sim_params.py Script containing simulation and system parameters - microcircuit.py Simulation script - can be left unchanged - network.py In which the network is set up - connectivity.py Definition of connection function - scaling.py Functions for computing numbers of synapses in full-scale and down-scaled networks - run_microcircuit.py Creates output directory, copies all scripts to this directory, creates sim_script.sh and submits it to the queue Takes all parameters from sim_params.sli and can be left unchanged - plotting.py Python script to create raster and firing rate plot Instructions: 1. Download and install your desired back-end. For NEST, see http://www.nest-initiative.org/index.php/Software:Download and to enable full-scale simulation, compile it with MPI support (use the --with-mpi option when configuring) according to the instructions on http://www.nest-initiative.org/index.php/Software:Installation 2. Install PyNN 0.8 according to the instructions on http://neuralensemble.org/docs/PyNN/installation.html 4. In sim_params.py adjust the following parameters: - Set the simulation time via 'sim_duration' - the number of compute nodes 'n_nodes' - the number of processes per node 'n_procs_per_node' - queuing system parameters 'walltime' and 'memory' - Adjust 'output_path', 'mpi_path', 'nest_path', and 'pyNN_path' to your system 5. In network_params.py: - Add dictionary to params_dict for the back-end you wish to use - Choose the network size via 'N_scaling' and 'K_scaling', which scales the numbers of neurons and in-degrees, respectively - Choose the external input via 'input_type' - Optionally activate thalamic input via 'thalamic_input' and set any thalamic input parameters 6. Run the simulation by typing 'python run_microcircuit.py' in your terminal (microcircuit.py and the parameter files need to be in the same folder) 7. Output files and basic analysis: - Spikes are written to .txt files containing IDs of the recorded neurons and corresponding spike times in ms. Separate files are written out for each population and virtual process. File names are formed as 'spikes'+ layer + population + MPI process + .txt - Voltages are written to .dat files containing GIDs, times in ms, and the corresponding membrane potentials in mV. File names are formed as voltmeter label + layer index + population index + spike detector GID + virtual process + .dat - If 'create_raster_plot' is set to True, a raster plot is saved as 'result.png' The simulation was successfully tested with NEST 2.4.1 and MPI 1.4.3. Plotting works with Python 2.6.6 including packages numpy 1.3.0, matplotlib 0.99.1.1, and glob. --------------------------------------------------- Simulation on a single process: 1. Go to the folder that includes microcircuit.py and the parameter files 2. Adjust 'N_scaling' and 'K_scaling' in network_params.py such that the network is small enough to fit on your system 3. Ensure that the output directory exists, as it is not created via the bash script anymore 4. Type 'python microcircuit.py' to start the simulation on a single process PyNN-0.10.0/examples/Potjans2014/connectivity.py000066400000000000000000000015731415343567000212650ustar00rootroot00000000000000################################################### ### Connection routine ### ################################################### import numpy as np from network_params import * from pyNN.random import RandomDistribution def FixedTotalNumberConnect(sim, pop1, pop2, K, w_mean, w_sd, d_mean, d_sd): n_syn = int(round(K * len(pop2))) conn = sim.FixedTotalNumberConnector(n_syn) d_distr = RandomDistribution('normal_clipped', [d_mean, d_sd, 0.1, np.inf]) if pop1.annotations['type'] == 'E': conn_type = 'excitatory' w_distr = RandomDistribution('normal_clipped', [w_mean, w_sd, 0., np.inf]) else: conn_type = 'inhibitory' w_distr = RandomDistribution('normal_clipped', [w_mean, w_sd, -np.inf, 0.]) syn = sim.StaticSynapse(weight=w_distr, delay=d_distr) proj = sim.Projection(pop1, pop2, conn, syn, receptor_type=conn_type) PyNN-0.10.0/examples/Potjans2014/microcircuit.py000066400000000000000000000047241415343567000212440ustar00rootroot00000000000000################################################### ### Main script ### ################################################### import network import plotting from neo.io import PyNNTextIO import time import pyNN from network_params import * import sys from sim_params import simulator_params, system_params sys.path.append(system_params['backend_path']) sys.path.append(system_params['pyNN_path']) # import logging # TODO! Remove if it runs without this line # prepare simulation # logging.basicConfig() # TODO! Remove if it runs without this line exec('import pyNN.%s as sim' % simulator) sim.setup(**simulator_params[simulator]) # create network start_netw = time.time() n = network.Network(sim) n.setup(sim) end_netw = time.time() if sim.rank() == 0: print('Creating the network took %g s' % (end_netw - start_netw,)) # simulate if sim.rank() == 0: print("Simulating...") start_sim = time.time() t = sim.run(simulator_params[simulator]['sim_duration']) end_sim = time.time() if sim.rank() == 0: print('Simulation took %g s' % (end_sim - start_sim,)) start_writing = time.time() for layer in n.pops: for pop in n.pops[layer]: io = PyNNTextIO(filename=system_params['output_path'] + "/spikes_" + layer + '_' + pop + '_' + str(sim.rank()) + ".txt") spikes = n.pops[layer][pop].get_data('spikes', gather=False) for segment in spikes.segments: io.write_segment(segment) if record_v: io = PyNNTextIO(filename=system_params['output_path'] + "/vm_" + layer + '_' + pop + '_' + str(sim.rank()) + ".txt") vm = n.pops[layer][pop].get_data('v', gather=False) for segment in vm.segments: try: io.write_segment(segment) except AssertionError: pass end_writing = time.time() print("Writing data took %g s" % (end_writing - start_writing,)) if create_raster_plot and sim.rank() == 0: # Numbers of neurons from which spikes were recorded n_rec = [[0] * n_pops_per_layer] * n_layers for layer, i in layers.items(): for pop, j in pops.items(): if record_fraction: n_rec[i][j] = round(N_full[layer][pop] * N_scaling * frac_record_spikes) else: n_rec[i][j] = n_record plotting.show_raster_bars(raster_t_min, raster_t_max, n_rec, frac_to_plot, system_params['output_path'] + '/') sim.end() PyNN-0.10.0/examples/Potjans2014/network.py000066400000000000000000000153771415343567000202470ustar00rootroot00000000000000################################################### ### Network definition ### ################################################### from network_params import * import scaling from connectivity import FixedTotalNumberConnect from pyNN.random import NumpyRNG, RandomDistribution import numpy as np class Network: def __init__(self, sim): return None def setup(self, sim): # Create matrix of synaptic weights self.w = create_weight_matrix() model = getattr(sim, 'IF_curr_exp') script_rng = NumpyRNG(seed=6508015, parallel_safe=parallel_safe) distr = RandomDistribution('normal', [V0_mean, V0_sd], rng=script_rng) # Create cortical populations self.pops = {} for layer in layers: self.pops[layer] = {} for pop in pops: self.pops[layer][pop] = sim.Population(int(N_full[layer][pop] * N_scaling), model, cellparams=neuron_params) self.pops[layer][pop].initialize(v=distr) # Store whether population is inhibitory or excitatory self.pops[layer][pop].annotate(type=pop) this_pop = self.pops[layer][pop] # Spike recording if record_fraction: num_spikes = int(round(this_pop.size * frac_record_spikes)) else: num_spikes = n_record this_pop[0:num_spikes].record('spikes') # Membrane potential recording if record_v: if record_fraction: num_v = int(round(this_pop.size * frac_record_v)) else: num_v = n_record_v this_pop[0:num_v].record('v') # Create thalamic population if thalamic_input: self.thalamic_population = sim.Population( thal_params['n_thal'], sim.SpikeSourcePoisson, {'rate': thal_params['rate'], 'start': thal_params['start'], 'duration': thal_params['duration']}) # Compute DC input before scaling if input_type == 'DC': self.DC_amp = {} for target_layer in layers: self.DC_amp[target_layer] = {} for target_pop in pops: self.DC_amp[target_layer][target_pop] = bg_rate * \ K_ext[target_layer][target_pop] * w_mean * neuron_params['tau_syn_E'] / \ 1000. else: self.DC_amp = {'L23': {'E': 0., 'I': 0.}, 'L4': {'E': 0., 'I': 0.}, 'L5': {'E': 0., 'I': 0.}, 'L6': {'E': 0., 'I': 0.}} # Scale and connect # In-degrees of the full-scale model K_full = scaling.get_indegrees() if K_scaling != 1: self.w, self.w_ext, self.K_ext, self.DC_amp = scaling.adjust_w_and_ext_to_K( K_full, K_scaling, self.w, self.DC_amp) else: self.w_ext = w_ext if sim.rank() == 0: print('w: %s' % self.w) for target_layer in layers: for target_pop in pops: target_index = structure[target_layer][target_pop] this_pop = self.pops[target_layer][target_pop] # External inputs if input_type == 'DC' or K_scaling != 1: this_pop.set(i_offset=self.DC_amp[target_layer][target_pop]) if input_type == 'poisson': poisson_generator = sim.Population(this_pop.size, sim.SpikeSourcePoisson, { 'rate': bg_rate * self.K_ext[target_layer][target_pop]}) conn = sim.OneToOneConnector() syn = sim.StaticSynapse(weight=self.w_ext) sim.Projection(poisson_generator, this_pop, conn, syn, receptor_type='excitatory') if thalamic_input: # Thalamic inputs if sim.rank() == 0: print('creating thalamic connections to %s%s' % (target_layer, target_pop)) C_thal = thal_params['C'][target_layer][target_pop] n_target = N_full[target_layer][target_pop] K_thal = round(np.log(1 - C_thal) / np.log((n_target * thal_params['n_thal'] - 1.) / (n_target * thal_params['n_thal']))) / n_target FixedTotalNumberConnect(sim, self.thalamic_population, this_pop, K_thal, w_ext, w_rel * w_ext, d_mean['E'], d_sd['E']) # Recurrent inputs for source_layer in layers: for source_pop in pops: source_index = structure[source_layer][source_pop] if sim.rank() == 0: print('creating connections from %s%s to %s%s' % (source_layer, source_pop, target_layer, target_pop)) weight = self.w[target_index][source_index] if source_pop == 'E' and source_layer == 'L4' and target_layer == 'L23' and target_pop == 'E': w_sd = weight * w_rel_234 else: w_sd = abs(weight * w_rel) FixedTotalNumberConnect(sim, self.pops[source_layer][source_pop], self.pops[target_layer][target_pop], K_full[target_index][source_index] * K_scaling, weight, w_sd, d_mean[source_pop], d_sd[source_pop]) def create_weight_matrix(): w = np.zeros([n_layers * n_pops_per_layer, n_layers * n_pops_per_layer]) for target_layer in layers: for target_pop in pops: target_index = structure[target_layer][target_pop] for source_layer in layers: for source_pop in pops: source_index = structure[source_layer][source_pop] if source_pop == 'E': if source_layer == 'L4' and target_layer == 'L23' and target_pop == 'E': w[target_index][source_index] = w_234 else: w[target_index][source_index] = w_mean else: w[target_index][source_index] = g * w_mean return w PyNN-0.10.0/examples/Potjans2014/network_params.py000066400000000000000000000135711415343567000216040ustar00rootroot00000000000000################################################### ### Network parameters ### ################################################### import sim_params params_dict = { 'nest' : { # Whether to make random numbers independent of the number of processes 'parallel_safe' : True, # Fraction of neurons to simulate 'N_scaling' : 1., # Fraction of in-degrees to simulate. Upon downscaling, synaptic weights are # taken proportional to 1/sqrt(in-degree) and external drive is adjusted # to preserve mean and variances of activity in the diffusion approximation. # In-degrees and weights of both intrinsic and extrinsic inputs are adjusted. # This scaling was not part of the original study, but this option is included # here to enable simulations on small systems that give results similar to # full-scale simulations. 'K_scaling' : 0.5, # Type of background input. Possible values: 'poisson' or 'DC' # If 'DC' is chosen, a constant external current is provided, equal to the mean # current due to the Poisson input used in the default version of the model. 'input_type' : 'poisson', # Whether to record from a fixed fraction of neurons in each population. # If False, a fixed number of neurons is recorded. 'record_fraction' : True, # Number of neurons from which to record spikes when record_fraction = False 'n_record' : 1000, # TODO: check if each population has at least this nr of neurons; PyNN otherwise just records fewer neurons & calculated rates may be wrong # Fraction of neurons from which to record spikes when record_fraction = True 'frac_record_spikes' : 1., # Whether to record membrane potentials 'record_v' : True, # Fixed number of neurons from which to record membrane potentials when # record_v=True and record_fraction = False 'n_record_v' : 20, # Fraction of neurons from which to record membrane potentials when # record_v=True and record_fraction = True 'frac_record_v' : 0.02, } } # Simulator back-end simulator = 'nest' # Load params from params_dict into global namespace globals().update(params_dict[simulator]) # Relative inhibitory synaptic weight g = -4. neuron_params = { 'cm' : 0.25, # nF 'i_offset' : 0.0, # nA 'tau_m' : 10.0, # ms 'tau_refrac': 2.0, # ms 'tau_syn_E' : 0.5, # ms 'tau_syn_I' : 0.5, # ms 'v_reset' : -65.0, # mV 'v_rest' : -65.0, # mV 'v_thresh' : -50.0 # mV } layers = {'L23': 0, 'L4': 1, 'L5': 2, 'L6': 3} n_layers = len(layers) pops = {'E': 0, 'I': 1} n_pops_per_layer = len(pops) structure = {'L23': {'E': 0, 'I': 1}, 'L4' : {'E': 2, 'I': 3}, 'L5' : {'E': 4, 'I': 5}, 'L6' : {'E': 6, 'I': 7}} # Numbers of neurons in full-scale model N_full = { 'L23': {'E': 20683, 'I': 5834}, 'L4' : {'E': 21915, 'I': 5479}, 'L5' : {'E': 4850, 'I': 1065}, 'L6' : {'E': 14395, 'I': 2948} } establish_connections = True # Probabilities for >=1 connection between neurons in the given populations. # The first index is for the target population; the second for the source population # 2/3e 2/3i 4e 4i 5e 5i 6e 6i conn_probs = [[0.1009, 0.1689, 0.0437, 0.0818, 0.0323, 0., 0.0076, 0. ], [0.1346, 0.1371, 0.0316, 0.0515, 0.0755, 0., 0.0042, 0. ], [0.0077, 0.0059, 0.0497, 0.135, 0.0067, 0.0003, 0.0453, 0. ], [0.0691, 0.0029, 0.0794, 0.1597, 0.0033, 0., 0.1057, 0. ], [0.1004, 0.0622, 0.0505, 0.0057, 0.0831, 0.3726, 0.0204, 0. ], [0.0548, 0.0269, 0.0257, 0.0022, 0.06, 0.3158, 0.0086, 0. ], [0.0156, 0.0066, 0.0211, 0.0166, 0.0572, 0.0197, 0.0396, 0.2252], [0.0364, 0.001, 0.0034, 0.0005, 0.0277, 0.008, 0.0658, 0.1443]] # In-degrees for external inputs K_ext = { 'L23': {'E': 1600, 'I': 1500}, 'L4' : {'E': 2100, 'I': 1900}, 'L5' : {'E': 2000, 'I': 1900}, 'L6' : {'E': 2900, 'I': 2100} } # Mean rates in the full-scale model, necessary for scaling # Precise values differ somewhat between network realizations full_mean_rates = { 'L23': {'E': 0.971, 'I': 2.868}, 'L4' : {'E': 4.746, 'I': 5.396}, 'L5' : {'E': 8.142, 'I': 9.078}, 'L6' : {'E': 0.991, 'I': 7.523} } # Mean and standard deviation of initial membrane potential distribution V0_mean = -58. # mV V0_sd = 5. # mV # Background rate per synapse bg_rate = 8. # spikes/s # Mean synaptic weight for all excitatory projections except L4e->L2/3e w_mean = 87.8e-3 # nA w_ext = 87.8e-3 # nA # Mean synaptic weight for L4e->L2/3e connections # See p. 801 of the paper, second paragraph under 'Model Parameterization', # and the caption to Supplementary Fig. 7 w_234 = 2 * w_mean # nA # Standard deviation of weight distribution relative to mean for # all projections except L4e->L2/3e w_rel = 0.1 # Standard deviation of weight distribution relative to mean for L4e->L2/3e # This value is not mentioned in the paper, but is chosen to match the # original code by Tobias Potjans w_rel_234 = 0.05 # Means and standard deviations of delays from given source populations (ms) d_mean = {'E': 1.5, 'I': 0.75} d_sd = {'E': 0.75, 'I': 0.375} # Parameters for transient thalamic input thalamic_input = False thal_params = { # Number of neurons in thalamic population 'n_thal' : 902, # Connection probabilities 'C' : {'L23': {'E': 0, 'I': 0}, 'L4' : {'E': 0.0983, 'I': 0.0619}, 'L5' : {'E': 0, 'I': 0}, 'L6' : {'E': 0.0512, 'I': 0.0196}}, 'rate' : 120., # spikes/s; 'start' : 700., # ms 'duration' : 10. # ms; } # Plotting parameters create_raster_plot = True raster_t_min = 0 # ms raster_t_max = sim_params.simulator_params[simulator]['sim_duration'] # ms # Fraction of recorded neurons to include in raster plot frac_to_plot = 0.01 PyNN-0.10.0/examples/Potjans2014/plotting.py000066400000000000000000000051411415343567000204020ustar00rootroot00000000000000import glob import os import matplotlib.pyplot as plt import numpy as np import matplotlib matplotlib.use('Agg') def show_raster_bars(t_start, t_stop, n_rec, frac_to_plot, path): # List of spike arrays, one entry for each population spikes = [] # Read out spikes for each population layer_list = ['L23', 'L4', 'L5', 'L6'] pop_list = ['E', 'I'] for i in range(8): layer = int(i / 2) pop = i % 2 filestart = path + 'spikes_' + str(layer_list[layer]) + '_' + str(pop_list[pop]) + '*' filelist = glob.glob(filestart) pop_spike_array = np.empty((0, 2)) last_id = 0 for file_name in filelist: spike_array = np.loadtxt(file_name) spike_array[:, 1] = spike_array[:, 1] + last_id pop_spike_array = np.vstack((pop_spike_array, spike_array)) last_id = pop_spike_array[-1, 1] spikes.append(pop_spike_array) # Plot spike times in raster plot and bar plot with the average firing rates of each population color = ['#595289', '#af143c'] pops = ['23E', '23I', '4E', '4I', '5E', '5I', '6E', '6I'] rates = np.zeros(8) fig = plt.figure() axarr = [] axarr.append(fig.add_subplot(121)) axarr.append(fig.add_subplot(122)) # Plot raster plot id_count = 0 print("Mean rates") for i in range(8)[::-1]: layer = int(i / 2) pop = i % 2 rate = 0.0 t_spikes = spikes[i][:, 0] ids = spikes[i][:, 1] + (id_count + 1) filtered_times_indices = [np.where((t_spikes > t_start) & (t_spikes < t_stop))][0] t_spikes = t_spikes[filtered_times_indices] ids = ids[filtered_times_indices] # Compute rates with all neurons rate = 1000 * len(t_spikes) / (t_stop - t_start) * 1 / float(n_rec[layer][pop]) rates[i] = rate #print(pops[-i] + np.round(rate, 2)) # Reduce data for raster plot num_neurons = frac_to_plot * np.unique(ids).size t_spikes = t_spikes[np.where(ids < num_neurons + id_count + 1)[0]] ids = ids[np.where(ids < num_neurons + id_count + 1)[0]] axarr[0].plot(t_spikes, ids, '.', color=color[pop]) id_count = ids[-1] # Plot bar plot axarr[1].barh(np.arange(0, 8, 1) + 0.1, rates[::-1], color=color[::-1] * 4) # Set labels axarr[0].set_ylim((0.0, id_count)) axarr[0].set_yticklabels([]) axarr[0].set_xlabel('time (ms)') axarr[1].set_ylim((0.0, 8.5)) axarr[1].set_yticks(np.arange(0.5, 8.5, 1.0)) axarr[1].set_yticklabels(pops[::-1]) axarr[1].set_xlabel('rate (spikes/s)') plt.savefig(path + 'result.png') PyNN-0.10.0/examples/Potjans2014/run_microcircuit.py000066400000000000000000000024231415343567000221220ustar00rootroot00000000000000from sim_params import system_params import os import shutil # Creates output folder if it does not exist yet, creates sim_script.sh, # and submits it to the queue system_params['num_mpi_procs'] = system_params['n_nodes'] * system_params['n_procs_per_node'] # Copy simulation scripts to output directory try: os.mkdir(system_params['output_path']) except OSError: pass shutil.copy('network_params.py', system_params['output_path']) shutil.copy('sim_params.py', system_params['output_path']) shutil.copy('microcircuit.py', system_params['output_path']) shutil.copy('network.py', system_params['output_path']) shutil.copy('connectivity.py', system_params['output_path']) shutil.copy('scaling.py', system_params['output_path']) shutil.copy('plotting.py', system_params['output_path']) job_script_template = """ #PBS -o %(output_path)s/%(outfile)s #PBS -e %(output_path)s/%(errfile)s #PBS -l walltime=%(walltime)s #PBS -l nodes=%(n_nodes)d:ppn=%(n_procs_per_node)d #PBS -q intel #PBS -l mem=%(memory)s . %(mpi_path)s mpirun -np %(num_mpi_procs)d python %(output_path)s/microcircuit.py """ f = open(system_params['output_path'] + '/sim_script.sh', 'w') f.write(job_script_template % system_params) f.close() os.system('cd %(output_path)s && %(submit_cmd)s sim_script.sh' % system_params) PyNN-0.10.0/examples/Potjans2014/scaling.py000066400000000000000000000046051415343567000201660ustar00rootroot00000000000000############################################################################# ### Functions for computing and adjusting connection and input parameters ### ############################################################################# import numpy as np from network_params import * def get_indegrees(): '''Get in-degrees for each connection for the full-scale (1 mm^2) model''' K = np.zeros([n_layers * n_pops_per_layer, n_layers * n_pops_per_layer]) for target_layer in layers: for target_pop in pops: for source_layer in layers: for source_pop in pops: target_index = structure[target_layer][target_pop] source_index = structure[source_layer][source_pop] n_target = N_full[target_layer][target_pop] n_source = N_full[source_layer][source_pop] K[target_index][source_index] = round(np.log(1. - conn_probs[target_index][source_index]) / np.log( (n_target * n_source - 1.) / (n_target * n_source))) / n_target return K def adjust_w_and_ext_to_K(K_full, K_scaling, w, DC): '''Adjust synaptic weights and external drive to the in-degrees to preserve mean and variance of inputs in the diffusion approximation''' K_ext_new = {} I_ext = {} for target_layer in layers: K_ext_new[target_layer] = {} I_ext[target_layer] = {} for target_pop in pops: target_index = structure[target_layer][target_pop] x1 = 0 for source_layer in layers: for source_pop in pops: source_index = structure[source_layer][source_pop] x1 += w[target_index][source_index] * K_full[target_index][source_index] * \ full_mean_rates[source_layer][source_pop] if input_type == 'poisson': x1 += w_ext*K_ext[target_layer][target_pop]*bg_rate K_ext_new[target_layer][target_pop] = K_ext[target_layer][target_pop]*K_scaling I_ext[target_layer][target_pop] = 0.001 * neuron_params['tau_syn_E'] * \ (1. - np.sqrt(K_scaling)) * x1 + DC[target_layer][target_pop] w_new = w / np.sqrt(K_scaling) w_ext_new = w_ext / np.sqrt(K_scaling) return w_new, w_ext_new, K_ext_new, I_ext PyNN-0.10.0/examples/Potjans2014/sim_params.py000066400000000000000000000017001415343567000206720ustar00rootroot00000000000000################################################### ### Simulation parameters ### ################################################### simulator_params = { 'nest': { 'timestep': 0.1, # ms 'threads': 1, 'sim_duration': 1000., # ms } } system_params = { # number of MPI nodes 'n_nodes': 1, # number of MPI processes per node 'n_procs_per_node': 2, # walltime for simulation 'walltime': '8:0:0', # total memory for simulation 'memory': '4gb', # file name for standard output 'outfile': 'output.txt', # file name for error output 'errfile': 'errors.txt', # absolute path to which the output files should be written 'output_path': 'results', # path to the MPI shell script 'mpi_path': '', # path to back-end 'backend_path': '', # path to pyNN installation 'pyNN_path': '', # command for submitting the job 'submit_cmd': 'qsub' } PyNN-0.10.0/examples/Potjans2014/validation_microcircuit.py000066400000000000000000000112731415343567000234530ustar00rootroot00000000000000################################################### ### validation_microcircuit ### ### a modification of the microcircuit.py ### ################################################### import network import plotting from neo.io import PyNNTextIO import time import pyNN from network_params import * import os import sys from sim_params import simulator_params, system_params sys.path.append(system_params['backend_path']) sys.path.append(system_params['pyNN_path']) # import logging # TODO! Remove if it runs without this line # prepare simulation # logging.basicConfig() # TODO! Remove if it runs without this line simulator = simulator_params.keys()[0] exec('import pyNN.%s as sim' % simulator) sim.setup(**simulator_params[simulator]) # Uncomment the two lines below when networkunit is installed #import sciunit #from networkunit.capabilities import ProducesSpikeTrains # ===================SciUnit Interface===================== # Uncomment the two lines below when networkunit is installed # class Potjans2014Microcircuit( sciunit.Model, # ProducesSpikeTrains ): # Comment the line below when networkunit is installed class Potjans2014Microcircuit(): ''' Use case: ''' def __init__(self): # prepare simulation #exec('import pyNN.%s as sim' % simulator) sim.setup(**simulator_params[simulator]) # The network takes a good amount of time so rather than make the user # wait to produce some capability it is better to create the network # when the capability is evoked def create_network(self): # create network start_netw = time.time() self.n = network.Network(sim) self.n.setup(sim) end_netw = time.time() if sim.rank() == 0: print('Creating the network took %g s' % (end_netw - start_netw,)) def simulate(self): # simulate if sim.rank() == 0: print("Simulating...") start_sim = time.time() t = sim.run(simulator_params[simulator]['sim_duration']) end_sim = time.time() if sim.rank() == 0: print('Simulation took %g s' % (end_sim - start_sim,)) def write_spiketrains(self): start_writing = time.time() for layer in self.n.pops: for pop in self.n.pops[layer]: io = PyNNTextIO(filename=system_params['output_path'] + "/spikes_" + layer + '_' + pop + '_' + str(sim.rank()) + ".txt") spikes = self.n.pops[layer][pop].get_data('spikes', gather=False) for segment in spikes.segments: io.write_segment(segment) if record_v: io = PyNNTextIO(filename=system_params['output_path'] + "/vm_" + layer + '_' + pop + '_' + str(sim.rank()) + ".txt") vm = self.n.pops[layer][pop].get_data('v', gather=False) for segment in vm.segments: try: io.write_segment(segment) except AssertionError: pass end_writing = time.time() print("Writing data took %g s" % (end_writing - start_writing,)) def plot_and_save(self): if create_raster_plot and sim.rank() == 0: # Numbers of neurons from which spikes were recorded n_rec = [[0] * n_pops_per_layer] * n_layers for layer, i in layers.items(): for pop, j in pops.items(): if record_fraction: n_rec[i][j] = round(N_full[layer][pop] * N_scaling * frac_record_spikes) else: n_rec[i][j] = n_record plotting.show_raster_bars(raster_t_min, raster_t_max, n_rec, frac_to_plot, system_params['output_path'] + '/') def create_results_directory(self): current_directory = os.getcwd() build_path = current_directory + os.sep + "results" if not os.path.exists("results"): os.makedirs(build_path) def produce_spiketrains(self): self.create_network() self.simulate() self.create_results_directory() self.write_spiketrains() sim.end() print("The model " + self.__class__.__name__ + " produces_spiketrains.") PyNN-0.10.0/examples/README000066400000000000000000000012761415343567000151300ustar00rootroot00000000000000------------------------------- - RUNNING EXAMPLES ------------------------------- 1. If you want to run all the examples with a specific simulator (nest, neuron, brian or mock), you write the following commands: > cd tools > python run_all_examples.py NEST/NEURON/Brian/MOCK The logs, results and the plots as png files will all be put in tools/Results/*. Note that 'mock' is a dummy backend, used to check the syntax of your PyNN code. 2. If you want to run all the examples with all the available simulators, just run the run_all_examples.py script without argument: > cd tools > python run_all_examples.py The logs, results and the plots as png files will all be put in tools/Results/*. PyNN-0.10.0/examples/StepCurrentSource.py000066400000000000000000000012261415343567000202540ustar00rootroot00000000000000""" Simple test of injecting time-varying current into a cell Andrew Davison, UNIC, CNRS May 2009 """ from pyNN.utility import get_script_args, normalized_filename simulator_name = get_script_args(1)[0] exec("from pyNN.%s import *" % simulator_name) setup() cell = create(IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0)) current_source = StepCurrentSource(times=[50.0, 110.0, 150.0, 210.0], amplitudes=[0.4, 0.6, -0.2, 0.2]) cell.inject(current_source) filename = normalized_filename("Results", "StepCurrentSource", "pkl", simulator_name) record('v', cell, filename, annotations={'script_name': __file__}) run(250.0) end() PyNN-0.10.0/examples/VAbenchmarks.py000066400000000000000000000253621415343567000171700ustar00rootroot00000000000000# coding: utf-8 """ Balanced network of excitatory and inhibitory neurons. An implementation of benchmarks 1 and 2 from Brette et al. (2007) Journal of Computational Neuroscience 23: 349-398 The network is based on the CUBA and COBA models of Vogels & Abbott (J. Neurosci, 2005). The model consists of a network of excitatory and inhibitory neurons, connected via current-based "exponential" synapses (instantaneous rise, exponential decay). Usage: python VAbenchmarks.py [-h] [--plot-figure] [--use-views] [--use-assembly] [--use-csa] [--debug DEBUG] simulator benchmark positional arguments: simulator neuron, nest, brian or another backend simulator benchmark either CUBA or COBA optional arguments: -h, --help show this help message and exit --plot-figure plot the simulation results to a file --use-views use population views in creating the network --use-assembly use assemblies in creating the network --use-csa use the Connection Set Algebra to define the connectivity --debug DEBUG print debugging information Andrew Davison, UNIC, CNRS August 2006 """ import socket from math import * from pyNN.utility import get_simulator, Timer, ProgressBar, init_logging, normalized_filename from pyNN.random import NumpyRNG, RandomDistribution # === Configure the simulator ================================================ sim, options = get_simulator( ("benchmark", "either CUBA or COBA"), ("--plot-figure", "plot the simulation results to a file", {"action": "store_true"}), ("--use-views", "use population views in creating the network", {"action": "store_true"}), ("--use-assembly", "use assemblies in creating the network", {"action": "store_true"}), ("--use-csa", "use the Connection Set Algebra to define the connectivity", {"action": "store_true"}), ("--debug", "print debugging information")) if options.use_csa: import csa if options.debug: init_logging(None, debug=True) timer = Timer() # === Define parameters ======================================================== threads = 1 rngseed = 98765 parallel_safe = True n = 4000 # number of cells r_ei = 4.0 # number of excitatory cells:number of inhibitory cells pconn = 0.02 # connection probability stim_dur = 50. # (ms) duration of random stimulation rate = 100. # (Hz) frequency of the random stimulation dt = 0.1 # (ms) simulation timestep tstop = 1000 # (ms) simulaton duration delay = 0.2 # Cell parameters area = 20000. # (µm²) tau_m = 20. # (ms) cm = 1. # (µF/cm²) g_leak = 5e-5 # (S/cm²) if options.benchmark == "COBA": E_leak = -60. # (mV) elif options.benchmark == "CUBA": E_leak = -49. # (mV) v_thresh = -50. # (mV) v_reset = -60. # (mV) t_refrac = 5. # (ms) (clamped at v_reset) v_mean = -60. # (mV) 'mean' membrane potential, for calculating CUBA weights tau_exc = 5. # (ms) tau_inh = 10. # (ms) # Synapse parameters if options.benchmark == "COBA": Gexc = 4. # (nS) Ginh = 51. # (nS) elif options.benchmark == "CUBA": Gexc = 0.27 # (nS) #Those weights should be similar to the COBA weights Ginh = 4.5 # (nS) # but the delpolarising drift should be taken into account Erev_exc = 0. # (mV) Erev_inh = -80. # (mV) ### what is the synaptic delay??? # === Calculate derived parameters ============================================= area = area*1e-8 # convert to cm² cm = cm*area*1000 # convert to nF Rm = 1e-6/(g_leak*area) # membrane resistance in MΩ assert tau_m == cm*Rm # just to check n_exc = int(round((n*r_ei/(1+r_ei)))) # number of excitatory cells n_inh = n - n_exc # number of inhibitory cells if options.benchmark == "COBA": celltype = sim.IF_cond_exp w_exc = Gexc*1e-3 # We convert conductances to uS w_inh = Ginh*1e-3 elif options.benchmark == "CUBA": celltype = sim.IF_curr_exp w_exc = 1e-3*Gexc*(Erev_exc - v_mean) # (nA) weight of excitatory synapses w_inh = 1e-3*Ginh*(Erev_inh - v_mean) # (nA) assert w_exc > 0; assert w_inh < 0 # === Build the network ======================================================== extra = {'threads' : threads, 'filename': "va_%s.xml" % options.benchmark, 'label': 'VA'} if options.simulator == "neuroml": extra["file"] = "VAbenchmarks.xml" node_id = sim.setup(timestep=dt, min_delay=delay, max_delay=1.0, **extra) np = sim.num_processes() host_name = socket.gethostname() print("Host #%d is on %s" % (node_id + 1, host_name)) print("%s Initialising the simulator with %d thread(s)..." % (node_id, extra['threads'])) cell_params = { 'tau_m' : tau_m, 'tau_syn_E' : tau_exc, 'tau_syn_I' : tau_inh, 'v_rest' : E_leak, 'v_reset' : v_reset, 'v_thresh' : v_thresh, 'cm' : cm, 'tau_refrac' : t_refrac} if (options.benchmark == "COBA"): cell_params['e_rev_E'] = Erev_exc cell_params['e_rev_I'] = Erev_inh timer.start() print("%s Creating cell populations..." % node_id) if options.use_views: # create a single population of neurons, and then use population views to define # excitatory and inhibitory sub-populations all_cells = sim.Population(n_exc + n_inh, celltype(**cell_params), label="All Cells") exc_cells = all_cells[:n_exc] exc_cells.label = "Excitatory cells" inh_cells = all_cells[n_exc:] inh_cells.label = "Inhibitory cells" else: # create separate populations for excitatory and inhibitory neurons exc_cells = sim.Population(n_exc, celltype(**cell_params), label="Excitatory_Cells") inh_cells = sim.Population(n_inh, celltype(**cell_params), label="Inhibitory_Cells") if options.use_assembly: # group the populations into an assembly all_cells = exc_cells + inh_cells if options.benchmark == "COBA": ext_stim = sim.Population(20, sim.SpikeSourcePoisson(rate=rate, duration=stim_dur), label="expoisson") rconn = 0.01 ext_conn = sim.FixedProbabilityConnector(rconn) ext_syn = sim.StaticSynapse(weight=0.1) print("%s Initialising membrane potential to random values..." % node_id) rng = NumpyRNG(seed=rngseed, parallel_safe=parallel_safe) uniformDistr = RandomDistribution('uniform', low=v_reset, high=v_thresh, rng=rng) if options.use_views: all_cells.initialize(v=uniformDistr) else: exc_cells.initialize(v=uniformDistr) inh_cells.initialize(v=uniformDistr) print("%s Connecting populations..." % node_id) progress_bar = ProgressBar(width=20) if options.use_csa: connector = sim.CSAConnector(csa.cset(csa.random(pconn))) else: connector = sim.FixedProbabilityConnector(pconn, rng=rng, callback=progress_bar) exc_syn = sim.StaticSynapse(weight=w_exc, delay=delay) inh_syn = sim.StaticSynapse(weight=w_inh, delay=delay) connections = {} if options.use_views or options.use_assembly: connections['exc'] = sim.Projection(exc_cells, all_cells, connector, exc_syn, receptor_type='excitatory') connections['inh'] = sim.Projection(inh_cells, all_cells, connector, inh_syn, receptor_type='inhibitory') if (options.benchmark == "COBA"): connections['ext'] = sim.Projection(ext_stim, all_cells, ext_conn, ext_syn, receptor_type='excitatory') else: connections['e2e'] = sim.Projection(exc_cells, exc_cells, connector, exc_syn, receptor_type='excitatory') connections['e2i'] = sim.Projection(exc_cells, inh_cells, connector, exc_syn, receptor_type='excitatory') connections['i2e'] = sim.Projection(inh_cells, exc_cells, connector, inh_syn, receptor_type='inhibitory') connections['i2i'] = sim.Projection(inh_cells, inh_cells, connector, inh_syn, receptor_type='inhibitory') if (options.benchmark == "COBA"): connections['ext2e'] = sim.Projection(ext_stim, exc_cells, ext_conn, ext_syn, receptor_type='excitatory') connections['ext2i'] = sim.Projection(ext_stim, inh_cells, ext_conn, ext_syn, receptor_type='excitatory') # === Setup recording ========================================================== print("%s Setting up recording..." % node_id) if options.use_views or options.use_assembly: all_cells.record('spikes') exc_cells[[0, 1]].record('v') else: exc_cells.record('spikes') inh_cells.record('spikes') exc_cells[0, 1].record('v') buildCPUTime = timer.diff() # === Save connections to file ================================================= #for prj in connections.keys(): #connections[prj].saveConnections('Results/VAbenchmark_%s_%s_%s_np%d.conn' % (benchmark, prj, options.simulator, np)) saveCPUTime = timer.diff() # === Run simulation =========================================================== print("%d Running simulation..." % node_id) sim.run(tstop) simCPUTime = timer.diff() E_count = exc_cells.mean_spike_count() I_count = inh_cells.mean_spike_count() # === Print results to file ==================================================== print("%d Writing data to file..." % node_id) filename = normalized_filename("Results", "VAbenchmarks_%s_exc" % options.benchmark, "pkl", options.simulator, np) exc_cells.write_data(filename, annotations={'script_name': __file__}) inh_cells.write_data(filename.replace("exc", "inh"), annotations={'script_name': __file__}) writeCPUTime = timer.diff() if options.use_views or options.use_assembly: connections = "%d e→e,i %d i→e,i" % (connections['exc'].size(), connections['inh'].size()) else: connections = u"%d e→e %d e→i %d i→e %d i→i" % (connections['e2e'].size(), connections['e2i'].size(), connections['i2e'].size(), connections['i2i'].size()) if node_id == 0: print("\n--- Vogels-Abbott Network Simulation ---") print("Nodes : %d" % np) print("Simulation type : %s" % options.benchmark) print("Number of Neurons : %d" % n) print("Number of Synapses : %s" % connections) print("Excitatory conductance : %g nS" % Gexc) print("Inhibitory conductance : %g nS" % Ginh) print("Excitatory rate : %g Hz" % (E_count * 1000.0 / tstop,)) print("Inhibitory rate : %g Hz" % (I_count * 1000.0 / tstop,)) print("Build time : %g s" % buildCPUTime) #print("Save connections time : %g s" % saveCPUTime) print("Simulation time : %g s" % simCPUTime) print("Writing time : %g s" % writeCPUTime) # === Finished with simulator ================================================== sim.end() PyNN-0.10.0/examples/brunel.py000066400000000000000000000202661415343567000161110ustar00rootroot00000000000000""" Conversion of the Brunel network implemented in nest-1.0.13/examples/brunel.sli to use PyNN. Brunel N (2000) Dynamics of sparsely connected networks of excitatory and inhibitory spiking neurons. J Comput Neurosci 8:183-208 Andrew Davison, UNIC, CNRS May 2006 """ from pyNN.utility import get_script_args, Timer, ProgressBar simulator_name = get_script_args(1)[0] exec("from pyNN.%s import *" % simulator_name) from pyNN.random import NumpyRNG, RandomDistribution timer = Timer() # === Define parameters ======================================================== downscale = 50 # scale number of neurons down by this factor # scale synaptic weights up by this factor to # obtain similar dynamics independent of size order = 50000 # determines size of network: # 4*order excitatory neurons # 1*order inhibitory neurons Nrec = 50 # number of neurons to record from, per population epsilon = 0.1 # connectivity: proportion of neurons each neuron projects to # Parameters determining model dynamics, cf Brunel (2000), Figs 7, 8 and Table 1 # here: Case C, asynchronous irregular firing, ~35 Hz eta = 2.0 # rel rate of external input g = 5.0 # rel strength of inhibitory synapses J = 0.1 # synaptic weight [mV] delay = 1.5 # synaptic delay, all connections [ms] # single neuron parameters tauMem = 20.0 # neuron membrane time constant [ms] tauSyn = 0.1 # synaptic time constant [ms] tauRef = 2.0 # refractory time [ms] U0 = 0.0 # resting potential [mV] theta = 20.0 # threshold # simulation-related parameters simtime = 100.0 # simulation time [ms] dt = 0.1 # simulation step length [ms] # seed for random generator used when building connections connectseed = 12345789 use_RandomArray = True # use Python rng rather than NEST rng # seed for random generator(s) used during simulation kernelseed = 43210987 # === Calculate derived parameters ============================================= # scaling: compute effective order and synaptic strength order_eff = int(float(order)/downscale) J_eff = J*downscale # compute neuron numbers NE = int(4*order_eff) # number of excitatory neurons NI = int(1*order_eff) # number of inhibitory neurons N = NI + NE # total number of neurons # compute synapse numbers CE = int(epsilon*NE) # number of excitatory synapses on neuron CI = int(epsilon*NI) # number of inhibitory synapses on neuron C = CE + CI # total number of internal synapses per n. Cext = CE # number of external synapses on neuron # synaptic weights, scaled for alpha functions, such that # for constant membrane potential, charge J would be deposited fudge = 0.00041363506632638 # ensures dV = J at V=0 # excitatory weight: JE = J_eff / tauSyn * fudge JE = (J_eff/tauSyn)*fudge # inhibitory weight: JI = - g * JE JI = -g*JE # threshold, external, and Poisson generator rates: nu_thresh = theta/(J_eff*CE*tauMem) nu_ext = eta*nu_thresh # external rate per synapse p_rate = 1000*nu_ext*Cext # external input rate per neuron (Hz) # number of synapses---just so we know Nsyn = (C+1)*N + 2*Nrec # number of neurons * (internal synapses + 1 synapse from PoissonGenerator) + 2synapses" to spike detectors # put cell parameters into a dict cell_params = {'tau_m' : tauMem, 'tau_syn_E' : tauSyn, 'tau_syn_I' : tauSyn, 'tau_refrac' : tauRef, 'v_rest' : U0, 'v_reset' : U0, 'v_thresh' : theta, 'cm' : 0.001} # (nF) # === Build the network ======================================================== # clear all existing network elements and set resolution and limits on delays. # For NEST, limits must be set BEFORE connecting any elements #extra = {'threads' : 2} extra = {} rank = setup(timestep=dt, max_delay=delay, **extra) print("rank =", rank) np = num_processes() print("np =", np) import socket host_name = socket.gethostname() print("Host #%d is on %s" % (rank+1, host_name)) if 'threads' in extra: print("%d Initialising the simulator with %d threads..." % (rank, extra['threads'])) else: print("%d Initialising the simulator with single thread..." % rank) # Small function to display information only on node 1 def nprint(s): if rank == 0: print(s) timer.start() # start timer on construction print("%d Setting up random number generator" % rank) rng = NumpyRNG(kernelseed, parallel_safe=True) print("%d Creating excitatory population with %d neurons." % (rank, NE)) celltype = IF_curr_alpha(**cell_params) E_net = Population(NE, celltype, label="E_net") print("%d Creating inhibitory population with %d neurons." % (rank, NI)) I_net = Population(NI, celltype, label="I_net") print("%d Initialising membrane potential to random values between %g mV and %g mV." % (rank, U0, theta)) uniformDistr = RandomDistribution('uniform', low=U0, high=theta, rng=rng) E_net.initialize(v=uniformDistr) I_net.initialize(v=uniformDistr) print("%d Creating excitatory Poisson generator with rate %g spikes/s." % (rank, p_rate)) source_type = SpikeSourcePoisson(rate=p_rate) expoisson = Population(NE, source_type, label="expoisson") print("%d Creating inhibitory Poisson generator with the same rate." % rank) inpoisson = Population(NI, source_type, label="inpoisson") # Record spikes print("%d Setting up recording in excitatory population." % rank) E_net.sample(Nrec).record('spikes') E_net[0:2].record('v') print("%d Setting up recording in inhibitory population." % rank) I_net.sample(Nrec).record('spikes') I_net[0:2].record('v') progress_bar = ProgressBar(width=20) connector = FixedProbabilityConnector(epsilon, rng=rng, callback=progress_bar) E_syn = StaticSynapse(weight=JE, delay=delay) I_syn = StaticSynapse(weight=JI, delay=delay) ext_Connector = OneToOneConnector(callback=progress_bar) ext_syn = StaticSynapse(weight=JE, delay=dt) print("%d Connecting excitatory population with connection probability %g, weight %g nA and delay %g ms." % (rank, epsilon, JE, delay)) E_to_E = Projection(E_net, E_net, connector, E_syn, receptor_type="excitatory") print("E --> E\t\t", len(E_to_E), "connections") I_to_E = Projection(I_net, E_net, connector, I_syn, receptor_type="inhibitory") print("I --> E\t\t", len(I_to_E), "connections") input_to_E = Projection(expoisson, E_net, ext_Connector, ext_syn, receptor_type="excitatory") print("input --> E\t", len(input_to_E), "connections") print("%d Connecting inhibitory population with connection probability %g, weight %g nA and delay %g ms." % (rank, epsilon, JI, delay)) E_to_I = Projection(E_net, I_net, connector, E_syn, receptor_type="excitatory") print("E --> I\t\t", len(E_to_I), "connections") I_to_I = Projection(I_net, I_net, connector, I_syn, receptor_type="inhibitory") print("I --> I\t\t", len(I_to_I), "connections") input_to_I = Projection(inpoisson, I_net, ext_Connector, ext_syn, receptor_type="excitatory") print("input --> I\t", len(input_to_I), "connections") # read out time used for building buildCPUTime = timer.elapsedTime() # === Run simulation =========================================================== # run, measure computer time timer.start() # start timer on construction print("%d Running simulation for %g ms." % (rank, simtime)) run(simtime) simCPUTime = timer.elapsedTime() # write data to file print("%d Writing data to file." % rank) (E_net + I_net).write_data("Results/brunel_np%d_%s.pkl" % (np, simulator_name)) E_rate = E_net.mean_spike_count()*1000.0/simtime I_rate = I_net.mean_spike_count()*1000.0/simtime # write a short report nprint("\n--- Brunel Network Simulation ---") nprint("Nodes : %d" % np) nprint("Number of Neurons : %d" % N) nprint("Number of Synapses : %d" % Nsyn) nprint("Input firing rate : %g" % p_rate) nprint("Excitatory weight : %g" % JE) nprint("Inhibitory weight : %g" % JI) nprint("Excitatory rate : %g Hz" % E_rate) nprint("Inhibitory rate : %g Hz" % I_rate) nprint("Build time : %g s" % buildCPUTime) nprint("Simulation time : %g s" % simCPUTime) # === Clean up and quit ======================================================== end() PyNN-0.10.0/examples/cell_type_demonstration.py000066400000000000000000000067621415343567000215550ustar00rootroot00000000000000""" A demonstration of the responses of different standard neuron models to current injection. Usage: python cell_type_demonstration.py [-h] [--plot-figure] [--debug] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. --debug Print debugging information """ from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(timestep=0.01, min_delay=1.0) # === Build and instrument the network ======================================= cuba_exp = sim.Population(1, sim.IF_curr_exp(i_offset=1.0), label="IF_curr_exp") hh = sim.Population(1, sim.HH_cond_exp(i_offset=0.2), label="HH_cond_exp") adexp = sim.Population(1, sim.EIF_cond_exp_isfa_ista(i_offset=1.0), label="EIF_cond_exp_isfa_ista") adapt = sim.Population(1, sim.IF_cond_exp_gsfa_grr(i_offset=2.0), label="IF_cond_exp_gsfa_grr") izh = sim.Population(1, sim.Izhikevich(i_offset=0.01), label="Izhikevich") all_neurons = cuba_exp + hh + adexp + adapt + izh all_neurons.record('v') adexp.record('w') izh.record('u') # === Run the simulation ===================================================== sim.run(100.0) # === Save the results, optionally plot a figure ============================= filename = normalized_filename("Results", "cell_type_demonstration", "pkl", options.simulator) all_neurons.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") Figure( Panel(cuba_exp.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[cuba_exp.label], yticks=True, ylim=(-66, -48)), Panel(hh.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[hh.label], yticks=True, ylim=(-100, 60)), Panel(adexp.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[adexp.label], yticks=True, ylim=(-75, -40)), Panel(adexp.get_data().segments[0].filter(name='w')[0], ylabel="w (nA)", data_labels=[adexp.label], yticks=True, ylim=(0, 0.4)), Panel(adapt.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[adapt.label], yticks=True, ylim=(-75, -45)), Panel(izh.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[izh.label], yticks=True, ylim=(-80, 40)), Panel(izh.get_data().segments[0].filter(name='u')[0], xticks=True, xlabel="Time (ms)", ylabel="u (mV/ms)", data_labels=[izh.label], yticks=True, ylim=(-14, 0)), title="Responses of standard neuron models to current injection", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/connections.py000066400000000000000000000235571415343567000171520ustar00rootroot00000000000000# coding: utf-8 """ Examples of connections and plots of the connection matrices. It loops over most connector types and plots the resulting connection matrices. The network used is made of: - a population of stimuli - a population of inhibitory neurons - a population of excitatory neurons Usage: python connections.py --plot-figure=name_figure is either mock, neuron, nest, brian,... It gives the results as png file, whose name is name_figure_ConnectorType, for each ConnectorType used. The connection parameters used are defined in the __main__ loop at the bottom of the file Joel Chavas, UNIC, CNRS June 2014 """ import os import socket from math import * from pyNN.utility import get_simulator, Timer, ProgressBar, init_logging, normalized_filename from pyNN.random import NumpyRNG, RandomDistribution from pyNN.core import IndexBasedExpression from numpy import nan_to_num, array, ones, savetxt def initialize(): global sim global options global extra global rngseed global parallel_safe global rng global n_ext global n_exc global n_inh sim, options = get_simulator( ("--plot-figure", "Plot the connections to a file.")) init_logging(None, debug=True) # === General parameters ================================================= threads = 1 rngseed = 98765 parallel_safe = True rng = NumpyRNG(seed=rngseed, parallel_safe=parallel_safe) # === general network parameters (except connections) ==================== n_ext = 60 # number of external stimuli n_exc = 60 # number of excitatory cells n_inh = 60 # number of inhibitory cells # === Options ============================================================ extra = {'loglevel': 2, 'useSystemSim': True, 'maxNeuronLoss': 0., 'maxSynapseLoss': 0.4, 'hardwareNeuronSize': 8, 'threads': threads, 'filename': "connections.xml", 'label': 'VA'} if sim.__name__ == "pyNN.hardware.brainscales": extra['hardware'] = sim.hardwareSetup['small'] if options.simulator == "neuroml": extra["file"] = "connections.xml" def build_connections(connector_type, connector_parameters): # === Setup ============================================================== node_id = sim.setup(**extra) np = sim.num_processes() host_name = socket.gethostname() print("Host #%d is on %s" % (node_id + 1, host_name)) print("%s Initialising the simulator with %d thread(s)..." % (node_id, extra['threads'])) # === Type references ==================================================== celltype = sim.IF_cond_exp synapsetype = sim.StaticSynapse # === Definition of the types of neurons, synapses and connections ======= progress_bar = ProgressBar(width=20) cell_stim = sim.SpikeSourceArray(spike_times=[1.0]) cell_exc = celltype() cell_inh = celltype() syn_stim = synapsetype() syn_exc = synapsetype() syn_inh = synapsetype() conn_stim = connector_type(**connector_parameters) conn_exc = connector_type(**connector_parameters) conn_inh = connector_type(**connector_parameters) # === Populations ======================================================== print("%s Creating cell populations..." % node_id) pop_stim = sim.Population(n_ext, cell_stim, label="spikes") pop_exc = sim.Population(n_exc, cell_exc, label="Excitatory_Cells") pop_inh = sim.Population(n_inh, cell_inh, label="Inhibitory_Cells") print("%s Connecting populations..." % node_id) connections = {} connections['stim2e'] = sim.Projection( pop_stim, pop_exc, connector=conn_stim, synapse_type=syn_stim, receptor_type='excitatory') connections['stim2i'] = sim.Projection( pop_stim, pop_inh, connector=conn_stim, synapse_type=syn_stim, receptor_type='excitatory') connections['e2e'] = sim.Projection( pop_exc, pop_exc, conn_exc, syn_exc, receptor_type='excitatory') connections['e2i'] = sim.Projection( pop_exc, pop_inh, conn_exc, syn_exc, receptor_type='excitatory') connections['i2e'] = sim.Projection( pop_inh, pop_exc, conn_inh, syn_inh, receptor_type='inhibitory') connections['i2i'] = sim.Projection( pop_inh, pop_inh, conn_inh, syn_inh, receptor_type='inhibitory') # === Output connection results =========================================== # for prj in connections.keys(): # connections[prj].saveConnections('Results/VAconnections_%s_%s_np%d.conn' # % (prj, options.simulator, np)) str_connections = "%d e->e %d e->i %d i->e %d i->i" % (connections['e2e'].size(), connections[ 'e2i'].size(), connections[ 'i2e'].size(), connections['i2i'].size()) str_stim_connections = "%d stim->e %d stim->i" % ( connections['stim2e'].size(), connections['stim2i'].size()) if node_id == 0: print("\n\n--- Connector : %s ---" % connector_type.__name__) print("Nodes : %d" % np) print("Number of Stims : %d" % n_ext) print("Number of Exc Neurons : %d" % n_exc) print("Number of Inh Neurons : %d" % n_inh) print("Number of Synapses : %s" % str_connections) print("Number of inputs : %s" % str_stim_connections) print("\n") def normalize_array(arr): res = nan_to_num(arr) res = (res != 0) return res.astype(int) if options.plot_figure: filename = options.plot_figure + '_' + connector_type.__name__ from pyNN.utility.plotting import Figure, Panel array_stim_exc = normalize_array( connections['stim2e'].get('delay', format="array")[0:20, :]) array_stim_inh = normalize_array( connections['stim2i'].get('delay', format="array")[0:20, :]) array_exc_exc = normalize_array( connections['e2e'].get('delay', format="array")[0:20, :]) array_exc_inh = normalize_array( connections['e2i'].get('delay', format="array")[0:20, :]) array_inh_exc = normalize_array( connections['i2e'].get('delay', format="array")[0:20, :]) array_inh_inh = normalize_array( connections['i2i'].get('delay', format="array")[0:20, :]) Figure( Panel(array_stim_exc, data_labels=["stim->exc"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), Panel(array_stim_inh, data_labels=["stim->inh"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), Panel(array_exc_exc, data_labels=["exc->exc"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), Panel(array_exc_inh, data_labels=["exc->inh"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), Panel(array_inh_exc, data_labels=["inh->exc"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), Panel(array_inh_inh, data_labels=["inh->inh"], line_properties=[ {'xticks': True, 'yticks': True, 'cmap': 'Greys', 'vmin': 0.}]), ).save(filename) # === Finished with simulator ============================================ sim.end() # =========================================================================== # Utility functions # =========================================================================== def build_connection_parameters(): global connection_list global path global array_connections connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), (5, 1, 0.4, 0.13), (0, 1, 0.5, 0.14), ] path = "test.connections" if os.path.exists(path): os.remove(path) savetxt(path, connection_list) array_connections = ones((60, 60), dtype=bool) array_connections[15, 15] = False class IndexBasedProbability(IndexBasedExpression): def __call__(self, i, j): return array((i + j) % 3 == 0, dtype=float) def displacement_expression(d): return 0.5 * ((d[0] >= -1) * (d[0] <= 2)) + 0.25 * (d[1] >= 0) * (d[1] <= 1) # =========================================================================== # =========================================================================== # MAIN PROGRAM # =========================================================================== # =========================================================================== if __name__ == "__main__": # === Initializes ======================================================= initialize() build_connection_parameters() # === Loop over connector types ========================================= connector_type = [ [ sim.FixedProbabilityConnector, {'p_connect': 1.0, 'rng': rng} ], [ sim.AllToAllConnector, {'allow_self_connections': False} ], [ sim.DistanceDependentProbabilityConnector, {'d_expression': "exp(-abs(d))", 'rng': rng} ], [ sim.IndexBasedProbabilityConnector, {'index_expression': IndexBasedProbability(), 'rng': rng} ], [ sim.DisplacementDependentProbabilityConnector, {'disp_function': displacement_expression, 'rng': rng} ], [ sim.FromListConnector, {'conn_list': connection_list} ], [ sim.FromFileConnector, {'file': path, 'distributed': False} ], [ sim.FixedNumberPreConnector, {'n': 3, 'rng': rng} ], [ sim.ArrayConnector, {'array': array_connections, 'safe': True} ] ] for conn in connector_type: build_connections(conn[0], conn[1]) PyNN-0.10.0/examples/current_injection.py000066400000000000000000000046551415343567000203520ustar00rootroot00000000000000""" Injecting time-varying current into a cell. There are four "standard" current sources in PyNN: - DCSource - ACSource - StepCurrentSource - NoisyCurrentSource Any other current waveforms can be implemented using StepCurrentSource. Usage: current_injection.py [-h] [--plot-figure] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file """ from pyNN.utility import get_simulator, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file", {"action": "store_true"})) sim.setup() # === Create four cells and inject current into each one ===================== cells = sim.Population(4, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, tau_m=10.0)) current_sources = [sim.DCSource(amplitude=0.5, start=50.0, stop=400.0), sim.StepCurrentSource(times=[50.0, 210.0, 250.0, 410.0], amplitudes=[0.4, 0.6, -0.2, 0.2]), sim.ACSource(start=50.0, stop=450.0, amplitude=0.2, offset=0.1, frequency=10.0, phase=180.0), sim.NoisyCurrentSource(mean=0.5, stdev=0.2, start=50.0, stop=450.0, dt=1.0)] for cell, current_source in zip(cells, current_sources): cell.inject(current_source) filename = normalized_filename("Results", "current_injection", "pkl", options.simulator) sim.record('v', cells, filename, annotations={'script_name': __file__}) # === Run the simulation ===================================================== sim.run(500.0) # === Save the results, optionally plot a figure ============================= vm = cells.get_data().segments[0].filter(name="v")[0] sim.end() if options.plot_figure: from pyNN.utility.plotting import Figure, Panel from quantities import mV figure_filename = filename.replace("pkl", "png") Figure( Panel(vm, y_offset=-10 * mV, xticks=True, yticks=True, xlabel="Time (ms)", ylabel="Membrane potential (mV)", ylim=(-96, -59)), title="Current injection example", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) PyNN-0.10.0/examples/distrib_example.py000066400000000000000000000033521415343567000177720ustar00rootroot00000000000000 from mpi4py import MPI from pyNN.utility import get_script_args import sys import numpy as np simulator = get_script_args(1)[0] exec("import pyNN.%s as sim" % simulator) comm = MPI.COMM_WORLD sim.setup(debug=True) print("\nThis is node %d (%d of %d)" % (sim.rank(), sim.rank() + 1, sim.num_processes())) assert comm.rank == sim.rank() assert comm.size == sim.num_processes() data1 = np.empty(100, dtype=float) if comm.rank == 0: data1 = np.arange(100, dtype=float) else: pass comm.Bcast([data1, MPI.DOUBLE], root=0) print(comm.rank, data1) data2 = np.arange(comm.rank, 10 + comm.rank, dtype=float) print(comm.rank, data2) data2g = np.empty(10 * comm.size) comm.Gather([data2, MPI.DOUBLE], [data2g, MPI.DOUBLE], root=0) if comm.rank == 0: print("gathered (2):", data2g) data3 = np.arange(0, 5 * (comm.rank + 1), dtype=float) print(comm.rank, data3) if comm.rank == 0: sizes = range(5, 5 * comm.size + 1, 5) disp = [size - 5 for size in sizes] data3g = np.empty(sum(sizes)) else: sizes = disp = [] data3g = np.empty([]) comm.Gatherv([data3, data3.size, MPI.DOUBLE], [data3g, (sizes, disp), MPI.DOUBLE], root=0) if comm.rank == 0: print("gathered (3):", data3g) def gather(data): assert isinstance(data, np.ndarray) # first we pass the data size size = data.size sizes = comm.gather(size, root=0) or [] # now we pass the data displacements = [sum(sizes[:i]) for i in range(len(sizes))] print(comm.rank, "sizes=", sizes, "displacements=", displacements) gdata = np.empty(sum(sizes)) comm.Gatherv([data, size, MPI.DOUBLE], [gdata, (sizes, displacements), MPI.DOUBLE], root=0) return gdata data3g = gather(data3) if comm.rank == 0: print("gathered (3, again):", data3g) sim.end() PyNN-0.10.0/examples/gif_neuron.py000066400000000000000000000114041415343567000167470ustar00rootroot00000000000000""" Demonstration of the Generalized Integrate-and-Fire model described by Pozzorini et al. (2015) We simulate four neurons with different parameters: 1. spike-triggered current, fixed threshold, deterministic spiking 2. no spike-triggered current, dynamic threshold, deterministic spiking 3 & 4. no spike-triggered current, fixed threshold, stochastic spiking Since neurons 1 and 2 have deterministic spiking, they should produce the same spike times with different simulators. Neurons 3 and 4, being stochastic, should spike at different times. Reference: Pozzorini, Christian, Skander Mensi, Olivier Hagens, Richard Naud, Christof Koch, and Wulfram Gerstner (2015) "Automated High-Throughput Characterization of Single Neurons by Means of Simplified Spiking Models." PLOS Comput Biol 11 (6): e1004275. doi:10.1371/journal.pcbi.1004275. """ import matplotlib matplotlib.use('Agg') from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(timestep=0.01, min_delay=1.0) # === Build and instrument the network ======================================= t_stop = 300.0 parameters = { 'neurons': { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 4.0, # Duration of refractory period in ms. 'tau_syn_E': 5.0, # Decay time of the excitatory synaptic conductance in ms. 'tau_syn_I': 5.0, # Decay time of the inhibitory synaptic conductance in ms. 'e_rev_E': 0.0, # Reversal potential for excitatory input in mV 'e_rev_I': -70.0, # Reversal potential for inhibitory input in mV 'v_reset': -65.0, # Reset potential after a spike in mV. 'i_offset': [0.0, 0.0, 0.0, 0.0], # Offset current in nA 'v_t_star': -55.0, # Threshold baseline in mV. 'lambda0': 1.0, # Firing intensity at threshold in Hz. 'tau_eta': (1.0, 10.0, 100.0), # Time constants for spike-triggered current in ms. 'tau_gamma': (1.0, 10.0, 100.0), # Time constants for spike-frequency adaptation in ms. # the following parameters have different values for each neuron 'delta_v': [1e-6, 1e-6, 0.5, 0.5], # Threshold sharpness in mV. 'a_eta': [(0.1, 0.1, 0.1), # Post-spike increments for spike-triggered current in nA (0.0, 0.0, 0.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)], 'a_gamma': [(0.0, 0.0, 0.0), # Post-spike increments for spike-frequency adaptation in mV (5.0, 5.0, 5.0), (0.0, 0.0, 0.0), (0.0, 0.0, 0.0)], }, 'stimulus': { 'start': 20.0, 'stop': t_stop - 20.0, 'amplitude': 0.6 } } neurons = sim.Population(4, sim.GIF_cond_exp(**parameters['neurons']), initial_values={'v': -65.0, 'v_t': -55.0}) print("i_offset = ", neurons.get('i_offset')) print("v_t_star = ", neurons.get('v_t_star')) print("delta_v = ", neurons.get('delta_v')) print("tau_eta = ", neurons.get('tau_eta')) print("a_gamma = ", neurons.get('a_gamma')) electrode = sim.DCSource(**parameters['stimulus']) electrode.inject_into(neurons) neurons.record(['v', 'i_eta', 'v_t']) # === Run the simulation ===================================================== sim.run(t_stop) # === Save the results, optionally plot a figure ============================= filename = normalized_filename("Results", "gif_neuron", "pkl", options.simulator, sim.num_processes()) neurons.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") data = neurons.get_data().segments[0] v = data.filter(name="v")[0] v_t = data.filter(name="v_t")[0] i_eta = data.filter(name="i_eta")[0] Figure( Panel(v, ylabel="Membrane potential (mV)", yticks=True, ylim=[-66, -52]), Panel(v_t, ylabel="Threshold (mV)", yticks=True), Panel(i_eta, ylabel="i_eta (nA)", xticks=True, xlabel="Time (ms)", yticks=True), annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/iaf_sfa_relref/000077500000000000000000000000001415343567000171715ustar00rootroot00000000000000PyNN-0.10.0/examples/iaf_sfa_relref/README000066400000000000000000000030771415343567000200600ustar00rootroot00000000000000This directory contains example scripts using the PyNN IF_cond_exp_gsfa_grr neuron model. Specifically, it includes a PyNN implementation of the model described in: Muller, E., Meier, K., & Schemmel, J. (2004). Methods for simulating high-conductance states in neural microcircuits. Proc. of BICS2004. The excitatory neuron parameterization was then the subject of an analytical study using the adapting Markov process in: Muller, E., Buesing, L., Schemmel, J., & Meier, K. (2007). Spike-frequency adapting neural ensembles: Beyond mean adaptation and renewal theories. Neural Computation, 19, 2958-3010. The standard parameters are in: standard_neurons.yaml The iaf_sfa_network.py is a MPI enabled simulation of a 10x10x10 lattice modeling cortical layer 4, as described in Muller et. al. 2004 (above), with a transient step increase in stimulation at 1s<=t<1.2s, and can be run as follows: $ /opt/mpich2/bin/mpiexec -n 4 python iaf_sfa_network.py It produces an output figure as myFigure.pdf, which can be compared to the expected output myFigure_expected.pdf. It is interesting to Increase the connection factor ICFactorE_E = 0.12 to something like 0.16 or 0.2, and observe the spontaneous ~10Hz oscillatory activity that results from excitatory avalanches due to the network being in a supra-critical excitatory feedback regime. The period of network oscillations/bursts is determined by the time-constant of Spike-Frequency Adapation, an intrinsic neuronal self-inhibition mechanism which transiently prevents the avalanches, and thus causes them to occur at regular intervals. PyNN-0.10.0/examples/iaf_sfa_relref/backend_comparison.py000066400000000000000000000063371415343567000233750ustar00rootroot00000000000000import NeuroTools.parameters from NeuroTools import stgen import pyNN.neuron as neuron import pyNN.nest as nest pyNN_backends = {'nest': nest, 'neuron': neuron} def run(sim): """ Run the mcb simulation with known random numbers for the given simulator backend""" stg = stgen.StGen() stg.seed(12345) rateE = 6.0 # firing rate of ghost excitatory neurons rateI = 10.5 # firing rate of ghost inhibitoryneurons connectionsE = 1000.0 # number of E connections every neuron recieves connectionsI = 250.0 # number of I connections every neuron recieves tsim = 1000.0 # ms globalWeight = 0.002 # Weights of all connection in uS dt = 0.01 # simulation time step in milliseconds sim.setup(timestep=dt, min_delay=dt, max_delay=30.0, debug=True, quit_on_end=False) params = NeuroTools.parameters.ParameterSet('standard_neurons.yaml') myModel = sim.IF_cond_exp_gsfa_grr popE = sim.Population((1,), myModel, params.excitatory, label='popE') popI = sim.Population((1,), myModel, params.inhibitory, label='popI') #poissonE_params = {'rate': rateE*connectionsE, 'start': 0.0, 'duration': tsim} #poissonE_params = {'rate': rateE, 'start': 0.0, 'duration': tsim} #poissonI_params = {'rate': rateI*connectionsI, 'start': 0.0, 'duration': tsim} #poissonI_params = {'rate': rateI, 'start': 0.0, 'duration': tsim} spike_times_E = stg.poisson_generator(rateE * connectionsE, 0.0, tsim, array=True) spike_times_I = stg.poisson_generator(rateI * connectionsI, 0.0, tsim, array=True) poissonE = sim.Population((1,), cellclass=sim.SpikeSourceArray, cellparams={'spike_times': spike_times_E}, label='poissonE') poissonI = sim.Population((1,), cellclass=sim.SpikeSourceArray, cellparams={'spike_times': spike_times_I}, label='poissonI') myconn = sim.AllToAllConnector(weights=globalWeight, delays=dt) prjE_E = sim.Projection(poissonE, popE, method=myconn, target='excitatory') prjI_E = sim.Projection(poissonI, popE, method=myconn, target='inhibitory') prjE_I = sim.Projection(poissonE, popI, method=myconn, target='excitatory') prjI_I = sim.Projection(poissonI, popI, method=myconn, target='inhibitory') ## Record the spikes ## popE.record(to_file=False) popE.record_v(to_file=False) popE.record_gsyn(to_file=False) popI.record(to_file=False) popI.record_v(to_file=False) popI.record_gsyn(to_file=False) #popE.record() #popE.record_v() #popI.record() #popI.record_v() sim.run(tsim) ## Get spikes ## spikesE = popE.getSpikes() v_E = popE.get_v() g_E = popE.get_gsyn() spikesI = popI.getSpikes() v_I = popE.get_v() g_I = popI.get_gsyn() #print(spikesE) # should be about 6.0Hz print(float(len(spikesE)) / tsim * 1000.0) # should be about 10.0Hz print(float(len(spikesI)) / tsim * 1000.0) sim.end() return spikesE, spikesI, v_E, v_I, g_E, g_I results = {} for sim_name, sim in pyNN_backends.items(): print(sim_name) results[sim_name] = run(sim) #v_E = results[sim_name][2] #plot(v_E[:,1],v_E[:,2], label=sim_name) g_E = results[sim_name][4] plot(g_E[:, 1], g_E[:, 2], label=sim_name) legend() PyNN-0.10.0/examples/iaf_sfa_relref/iaf_sfa_network_INH_GAMMA.py000066400000000000000000000472071415343567000243160ustar00rootroot00000000000000""" A PyNN version of the network architecture described in: Muller, E., Meier, K., & Schemmel, J. (2004). Methods for simulating high-conductance states in neural microcircuits. Proc. of BICS2004. http://neuralensemble.org/people/eilifmuller/Publications/bics2004_mueller.pdf Script written by Lucas Sinclair & Eilif Muller. LCN, EPFL - October 2009 """ ## Import modules ## import numpy as np, pylab, time import pyNN.nest as sim import pyNN.common as common import pyNN.connectors as connectors from pyNN.utility import init_logging import nest from mpi4py import MPI import logging ###################### CLASSES ########################### class LatticeConnector(connectors.Connector): """ Each post-synaptic neuron is connected to exactly n pre-synaptic neurons chosen at random. For every connection that is made the delay is set proportional to the distance that separates the two neurons plus some random noise coming from a gamma distribution. N cannot be drawn from a random distribution. Self connections are always enabled. """ def __init__(self, weights=0.0, dist_factor=1.0, noise_factor=0.01, n=1.0): """ Create a new connector. `weights` -- The weights of all the connections made. `dist_factor` -- A factor to control the delay (conversion of distance to milliseconds of delay). `noise_factor` -- A factor to control the noise (scale of gamma distribution). `n` -- Number of connections to make for each neuron. """ connectors.Connector.__init__(self, weights, dt) self.dist_factor = dist_factor self.noise_factor = noise_factor self.n = n def connect(self, projection): """Connect-up a Projection.""" # Timers global rank timer0 = 0.0 timer1 = 0.0 timer2 = 0.0 timer3 = 0.0 timer4 = 0.0 # Recuperate variables # n = self.n dist_factor = self.dist_factor noise_factor = self.noise_factor # Do some checking # assert dist_factor >= 0 assert noise_factor >= 0 if isinstance(n, int): assert n >= 0 else: raise Exception("n must be an integer.") # Get posts and pres # listPostIDs = projection.post.local_cells listPreIDs = projection.pre.all_cells countPost = len(listPostIDs) countPre = len(listPreIDs) listPreIndexes = np.arange(countPre) listPostIndexes = map(projection.post.id_to_index, listPostIDs) # Prepare all distances # allDistances = self.space.distances(projection.post.positions, projection.pre.positions) # Get weights # weights = np.empty(n) weights[:] = self.weights is_conductance = common.is_conductance(projection.post[listPostIndexes[0]]) weights = common.check_weight(weights, projection.synapse_type, is_conductance) for i in range(len(listPostIDs)): currentPostIndex = listPostIndexes[i] currentPostID = listPostIDs[i] #currentPostIDAsList = [currentPostID] # Pick n neurons at random in pre population myTimer = time.time() chosenPresIndexes = list(np.random.permutation(np.arange(countPre))[0:n]) chosenPresIDs = list(projection.pre[chosenPresIndexes].all_cells) #if rank==0: # print(chosenPresIDs) #chosenPresIDs = chosenPresIDs.tolist() timer0 += time.time() - myTimer # Get distances myTimer = time.time() #distances = allDistances[currentPostIndex,chosenPresIndexes] distances = allDistances[currentPostIndex, chosenPresIndexes] timer1 += time.time() - myTimer # Generate gamme noise noise = np.random.gamma(1.0, noise_factor, n) # Create delays with distance and noise myTimer = time.time() delays = dist_factor * distances * (1.0 + noise) timer2 += time.time() - myTimer #delays[:] = 1.0 # Check for small and big delays myTimer = time.time() delaysClipped = np.clip(delays, common.get_min_delay(), common.get_max_delay()) howManyClipped = len((delays != delaysClipped).nonzero()[0]) if (howManyClipped > 1): print("Warning: %d of %d delays were cliped because they were either bigger than the max delay or lower than the min delay." % (howManyClipped, n)) delaysClipped = delaysClipped.tolist() timer3 += time.time() - myTimer # Connect everything up yTimer = time.time() projection._convergent_connect(chosenPresIDs, currentPostID, weights, delaysClipped) timer4 += time.time() - myTimer # Print timings if rank == 0: print("\033[2;46m" + ("Timer 0: %5.4f seconds" % timer0).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 1: %5.4f seconds" % timer1).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 2: %5.4f seconds" % timer2).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 3: %5.4f seconds" % timer3).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 4: %5.4f seconds" % timer4).ljust(60) + "\033[m") ###################### FUNCTIONS ########################### def printTimer(message): global currentTimer, rank if rank == 0: string1 = "\033[0;46m" + (message + ": ").ljust(30) + "\033[m" string2 = "\033[1;46m" + ("%5.2f" % (time.time() - currentTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) currentTimer = time.time() def printMessage(message): global rank if rank == 0: print("\033[2;46m" + (message).ljust(60) + "\033[m") ###################### MAIN BODY ########################### ## Rank for MPI ## numberOfNodes = sim.num_processes() rank = sim.rank() # Log to stderr, only warnings, errors, critical init_logging(None, num_processes=numberOfNodes, rank=rank, level=logging.WARNING) ## Start message ## if rank == 0: print("\033[1;45m" + (("Lattice Simulation").rjust(38)).ljust(60) + "\033[m") print("\033[0;44m" + ("MPI_Rank: %d " % rank + " MPI_Size: %d " % numberOfNodes).ljust(60) + "\033[m") ## Timer ## currentTimer = time.time() totalTimer = time.time() ## Default global parameters ## dt = 0.1 # simulation time step in milliseconds tinit = 500.0 # simtime over which the network is allowed to settle down tsim = 2000.0 # total simulation length in milliseconds globalWeight = 0.002 # Weights of all connection in uS latticeSize = 10 # number of neurons on one side of the cube propOfI = 0.2 # proportion of neurons that are inhibitory ## Connections ## # Rate of bkgnd # rateE_E = 6.0 # firing rate of ghost excitatory to E neurons rateE_I = 6.0 # firing rate of ghost excitatory to I neurons rateI_E = 10.0 # firing rate of ghost inhibitory to E neurons rateI_I = 10.0 # firing rate of ghost inhibitory to I neurons # Total number of connections (constant) # connectionsE_E = 1000.0 # number of E connections every E neuron recieves connectionsE_I = 1000.0 # number of E connections every I neuron recieves connectionsI_E = 250.0 # number of I connections every E neuron recieves connectionsI_I = 250.0 # number of I connections every E neuron recieves # Proportion of connections coming from inside # ICFactorE_E = 0.12 # proportion of E connections every E neuron recieves that will be converted ICFactorE_I = 0.2 # proportion of E connections every I neuron recieves that will be converted ICFactorI_E = 0.2 # proportion of I connections every E neuron recieves that will be converted ICFactorI_I = 0.2 # proportion of I connections every E neuron recieves that will be converted ## Change connections ## # Number of new connections to make # NumOfConE_E = int(connectionsE_E * ICFactorE_E) NumOfConE_I = int(connectionsE_I * ICFactorE_I) NumOfConI_E = int(connectionsI_E * ICFactorI_E) NumOfConI_I = int(connectionsI_I * ICFactorI_I) # Print out chosen values if rank == 0: print("\033[0;44m" + ("E_E:%5.2f " % ICFactorE_E + " E_I:%5.2f " % ICFactorE_I + " I_E:%5.2f " % ICFactorI_E + " I_I:%5.2f" % ICFactorI_I).ljust(60) + "\033[m") # The max distance is 15 approx printMessage("Now strating lattice simulation setup.") distanceFactor = 0.25 # How does distance convert to milliseconds of delay ? noiseFactor = 0.2 # How much do axons tend to wonder ? ## Load standard parameter file (2 neurons) ## # url_distant = "https://neuralensemble.org/svn/NeuroTools/trunk/std_params/" # param_file = "PyNN/IF_cond_exp_gsfa_grr/muller_etal2007.param" # internetPath = url_distant+param_file localPath = "./standard_neurons.yaml" import NeuroTools.parameters params = NeuroTools.parameters.ParameterSet(localPath) ## Effectively zero the refractory period ## params.excitatory.tau_refrac = dt params.inhibitory.tau_refrac = dt ## Chose the neuron type ## # Works only in NEST # Warning: don't try to use sim.cells.IF_cond_exp_gsfa_grr myModel = sim.IF_cond_exp_gsfa_grr ## Simulation creation ## # Creates a file that never closes sim.setup(timestep=dt, min_delay=dt, max_delay=30.0, debug=True, quit_on_end=False) # dynamic stimulus E->E, E->I f = np.array([rateE_E, rateE_E * 1.5, rateE_E, rateE_E]) tbins = np.array([0.0, 1000.0, 1200.0, 2000.0]) a = np.array([3.0] * 4) b = 1.0 / (f * a) ## Stochastic input preparation ## gammaE_Eparams = {'tbins': tbins, 'a': a, 'b': b} gammaE_Iparams = {'tbins': tbins, 'a': a, 'b': b} gammaE_E = sim.Population((1,), cellclass=sim.SpikeSourceInhGamma, cellparams=gammaE_Eparams) gammaE_I = sim.Population((1,), cellclass=sim.SpikeSourceInhGamma, cellparams=gammaE_Iparams) # these generators are to be silenced after the initial 500ms gammaE_E_silenced = sim.Population((1,), cellclass=sim.SpikeSourceInhGamma, cellparams=gammaE_Eparams) gammaE_I_silenced = sim.Population((1,), cellclass=sim.SpikeSourceInhGamma, cellparams=gammaE_Iparams) gammaE_I_silenced.stop = tinit gammaE_E_silenced.stop = tinit # inhibitory is Poisson poissonI_Eparams = {'rate': rateI_E * connectionsI_E, 'start': 0.0, 'duration': tsim} poissonI_Iparams = {'rate': rateI_I * connectionsI_I, 'start': 0.0, 'duration': tsim} poissonI_E = sim.Population((1,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonI_Eparams, label='poissonI_E') poissonI_I = sim.Population((1,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonI_Iparams, label='poissonI_I') ## Define popultation ## myLabelE = "Simulated excitatory adapting neurons" myLabelI = "Simulated inhibitory adapting neurons" numberOfNeuronsI = int(latticeSize**3 * propOfI) numberOfNeuronsE = int(latticeSize**3 - numberOfNeuronsI) popE = sim.Population((numberOfNeuronsE), myModel, params.excitatory, label=myLabelE) popI = sim.Population((numberOfNeuronsI), myModel, params.inhibitory, label=myLabelI) #all_cells = Assembly("All cells", popE, popI) ## Set the position in space (lattice)## np.random.seed(0) latticePerm = np.random.permutation(latticeSize**3) positionE = np.empty([3, numberOfNeuronsE]) positionI = np.empty([3, numberOfNeuronsI]) for x in np.arange(latticeSize): for y in np.arange(latticeSize): for z in np.arange(latticeSize): index = x * (latticeSize**2) + y * (latticeSize) + z currentNeuron = latticePerm[index] if currentNeuron > numberOfNeuronsE - 1: positionI[:, currentNeuron - numberOfNeuronsE] = (float(x), float(y), float(z)) else: positionE[:, currentNeuron] = (float(x), float(y), float(z)) popE.positions = positionE popI.positions = positionI ## Random seeds ## # seed which is different every time #np.random.seed(int(time.time()*10+rank)) # seeds which are same every time np.random.seed(rank) from pyNN.random import NumpyRNG # some random seeds which are different everytime # and different for each node. #seeds = np.arange(numberOfNodes) + int((time.time()*100)%2**32) # seeds which are same every time, different for each node seeds = np.arange(numberOfNodes) # bcast, as we can't be sure each node has the same time, and therefore # different seeds. This way, all nodes get the list from rank=0. seeds = MPI.COMM_WORLD.bcast(seeds) #rng = NumpyRNG(seed=seeds[rank], parallel_safe=False, rank=rank, # num_processes=numberOfNodes) nest.SetKernelStatus({'rng_seeds': list(seeds)}) ## Connections ## #myConnectorE = sim.AllToAllConnector(weights=globalWeight, delays=0.1) myConnectorI = sim.AllToAllConnector(weights=globalWeight, delays=0.1) # Connectors which make the specified number of connections from pre to post # the inh_gamma_generator sends a independent realization to each post connection # So this is "as if" there where "num connection" independent inh_gamma_generators # impinging on the target myConnectorE_E = sim.FixedNumberPreConnector(int(connectionsE_E) - NumOfConE_E, weights=globalWeight, delays=0.1) myConnectorE_I = sim.FixedNumberPreConnector(int(connectionsE_I) - NumOfConE_I, weights=globalWeight, delays=0.1) # a sub-set of the inh_gamma_generaters are silenced after a time "tinit" myConnectorE_E_silenced = sim.FixedNumberPreConnector(NumOfConE_E, weights=globalWeight, delays=0.1) myConnectorE_I_silenced = sim.FixedNumberPreConnector(NumOfConE_I, weights=globalWeight, delays=0.1) #myConnectorI = sim.AllToAllConnector(weights=globalWeight, delays=0.1) # InhGamma Generators need "_S" (selective) type synapses # Passing this class to the Projection in a ComposedSynapseType object # is how to get them: #sd = NativeSynapseType('static_synapse_S') #prjE_E = sim.Projection(gammaE_E, popE, method=myConnectorE_E, target='excitatory', synapse_type=sd) #prjE_I = sim.Projection(gammaE_I, popI, method=myConnectorE_I, target='excitatory', synapse_type=sd) prjE_E = sim.Projection(gammaE_E, popE, method=myConnectorE_E, target='excitatory') prjE_I = sim.Projection(gammaE_I, popI, method=myConnectorE_I, target='excitatory') # silenced excitatory input prjE_E = sim.Projection(gammaE_E_silenced, popE, method=myConnectorE_E_silenced, target='excitatory', synapse_type=sd) prjE_I = sim.Projection(gammaE_I_silenced, popI, method=myConnectorE_I_silenced, target='excitatory', synapse_type=sd) # return to default synapse type (non-selective) sd = None #prjI_E = sim.Projection(poissonI_E, popE, method=myConnectorI, target='inhibitory', synapse_type=sd) #prjI_I = sim.Projection(poissonI_I, popI, method=myConnectorI, target='inhibitory', synapse_type=sd) prjI_E = sim.Projection(poissonI_E, popE, method=myConnectorI, target='inhibitory') prjI_I = sim.Projection(poissonI_I, popI, method=myConnectorI, target='inhibitory') ## Record the spikes ## popE.record(to_file=False) popI.record(to_file=False) printTimer("Time for setup part") ###################### RUN PART ########################### ## Run the simulation without inter-connection ## printMessage("Now running without inter-lattice connections.") sim.run(int(tinit)) printTimer("Time for first half of run") # Lower the external network "ghost" processes according to how many connections of that # type were added. poissonI_E.rate = rateI_E * (connectionsI_E - NumOfConI_E) poissonI_I.rate = rateI_I * (connectionsI_I - NumOfConI_I) # E->X connections are running inh_gamma_generators, so they are handled differently # i.e. a subset of them have a stop time of tinit. #poissonE_E.cellparams["rate"] = rateE_E * (connectionsE_E - NumOfConE_E) #poissonE_I.cellparams["rate"] = rateE_I * (connectionsE_I - NumOfConE_I) # Prepare the connectors # myConnectorE_E = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConE_E) myConnectorE_I = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConE_I) myConnectorI_E = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConI_E) myConnectorI_I = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConI_I) # Execute the projections # printMessage("Now changing E_E connections for " + str(NumOfConE_E) + " new connections") prjLatticeE_E = sim.Projection(popE, popE, method=myConnectorE_E, target='excitatory', synapse_type=sd) printTimer("Time for E_E connections") printMessage("Now changing E_I connections for " + str(NumOfConE_I) + " new connections") prjLatticeE_I = sim.Projection(popE, popI, method=myConnectorE_I, target='excitatory', synapse_type=sd) printTimer("Time for E_I connections") printMessage("Now changing I_E connections for " + str(NumOfConI_E) + " new connections") prjLatticeI_E = sim.Projection(popI, popE, method=myConnectorI_E, target='inhibitory', synapse_type=sd) printTimer("Time for I_E connections") printMessage("Now changing I_I connections for " + str(NumOfConI_I) + " new connections") prjLatticeI_I = sim.Projection(popI, popI, method=myConnectorI_I, target='inhibitory', synapse_type=sd) printTimer("Time for I_I connections") ## Run the simulation once lattice is inter-connected ## printMessage("Now running with inter-lattice connections.") sim.run(int(tsim - tinit)) printTimer("Time for second half of run") simTimer = time.time() ###################### END PART ########################### printMessage("Now creating graph.") time.sleep(2) ## Get spikes ## spikesE = popE.getSpikes() spikesI = popI.getSpikes() ## Process them ## if rank == 0: highestIndexE = np.max(spikesE[:, 0]) listNeuronsE = list(spikesE[:, 0]) listTimesE = list(spikesE[:, 1]) listNeuronsI = list(spikesI[:, 0] + highestIndexE) listTimesI = list(spikesI[:, 1]) ## Kill the bad dir ## #print("Tempdir: ", sim.tempdirs) #theBadDir = sim.tempdirs #thePipe = os.popen("lsof -F f +D " + theBadDir[0]) #theText = thePipe.read() #sts = thePipe.close() #m = re.search('\nf(\w+)\n', theText) #theFD = m.group(1) #os.close(int(theFD)) ## Close the simulation ## sim.end() ###################### PLOTTING ########################### if rank == 0: ## Graph Burst ## pylab.figure() allSpikes = listTimesE + listTimesI allNeurons = listNeuronsE + listNeuronsI pylab.plot(allSpikes, allNeurons, 'r.', markersize=1, label='Action potentials') pylab.xlabel("Time [milliseconds]") pylab.ylabel("Neuron (first E, then I)") pylab.title(("$C_{E\\rightarrow E}=%.2f$, $C_{E\\rightarrow I}=%.2f$," + "$C_{I\\rightarrow E}=%.2f$, $C_{I\\rightarrow I}=%.2f$,") % (ICFactorE_E, ICFactorE_I, ICFactorI_E, ICFactorI_I)) pylab.suptitle("Layer 4 model with Connection Factors:") #pylab.legend() axisHeight = pylab.axis()[3] pylab.vlines(tinit, 0.0, axisHeight / 8, linewidth="4", color='k', linestyles='solid') pylab.plot(tbins, axisHeight / 8 / np.max(f) * f, linewidth="2", color='b', linestyle='steps-post') if numberOfNodes != 0: pylab.savefig("myFigure.pdf") #os.system("display myFigure.pdf &") #os.popen("display myFigure.pdf &") #pid = os.spawnlp(os.P_NOWAIT, "display", "myFigure.pdf") ## Total times ## printTimer("Time for graphing results") string1 = "\033[0;44m" + ("Total simulation time: ").ljust(30) + "\033[m" string2 = "\033[1;44m" + ("%5.2f" % (simTimer - totalTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) string1 = "\033[0;44m" + ("Total time: ").ljust(30) + "\033[m" string2 = "\033[1;44m" + ("%5.2f" % (time.time() - totalTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) PyNN-0.10.0/examples/iaf_sfa_relref/iaf_sfa_network_STATIC.py000066400000000000000000000425661415343567000237700ustar00rootroot00000000000000""" A PyNN version of the network architecture described in: Muller, E., Meier, K., & Schemmel, J. (2004). Methods for simulating high-conductance states in neural microcircuits. Proc. of BICS2004. http://neuralensemble.org/people/eilifmuller/Publications/bics2004_mueller.pdf Script written by Lucas Sinclair & Eilif Muller. LCN, EPFL - October 2009 """ ## Import modules ## import numpy as np, pylab, math, time, os, re #import pyNN.nest as sim from mpi4py import MPI import pyNN.neuron as sim import pyNN.common as common import pyNN.connectors as connectors import pyNN.space as space from pyNN.utility import init_logging from pyNN import standardmodels #import nest import logging ###################### CLASSES ########################### class LatticeConnector(connectors.Connector): """ Each post-synaptic neuron is connected to exactly n pre-synaptic neurons chosen at random. For every connection that is made the delay is set proportional to the distance that separates the two neurons plus some random noise coming from a gamma distribution. N cannot be drawn from a random distribution. Self connections are always enabled. """ def __init__(self, weights=0.0, dist_factor=1.0, noise_factor=0.01, n=1.0): """ Create a new connector. `weights` -- The weights of all the connections made. `dist_factor` -- A factor to control the delay (conversion of distance to milliseconds of delay). `noise_factor` -- A factor to control the noise (scale of gamma distribution). `n` -- Number of connections to make for each neuron. """ connectors.Connector.__init__(self, weights, dist_factor * 1.0) self.dist_factor = dist_factor self.noise_factor = noise_factor self.n = n def connect(self, projection): """Connect-up a Projection.""" # Timers global rank timer0 = 0.0 timer1 = 0.0 timer2 = 0.0 timer3 = 0.0 timer4 = 0.0 # Recuperate variables # n = self.n dist_factor = self.dist_factor noise_factor = self.noise_factor # Do some checking # assert dist_factor >= 0 assert noise_factor >= 0 if isinstance(n, int): assert n >= 0 else: raise Exception("n must be an integer.") # Get posts and pres # listPostIDs = projection.post.local_cells listPreIDs = projection.pre.all_cells countPost = len(listPostIDs) countPre = len(listPreIDs) listPreIndexes = np.arange(countPre) listPostIndexes = map(projection.post.id_to_index, listPostIDs) # Prepare all distances # allDistances = self.space.distances(projection.post.positions, projection.pre.positions) # Get weights # weights = np.empty(n) weights[:] = self.weights is_conductance = common.is_conductance(projection.post[listPostIndexes[0]]) weights = common.check_weight(weights, projection.synapse_type, is_conductance) np.random.seed(12345) for i in range(len(listPostIDs)): currentPostIndex = listPostIndexes[i] currentPostID = listPostIDs[i] #currentPostIDAsList = [currentPostID] # Pick n neurons at random in pre population myTimer = time.time() chosenPresIndexes = list(np.random.permutation(np.arange(countPre))[0:n]) chosenPresIDs = list(projection.pre[chosenPresIndexes].all_cells) #if rank==0: # print(chosenPresIDs) #chosenPresIDs = chosenPresIDs.tolist() timer0 += time.time() - myTimer # Get distances myTimer = time.time() #distances = allDistances[currentPostIndex,chosenPresIndexes] distances = allDistances[currentPostIndex, chosenPresIndexes] timer1 += time.time() - myTimer # Generate gamme noise noise = np.random.gamma(1.0, noise_factor, n) # Create delays with distance and noise myTimer = time.time() delays = dist_factor * distances * (1.0 + noise) timer2 += time.time() - myTimer #delays[:] = 1.0 # Check for small and big delays myTimer = time.time() delaysClipped = np.clip(delays, sim.get_min_delay(), sim.get_max_delay()) howManyClipped = len((delays != delaysClipped).nonzero()[0]) if (howManyClipped > 1): print("Warning: %d of %d delays were cliped because they were either bigger than the max delay or lower than the min delay." % (howManyClipped, n)) delaysClipped = delaysClipped.tolist() timer3 += time.time() - myTimer # Connect everything up yTimer = time.time() projection._convergent_connect(chosenPresIDs, currentPostID, weights, delaysClipped) timer4 += time.time() - myTimer # Print timings if rank == 0: print("\033[2;46m" + ("Timer 0: %5.4f seconds" % timer0).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 1: %5.4f seconds" % timer1).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 2: %5.4f seconds" % timer2).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 3: %5.4f seconds" % timer3).ljust(60) + "\033[m") print("\033[2;46m" + ("Timer 4: %5.4f seconds" % timer4).ljust(60) + "\033[m") ###################### FUNCTIONS ########################### def printTimer(message): global currentTimer, rank if rank == 0: string1 = "\033[0;46m" + (message + ": ").ljust(30) + "\033[m" string2 = "\033[1;46m" + ("%5.2f" % (time.time() - currentTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) currentTimer = time.time() def printMessage(message): global rank if rank == 0: print("\033[2;46m" + (message).ljust(60) + "\033[m") ###################### MAIN BODY ########################### ## Rank for MPI ## numberOfNodes = sim.num_processes() rank = sim.rank() # Log to stderr, only warnings, errors, critical init_logging('sim.log', num_processes=numberOfNodes, rank=rank, level=logging.DEBUG) ## Start message ## if rank == 0: print("\033[1;45m" + (("Lattice Simulation").rjust(38)).ljust(60) + "\033[m") print("\033[0;44m" + ("MPI_Rank: %d " % rank + " MPI_Size: %d " % numberOfNodes).ljust(60) + "\033[m") ## Timer ## currentTimer = time.time() totalTimer = time.time() ## Default global parameters ## dt = 0.1 # simulation time step in milliseconds tinit = 500.0 # simtime over which the network is allowed to settle down tsim = 2000.0 # total simulation length in milliseconds globalWeight = 0.002 # Weights of all connection in uS latticeSize = 8 # number of neurons on one side of the cube propOfI = 0.2 # proportion of neurons that are inhibitory ## Connections ## # Rate of bkgnd # rateE_E = 6.0 # firing rate of ghost excitatory to E neurons rateE_I = 6.0 # firing rate of ghost excitatory to I neurons rateI_E = 10.0 # firing rate of ghost inhibitory to E neurons rateI_I = 10.0 # firing rate of ghost inhibitory to I neurons # Total number of connections (constant) # connectionsE_E = 1000.0 # number of E connections every E neuron recieves connectionsE_I = 1000.0 # number of E connections every I neuron recieves connectionsI_E = 250.0 # number of I connections every E neuron recieves connectionsI_I = 250.0 # number of I connections every E neuron recieves # Proportion of connections coming from inside # ICFactorE_E = 0.12 # proportion of E connections every E neuron recieves that will be converted ICFactorE_I = 0.2 # proportion of E connections every I neuron recieves that will be converted ICFactorI_E = 0.2 # proportion of I connections every E neuron recieves that will be converted ICFactorI_I = 0.2 # proportion of I connections every E neuron recieves that will be converted ## Change connections ## # Number of new connections to make # NumOfConE_E = int(connectionsE_E * ICFactorE_E) NumOfConE_I = int(connectionsE_I * ICFactorE_I) NumOfConI_E = int(connectionsI_E * ICFactorI_E) NumOfConI_I = int(connectionsI_I * ICFactorI_I) # Print out chosen values if rank == 0: print("\033[0;44m" + ("E_E:%5.2f " % ICFactorE_E + " E_I:%5.2f " % ICFactorE_I + " I_E:%5.2f " % ICFactorI_E + " I_I:%5.2f" % ICFactorI_I).ljust(60) + "\033[m") # The max distance is 15 approx printMessage("Now strating lattice simulation setup.") distanceFactor = 0.25 # How does distance convert to milliseconds of delay ? noiseFactor = 0.2 # How much do axons tend to wonder ? ## Load standard parameter file (2 neurons) ## # url_distant = "https://neuralensemble.org/svn/NeuroTools/trunk/std_params/" # param_file = "PyNN/IF_cond_exp_gsfa_grr/muller_etal2007.param" # internetPath = url_distant+param_file localPath = "./standard_neurons.yaml" import NeuroTools.parameters params = NeuroTools.parameters.ParameterSet(localPath) ## Effectively zero the refractory period ## params.excitatory.tau_refrac = dt params.inhibitory.tau_refrac = dt ## Chose the neuron type ## # Works only in NEST and NEURON # Warning: don't try to use sim.cells.IF_cond_exp_gsfa_grr myModel = sim.IF_cond_exp_gsfa_grr ## Simulation creation ## # Creates a file that never closes sim.setup(timestep=dt, min_delay=dt, max_delay=30.0, default_maxstep=distanceFactor, debug=True, quit_on_end=False) ## Define popultation ## myLabelE = "Simulated excitatory adapting neurons" myLabelI = "Simulated inhibitory adapting neurons" numberOfNeuronsI = int(latticeSize**3 * propOfI) numberOfNeuronsE = int(latticeSize**3 - numberOfNeuronsI) popE = sim.Population((numberOfNeuronsE,), myModel, params.excitatory, label=myLabelE) popI = sim.Population((numberOfNeuronsI,), myModel, params.inhibitory, label=myLabelI) #all_cells = Assembly("All cells", popE, popI) # excitatory Poisson poissonE_Eparams = {'rate': rateE_E * connectionsE_E, 'start': 0.0, 'duration': tsim} poissonE_Iparams = {'rate': rateE_I * connectionsE_I, 'start': 0.0, 'duration': tsim} poissonE_E = sim.Population((numberOfNeuronsE,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonE_Eparams, label='poissonE_E') poissonE_I = sim.Population((numberOfNeuronsI,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonE_Iparams, label='poissonE_I') # inhibitory Poisson poissonI_Eparams = {'rate': rateI_E * connectionsI_E, 'start': 0.0, 'duration': tsim} poissonI_Iparams = {'rate': rateI_I * connectionsI_I, 'start': 0.0, 'duration': tsim} poissonI_E = sim.Population((numberOfNeuronsE,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonI_Eparams, label='poissonI_E') poissonI_I = sim.Population((numberOfNeuronsI,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonI_Iparams, label='poissonI_I') ## Set the position in space (lattice)## np.random.seed(0) latticePerm = np.random.permutation(latticeSize**3) positionE = np.empty([3, numberOfNeuronsE]) positionI = np.empty([3, numberOfNeuronsI]) for x in np.arange(latticeSize): for y in np.arange(latticeSize): for z in np.arange(latticeSize): index = x * (latticeSize**2) + y * (latticeSize) + z currentNeuron = latticePerm[index] if currentNeuron > numberOfNeuronsE - 1: positionI[:, currentNeuron - numberOfNeuronsE] = (float(x), float(y), float(z)) else: positionE[:, currentNeuron] = (float(x), float(y), float(z)) popE.positions = positionE popI.positions = positionI ## Random seeds ## # seed which is different every time #np.random.seed(int(time.time()*10+rank)) # seeds which are same every time np.random.seed(rank) from pyNN.random import NumpyRNG # some random seeds which are different everytime # and different for each node. #seeds = np.arange(numberOfNodes) + int((time.time()*100)%2**32) # seeds which are same every time, different for each node seeds = np.arange(numberOfNodes) # bcast, as we can't be sure each node has the same time, and therefore # different seeds. This way, all nodes get the list from rank=0. seeds = MPI.COMM_WORLD.bcast(seeds) #rng = NumpyRNG(seed=seeds[rank], parallel_safe=False, rank=rank, # num_processes=numberOfNodes) #nest.SetKernelStatus({'rng_seeds': list(seeds)}) myconn = sim.OneToOneConnector(weights=globalWeight, delays=dt) prjE_E = sim.Projection(poissonE_E, popE, method=myconn, target='excitatory') prjE_I = sim.Projection(poissonE_I, popI, method=myconn, target='excitatory') prjI_E = sim.Projection(poissonI_E, popE, method=myconn, target='inhibitory') prjI_I = sim.Projection(poissonI_I, popI, method=myconn, target='inhibitory') ## Record the spikes ## popE.record(to_file=False) popI.record(to_file=False) printTimer("Time for setup part") ###################### RUN PART ########################### ## Run the simulation without inter-connection ## printMessage("Now running without inter-lattice connections.") sim.run(int(tinit)) printTimer("Time for first half of run") # Lower the external network "ghost" processes according to how many connections of that # type were added. poissonI_E.rate = rateI_E * (connectionsI_E - NumOfConI_E) poissonI_I.rate = rateI_I * (connectionsI_I - NumOfConI_I) poissonE_E.rate = rateE_E * (connectionsE_E - NumOfConE_E) poissonE_I.rate = rateE_I * (connectionsE_I - NumOfConE_I) # Prepare the connectors # myConnectorE_E = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConE_E) myConnectorE_I = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConE_I) myConnectorI_E = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConI_E) myConnectorI_I = LatticeConnector(weights=globalWeight, dist_factor=distanceFactor, noise_factor=noiseFactor, n=NumOfConI_I) # Execute the projections # printMessage("Now changing E_E connections for " + str(NumOfConE_E) + " new connections") prjLatticeE_E = sim.Projection(popE, popE, method=myConnectorE_E, target='excitatory') printTimer("Time for E_E connections") printMessage("Now changing E_I connections for " + str(NumOfConE_I) + " new connections") prjLatticeE_I = sim.Projection(popE, popI, method=myConnectorE_I, target='excitatory') printTimer("Time for E_I connections") printMessage("Now changing I_E connections for " + str(NumOfConI_E) + " new connections") prjLatticeI_E = sim.Projection(popI, popE, method=myConnectorI_E, target='inhibitory') printTimer("Time for I_E connections") printMessage("Now changing I_I connections for " + str(NumOfConI_I) + " new connections") prjLatticeI_I = sim.Projection(popI, popI, method=myConnectorI_I, target='inhibitory') printTimer("Time for I_I connections") ## Run the simulation once lattice is inter-connected ## printMessage("Now running with inter-lattice connections.") sim.run(int(tsim - tinit)) printTimer("Time for second half of run") simTimer = time.time() ###################### END PART ########################### printMessage("Now creating graph.") time.sleep(2) ## Get spikes ## spikesE = popE.getSpikes() popE.printSpikes('spikes.dat') spikesI = popI.getSpikes() ## Process them ## if rank == 0: highestIndexE = np.max(spikesE[:, 0]) listNeuronsE = list(spikesE[:, 0]) listTimesE = list(spikesE[:, 1]) listNeuronsI = list(spikesI[:, 0] + highestIndexE) listTimesI = list(spikesI[:, 1]) ## Kill the bad dir ## #print("Tempdir: ", sim.tempdirs) #theBadDir = sim.tempdirs #thePipe = os.popen("lsof -F f +D " + theBadDir[0]) #theText = thePipe.read() #sts = thePipe.close() #m = re.search('\nf(\w+)\n', theText) #theFD = m.group(1) #os.close(int(theFD)) ## Close the simulation ## sim.end() ###################### PLOTTING ########################### if rank == 0: ## Graph Burst ## pylab.figure() allSpikes = listTimesE + listTimesI allNeurons = listNeuronsE + listNeuronsI pylab.plot(allSpikes, allNeurons, 'r.', markersize=1, label='Action potentials') pylab.xlabel("Time [milliseconds]") pylab.ylabel("Neuron (first E, then I)") pylab.title(("$C_{E\\rightarrow E}=%.2f$, $C_{E\\rightarrow I}=%.2f$," + "$C_{I\\rightarrow E}=%.2f$, $C_{I\\rightarrow I}=%.2f$,") % (ICFactorE_E, ICFactorE_I, ICFactorI_E, ICFactorI_I)) pylab.suptitle("Layer 4 model with Connection Factors:") #pylab.legend() axisHeight = pylab.axis()[3] pylab.vlines(tinit, 0.0, axisHeight / 8, linewidth="4", color='k', linestyles='solid') #pylab.plot(tbins,axisHeight/8/np.max(f)*f,linewidth="2",color='b',linestyle='steps-post') if numberOfNodes != 0: pylab.savefig("myFigure.pdf") #os.system("display myFigure.pdf &") #os.popen("display myFigure.pdf &") #pid = os.spawnlp(os.P_NOWAIT, "display", "myFigure.pdf") ## Total times ## printTimer("Time for graphing results") string1 = "\033[0;44m" + ("Total simulation time: ").ljust(30) + "\033[m" string2 = "\033[1;44m" + ("%5.2f" % (simTimer - totalTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) string1 = "\033[0;44m" + ("Total time: ").ljust(30) + "\033[m" string2 = "\033[1;44m" + ("%5.2f" % (time.time() - totalTimer) + " seconds").rjust(30) + "\033[m" print(string1 + string2) PyNN-0.10.0/examples/iaf_sfa_relref/mcb.py000066400000000000000000000044531415343567000203120ustar00rootroot00000000000000import NeuroTools.parameters #import pyNN.neuron as sim import pyNN.nest as sim from time import time ### Global Parameters ### rateE = 6.0 # firing rate of ghost excitatory neurons rateI = 10.5 # firing rate of ghost inhibitoryneurons connectionsE = 1000.0 # number of E connections every neuron recieves connectionsI = 250.0 # number of I connections every neuron recieves tsim = 10000.0 # ms globalWeight = 0.002 # Weights of all connection in uS dt = 0.01 # simulation time step in milliseconds sim.setup(timestep=dt, min_delay=dt, max_delay=30.0, debug=True, quit_on_end=False) ### Neurons ### params = NeuroTools.parameters.ParameterSet('standard_neurons.yaml') myModel = sim.IF_cond_exp_gsfa_grr popE = sim.Population((1,), myModel, params.excitatory, label='popE') popI = sim.Population((1,), myModel, params.inhibitory, label='popI') ### Poisson input ### poissonE_params = {'rate': rateE * connectionsE, 'start': 0.0, 'duration': tsim} #poissonE_params = {'rate': rateE, 'start': 0.0, 'duration': tsim} poissonI_params = {'rate': rateI * connectionsI, 'start': 0.0, 'duration': tsim} #poissonI_params = {'rate': rateI, 'start': 0.0, 'duration': tsim} poissonE = sim.Population((1,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonE_params, label='poissonE') poissonI = sim.Population((1,), cellclass=sim.SpikeSourcePoisson, cellparams=poissonI_params, label='poissonI') myconn = sim.AllToAllConnector(weights=globalWeight, delays=dt) ### Connections ### prjE_E = sim.Projection(poissonE, popE, method=myconn, target='excitatory') prjI_E = sim.Projection(poissonI, popE, method=myconn, target='inhibitory') prjE_I = sim.Projection(poissonE, popI, method=myconn, target='excitatory') prjI_I = sim.Projection(poissonI, popI, method=myconn, target='inhibitory') ## Record the spikes ## popE.record(to_file=False) popE.record_v(to_file=False) popI.record(to_file=False) popE.record_gsyn(to_file=False) t1 = time() sim.run(tsim) t2 = time() print("Elapsed %f seconds." % (t2 - t1,)) ## Get spikes ## spikesE = popE.getSpikes() v_E = popE.get_v() gsyn = popE.get_gsyn() spikesI = popI.getSpikes() #print(spikesE) # should be about 6.0Hz print(float(len(spikesE)) / tsim * 1000.0) # should be about 10.0Hz print(float(len(spikesI)) / tsim * 1000.0) sim.end() PyNN-0.10.0/examples/iaf_sfa_relref/myFigure_expected.pdf000066400000000000000000002617541415343567000233530ustar00rootroot00000000000000%PDF-1.4 % 1 0 obj << /Type /Catalog /Pages 3 0 R >> endobj 2 0 obj << /CreationDate (D:20101210172420+02'00') /Producer (matplotlib pdf backend) /Creator (matplotlib 0.99.1.1, http://matplotlib.sf.net) >> endobj 8 0 obj << /Pattern 6 0 R /XObject 7 0 R /Font 4 0 R /ExtGState 5 0 R /ProcSet [ /PDF /Text /ImageB /ImageC /ImageI ] >> endobj 10 0 obj << /Contents 9 0 R /Type /Page /Resources 8 0 R /Parent 3 0 R /MediaBox [ 0 0 576 432 ] >> endobj 9 0 obj << /Filter /FlateDecode /Length 11 0 R >> stream x͎,۲_O/PIfVvґh :3"<4Uczo.m?|}}~~_G_}q~9\ߞ#YG?O\Or~ߨ~~~Ώ}o3vnxG.zLu=e}\uO6?ozy?^o^{?ӟ?rYO|۟~~GE|yKz4dӭ-63q~7ɟ_cK#zŷx? x񓞮^|=uݝ;~ =1ߪC>rWky|7Ozs~__~{G_ƭu&>Ӽ &^+_}_:ph)&gx|+KWbJtg9\_^|}s>ih8>ssN[}|_ ^uh}_K_|Ώo O:;? fNOd~܍uvm==ֱ}Jz~O֣mjQ^fJm,TOv]i?g>+Wrmbj߯uz]륹?:Wn/N{8~UodmMir^Du3bnl*ѠN.W)[գ_’,*Up#?AzHwRGjn'7ܕ[_g+?EI$~AGvTJ~vVj/ \7떃|zw2U|lRgwwmC[uf4*Onl*~^o)@S Pu=达 O7 LǸg/?'(\GHE86*[$H~:W+A7@?# !-o\BRB~+uaorIqC`>SmLѵ?qPR~RB] X >bƛ wh>z䘕>~}Z֏:JI%ʌ->nK <#4e:_&_ M5gx' U3iA'ջ^ډDQ_߫Wu.ACY k#@}M#M|_~[سirzև`$ '=q-z~-ήzt]ӭK\~.=͠A|p{yqkT^w j?E8p0\:,gN$UyR—F)G!%+H[*)j~O];9c2o]}\xw[Y=$ nD;и-EP6q3 :&7u(U4f =e2uA!B޷s{ G_V/m;xbs(KsQ;|6ZƪFXN=h&C=;YO(BS#ԌU%SzdQ%Sޏ/!f%Dh>[W2pt+үhˌZnh3 0 'xP W =>Nš8F+=0 u>~f}f0|?a,S[``(B'#?aX8znB]Ln֓Y?422ɺ]|TKMn{L"~o%} ?"zbr{FB`*EFTu$P`Pdb@ @b49 oOHs|nm _XZ)&N?`Taz뻴g?(WNE8\C (X8 Abu;ǜ`{\_>sA{yE&5 fh=^ i ̻G oщqˉ-junܣ[(v깰Tzi;Kx'H&- P;mv;JH]q]R $?@5*̴s!1&x_WdjڂW8wܪx 㵪'%3cU$4eBxÊ20Qi=ȅ8yI^- `. P(l T_%1n*^+ :%R}ڬ;"P;̛oSâi1/MN Hyl4Z;CQ{3N ƿL(r xEh^K:V#\]j5Ct+|Xf/01k+@1ahh3N~i RB0m73ρ =]eli^禒ysM|N u3ϯHD f*@?x!C>bEu %3<"?2KE=Q-U7yn eڭ!3 ì׵=.ڽgR$zUu5]"v#q"nsĄ!`""tҷǺR-(^pZ :Ynem/9N v$ U-yhW7v" ʪƼD:by9\fue춫v@\сK`w;lԑp):{Yzn0mMDwoX . 2VX`b R\򊔸'0{4U.KT㔂ds".Č= 41C4JϞSD!,l=2@uRx,ЋጀİqHoh0YakLC*OxD凕Zɳ֞-pz9c /BD*Ksn;Vp,T"a-jzک2!ռwaENTg}8+UQx *ʠI8 a/nhRIT"p(8~$( : 1!v|ʪ &F*!)PO[ZЇ$W'sZ/V80V_crL$gNfPs\9FO~ ¢^4(|͔yM-Dat05 (i8J Ke֯ۺslxDOr'Xj+^[J +CVRw N ( \*T+צ;ad4;I ̾v$ui S /2G8ߡ*>gkFNvQ v?QR<~ Py$ӊ=>JN/nh`^̗ٹPb?U#>N5yOͼ&ڬLR%3$HZEY$4fzn^(R$D'tA2\zA`Jg{HoECkP d'מIō}^s]FOv%s7&) PPKVIlbw M̭v]* TMLse0P !!bY.lsmdsWupgc25b.NۉQ!3 g^ﷺ^@X{%sڢrٌDZ~1rH+@߭_K^*dB 4Y?w ! R$$ɪDqX鯃%}5`"XXy17YrØR މ@K&]`5 EOd1 tiPpP*Kj7x,X?-nU0ʛ43y=Ɣqv=5*C`-, 6pz.n=nֱ?j|b1YLg럾68&-*?^f֓m[ z]֐<hf66#TXA k^wcw{ U~=/a;!jMftMڡ%*WVpl y< mյ2T1Fiaj쉼m[CLAz_7aH-B^Ϸ!TEnD2G6BMOx4y #&4M.<s #Fp]|&Pڳژ76:*Y%kwfL%GNh@&/wLj}B94`$jҢrINNa؞U N;.^N0E[hUlgu $yDC]Usu ,';%pBeqA\r'EirG+(,JM3 *SA /88o4zT-_ޥ]{ôU|Qv|Kѐ"rK"-!sh=/ZF9NVX+\?*Pey.235MTZ~y=Ԓ~yX?:"TT+:mɚ@Ơ$2M%ir -5Q uʉ%d8h _e3M<NI\~C᯷VSݺɨ*4,0؁q=V^tK J9sJ,6#HƐ=`Q@d몣G=":>־KȰGɲ+ ɵy3DcPo %mg>@C +?jWPRxXI܄%ooFΠl0;@d!zL?G$)=@>͌&|RpSCY'hF60A~8AS-Z%ؐ. Ygߗl:xv.7y],|#DVgC@ij!~*&sz=l@`p!p"8,޾"T<@eZ>\9knh`xl+DfGV`q˶$AZ鹽 y hPF%3ӏ捻&`7T-_d e+-*虛jH<zvťJ$1" v4 x0XeڿԭW]u{UZWl]5HR"r, =li?by'-CVejIݜ VݡYU]TI_=4c&^ZEa\W7ZzP,R$: "Zh@3N%'lA@hc솎٢ (_Z(y5(I{za13åʵYׂ1i dP%C}ju* l"OB*)ZJcj] lgRq]ᦷmUU=؅(I0 #( bak~DB#Z&E__F-f9=D4f% Jp@D_`mTGSG}R ptt!oMV#Y!d>XAfkP|n TS߂}\hT߈npD &o!8 _OTO¡KA;}l p4=vz[t*?02ǔ(q^]N3րH)=~RyS OSkcȔU̸g"ZGW@VYz.A(F#X㱂 #hYjP.0C%4ԉƓlD!+PwMUya0mB1!>xnRyytմc 2`}"e1V: Ŀ#~Oĭá8ۨSoZ5D`46X\ Ҙ&*31T1 HDhlĒ膠 "]܏vp ނ<( XecBi ?8)J4½^?ns_ /~7$Y.c(Nbw?Mh u 7>O^3hjk.ԟj^Ͳ\Ud" wد져%?Qۿ{g4G ?N&Y:4AP}puZ&( VJ-vm%Ph(:d~IQ͏Ef;0Hvհ Z-鮃ԏ&DH@jn F#z E ZDj?YAKǬ E=>~j \JV,  $$Bs=Yk%d?~@Tjz&yVmWvK} rQ; ЌGP(G@ۻ؄ځYԏ-],CsSʲAL@DV3t2TDm$eɢ'h4J5>HVOlhe½5x[gc#aX]6PK lT:j+MNSiYO׾Eiɠ$"e.d<䏒I %&r U!s ȫ CFDOT6Eu Ɏ%QqM G:>z%^\Gz!Q yv:> ]Lc0z_\_Ljnhnd$snTSsw`D ? ׇnol0U4i3vɈgă]}tWC Q(_6~СhV{\n .(+w*([ZI *_l*pάp6:7vٝR~K未`EI6oWdB>|2 2h]}qXyݏDYuHZez*K\*{`^voqay4VUߛD?}\ \o sVprj;Z2>uL"KPtHjV"~R(Rj=quz_$yLe>]M2:b0#5}3CH6D 2OKDdhvFVbzWfg|(T@{jd3P-A<8Q>*2h|`T(pYAbjBp& IBbv+٤FDQtf)%_dt[}+yn5-Z8 Ūpg>en`$eR9V؃N cY4} (jHntHMyq[q/ 4d6 IR…ħ _NJ +~RKYWSQ7hɨ(BV=rVJ_ -X;5*2mul̀PIitbiT;PjeAeR0Di_QL;^H;ku|#DzBA\ *s4QD~ Jz{[HݦsTPAm|{О%)p 5!w;-z\ˡrOl^gHЯU﫲8%\O3\ȕ?5ԣ C% C 1/dzo!j~WN*+O3ާ=^Q] Bz4,5C"Ou3lVEw@z+LN]~~JS,puCNt#s6%L)Wyӆ>, ĝIu@(@;3ʝn$Yy {GH xBÒJ1]&]:GչG^lE礭EW||!ሄ]Bs3.z C/Wi늇c cERkLD\&0SQ:UKۢY֥l+)Af-1B7R,\PVr1ҫ}}v/!yFAu^Ok ь\CA95_ hR#% ޹`a7$atxnlB $qI(ؼNz$}ʃVzG:ζ\Hw)'guŊۃG!%D(d9l=C'6B9 o7B%4Fs"3U/I͙| u-"X y٠*!-l܍IᘫJ3).Q

Α7=P[&m(yxՕ]lx3z-$tnGN89xB0l p'`ЍwaM$ʨnjv9ݑ6@75Y_$[`'ҒzRрv59V}X(+*)p惌}1j͆DzC!hTWEsPYx7&zǮy^6d`} ) {WWJ>50KcXG".t nP{(߾iow曀I-m1H]L|!Y)3ޔ@hep_~hh̎4UWкNB:N%/(l !Q:sEb&23VW%{_SFDP$6 Y4^Xęz1 {4Pva۩Il=.rTF e q].uWAb9~g퉁/[O]YC/!#^#>n-k)0\w2@J^i}3uT_p(|r5t52P$F7klP( aUdG=C.݄\N^#Sxyj~ GAu) &'>D~K _$cƋ :ifn+>=Z/.0uDJ,r6Al d֌ Q%~@#m ``*(hr /?`fLTuACnV556 a` "JE Y5>czwR:Þ!Jظ;/Btԛ88e+ xvΠ|^ջvd4X#L2(@̝k%__0#[l@P^Mc;!B.orJvGlL}5txYI11 IEmpJ`Og35@v͡YO 9j &FG0ZFiߜƱ )aEm6V`fJTI#K@>l4LdSѱƕ8dyUȤFx'l1UQҺ6FJe$P?Hh6)R'cCQG5o\xC)^`myRDŽ)b܉cR\:leCbys`@U{QlEYsˎ4!3fDxc8SzY6PJ`&;%-ځpYaIȃ ͭd40Ydm Ϊ ryX F'㽽M(~C[O}5 }FUuh=û8e;jdz悸H?[/Y)m?(,~Z+a{$i$5dNLQ? nqp!Rn밿Z0y$EʹUM>E}UQ|'Y8!}Ju` aU͙1C& 눞Rt_X:"ʹ]л.ɣC{z@4k'.e@`Bh}aH43u~vD1%CU4Dqx0`%rW!2lQfѨ35.*`b1* N#`eg{t`e e}rpڲrh,]ۖy}>d>^u)`%%쵔6q?SM(C7a5h/L&Ub) V+}Cl@Q睷4i2]gO/_GC"\rJJX:dsc ~!Mb?Xe0|_MXᮾ NހdB(s!߁-."W15k4Q@$fo@˺|knb&2~.Ai#HcVeO}REg%_SY`>v/SEs׃cBWS <  km"HO4oy뎞%FÜ< &@x U=(*x ^>ŒBZ?:`qMJí|wy WX)0 ͨS~ܓX`"H ?;'u!YMK goP~@tfq1-)|>|ȅCRP﵅Byce < T?n4Ҥ ApJm]H4`vp p a |,}1v)a6;ΗɆS~&vVzuZ}Ǟ: BC]Ǥ*dSQ1bP08L`#K 8Xd#_36 ԺRt/])zע;YR8l H[Dq4C5H[9ڳ>:* @`)C i?IV@[9Ѱ3Gw"j "!Ρwb2m,rupFb1⓯u*A\X|܊ci<{*0D uAu1 c W^NłB-\n97^`:}w |sA7h]mKBRx\G]^GJZ$PAh(wZa 's `"E췊V9Ѹ ETƜ< ^jq2ilw)@3ӅmX WAWۻ)rcdݒem;u>H\@0Jj~d/(є6\U):`l-<#6" o춽j^$! T8B)AvALJAVV#bi(Vz=9-@e=JČy(e"V"rNԳQU iٕ܅P& f/~KDdL48U%.n\5md MVtTm;Mx :ϰUHSc #}'l|<YfD$Pm0ty2S}d# "+CfC%:a:~їō/BC-Ph/ZcvQYc3u44w8E*ez>/4-ߘm ]vj9XBJ6=b'~Aj EitD;̑9aRcV$9MiH4>g^IGErE E6,Ga:ӽHn=K|B"'[T"qOruVxQV氜U@`}Q*~t؟7JA FHLg6[VWz G暇;‡: )|V3|R44kOs|+Cy(XlƢ)`Q';7sȥ2$bRjE] (*eF $Rn zm $?oJ%r7@bcvJ+.vzb&X/M5R$PpvΊb v컚BeRb­\QHÙ0Echf@6틶()(CW[fQ+=GR/g:4pTR7z*^HgZ {׃"rKåIm ܛUlg9NǢOL۷c`Dڃ/B Դ=H@QĞbc44 0G\@r (_'+S j؍gN܊]aB<ub)i MY {-dcꯠлzG?C!-J Ɠ'ƽri#چ&m1ަY#~I6`Ɵ-eӂ["hwʃ.4ٱAq[M!0w_$ӾimKV Ж &{QP@lNNڬj"U.6e7(J E D4B_B6i"vQ*T d<\cqP7 ӕ _v͘DuuU@\#2$w.YD+% ֒kWxgTT\V3E!WQp yoUjwXKV l i.gH 4I?-P[Ih;SE$)ǐ)ru<)]퉊&塜i*yUx1eŖ HP8Ah(plbF_ip@GӣA%`ǻ]\h)u{Ok(D"6:2OJ: hc3TeQ^@N/fuV2l4qՐ0 :DL(gtSĄDA0 Xx\W7:B`'BdNb2+MK0zՆ HD[z'֦(T@Vz8v  E[;`Ҩ b^Mn+bʣع0w 1 ZԅjV4T AC#dH~ jQu"B{p*h5"M۳8tgRƂ0j!zE|,{yے}$[7(ѣ2Wĕ:Dba@SLK0" *oAbDt_y0AGqj$30g$,`!N0 Z11  %x7B(%'u0 ux1:hkqadeDaq! j_x73 dwd0?eW@6: { gM& p})Vf$(s&`L`ӯ%`A2hQ AN+D{LEPBC _-"x bLᓷ' YX|bWH#ϑI| H#B+F5'j7p!ϑ5k?*mU[2 #ɡ\2(Dȱ"fA= Px,7"3mJDyل~[ t BMwMND(gz%^ 4^ "4B1.t, 9q;CŞoPRZG&#ƄK֓N7#4Ŀ#LGp@&㿀(&tRax+hܶ`E9ϖ+)̎`C8xqQk"+؛ qmA"|s=Oe@qn !pY8$4!]p[.s-#Ancatt# $ќkdeYrdr?PnmU451?kG[M\?v@7q'zErQ(SY└NIFu&ޙ_&*fa/uxzt3;5L Yز[HZK@$}b@_XxR l<梲/ Q?CFG}J,~9z?6_Zg:PIyo.3`Sy<8RL3YW>5Cld&ceow60'V}]d_[*12߯6oc$(\GGN !ݖc3-W`n$FRզ_͞'j=}:ح/;Q7%J!_EaVűotΦ-4q=M~:ԺQ l}"*{Gl-x N>KLmT`" CB# *h񝒄~܇9 \z5rC:"K-+t[=l) SDD*@0D I&Ʒrఄs=̭DuJy6N.-rjG<%^/c+-B=̺ (CD(@ZGPOX caΣ=XR`G49ZP07D{ QPhby&HNc$K#w k+@\2Q5a6G{?>H`*Hs2zmƈQi@B ^ -~V?{4f('X36<ҁ Kh* {@y.5{[PHFnbxMu^V3|W̆N}p =eҵo a7P28Jo/y9 lr8!5B2̩CQiJHwu<˳(:B { 0 ΤL KtTRϾG"[\?Һ@B5w&R3j Yˍ4q" XחbdReJH$K`hm%I0to1ID,'B?ZԪΤ>Zlc?I6ЁZ{KMX?jZHYabz|{EᑂM ?8#Lb%Ǡe@\ݑoMf>W/{Gz7I,CW zJVC鐷6nAd̨?̬BAˢc7HZ[P `ב EXu7J d:{<*L)04fj@pb e5N@qFծ=W 2,Ņdf@7"2INT6?1 sG,7=t<mj7P;iU ]Sܖ@P@Ѱ$CG*; ڏ/-o{aOJ骮n:5jlH6 B+Ճ C/=+=أ!AUS!'ro^V%򯥻3|@~ KњBOSYOؾנ¯-Ak/RACU_MrIL A?0 XB1C_^9THR@0M@x˖h|SJ"-_j9<ܽYW+^|YNY',:fWFS_sl"zz34z /L, 6)+38"j0)Ֆ׀\BHwvNj18c`-ixȞHuUIYo*chp1U=G8>X cP,S 5yTRC0f1, ZeFKDnTCke2ÎVv7fH vi< 9"(`m j 8-XW=JgXdǣk 'b뤿_HuD?L{Yn =rUXγ%b3 UO4ac9xxo% PS4Qշ  |3=6xXD$CB\biFTAȢ<Zm MLQLN""*(,l8 nZ*[&A`liؖ7hM葙yv`FrIՏ@Uxc\ԗFVD; I2Za̾՚).Q*y 8Q%=eƁ24ޖDPcnIh];NF5%g%4zLh7#o>s=n\qF-=8ot$XQGeh01 Mh8<`y7ٱFKnUd (i56ϝ'd3YYazZY >Lo%U<b{;cbțVZdT[纥Z n԰x) tZ50Tyː"1gPL"0m43(Pw(jl`wiAHǀ1c{N(5`Vj6 D^\>dC&G b]y^0Rۢ:({`MY(îkth1p%"~Q`VBxz;e:JuqAaq VtB"_ҞYS]_I AxX%N_n"xFFFRdf&^Xa]6qc=鵳5{R( D}C.g;>rV ̅g՘7&U8˳ABg&[€&݁83{22?Q.IYL6DL= ԉZo2@ԉ WJ6)4Z3Zf%:БX'Eycǹ¼j5@2(.7wUfD*3C]F & F<ʫOG͖}ƪa=cL=qJ\@pDO'4 Ua1}bsmAhGa0N]pFXB]uE%'N \񀺎= & _xTJ1#'Bbx`"9kvKWI~(o ~n{OJ^5vē+\3]L8-h]'zغ}#bn|2v _bc\%yK!sk LZwPK,oq. D Q'kpuht.LideރaBpY%tMlZ"% d@q(3U=2 Ŗ7Fc=u5iE-0wp~eXh##iLT`#ֹ 1`3G1QL0Pю;1P5U7([DhP dkDžA] u40"FߊI nt5~SɆyD^UՍ+mpj.yapx7 ։_& f&0 D rKxMk4a>EP;GP4q/Π9Tm8vbJ _լf8՟ I1PVWE]@'dQ'\mH;æi&Mʪ z5 K=4IeΪe \{ZC J*[3I*3E0c, BS夎p-VJ$fUI4Е) _A)E!I L31p1n؛y#~nb젧Moub@}GέY};P%C撦Q߯HhY$Sʺ%8 .#PphWi͉n?&k/CLE h{ ļ" AseG[kQ+f83&N>3Q+~tb]S}5UHͨIɖ;8_>4gPCDBl1_"c[v 9@Y ech"` !FFx=TؑQ"2n¡K󱔘x_䳨>D`uopJV=7P(jE)e[ݏR_;X0pLLa+}H}h4x^$^@WO󈂉 W `8`DB%O8 &!|L!UZ9z?mL LLj 'F<=xcLP].5[-RNvUӿ~rm1K}jdqF [b, LGшر ZTCuT~4|#3Ȍ5pH+4VrIz; idɴȭϵgéH$Q$2/i`g%M#a 3yU|)8vQVQuuSA b TZB"Қ* Pg80PG:2^+/`tLoGV\Q%O00YR ?LI`* 7chAjppF<Aig`ܣZ>=l S`4Ć7Gb[,.NmTIiߨvbG06_P8fF"P.$D@*Kf1\c>\g`I@Vj';_]uLʢR)0ѷ_=*@ e*{ e8\kؾvd*^i2kV%U&Ύ3̛b=T*ȠM&ȼIE.Yaxg41恈8!ۡ_"?2܀U\[ wDb1 R~h,ɃPTtߐ&)? T3K8-RmiՌNJ./`$&8sF"I`#~kd…/ e'ҚI,>[pAg-k"&6Z^3`zIﳈQ4G}Y>d"b RB/ B&1}Ѹ5D[]pȕ'<`b>!A^:ɉ/5H<|8 N%kKNSM<"`O dJLv1T" M5}I琍Y{}Xi#iYx7Y:{Ze+B9h,@Y9dg8`)-Й*#U'3Aw0Wop} 1PLpݿ;8H)a]:#;O.F[W}%f>/2~3p?/`\e^H>^5 ɵAZ4i ;OW)#ƕF\*J,l,U}k\;Of_f?Iԅ(=Y  9!+ēe{p@E]"#]79,A2OoФ'B /1Rr{vDc(6k w$o/kw(H=v]p/ ;`F#y6JGAoVE&gk'm`\hy9sԞM.ܑ}\5W׳಻5kz޷`EB\e@VAL/BUf@d_!:TujKaagV55EBa.U (@\}v\hV3p$wPyW!T{0d"~ ({g)of`l:H 0ixOɷǠN\~st]3+G) I@ԼȚQ.`b$ "$bosos0+6|5[D;bvJ6%8Acb,ibj "Q.?!@epH>BH Sz6aӰњ=C:شXX+\ 쯋JI?OR&iY)|lT]xnWwolj0i9&-hr;zhfyۆ&݁<7"3I?蒧ֿuاdݝǁ Jng%t6/"7ٓaJGe+Qfe*\1PjoJ[Eh{@ bORfqP;MLb_Zl$3xVC~Q#Ny2&o! y+ pTOo7z!%e&3$3a=} GI2Zc2zV,D(fMe›`vIbȥRR[hJƔ#2:owC( r 9 !IQEvUcU[ .$^hW">Fg]r7{6rZb<6p!|޿WʄxSόLݒsK= VPNl܂TFo3&?FtU+tj> 9A}XN3@A+:RCml=g!rDAn(te`RÚ-F֌Ly"ʌy5XcuahA:>>" O nȒSLcFcW5 P-$M(%bv7w=id1ٸG!tg_||\F! ʓ>҈4/qƑN Sa mI"7Gݘn`d? 6B!T-5?]Hx5iԙ1$g)1;MJeN+%jDG=7Lu*9)HxRHr %G+<-ۧ,i0;rU}{A݄TW@`"-c K ƚ̡Wb_ǁ[=bO7MA^OO]1B4S@Z ͠Z$ol2hh{P.H=Ř7Q,1B՚ 舂#b.F@M@ѷ1c`M&F}n^[|ڰ}Uq>,I]>G)kotk$V9s%}cD(_(yuw -7h 45+=uE=8}BY:!G]GJNnnG0j:}Y䏼JDFJ'u&F-CEQ<3-Uh`SKlX ?(!lƆ50g Vuᅵ\PBIXPPk'XO@na͂PX+ji'n;}x Q%aƖjύk +$#AD &gz j( QWĉjԗ>^QS"!  >m&qd 85hsAB8ؙ, M #/4VY4CDƾ=L$PB$T'01a38l Wt'и'$WJ[p1W!zUaqCH=3Ohh90}I9U +vHqHj+ :;EM q@@s켃E*p5sHFHq bØgpй#KsFZ0Ҡ; #|a+V?%Uoလ^d0P>}v{ 'KclKt<e"m#-F rեh4Al -q'gpaKLJx4e=OSu :elʁ%- ^#׼h.~3 `D#q1ś?]=E:H%M"W83m{b$HST~덲'*I8m7;@|z!tVL:lxfk,8XʕKXym$ ̤ Nwa|6GVuQmlvz_:ЪV.jG&H|nzKJ]G }aylex:[z/w+-@e z5Cìn>U'+Vg}[,мUM\qҋERro TaZ5"-{*R}wS(ߘƾs#jNtg vhY ^_n&~B%O@Q=:߸>iχ3Z* %rV=أՊhINE? guWK`Z #me]ږi%ɝd^> ](2)0%@XG/mq)+=|o$; yau:I%m78s>cDbGhK' eg`!hF)0NcO@Ra!]G^\߆>4&?uS\1nJp~? ܾ?Jm1ܦr}ֹtgF!F74B<ֺ\_g-=( 5sAfFZTnkvxf ? !Z={-d\?+;w}!Jq!ID|ǐ8CP $im wn?k˽k:e5h >qUW+iӒ"|6&_|BMȥ;|OsAHf\aF9$u x.)iB]`ZԡxYʥwMBԂmPіHkLW'~KDw*{$>N$W:x8N ,uGQMNPV '%*¤zb3ָ1: 4^?"I1!mD 2w & : IXeMfÐ!c-YT$u0P8D!(sCC(:?(!{&?1`zgxtpr`(,Jl1 _3:H#޾JKk%ڮ`? ,!@goS?؏iCĄypXřъ++@8|c{i^_u>eWdMҖnI8;BL:L@)U`CEW҆ѧGTUTLJ{HrJZ>TX5bMdΣh~ @y%`"'LUfLd(;HLC8j!)<:7huak'!h_TrPB[#_V͢+"N sniVrPNH\H"`G1mPC錡4~ќP@-9n"V~p؟ȀzAi׃eFpdtO*L;c]Q}CRD#7xy=WÝm;%ItuՌ5 q: =rVoWj-7ְ7_!*Z*dȂHByA >LҚ}WoKYA>:-1PwkEuf+&Xf?WsiP-pvHYf>]f 2"`EkH3]ڲ" 2Elx%~]ҭWu(;uu퓢7 MwNY_ UHjhBF`Y:L`M?ɏMA3d(Ǭ-3BꥯD\(j՞s!:4{=B=bi( /hg\qV" #^hflyf$qm`cH!69Y~'5Xg'4I܏ǏZ0&?rJ+l`IVXMȨ Prg1vlj'4eVal׉a;UssRܒ\F0UP2!L\αHVxAΦg~p=254+`EqI vAnpִg9*Q VV;rG|zN$m31T¢H3^%@(z _$Q]X Ez,D_djh VV(qu01+4/,\FCIaD$wqmh2ge{vtyB3 qZ a)5Q+ pjCI.vramJ,Jh E*[z$ e!:3b(&D::@:nۊz} G*hwk'j#q[1a'c{G@cRr< |> >:$şZذmfpI~l&nЋ <4xz,PPY8; 4:,󲪜:@ EaJRbp2QB)#oc.D aQ< 7BFI7 e ZTHC&uQnvQr2abAL4͂/u_-}֋y)E@a2=l̆XqV; ^vHaIPm͉Cg=!   f[`vs܄0>yEDjJ 9nNc¢G-ARѠ\TfhyJ l8qj5k6y@( ,9&E{h6O\ӑ[ Gs3<-m~-I4{e?RHf/T"1̢#h9!dQuqͯ V$ w"P=Pމ* J9F\3A}txs&ʂU.g2!)l71&94ɨIܢ5(@[Akм92@JqħF&vBBaQ:B{, T Ԋyn@hxj-NzToRGly;DŽC=q+p9 #o@W^dohD92`RO +Bg`T丠Mu'20n@/o5,ru?}rK VmpPp;{sX vxJh [ 8+yo$;ӊ%c4{ƺ, z˟Fւ{D8f%hʲHvpy*oEMF݇?S,^&0z0Z 6aZZf_ ,<@}@?D _crKPw!+LS@qsg֩,1s:B!h2=P,uTX"ȧ~" HڠЖS dԍFMvI4X :qn!=7pO'Ny Ozfk(_9Ȁԩwg}b&(kA2ɡ!``E/4Rl Jz@M%'@2o) wV'M g&?h!C@RDn 9V˚5&Jam<"ONG_# UjM2jfP19<`8wX UWH ^/ c&@g7TZ3E$K)d}\3i Z,j(iiu2{}ʫ4G6 /:L.<YeE1F}/dխlDBpM剾A(ZIsc-9{t#-fG>bBTOgj,Z(/"x/b!;y`8ťG`N qYP,)hijA?޺d赛t%y,4|e&M:@/'ۚd5d0 ;p Q☯B>wEC]WZuJ F3,d0'?)*NИ" )-P>jeO&dPg@nA%i6KDeߎǙ)D#U2OHaڕ5%/8Ž_;;VKzmd)R@ʕ(U;L} rYisVKe,)]_,h.Jo؛Hl1 yT#L '+oH&mVŦ -kޕr򾊙Mr] /f̙~!gkfnAcNd 3o`q>^W̎$(wY-9`yɒJqp^4Zf- )(^D_nm# vۍf2yF=hXV?_5#9a=:Q&SSbYCٕ.$qG_|;&)]w9N1|L5ךotM([OEEkմaK|[,S'M@;J4#bUibՈJ "à@v-,IQwv:U{aBif'18v&9F~VN:"\e[gk#sdB0F(oad7v"Sژ}Ltt݁!+US_kKsM?iG*1= { hWfq쁊F\(ws˩tN0A]q,`h RoܱC@ W# st#xf9@Zny=CyzJ=޿ ߁N?=y[apdxBU3J_[Ǘap !`dzgp-+)a( I%]%ĭMs~w!V0vzmx:fQ-&e$Tl2KީVmPF}&NIjƀ$ ! X;Z3)$&Ӧñ}gQ25E7jxĽ!գ{&C=R,Nc:f./.k]Yf qR|"jkiT ;Ѫ8 5jwac:4xCeW:0ңϹH>z Za8XPygnWDq}w[kj{z΅"> 42O]ၺoegD?"qq RNxfeޞ65;1F*ctB:D4rlThGL(-w8&ݘ)䇣>Xy\1S=VE`<)b֢#䇯swQCE+ x A+Եrg{ٜǸ:#V0p箮`ߕO[B:W%8P+QXJ9U/qӒF?ޗ0Q(|"qk3l4@{EvoL .ʇ]b7Tݢ'a ztazs*[9|!i eXF-88zRUӢD,M%5[+GjiD3@t*> ׯB:4{lf{ fvӮp 5gT֖Ƭ\4O8>(釱kq 6& Ɣԓ˛|n*fBKuy%|/HXjN`$CUWts4|2{gor/heSPܮXfKRf\3 pٞ{rCԸQk6>OAp yBC)'ԧR"sU`|5ަzZp+ĤB]ƝPQqUw}s59rg}u|Dg ޔq[&z>"uc3THkɠy-3?>gl5dľy^[~xIkڪ w%/1Ck[bh8Dq6rI:;࢑vaR"v¯}Ե OWϵ-Ηg 4ANJb oG\؄G sztY֦Q&t 09XZ* -ʫcH>c3I< Ka*!G "yPbң>&n@ 3I| cc؃B~V*=q$\> 5;>P9)Pڗ4OSwk>tǨL&5>.K't⋀ą`d.?p{jd% #4Q,_zA ڝx#V> qnkjw`)>5a"'"bHj&ubzdp7o:k8+84`S0BwU_]-sܦDu " A7g_XX4!9uP<=I:u> A۶-Ĵ"]>OlXoCAzT"(P ,- f0ԅB:L< k ĊeBrHl$uA˥}XIS[Jb:{Z* Z/CN{IKr+ W"&T rGpj麝[t\u9 桢|k v.Hk~]'n|Ạ0b)bѴ08FB}@ 2jHDCr1i`$A@5^Kc'ň8ߔ~b>}Ax7V ) .'L)4MT: hN4!gMdnEJDG~-l`@ǒBw#8?YUz4 MbWn4(vKI}G N@7iGcP!!x3T;qjBRXL@.)t,/cZ];^IDany*MRL(8B-ЌhԒ߭vou [7_Q5*Q?~F $D傚8\WˇFj" |@@XB\ U҃Œ.~Mu>r`WYEz_*: T(j (` F%H+St)F?)o"ʧxz"鷤%R>u(|2)+sc ?CR9I }I}d9T]TbٚdYg3*<Z*:6pCt3PLQ3W S^z<%wAno[8'C7ن1jWxZFw g-RHmbd9reE"~NWK7A;}m'^0]4sf\Z?k"i9k04mʨFȑ7Lx8wk[? yM;! Uf'MҾ!߉ЀWpC@BT{Rj!\keim$ulE `47C<7ón TaKS|L˞ |j7IkSQ?2, # @} { g6h-iv@Gno-g|1kXh@?zeU^YD`b墈j4:x` Vp$Q%Hڝq<Ius0Ԯ7A8( L AK>cRhtB&풭 )(Bynꬽ p c`5ٜƝ@uH <H+R4Y_ԑ5w欖$)Hn/Q8z9 ~ |اt[Ǒo%FԩnÃP/k秽0LD5tT5xdDھ3I?l]ԝ)A=îËQ偒߹ =kZ1ak-Hx΁\bmq |+fT Q1L$ǖ]f*XԁAw݆~>p鬄( T>`x~mD۷3yi: U!'P:=]t͚vU(HS$ǭa~ OT,蛁єj^\Om>,t{feT{=r2 2R2cA[jB#D&iA:ͫ@_nMb_qĕ8j!$$4 *`MQJCatdןD'% t}k|bzlk@ƙ: Lr1t"@_[5Ƨyảvp%TP 4!sa(ؑ%!vb[AVlgbB m-Ic0_[6a$1sVrg8VXZ":2EXR tb5hμSm+`}[RO ]|@G:RC?֞9’|.`.({+"DB{_LPP= Ҏe'}VzIԍ1xf3Amdc+\O$RB R);Bx4$XFID6>Hf,ns}>bMԧ#tN#! a4ݰ'S$ָe?:uYLh"D#Wui3V2U2/0*4N8XRH*]Op~X,'ǞUλX]$IŢCmkcP;)4 D4Z\복*.r*W FQO ŧ?rb#@nci'Q_C$8ډ#)_%G>hoEAPUߎHGb+;DJCI38Z UDAi7u L`Ka$I)L%uMa*ӘPXiMR-e\JdB*4GPBԯ1MMnhH-$9.@ͦE;3uDӓ NXRuncg~t6a-H'9[ː?zh`%' ^_˝TCmD>HA*C몀\r6՛JC ph5 ڮͽn9T*ZBqq lWVE8 ""aK: J5+Q؁5ZS)eN){c㰝fOBcD3tWIiEғޡgAUφ"0Ek#+3qRDIkq;T4\Eݨ[L5+bv> +W#ץЭLVswCNi >:Ց/\c0>ᇼsJi79{R:]b6UYi{ov0[ CJq&nTʀ 9r .h6\K@yeewoU5H/N2pK5<.l;?clcFR:Qbk|vL0Np<-X1e()җ 4.'kT[P3 zd#C0V tE8R"^PL986*87ͱV:Fjn9:m)wS>^ -ĘsQ@8ۚ`"j%~aP mZ_DoRac_Q4~txQ֟O Ez4+r)ڞ]s—>VuMr]DYPfIHa4shNPyI9>џZkנ:ȰJ#L,JbAS rſ|2Zsal3 |(Yg{J=}RA3d>0ܗ> tzXACĊ@nU43%ygwcNrDZKpuۗS6X/1JXfjqZAffmieP a:%i i\T1L4{{y\v!d%Pc$@-q˫5 I!%ҐNLCy꜔ aJL»apq@I  J7 G] YF>M@\~+AbldУM`^j.}4:l0CTs?Έx1-pE':xAm}Տ=3?fg5ՅǩВp<|/۝ܣ u{}=nQv/0%:)띐uY6BlkW\x:2ֆ6WƒuˈxTv}8@kӞ/AbM`\9JX.SWN ?&T_z@!vQ>@Ẋ=_x0(nu*g(,uy<UX JFE{z9,)h4P&C5.a#D@l{q;UkK¬ w*5nl\8mλε'Io5f/Rgl`Q'x0b:vNY__Ȗ.2.Qt0Qȧg\kO1+ ʠ k}LMhvEṙs*,NͦU:]:)MR!ԦjPo&)o;BjfK06t$26-\bozHjJ~lW&p{%!@H'Pl}jȨS9}^L,?.hS扑`vS!BnZu(ةGI'xOrqhJN**4W]bv $A9-8A|8YSfzmt lz.`0⼖v7YuM dnK=ШrMqQ^l/UHxn-2SuѱluGc:7S\~zbӾ @ߊą {#i/]cQn[EqL'iYp*H+ 6M")ܤZ.Իl&Gue\'2@SksDy@`^V8ugTOR7jQ%7l+[FJZS4 Jɦ:n>ihvq*灪 ጳ> ^cv{9m81JHY-@;&(r"ޱH \Κ!mDM5i4P"1!tK9t&K'Ǩ#AVE[vr]Co8{{l69Hh~iPA*%BUQsXMxuh HDqm5Ds̡K=@yƁ+jNثAJ*b3SOɱA(ڭ)}}rOe| ONd 'WrF$a.#DXb]Kr9ݴ-C 0HNM(dRT,AM'X௧VQ܋8I7HzK| 9\ %n;#vI^:5VVZ؏E[@KIvP!¬r$F3V.ivVgZ'kX5Q+Oquͱ*?4)ʳgmZb4Ӿ$U^\vZ4*%yO6>+D#C\xuuEL+܍ >.7Lˊ~:"aa*}hW2jpV) j:DR$iWFֱkK6 'TyYnNS5.]qGn{&7"@[ cQeKt[wZKGo8~nGݗW]r!DJ(WXZNe ztUE+{)aBoZ~[n7"$1c^skkǽU/V8DH1J)&2%腶%$eSPW Ť kpw%L!v;ƈa, 5J"K|ͷx+UрȂ8[ $~!5V)&42q .⩫p֋YPV`e2p`S>Po^>Q_UM* D*t,Bx`=jY-Mc]wi`+H?Eu1׭XHoFxHHVĂ V.3eᦧ.l_$+:+vJH/2 bGFvc+O={+uL[-i)ʃ S]}%i5V!x\{͒-W3)6ֲyU(љXʧ# z-? 004\( FBNLJr+n'^z1 DwknUC谩cD(zRPbhV[ap2uqlSeiyuB|? QhY4h1zh3),F^Uڼ쀇DȇaNnkB? Wr]06!5AL1ˁ4L;ç UE'rE5}.T jTA[ %-wqpy[ r>?7 ^g? Hoٍ:YU.L._0ݢ+Չ>8$Z'C˳ύ~'v۞y2/ºTK\yŎU'#S"> DGNd/JoʏH1:TL!3S&U)Υ >&)0=yp.sRc7 Dgd;96$ilU\KU$U˗pSCթ{ȏZا<ڍ3D+& 4gFݸ<_"Zxf?#&U(evjZ@o2ՓQdN>4J'4?t9yUtD2|0U ?#& l,@ Yo~U7c8/8MThSjC*_wc3WVשp^.9,3bI{шTtj{ԫoĥYB" ^8t }qp/Gƻ&f-MB^:O_-ŖNX |UDS??Ci{™֟Cklt߷7+'XJÂY|9HTM_y}<򠐴|;oc{λ¡&XJCo$|s^doJD3o.SReT}aX,Pi lKlB0SE`)tVmҌn4z/6dy^%W-|0c`I 'Uխ)3gvn>z i%N!"c<]vnX_XGKa|)SwjX(vvE|)[VɩR'?fXT5լAg?BD*R'1S32x#_zp/;mRA:"f9֐?@ڍ M'Pg381JT7*AKU 4W<0d'e UM> Ev]wԸ#޳)g;.EXlm[R!;5Ҝ ,}7ܛ&Pd, i"M Ltij@O*3qaOR`F4MsOLPi,2L=.׬H>P/~E;TRAf` vJ.Q|ؕz"L=R(e(S<&!q"]n~A~p0&P0aV.426çy&cA@zF=SՋ աNp\klGzXH*@J:ZVGr€KIhcos K ;i K>($RcGO\Xv7k3gHv [:aOgc4]-7;[,5i8N iW;+hzkn34 XOPM䕚}Stz|W_|_]~SM@Toku#[Oմ%t"rL#y 5LMcgwZ=ijLn g&3XJ4Ut"mMzG [-&)ij0CQPqKY!`Z<)WШN-m"n:lPDթBc$USsZ]{Ň 2`@LwR(S@ΑqB8SVvȎrv'vI*ֶDjG!l>AN$* 83b}!Sϭx(`~o~0FÝTD;v8Ha л vJUkw{ ?}ΔCqͺو sluJZR㡓Mm%X֩.1=]cKSc<'-^g0- x?|r f!V.f ˫V3my{Nnz# nA]Ȟ7ˁbG0VG/h§aI:TDNv/bcJs$2K>r-tMV ?5R CO,(tR7:;01ž|:OiXd>4SST86"bZ o^ypҀ,m#CJD^O<ә mp^;tM9)2jN2\D>SI'0ݪ.aB&u(J\Af`vy/Fjr8ɛx&4,(̈́+TlGNepe|ʅ1ﱱkk5Q%xBr.WͤwEl(̩'!r: OQ0Zf$VP^APrJ؅v:ԧ8gO3#I\ (F*zD@,.5)h,r֩XHծ* |tdIP^Vzӽtma؝:pN&GWtgjBzrFH4Z~qiI*'6Kv'_1H~˜eq_ EfDU)@2'qNȚj֌gPkX\rXkT:t ʖzy>JV5.1qzW>͡%*[¾pKSckvOGRi`"ϝ[*2ya.\ckgă 8s0+嶇]m72 ~ssVi;NLKp]&}Pqk#;qlk/, `ymȸe8IHX`hОW!N2*ʰTvBGT7=*W+t)򸼙e` PE6u*ꗢ}`a [ *\ޯt894_-Ğ>N]?=fs $?H2*CPn Qc|=b>?.ȭ58LEz:OrJ{i*n]o+;D{;r_6ZB0MH3HLNUCWq-51q9CpQ~? $yO.G# W BBp3[ՂZ[J&οvS`P2 WXkFbt'xB#7Dq# h1512&suFIhՆ_r3PYT!YF;+( /`pr AI I'x #tř?/,091f(RDLyI:]~IӜ#g1h1RHhoY6* គ j mj EA:T{nhqWE3Nf;Stߛ;EőrϱD;YGgl#MG\4M"$uc-(C:(:\|!Kٯқz`紊&: ޲u.'lt#M'ݺ%`ڿ"3'[P}lנ.ljj9B@~mtx݋)5H0KM DcK"VIs{ᕄH Q]ќ0 c$,J T# gRK/]98(e9Jb`lI†1#7Z'nzs.aruw@݇G̵*aAz+]-?uy:b7ql5AՀs)w`^ؾAceMVhsL>5iSIP<>z#zB1 uo,qrnoy;p\u{{<t7UکA T;tnPmQ}hx J\PC-tv7 =Nk-'>vmJF k݁$?_-'s4 0@nڀΣ m{) ^6;h(E=DKt,Nu` 9[Pׯ s%otܥϔޙAAK?!B+{ЬO<9Py-u"OЂJQ >!x(Rw\FWGv]jLQdĩA0{BY @ &syrEz>g;A`F_IɬǝL,= \WS,hO#oZ4-Y-E;ˮK|VK/%i39crD30F p_,OTh5Q SJ)vSqcW y265"؅"!1L+5~hR΍O9P-C@]U. Ֆki^C"[u5ͫ!u:;p {ۋ1h*pǶI5x{)ln0 YW[|h'̃ʃ^OHԊkX9a4%%a[OȔˮT@*z' k*uł3Y= :-̌UuDE܍X-=@׎R;`FTnSZ}s5hjf-ϸlu4'AX4pP{:]bDs8#P:UH=V YЊN< ]+h`-7*:u耓Xٻ۰&䙽TНNܝH @KrcQe (8]Ƙ &0"~F#ļP)$(-&9,C$'nXM4ΔZjyh>+U`H%L kv5\w}9ybl(wZDc mtl>ф\!3CS -.%XUqbc[4%@s$v # iC䦶\.-m2h_KtЀwRy=nΠtA옟p r q.gik>Sw_#zDCIzRpa>_He 1.=` 񲪑N[lr|2k9iZ#{oBp*`S E_Ld*O(2]=wG$t;^ YObЃtqQݼs@7Z`6]op>eK'<'"0^@2f[ _qvc y(uLf0Izv6cϷeE'vjoҮUl@fJYkH^p&Vf7OP7\f'[=HY:p]fnhѬ]2۟^kGh\X 4īNH4)_=6;S`FIvt0FJjH #W60%'YTFr!n4u{+} su"#ṕQ0 5X'C4sNt Ih+w|}|]U$rwQ= Pd,g4vVJ%Y=7Ug]yeE:mGt!{A\ $5p j`e`W7"Ҿ;(ۉѧ*Y]3@^?T lkۧ BP"GdJ ɹ]y̤@ N{m6au!5S.guD O~7rQF vSF+ei.jE*֩-*֭毎wT%*Ave`*vT+SR:u94 ^mߑ\pSdDln?xjN>T575S#d{sX c~%&cgvwӴRbgNJi1r-SkYMFߜN7Ӽjx>n*G*"IqYl>Tc@o _7'{*a(_[VbFtDN(KYtԖ U0*;L o 9xѣYw 8 QTU`0(Q[Hu pqWvWkt\Z~ AM4t>0 5 ; ^Y]u^Q`ꟕ5U0b^tL $9ܦT,uкISa]h-ZpAoL弿TޚШ*Ňn.(¾3@$\t͘ZnK 5e8YG||C] h3"UDNdUt=2Mc{:;Lv?@TwEĮ.c:G ;6f@Wx4AMHXr'9:s_Ͷ>. Q;/W ުwHMk9P/s JW`;4B* E( ]LbXlO!Xb"*qVJP(4chz|œ?SS8Y^7w ܏ 5N T8dR'g[uf9=(7ԑF)쿍C.eg|^8 T0Znwy/~K3T0.&.ڬ M[+K(s`,Ct+sJa$j$W:?O]8Dž`Sw&rYT/=8'=t(pϠOg gb g F_GrMfgJg Y&[*q>|Krm_'}Sh! #ba,kU+HtZCx6\IPI]'g"uPTh1:l FDGtG:.NPjȩ2y#tw V&@6; Xk8ڣZ%FddX,sWvu|U2lxd?M|c=t\AL:nHxϊYzc?gPԭ;t UN)൚Kb3 @!KhJߴ H$7ꎇOV6|>_/OӐhznr RP!2LjZKå@oݒy8FPЌX]MJ%yRAqaSRɥ݂A94fBc0X,Q!* Is@{=n[%ñ@ERPVnSx_a/vw"l|t]I00$zi Z`DFnp{/N*Fs IZ %?ȕT;Gti 7/&h֏4 1d:p3&;Aָp3'A"nx/"_ I'0 {J@OΟDP-|f=骸Sx)8;,? ۥvZQ(|0wQ{&Ap\߶Tl).Z1;}n_fO pQZ#ND|D2ejT1 ʩ5UJ=` ?F˂F;AP?cP3_ꠊT dZi6C.F5_: l5umI7䥉,b 5r]Hը́@T1R 2 ҩha;?RDAN3`=GRlꮖ*_. o`Lؘ(V=*?nշD }jX>L[<(Xy,< ;{aέl{ tՉ檎FWXz untdA‚63t׻v8aT ҈Z\P/NKT,[*0ʮÕrdI[ȒX唎0Kʂi"-4钜MA +POF`4u $Vؔߥ(ET] 2UGMwVd@6 U!)qwإ+~@Z[tSBU!20VAyN1oУ(e^wuCNj/dH B%i`k[kDoޚwԏ<\?td=r lBL6Y]GjuEPN=Dž.-5\A{q8IozOSY 5Nuo8UkSv!) ^I0< M"5Ӯk̕8o2MjV5Оu񘥔a\Q 0X?!uҚ ;a gu+"J`CBb)浘)8m?/tLm$VwY۟oVkmEu+I2Ed)t@JS4mx@>;E@M-]#_l%=DyDI&FQS|h=Q@E.={;eR2ZF?34Jg5a׺KE*e=HCefEE$7&P^7@LX3'IHX8}d#~;KU.V^q*KKC Ԕ ]"$6vwhpn0=frl "X` Nd\:2uNvʛ̱ZM{4 huE^v@@o傋t&[$7>!uӂ T{m֏ vU_$&hN²7b׈0-ӱ\s$۱r'g|^:Tqۼ3=Jg==}v\,ɎC~5ZO@x}SDZz0]H+P"Ͱlhz?/ՇmW߾8}wF뢤+'J)1ٶ=ՠJجNF A6M#́_:jg>"Γ_SQy kF&Ώ7zVc]Y>`m;ISm 2u. EÄcM}Qϳ]>ϧ1&ƌ#k4܉p[E59AWpH{6gcЅ09 kVyK_W(68F@T._󶺷Q[e&2bPx [+ 1 e|@9>S:L:h6\߱F(wᡓJ᠐iPG/VN@{T7%o\SCH(Tu/g=zW#TMu*3mS=ӃҸF\xR ,`DP L! $.ҊdntHJ?bn"#lOQ;ӳ5zx%*U0 Lj! D F_|`sTt"wNMQ,gRrT7"ɡM9@ SRf6(q.#g$VK/镺p%5Ż#yhB[:VbrZLMK^>J\r!L!J,8v2@oYL$0S`$#(a碅IDtx}qSSɓfQy'!5eTͶ.ذQwn :9'6*4%3ִF4cTs{P'FXI(%v FcO{a#䂚8\8gt1CƏъUe'L@IWJ#ڱAIty1uҦ)gD,D.+ ` # M,7[|Nl{<@(1񶈝FzPkQ Kl,3*hKAUՌL=]rzA笜WX#h/oI_  y!tI{ZeUovȾv37^~G@Q1q쫇*nǍ 2~\T&ӆ?MN^3Z{v侣N2ZQuBgwe"L؉A "KS6:.Dm9lPPDlg/*RCæ9WgzsR=ZfPGk@f̥U1R rG*5k|Bӝ*4:fv>Yt!؉ie)Òa r]ڊ0N 'cK00utQI**_s ([dRDAi;;!pWЉ- ~B0`6 :AUѪ+B,y` RG($hyOR4 uWCܩnAEJ3;(BP0'SBޑP#^PD ށhFVulhA'CڡrFGXyp|\=l&B..W44͕ 1ZAѭg;;x7n)-)[.Kr75S3>b_T&F2c 4irWw]ee;΁ހ'pÓzC^ȥs{lr9 <2e\[rx\D˪eA]2T GZG#2^ i<:FԲM9uxFn9{oGy$w@xw$Ƭc"8ty*OV$שM=v_Pj+$fβ{x^Wi4S)t{1|֖U0FOXz:xd6)ejfTOrWa;ϖil/n;B@%i;!>nоn4h*{-!7G8A/o:85XUT΄i0^C[xiy Gw~ZUɈ3d'pu!#Y=ES_X\ӖmK2 d֖ $ښxDQO}Blp7Z0Ӓ^zTYFx#!A+zk,`YoznU{bp*ٰs:=ywxE5$ߝRy=o~ގGxlNyם`+c}"bTh[dIX&f4rX[Q^oCܳ$7E պAq[[]` 2"V֞ƪy_w} -gTdʪHd.c"U\ 80=B P:nyi}^( F*4wAeuQWKhYlp]Fnk6|.6'UoyL'#S()fQi S4LH &JLiD=[! `! L| 4_rq|ԻV;CVXC deJ]q~huK({"ֱ݀Z!P"G;Tǃ/:ҖīA *+Gw&$4t,V$oENHk\BIQ `ɠ=&*K {ެ65Q/U9ܙrՎX^i4Ugܝh%"0a|Eg5NWJAmrheܛs.]XeG-#z0D=tc?Լ3m2g{TAJU"3Qs:MF{buah k-#ӷLyF |kw{}5Jl՗>;tHF%}yZgaQ؞"x ΅m3 yMTV <q$e-*G82x.v1+qV6i&*y,+VV,eM%$ٞCw(`h*Qh 8\O$P̐^+Cz袾6\Qb\aa.[ǸdL-ND`@Wiqn<Bw+I٤ބw7t(Ħ }+R 7vKŷU E(>?mUZ+icOq V01R&9׳VZ0Zs(8_J5%m:yV{m?`?zs4XU⃡Iq􁺫-énNpy؝U\G:u]R"!i Fe4hJL) &R/Œo ɱRϵ ᶓ AO)hW8\,WRp(Hݪ֩V]|?UƝɢDMv8n~th)lap=Am } KidIpNw頶Q QJOۻH>m(FҾ]<*ac"FTWؿdRp\hWs=~2?N=|{•V㭠:7fBTKdҥ UjآjfWqA`1}as:-NX{j 0Z*uC`xlɖ\54ʿUbgж ==9 H(AD8[4L.j,}\k\0.nhB-3^ZѰ +t4sts;0nt;3=ZKDޱ ޥZ^B~Kr=V-z' C)(:p)U`3Iۨ 7$.`x}FXAμ&Xj/ζ9gDJ ʺ\AbEvn:ĸN.ա˲V|u@\|M4+`$GWW&*ݾĎ)(Pл?dv1 #V9OQ~:f=}$Z kȸuׅXp5Ӧ}LO?2/a]4,zJ~{ᥱ4&[+~(f(ԭ~U]n Z=UHDŽɎP‚-PK:}n?$ w’PC;MM+g | p3Ge@ƺtߚbpR!*qK~{:yX})oS &a읨D D6BR.7$QrdvzaدL\,'@wmH]ǰn<ρ(zBmѤDU]D! " صkD`s-#zxѸ\P82LY,D?ۏΧ]yJm|t㏿~u[_r{#4>v9?Wǿ3Ƶj\>u}{<~޷c[˾Ȏ᏾gO~vc{O_zbON_MO۲/{߿\<|:mɾ7?JF~eo_W/w?hIz~;:?Rj ?b]m/G?۞nSy};?~v>/_5~?~GcןӖH^?Z/席-?S;cٌ1? ?`/jHϯٯlE__??ǟϯ?~ӏo'_'ӏQK^~9o?:>uѧec?e->Uxp*~m "?9>_~k|\nCӏ_l|ll|\=|̫/x>}|~OYۉY_*ad[J/?7w?o<~0| endstream endobj 11 0 obj 69193 endobj 17 0 obj << /Filter /FlateDecode /Length 323 >> stream x5R!O gŠHa\V+/hrxs%,nc SZkAeSr #O `n>*ȷEF."#3k1 YH2PQY`@#wGyVa 77Q5Ra$ aTl:.;e_Qm]KI mnD?Ĩ)-K.Dd孆T^K+Րu4B# 2"o*&5E.HGA a:ScScʤ\5k:t4q4~nGsh` endstream endobj 18 0 obj << /Filter /FlateDecode /Length 472 >> stream x5K09%cϓ~t0&&C\gd K!%jC4y&ydw+dm3TP%ڼګTbNI_&>u͊oĖ'bY}BCbNF[^_ޟ'X߬r`FfI$/UMegG5cvL=> :q\٠} ~Ѽ.6- 7_dB %?(甹8%v"^ެ! \cPz]>[@AޘS!%> stream x5M,!u Y}5i2k\{=ޯ3f+{YE/9ϊ)kX`/c,_ɾc9sZؼ8Oyt;l]>..~yynD:6S{h)-#{|-+V&Bs.tt!^Dׂ۫syxYEB> stream x-N0 g F0<_l f ;R 1 } |\(avȡ.G CH#."V'5(;R[ǻB;rJQ}$ߛ?** endstream endobj 15 0 obj << /FontDescriptor 14 0 R /Name /Cmmi10 /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /Cmmi10 /Widths 13 0 R /Subtype /Type3 /CharProcs 16 0 R /Type /Font /FirstChar 0 /FontBBox [ -35 -250 1048 750 ] /Encoding << /Differences [ 58 /period 67 /C 69 /E 73 /I ] /Type /Encoding >> /LastChar 255 >> endobj 14 0 obj << /Descent -250 /FontBBox [ -35 -250 1048 750 ] /StemV 0 /Flags 32 /XHeight 443 /Type /FontDescriptor /FontName /Cmmi10 /MaxWidth 1000 /CapHeight 706 /ItalicAngle 0 /Ascent 750 >> endobj 13 0 obj [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 622 466 591 828 517 362 654 1000 1000 1000 1000 277 277 500 500 500 500 500 500 500 500 500 500 500 500 277 277 777 500 777 500 530 750 758 714 827 738 643 786 831 439 554 849 680 970 803 762 642 790 759 613 584 682 583 944 828 580 682 388 388 388 1000 1000 416 528 429 432 520 465 489 477 576 344 411 520 298 878 600 484 503 446 451 468 361 572 484 715 571 490 465 322 384 636 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 615 833 762 694 742 831 779 583 666 612 750 750 772 639 565 517 444 405 437 496 469 353 576 583 602 494 437 570 517 571 437 540 595 625 651 277 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 ] endobj 16 0 obj << /I 17 0 R /C 18 0 R /E 19 0 R /period 20 0 R >> endobj 25 0 obj << /Filter /FlateDecode /Length 149 >> stream x5K! C9/0R~p6aZ /KL`c%TO>$#I9P7OhL 3l%t2[VNH6f^S $jeF2T`<3\脇ʰN1fF~{20c endstream endobj 26 0 obj << /Filter /FlateDecode /Length 80 >> stream xE 0D{`~&f( JpO{:2Sa ,S`5FR죰n_uzS*Ovvq= endstream endobj 27 0 obj << /Filter /FlateDecode /Length 90 >> stream xMA "OPDtz_NE5jK02kP)U0\ 2IL{qIqzz"X endstream endobj 28 0 obj << /Filter /FlateDecode /Length 210 >> stream x5P C1g dVukm;aBXȔy)K>:L." u%ʚ +`p&^7`i5tႦ.B%|u{OxjrvC` jMX> stream x366W0P04FF @V!HD! $bY@8i -D6 G endstream endobj 30 0 obj << /Filter /FlateDecode /Length 147 >> stream x=O 1 =8z,[\FR"@eG \b9/i?iX5GaXn,JW-^ B]HޭLd<;p'>TYz@;DYIXV]ju=̪'k)PTBt{ o/ endstream endobj 31 0 obj << /Filter /FlateDecode /Length 17 >> stream x36P0C. endstream endobj 32 0 obj << /Filter /FlateDecode /Length 317 >> stream x5RKrC1ۿSpΘ}tj'+-@B./YK~%ۥW%B>R-G- Q=2'":xa>N)x_xN;2$KMH=I+4t~&+s{rj X+)$=Hr7VސWg%&&MܕBXtLX㰄*aՃM5fcdxLP} #GMv²[6!D3,($Nc$ Ұ9 9e, mh%zМaמE[{ endstream endobj 33 0 obj << /Filter /FlateDecode /Length 248 >> stream x-Q9AzBsˑ C :-qPO+Uwu9HTM]vf5,?c 7zqxLu5{kOfP2+qSușO \ ȹeƌ#M!RH&3AQ~#aU#j \Ks4;<9GW +ET<pC7ҹ^s0XM7/=[ endstream endobj 34 0 obj << /Filter /FlateDecode /Length 392 >> stream x=RKn1)@Mr[T /1 %?ꒈ3L~r]Qljg!.6Xr_rњbO/ȴTXVݣC(-װr{d`Jn@CHYAaPl( WԬtb ) ٠[]aP[[xfޑ3qYk?=Q2QMg|2RCgB'`$Ip#A 1qOl)V;ޒ{,\L'ib?lK\+E(~Aq|XdDw#h% 0xyDhDԎ=(ͱ&{ǫvzcw. endstream endobj 35 0 obj << /Filter /FlateDecode /Length 67 >> stream x34P0P4W546T060Q073PH11s,lLeb ̀Ɓ% 9pr΃ endstream endobj 36 0 obj << /Filter /FlateDecode /Length 232 >> stream x5Q;r1} ] 3og3JFfa =؈ėoYf~'Y)QTEX!Yjs#Sr&>'b8Ύf01h9f=!#n4U i[ZSEȺ)Z[=- c _ĜE'~3尒4#15όO}>hw/̈́LHœƘ1T$?г>0TG endstream endobj 37 0 obj << /Filter /FlateDecode /Length 79 >> stream xMͻ О)<>Q"Bc?N:hwZQ}Ps=DX3%:_0ØȶV endstream endobj 38 0 obj << /Filter /FlateDecode /Length 74 >> stream x357U0P F )\@>L̀,CKd!eba26"X@lM4q endstream endobj 39 0 obj << /Filter /FlateDecode /Length 49 >> stream x34U0P F )\@> 4XiLEWD endstream endobj 40 0 obj << /Filter /FlateDecode /Length 59 >> stream x355W0PF F )\@>ehifY@Has`zrZ endstream endobj 41 0 obj << /Filter /FlateDecode /Length 75 >> stream x50{`ɥ htMMBQ0tf}qhV^hJA endstream endobj 42 0 obj << /Filter /FlateDecode /Length 247 >> stream xMQmD1 \ky R]oC /)%K [UC?13,=?TPbht/"+ߏe s`&4`oI&ռ3d‰ATwM,3V7: lx%D`r Z`Q+ tĺv7C/਺x} K{,|BL;wI#fR:=b}@e+ (\* endstream endobj 43 0 obj << /Filter /FlateDecode /Length 71 >> stream x34P0P0S546T04V073PH1 X\00247CbBeX r`J# endstream endobj 44 0 obj << /Filter /FlateDecode /Length 64 >> stream x334T0P5f& F )\@>L̀,cSS$625pD)ON endstream endobj 45 0 obj << /Filter /FlateDecode /Length 304 >> stream x=;0 C{Ȍd'>2VI(/u< i& b;w؞D/)ϡ+E:Ū0[M*K õ}74uK hY pu;Gw5<TQ!OJ|<(!\{0FS@\^BAjI'> stream x5QIn0 .AO ``hKxA[֌]< 'Q^{ .o8|^Z9O2 D`@ai'ώH5YN_KK(O~ J.kO8'O [WLcD.A|!]'@;綟Wuu)O645I"%Ҹ[{TS endstream endobj 47 0 obj << /Filter /FlateDecode /Length 245 >> stream xEPC1 =`,{wHۿ=JFp!Z?ZK oGFA= 3A΄@xFnvpμ39Zpә\'mBITqTqLύׁlӑ!KI%&~S*)[*EH䁓M4,?Cb̠Q0qGuٜ9-L|X&Q)2>'\N}䢥Uޑ"ۡW%Qէ<Y> endstream endobj 48 0 obj << /Filter /FlateDecode /Length 227 >> stream x5O;! 9.m`ϳT/od HDG&^S=Ä=Ba$5븛ſ]3 b0řvX'qNcDlc]L(!0%)}9:Nb.a}1WOdP=&ՍI4^2`a$YNkGQ"1'2Ҝb :; *s>h][M endstream endobj 49 0 obj << /Filter /FlateDecode /Length 133 >> stream xMA0~tzJ-6팀03 endstream endobj 50 0 obj << /Filter /FlateDecode /Length 68 >> stream x32P0P4& f )\@B.H  %[B4AXf&fI8"ɴ endstream endobj 51 0 obj << /Filter /FlateDecode /Length 163 >> stream xEu1 CsUx:?G i@xx= r]Ņ ?޶42܍e@N"WI3Tb\/:"̒@#|:C[ۙ~:!**na.@RԏQꚡ*+kjڿ"}\Ne{g+W}: endstream endobj 52 0 obj << /Filter /FlateDecode /Length 255 >> stream xEK D#> stream x32P0P4& f )\V.L,іp "} endstream endobj 54 0 obj << /Filter /FlateDecode /Length 214 >> stream x=PC1= |7˥m$B6BLɔ:ʒ)O>Kbnd6%*E/% }ՖC4h9~ 3*K6p*3 mtV[ Ф`׶ r " JMrR=ot-N=Dkq: DpFjtaŲC5=kz7hGt4CָR endstream endobj 55 0 obj << /Filter /FlateDecode /Length 161 >> stream xEK CBGG|tJ■!M@w'/mK >[ x6n5uVhR}ith6s+ fz:rGp_Gdf)|Q]dcnk]3s: endstream endobj 56 0 obj << /Filter /FlateDecode /Length 332 >> stream x-R9$1 ~`LtIUls#h/#xE=f۴[iGiK,W ;BjW0wy.2meDkag؏]e8*Jl !2J'Qw\I2[E™w2;yNE{ kF9+%|6vzrYɩHHӺ NKؖߗ3| endstream endobj 57 0 obj << /Filter /FlateDecode /Length 157 >> stream xEC1DsUA wJo-%S'"h0yM%V,&rAJ1xN1븨ufihW3=5'M<[ }@8IP1}bv">G)#qbn fW7y endstream endobj 58 0 obj << /Filter /FlateDecode /Length 163 >> stream xMK1 C=/yoIʀXT~rICpn4Ke>\1'X *b8j`Nhҡ+[a v悓 |:a̶ͮ]cwIi׾zx! [YSoM-<ʦ}>9o:k8. endstream endobj 59 0 obj << /Filter /FlateDecode /Length 131 >> stream xE ! CT>՞0ABA";06Ѣ76իc,zRV鐇Pi0QąYLCaΘȖ2MlTv<e~ma, U^ ?KwUBS0 endstream endobj 60 0 obj << /Filter /FlateDecode /Length 88 >> stream x50C{OɥJoЀw:A).nWz&LC`EՋZ-_ncb*?$ u^8{ endstream endobj 61 0 obj << /Filter /FlateDecode /Length 138 >> stream x=A1y?)vBX޳UO_K^1BCoj NjK)Jș`gzb8V}F%hGSiܖq5)\W4ݴk8߽U__. endstream endobj 62 0 obj << /Filter /FlateDecode /Length 66 >> stream x36P0P07W544U022P042QH12443s`9`a$r`Zs: P9\iM8 endstream endobj 23 0 obj << /FontDescriptor 22 0 R /Name /DejaVuSans /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /DejaVuSans /Widths 21 0 R /Subtype /Type3 /CharProcs 24 0 R /Type /Font /FirstChar 0 /FontBBox [ -1021 -351 1681 1167 ] /Encoding << /Differences [ 32 /space 40 /parenleft /parenright 44 /comma 48 /zero /one /two 52 /four /five /six 56 /eight 58 /colon 67 /C 69 /E /F 73 /I 76 /L 78 /N 84 /T 91 /bracketleft 93 /bracketright 97 /a 99 /c /d /e /f 104 /h /i 108 /l /m /n /o 114 /r /s /t /u 119 /w 121 /y ] /Type /Encoding >> /LastChar 255 >> endobj 22 0 obj << /Descent -236 /FontBBox [ -1021 -351 1681 1167 ] /StemV 0 /Flags 32 /XHeight 0 /Type /FontDescriptor /FontName /DejaVuSans /MaxWidth 1342 /CapHeight 0 /ItalicAngle 0 /Ascent 929 >> endobj 21 0 obj [ 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 600 318 401 460 838 636 950 780 275 390 390 500 838 318 361 318 337 636 636 636 636 636 636 636 636 636 636 337 337 838 838 838 531 1000 684 686 698 770 632 575 775 752 295 295 656 557 863 748 787 603 787 695 635 611 732 684 989 685 611 685 390 337 390 838 500 500 613 635 550 635 615 352 635 634 278 278 579 278 974 634 612 635 635 411 521 392 634 592 818 592 592 525 636 337 636 838 600 636 600 318 352 518 1000 500 500 500 1342 635 400 1070 600 685 600 600 318 318 518 518 590 500 1000 500 1000 521 400 1023 600 525 611 318 401 636 636 636 636 337 500 500 1000 471 612 838 361 1000 500 500 838 401 401 500 636 636 318 500 401 471 612 969 969 969 531 684 684 684 684 684 684 974 698 632 632 632 632 295 295 295 295 775 748 787 787 787 787 787 838 787 732 732 732 732 611 605 630 613 613 613 613 613 613 982 550 615 615 615 615 278 278 278 278 612 634 612 612 612 612 612 838 612 634 634 634 634 592 635 592 ] endobj 24 0 obj << /parenright 25 0 R /one 26 0 R /four 27 0 R /zero 28 0 R /colon 29 0 R /parenleft 30 0 R /space 31 0 R /six 32 0 R /two 33 0 R /comma 62 0 R /eight 34 0 R /bracketright 35 0 R /C 36 0 R /E 37 0 R /F 38 0 R /I 39 0 R /L 40 0 R /N 41 0 R /bracketleft 43 0 R /T 44 0 R /a 45 0 R /c 46 0 R /e 47 0 R /d 48 0 R /f 49 0 R /i 50 0 R /h 51 0 R /m 52 0 R /l 53 0 R /o 54 0 R /n 55 0 R /s 56 0 R /r 57 0 R /u 58 0 R /t 59 0 R /w 60 0 R /y 61 0 R /five 42 0 R >> endobj 67 0 obj << /Filter /FlateDecode /Length 331 >> stream x-R;r1ƀϓL8ZBXExUp|a[ܰ%} 70B=fK8:dǢ#ѽRнkSR*ddЮ.M'b$UԄkW8|ntG%$lCd7f~iC3yudqR.s2y%uH!ZX(Qnj*#-9a;{*hڮGyL pyܧՊaРrPj\UF ?}\-^^ۻG7xͅL!.<-x>clQԍ\ϚB^YAP9n>R5}{ endstream endobj 65 0 obj << /FontDescriptor 64 0 R /Name /Cmsy10 /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /Cmsy10 /Widths 63 0 R /Subtype /Type3 /CharProcs 66 0 R /Type /Font /FirstChar 0 /FontBBox [ -29 -960 1124 779 ] /Encoding << /Differences [ 33 /arrowright ] /Type /Encoding >> /LastChar 255 >> endobj 64 0 obj << /Descent -960 /FontBBox [ -29 -960 1124 779 ] /StemV 0 /Flags 32 /XHeight 0 /Type /FontDescriptor /FontName /Cmsy10 /MaxWidth 1200 /CapHeight 707 /ItalicAngle 0 /Ascent 779 >> endobj 63 0 obj [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 1000 500 500 1000 1000 1000 777 1000 1000 611 611 1000 1000 1000 777 275 1000 666 666 888 888 0 0 555 555 666 500 722 722 777 777 611 798 656 526 771 527 718 594 844 544 677 762 689 1200 820 796 695 816 847 605 544 625 612 987 713 668 724 666 666 666 666 666 611 611 444 444 444 444 500 500 388 388 277 500 500 611 500 277 833 750 833 416 666 666 777 777 444 444 444 611 777 777 777 777 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 0 777 277 777 500 777 500 777 777 777 777 750 750 777 777 777 1000 500 500 777 777 777 777 777 777 777 777 777 777 777 777 1000 1000 777 777 1000 777 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 ] endobj 66 0 obj << /arrowright 67 0 R >> endobj 72 0 obj << /Filter /FlateDecode /Length 323 >> stream x5Rm,1 Olv=[$b%P)q\ka!.!{5c}&3swT5>!L C8.TNvMEe*~9YDq&Rdt:TpA~zSw!]9"|.~/}VB*?aOB-8"}Y˟S,l#KcKw6rmF67`W쀟7FTBuN$1VR&WdrJm-٦#Zs2r=NN1~Wc~FM=&vzHoZ$ʊ,j4t@@&~a3{( endstream endobj 73 0 obj << /Filter /FlateDecode /Length 205 >> stream x-P0{ FfJ UeEVB/ߕ@Krٽ7D/ iB-w/ˍZgB^u@iT3rfy嗴S&IJxLgM>d1 b0lṘc!7=.]:TUuk+3)nuGiLXB endstream endobj 74 0 obj << /Filter /FlateDecode /Length 411 >> stream x-˕c1D/ B_IOz΅  εdIWĕ}6o"M>-5bK+`]|6MMũw'id2`ݫ3=4p36 I޲ReGms+WxϣS&.3p0^ϳEKV қ4Q$K?EteW3$xJ$6NL"21%<;m?y&:z?Qw̼WFpyUkA] ej t́دx7ilc|'p!8BYYϹ%*6~ZP^6`uy1ɶΙYėQcLNTUhrC1z!v;hr53's3' endstream endobj 75 0 obj << /Filter /FlateDecode /Length 134 >> stream x-1DsUA U9ZzN4_b3S%K"^rps$c@I$g[PFNbMZZRc 5.K'a~$xa(3r. 0ʻs_4-[ endstream endobj 70 0 obj << /FontDescriptor 69 0 R /Name /Cmr10 /FontMatrix [ 0.001 0 0 0.001 0 0 ] /BaseFont /Cmr10 /Widths 68 0 R /Subtype /Type3 /CharProcs 71 0 R /Type /Font /FirstChar 0 /FontBBox [ -44 -250 1009 750 ] /Encoding << /Differences [ 48 /zero /one /two 61 /equal ] /Type /Encoding >> /LastChar 255 >> endobj 69 0 obj << /Descent -216 /FontBBox [ -44 -250 1009 750 ] /StemV 0 /Flags 32 /XHeight 453 /Type /FontDescriptor /FontName /Cmr10 /MaxWidth 1027 /CapHeight 706 /ItalicAngle 0 /Ascent 706 >> endobj 68 0 obj [ 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 333 277 500 833 500 833 777 277 388 388 500 777 277 333 277 500 500 500 500 500 500 500 500 500 500 500 277 277 277 777 472 472 777 750 708 722 763 680 652 784 750 361 513 777 625 916 750 777 680 777 736 555 722 750 750 1027 750 750 611 277 500 277 500 277 277 500 555 444 555 444 305 500 555 277 305 527 277 833 555 500 555 527 391 394 388 555 527 722 527 527 444 500 1000 500 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 333 625 833 777 694 666 750 722 777 722 777 750 750 722 583 555 555 833 833 277 305 500 500 750 500 500 750 444 500 722 777 500 902 1013 777 277 500 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 750 ] endobj 71 0 obj << /zero 72 0 R /one 75 0 R /equal 73 0 R /two 74 0 R >> endobj 4 0 obj << /F2 15 0 R /F1 23 0 R /F4 70 0 R /F3 65 0 R >> endobj 5 0 obj << >> endobj 6 0 obj << >> endobj 7 0 obj << /M0 12 0 R >> endobj 12 0 obj << /Filter /FlateDecode /Subtype /Form /Length 142 /Type /XObject /BBox [ -0.5 -0.5 0.5 0.5 ] >> stream xu; E{V004if$ƂL^|Tj)TIFtMeYj]xZӻ5rr72hUy:Nh)u8Bg)A \< #XcisW ' ff endstream endobj 3 0 obj << /Count 1 /Kids [ 10 0 R ] /Type /Pages >> endobj xref 0 76 0000000000 65535 f 0000000016 00000 n 0000000065 00000 n 0000089457 00000 n 0000089045 00000 n 0000089110 00000 n 0000089131 00000 n 0000089152 00000 n 0000000218 00000 n 0000000453 00000 n 0000000346 00000 n 0000069721 00000 n 0000089184 00000 n 0000072070 00000 n 0000071873 00000 n 0000071562 00000 n 0000073116 00000 n 0000069743 00000 n 0000070139 00000 n 0000070684 00000 n 0000071360 00000 n 0000082543 00000 n 0000082343 00000 n 0000081796 00000 n 0000083596 00000 n 0000073183 00000 n 0000073405 00000 n 0000073557 00000 n 0000073719 00000 n 0000074002 00000 n 0000074142 00000 n 0000074362 00000 n 0000074451 00000 n 0000074841 00000 n 0000075162 00000 n 0000075627 00000 n 0000075766 00000 n 0000076071 00000 n 0000076222 00000 n 0000076368 00000 n 0000076489 00000 n 0000076620 00000 n 0000076767 00000 n 0000077087 00000 n 0000077230 00000 n 0000077366 00000 n 0000077743 00000 n 0000078046 00000 n 0000078364 00000 n 0000078664 00000 n 0000078870 00000 n 0000079010 00000 n 0000079246 00000 n 0000079574 00000 n 0000079691 00000 n 0000079978 00000 n 0000080212 00000 n 0000080617 00000 n 0000080847 00000 n 0000081083 00000 n 0000081287 00000 n 0000081447 00000 n 0000081658 00000 n 0000084963 00000 n 0000084768 00000 n 0000084471 00000 n 0000086014 00000 n 0000084067 00000 n 0000087925 00000 n 0000087729 00000 n 0000087420 00000 n 0000088972 00000 n 0000086055 00000 n 0000086451 00000 n 0000086729 00000 n 0000087213 00000 n trailer << /Info 2 0 R /Root 1 0 R /Size 76 >> startxref 89517 %%EOF PyNN-0.10.0/examples/iaf_sfa_relref/standard_neurons.yaml000066400000000000000000000017131415343567000234300ustar00rootroot00000000000000# These are the parameters for the excitatory and inhibitory neurons used in: # # Muller, E., Buesing, L., Schemmel, J., & Meier, K. (2007). Spike-frequency # adapting neural ensembles: Beyond mean adaptation and renewal theories. # Neural Computation, 19, 2958-3010. # # and # # Muller, E., Meier, K., & Schemmel, J. (2004). Methods for simulating # high-conductance states in neural microcircuits. Proc. of BICS2004. excitatory: cm: 0.2895 tau_m: 10.0 v_reset: -70.0 v_rest: -70.0 v_thresh: -57.0 e_rev_E: 0.0 e_rev_I: -75.0 tau_syn_E: 1.5 tau_syn_I: 10.0 tau_refrac: 0.1 e_rev_rr: -70.0 e_rev_sfa: -70.0 q_rr: 3214.0 q_sfa: 14.48 tau_rr: 1.97 tau_sfa: 110.0 inhibitory: cm: 0.141 e_rev_E: 0.0 e_rev_I: -75.0 e_rev_rr: -70.0 e_rev_sfa: -70.0 q_rr: 1565.0 q_sfa: 0.0 tau_m: 6.664 tau_refrac: 0.1 tau_rr: 1.97 tau_sfa: 110.0 tau_syn_E: 1.5 tau_syn_I: 10.0 v_reset: -70.0 v_rest: -70.0 v_thresh: -54.5 PyNN-0.10.0/examples/inhomogeneous_network.py000066400000000000000000000044601415343567000212500ustar00rootroot00000000000000""" Small, inhomogeneous network Andrew Davison, UNIC, CNRS December 2012 """ from pyNN.utility import init_logging, normalized_filename from pyNN.parameters import Sequence from pyNN.space import Grid2D from importlib import import_module import numpy as np from lazyarray import sqrt import argparse parser = argparse.ArgumentParser() parser.add_argument('simulator_name') parser.add_argument("--debug", action="store_true") args = parser.parse_args() init_logging(None, debug=args.debug) sim = import_module("pyNN.%s" % args.simulator_name) simtime = 100.0 input_rate = 20.0 n_cells = 9 sim.setup() cell_type = sim.IF_cond_exp(tau_m=10.0, v_rest=lambda i: -60.0 + i, v_thresh=lambda i: -55.0 + i) cells = sim.Population(n_cells, cell_type, structure=Grid2D(dx=100.0, dy=100.0), initial_values={'v': lambda i: -60.0 - i}, label="cells") print("positions:") print(cells.positions) for name in ('tau_m', 'v_rest', 'v_thresh'): print(name, "=", cells.get(name)) number = int(2 * simtime * input_rate / 1000.0) np.random.seed(26278342) def generate_spike_times(i): gen = lambda: Sequence(np.add.accumulate(np.random.exponential(1000.0 / input_rate, size=number))) if hasattr(i, "__len__"): return [gen() for j in i] else: return gen() assert generate_spike_times(0).max() > simtime spike_source = sim.Population(n_cells, sim.SpikeSourceArray(spike_times=generate_spike_times)) connections = sim.Projection(spike_source, cells, sim.FixedProbabilityConnector(0.5), sim.StaticSynapse(weight='1/(1+d)', delay=0.5) ) print("weights:") print(str(connections.get('weight', format='array')).replace('nan', ' . ')) print("delays:") print(str(connections.get('delay', format='array')).replace('nan', ' . ')) cells.record(['spikes', 'v']) sim.run(100.0) filename = normalized_filename("Results", "inhomogeneous_network", "pkl", args.simulator_name) cells.write_data(filename, annotations={'script_name': __file__}) print("Mean firing rate: ", cells.mean_spike_count() * 1000.0 / sim.get_current_time(), "Hz") sim.end() PyNN-0.10.0/examples/multiquantal_synapses.py000066400000000000000000000102241415343567000212600ustar00rootroot00000000000000# encoding: utf-8 """ ... """ from pyNN.utility import get_simulator, init_logging, normalized_filename import neo import numpy as np import matplotlib matplotlib.use('Agg') # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(quit_on_end=False) # === Build and instrument the network ======================================= spike_times = np.hstack((np.arange(10, 100, 10), np.arange(250, 350, 10))) spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=spike_times)) connector = sim.AllToAllConnector() depressing = dict(U=0.8, tau_rec=100.0, tau_facil=0.0, weight=0.01, delay=0.5) facilitating = dict(U=0.04, tau_rec=50.0, tau_facil=200.0, weight=0.01, delay=0.5) synapse_types = { 'depressing, n=1': sim.MultiQuantalSynapse(n=1, **depressing), 'depressing, n=5': sim.MultiQuantalSynapse(n=5, **depressing), 'facilitating, n=1': sim.MultiQuantalSynapse(n=1, **facilitating), 'facilitating, n=5': sim.MultiQuantalSynapse(n=5, **facilitating), } populations = {} projections = {} for label in synapse_types: populations[label] = sim.Population( 1000, sim.IF_cond_exp(e_rev_I=-75, tau_syn_I=4.3), label=label) populations[label].record('gsyn_inh') projections[label] = sim.Projection(spike_source, populations[label], connector, receptor_type='inhibitory', synapse_type=synapse_types[label]) projections[label].initialize(a=synapse_types[label].parameter_space['n'], u=synapse_types[label].parameter_space['U']) spike_source.record('spikes') #if "nest" in sim.__name__: # print(sim.nest.GetStatus([projections['depressing, n=5'].nest_connections[0]])) # === Run the simulation ===================================================== sim.run(400.0) # === Save the results, optionally plot a figure ============================= for label, p in populations.items(): filename = normalized_filename("Results", "multiquantal_synapses_%s" % label, "pkl", options.simulator) p.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel # figure_filename = normalized_filename("Results", "multiquantal_synapses", # "png", options.simulator) figure_filename = "Results/multiquantal_synapses_{}.png".format(options.simulator) data = {} for label in synapse_types: data[label] = populations[label].get_data().segments[0] gsyn = data[label].filter(name='gsyn_inh')[0] gsyn_mean = neo.AnalogSignal(gsyn.mean(axis=1).reshape(-1, 1), sampling_rate=gsyn.sampling_rate, array_annotations={"channel_index": np.array([0])}) gsyn_mean.name = 'gsyn_inh_mean' data[label].analogsignals.append(gsyn_mean) def make_panel(population, label): return Panel(population.get_data().segments[0].filter(name='gsyn_inh')[0], data_labels=[label], yticks=True) labels = sorted(synapse_types) panels = [ Panel(data[label].filter(name='gsyn_inh_mean')[0], data_labels=[label], yticks=True) for label in labels ] # add ylabel to top panel in each group panels[0].options.update(ylabel=u'Synaptic conductance (µS)') ##panels[len(synapse_types)].options.update(ylabel='Membrane potential (mV)') # add xticks and xlabel to final panel panels[-1].options.update(xticks=True, xlabel="Time (ms)") Figure(*panels, title="Example of facilitating and depressing multi-quantal release synapses", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/nineml_brunel.py000066400000000000000000000070321415343567000174470ustar00rootroot00000000000000# encoding: utf-8 """ Simulation script for the Brunel (2000) network model as described in NineML. This script imports a Python lib9ml network description from "brunel_network_alpha.py", exports it as XML, and then runs a simulation using the pyNN.nineml module with the NEURON backend. """ import numpy as np from neo import AnalogSignal from quantities import ms, dimensionless import pyNN.neuron as sim from pyNN.nineml.read import Network from pyNN.utility import SimulationProgressBar from pyNN.utility.plotting import Figure, Panel import argparse import ninemlcatalog cases = ['SR', 'SR2', 'SR3', 'SIfast', 'AI', 'SIslow'] parser = argparse.ArgumentParser() parser.add_argument('simulator_name', help=("The simulator to use")) parser.add_argument('case', help=("The simulation case to run, can be one of '{}'" .format("', '".join(cases)))) parser.add_argument('--plot', action='store_true', help=("Plot the resulting figures")) parser.add_argument('--limits', nargs=2, default=(900, 1200), metavar=('LOW', 'HIGH'), help="The stop and start time of the plot") args = parser.parse_args() sim.setup() if args.case not in cases: raise Exception("Unrecognised case '{}', allowed cases are '{}'" .format(args.case, "', '".join(cases))) document = ninemlcatalog.load('/network/Brunel2000/' + args.case) xml_file = document.url print("Building network") net = Network(sim, xml_file) if args.plot: stim = net.populations["Ext"] stim[:100].record('spikes') exc = net.populations["Exc"] exc.sample(50).record("spikes") exc.sample(3).record(["nrn_v", "syn_a"]) inh = net.populations["Inh"] inh.sample(50).record("spikes") inh.sample(3).record(["nrn_v", "syn_a"]) else: all_neurons = net.assemblies["All"] # all.sample(50).record("spikes") all_neurons.record("spikes") print("Running simulation") t_stop = args.limits[1] pb = SimulationProgressBar(t_stop / 80, t_stop) sim.run(t_stop, callbacks=[pb]) print("Handling data") if args.plot: stim_data = stim.get_data().segments[0] exc_data = exc.get_data().segments[0] inh_data = inh.get_data().segments[0] else: all_neurons.write_data("brunel_network_alpha_%s.h5" % args.case) sim.end() def instantaneous_firing_rate(segment, begin, end): """Computed in bins of 0.1 ms """ bins = np.arange(begin, end, 0.1) hist, _ = np.histogram(segment.spiketrains[0].time_slice(begin, end), bins) for st in segment.spiketrains[1:]: h, _ = np.histogram(st.time_slice(begin, end), bins) hist += h return AnalogSignal(hist, sampling_period=0.1 * ms, units=dimensionless, channel_index=0, name="Spike count") if args.plot: Figure( Panel(stim_data.spiketrains, markersize=0.2, xlim=args.limits), Panel(exc_data.analogsignals[0], yticks=True, xlim=args.limits), Panel(exc_data.analogsignals[1], yticks=True, xlim=args.limits), Panel(exc_data.spiketrains[:100], markersize=0.5, xlim=args.limits), Panel(instantaneous_firing_rate(exc_data, *args.limits), yticks=True), Panel(inh_data.analogsignals[0], yticks=True, xlim=args.limits), Panel(inh_data.analogsignals[1], yticks=True, xlim=args.limits), Panel(inh_data.spiketrains[:100], markersize=0.5, xlim=args.limits), Panel(instantaneous_firing_rate(inh_data, *args.limits), xticks=True, xlabel="Time (ms)", yticks=True), ).save("brunel_network_alpha.png") PyNN-0.10.0/examples/nineml_neuron.py000066400000000000000000000105611415343567000174670ustar00rootroot00000000000000""" Example of using a cell type defined in 9ML """ import sys from copy import deepcopy from nineml.abstraction import ( Dynamics, Regime, On, OutputEvent, StateVariable, AnalogReceivePort, AnalogReducePort, AnalogSendPort, EventSendPort, Parameter) from nineml import units as un from pyNN.utility import init_logging, get_simulator, normalized_filename sim, options = get_simulator(("--plot-figure", "plot a figure with the given filename")) init_logging(None, debug=True) sim.setup(timestep=0.1, min_delay=0.1, max_delay=2.0) iaf = Dynamics( name="iaf", regimes=[ Regime( name="subthresholdregime", time_derivatives=["dV/dt = ( gl*( vrest - V ) + ISyn)/(cm)"], transitions=On("V > vthresh", do=["tspike = t", "V = vreset", OutputEvent('spikeoutput')], to="refractoryregime"), ), Regime( name="refractoryregime", transitions=[On("t >= tspike + taurefrac", to="subthresholdregime")], ) ], state_variables=[ StateVariable('V', un.voltage), StateVariable('tspike', un.time), ], analog_ports=[AnalogSendPort("V", un.voltage), AnalogReducePort("ISyn", un.current, operator="+"), ], event_ports=[EventSendPort('spikeoutput'), ], parameters=[Parameter('cm', un.capacitance), Parameter('taurefrac', un.time), Parameter('gl', un.conductance), Parameter('vreset', un.voltage), Parameter('vrest', un.voltage), Parameter('vthresh', un.voltage)]) coba = Dynamics( name="CobaSyn", aliases=["I:=g*(vrev-V)", ], regimes=[ Regime( name="cobadefaultregime", time_derivatives=["dg/dt = -g/tau", ], transitions=On('spikeinput', do=["g=g+q"]), ) ], state_variables=[StateVariable('g', un.conductance)], analog_ports=[AnalogReceivePort("V", un.voltage), AnalogSendPort("I", un.current)], parameters=[Parameter('tau', un.time), Parameter('q', un.conductance), Parameter('vrev', un.voltage)]) iaf_2coba = Dynamics( name="iaf_2coba", subnodes={"iaf": iaf, "excitatory": coba, "inhibitory": deepcopy(coba)}) iaf_2coba.connect_ports("iaf.V", "excitatory.V") iaf_2coba.connect_ports("iaf.V", "inhibitory.V") iaf_2coba.connect_ports("excitatory.I", "iaf.ISyn") iaf_2coba.connect_ports("inhibitory.I", "iaf.ISyn") celltype_cls = sim.nineml.nineml_celltype_from_model( name="iaf_2coba", nineml_model=iaf_2coba, synapse_components=[ sim.nineml.CoBaSyn(namespace='excitatory', weight_connector='q'), sim.nineml.CoBaSyn(namespace='inhibitory', weight_connector='q')]) parameters = { 'iaf_cm': 1.0, 'iaf_gl': 50.0, 'iaf_taurefrac': 2.0, 'iaf_vrest': -65.0, 'iaf_vreset': -65.0, 'iaf_vthresh': -55.0, 'excitatory_tau': 2.0, 'inhibitory_tau': 5.0, 'excitatory_vrev': 0.0, 'inhibitory_vrev': -70.0, } print(celltype_cls.default_parameters) cells = sim.Population(1, celltype_cls, parameters) cells.initialize(iaf_V=parameters['iaf_vrest']) cells.initialize(iaf_t_spike=-1e99) # neuron not refractory at start input = sim.Population(2, sim.SpikeSourcePoisson, {'rate': 500}) connector = sim.OneToOneConnector() syn = sim.StaticSynapse(weight=5.0, delay=0.5) conn = [sim.Projection(input[0:1], cells, connector, syn, receptor_type='excitatory'), sim.Projection(input[1:2], cells, connector, syn, receptor_type='inhibitory')] cells.record(('spikes', 'iaf_V', 'excitatory_g', 'inhibitory_g')) sim.run(100.0) cells.write_data( normalized_filename("Results", "nineml_cell", "pkl", options.simulator, sim.num_processes()), annotations={'script_name': __file__}) data = cells.get_data().segments[0] sim.end() if options.plot_figure: from pyNN.utility.plotting import Figure, Panel Figure( Panel(data.filter(name='iaf_V')[0]), Panel(data.filter(name='excitatory_g')[0], data.filter(name='inhibitory_g')[0], data_labels=['exc', 'inh'], ylabel="Synaptic conductance", xlabel="Time (ms)", xticks=True), title=__file__ ).save(options.plot_figure) print(data.spiketrains) PyNN-0.10.0/examples/nrn_artificial_cell.py000066400000000000000000000051401415343567000205770ustar00rootroot00000000000000# encoding: utf-8 """ Small network created with NEURON ARTIFICIAL_CELL models Usage: nrn_artificial_cell.py """ import numpy as np from pyNN.utility import get_simulator, init_logging, normalized_filename from pyNN.parameters import Sequence from pyNN.random import RandomDistribution as rnd import pyNN.neuron as sim # === Define parameters ======================================================== n = 20 # Number of cells w = 0.1 # synaptic weight (dimensionless) cell_params = { 'tau' : 20.0, # (ms) 'refrac' : 2.0, # (ms) } dt = 0.1 # (ms) syn_delay = 1.0 # (ms) input_rate = 50.0 # (Hz) simtime = 1000.0 # (ms) # === Build the network ======================================================== sim.setup(timestep=dt, max_delay=syn_delay) cells = sim.Population(n, sim.IntFire1(**cell_params), initial_values={'m': rnd('uniform', (0.0, 1.0))}, label="cells") number = int(2 * simtime * input_rate / 1000.0) np.random.seed(26278342) def generate_spike_times(i): gen = lambda: Sequence(np.add.accumulate(np.random.exponential(1000.0 / input_rate, size=number))) if hasattr(i, "__len__"): return [gen() for j in i] else: return gen() assert generate_spike_times(0).max() > simtime spike_source = sim.Population(n, sim.SpikeSourceArray(spike_times=generate_spike_times)) spike_source.record('spikes') cells.record('spikes') cells[0:2].record('m') syn = sim.StaticSynapse(weight=w, delay=syn_delay) input_conns = sim.Projection(spike_source, cells, sim.FixedProbabilityConnector(0.5), syn, receptor_type="default") # === Run simulation =========================================================== sim.run(simtime) filename = normalized_filename("Results", "nrn_artificial_cell", "pkl", "neuron", sim.num_processes()) cells.write_data(filename, annotations={'script_name': __file__}) print("Mean firing rate: ", cells.mean_spike_count() * 1000.0 / simtime, "Hz") plot_figure = True if plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") data = cells.get_data().segments[0] m = data.filter(name="m")[0] Figure( Panel(m, ylabel="Membrane potential (dimensionless)", yticks=True, ylim=(0, 1)), Panel(data.spiketrains, xlabel="Time (ms)", xticks=True), annotations="Simulated with NEURON" ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/parameter_changes.py000066400000000000000000000011651415343567000202670ustar00rootroot00000000000000""" """ from pyNN.utility import get_simulator sim, options = get_simulator() sim.setup(timestep=0.01) cell = sim.Population(1, sim.EIF_cond_exp_isfa_ista(v_thresh=-55.0, tau_refrac=5.0)) current_source = sim.StepCurrentSource(times=[50.0, 200.0, 250.0, 400.0, 450.0, 600.0, 650.0, 800.0], amplitudes=[1.0, 0.0, 1.0, 0.0, 1.0, 0.0, 1.0, 0.0]) cell.inject(current_source) cell.record('v') for a in (0.0, 4.0, 20.0, 100.0): print("Setting current to %g nA" % a) cell.set(a=a) sim.run(200.0) cell.write_data("Results/parameter_changes_%s.pkl" % options.simulator) sim.end() PyNN-0.10.0/examples/random_distributions.py000066400000000000000000000042421415343567000210600ustar00rootroot00000000000000""" Illustration of the different standard random distributions and different random number generators """ import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import scipy.stats import pyNN.random as random try: from neuron import h except ImportError: have_nrn = False else: have_nrn = True from pyNN.neuron.random import NativeRNG n = 100000 nbins = 100 rnglist = [random.NumpyRNG(seed=984527)] if random.have_gsl: rnglist.append(random.GSLRNG(seed=668454)) if have_nrn: rnglist.append(NativeRNG(seed=321245)) cases = ( ("uniform", {"low": -65, "high": -55}, (-65, -55), scipy.stats.uniform(loc=-65, scale=10)), ("gamma", {"k": 2.0, "theta": 0.5}, (0, 5), scipy.stats.gamma(2.0, loc=0.0, scale=0.5)), ("normal", {"mu": -1.0, "sigma": 0.5}, (-3, 1), scipy.stats.norm(loc=-1, scale=0.5)), ("exponential", {'beta': 10.0}, (0, 50), scipy.stats.expon(loc=0, scale=10)), ("normal_clipped", {"mu": 0.5, "sigma": 0.5, "low": 0, "high": 10}, (-0.5, 3.0), None), ) fig = plt.figure(1) rows = len(cases) cols = len(rnglist) settings = { 'lines.linewidth': 0.5, 'axes.linewidth': 0.5, 'axes.labelsize': 'small', 'axes.titlesize': 'small', 'legend.fontsize': 'small', 'font.size': 8, 'savefig.dpi': 150, } plt.rcParams.update(settings) width, height = (2 * cols, 2 * rows) fig = plt.figure(1, figsize=(width, height)) gs = gridspec.GridSpec(rows, cols) gs.update(hspace=0.4) for i, case in enumerate(cases): distribution, parameters, xlim, rv = case bins = np.linspace(*xlim, num=nbins) for j, rng in enumerate(rnglist): rd = random.RandomDistribution(distribution, rng=rng, **parameters) values = rd.next(n) assert values.size == n plt.subplot(gs[i, j]) counts, bins, _ = plt.hist(values, bins, range=xlim) plt.title("%s.%s%s" % (rng, distribution, parameters.values())) if rv is not None: pdf = rv.pdf(bins) scaled_pdf = n * pdf / pdf.sum() plt.plot(bins, scaled_pdf, 'r-') plt.ylim(0, 1.2 * scaled_pdf.max()) plt.xlim(xlim) plt.savefig("Results/random_distributions.png") PyNN-0.10.0/examples/random_numbers.py000066400000000000000000000102371415343567000176320ustar00rootroot00000000000000""" An example to illustrate random number handling in PyNN In particular, this shows the difference between "native" and Python random number generators. If you run this script with two different simulators, e.g. NEST and NEURON, the weight matrix created with the Python RNG will be the same for both simulations, the weights created with the native RNG will be different in the two cases. The potential advantage of using a native RNG is speed: for large networks, using the `NativeRNG` class can reduce network construction time, but at the expense of cross-simulator repeatability. Usage: random_numbers.py [-h] [--plot-figure] [--debug DEBUG] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure plot the simulation results to a file --debug DEBUG print debugging information """ import numpy as np from pyNN.random import NumpyRNG, RandomDistribution from pyNN.utility import get_simulator # === Configure the simulator ================================================ sim, options = get_simulator( ("--plot-figure", "plot the simulation results to a file", {"action": "store_true"}), ("--debug", "print debugging information")) sim.setup() # === Create random number generators ======================================== python_rng = NumpyRNG(seed=98497627) native_rng = sim.NativeRNG(seed=87354762) # === Define the neuron model and initial conditions ========================= # not possible with NEST to use NativeRNG here cell_type = sim.IF_cond_exp(tau_m=RandomDistribution('normal', (15.0, 2.0), rng=python_rng)) v_init = RandomDistribution('uniform', (cell_type.default_parameters['v_rest'], cell_type.default_parameters['v_thresh']), rng=python_rng) # not possible with NEST to use NativeRNG here # === Create populations of neurons, and record from them ==================== # in the current version, can't specify the RNG - it is always native p1 = sim.Population(10, sim.SpikeSourcePoisson(rate=100.0)) p2 = sim.Population(10, cell_type, initial_values={'v': v_init}) p1.record("spikes") p2.record("spikes") p2.sample(3, rng=python_rng).record("v") # can't use native RNG here # === Create two sets of synaptic connections, one for each RNG ============== connector_native = sim.FixedProbabilityConnector(p_connect=0.7, rng=native_rng) connector_python = sim.FixedProbabilityConnector(p_connect=0.7, rng=python_rng) synapse_type_native = sim.StaticSynapse(weight=RandomDistribution('normal', mu=0.5, sigma=0.01, rng=native_rng), delay=0.5) synapse_type_python = sim.StaticSynapse(weight=RandomDistribution('normal', mu=0.5, sigma=0.01, rng=python_rng), delay=0.5) projection_native = sim.Projection(p1, p2, connector_native, synapse_type_native) projection_python = sim.Projection(p1, p2, connector_python, synapse_type_python) # === Print the synaptic weight matrices ===================================== weights_python = projection_python.get("weight", format="array") weights_native = projection_native.get("weight", format="array") print(weights_python) print(weights_native) # === Run the simulation ===================================================== sim.run(100.0) sim.end() # === Optionally, plot the synaptic weight matrices ========================== if options.plot_figure: from pyNN.utility import normalized_filename from pyNN.utility.plotting import Figure, Panel filename = normalized_filename("Results", "random_numbers", "png", options.simulator) # where there is no connection, the weight matrix contains NaN # for plotting purposes, we replace NaN with zero. weights_python[np.isnan(weights_python)] = 0 weights_native[np.isnan(weights_native)] = 0 Figure( Panel(weights_python, cmap='gray_r', vmin=0.45, vmax=0.55, xlabel="Python RNG"), Panel(weights_native, cmap='gray_r', vmin=0.45, vmax=0.55, xlabel="Native RNG"), annotations="Simulated with %s" % options.simulator.upper() ).save(filename) print(filename) PyNN-0.10.0/examples/simpleRandomNetwork.py000066400000000000000000000053311415343567000206220ustar00rootroot00000000000000""" Simple network with a 1D population of poisson spike sources projecting to a 2D population of IF_curr_exp neurons. Andrew Davison, UNIC, CNRS August 2006, November 2009 """ from pyNN.random import NumpyRNG, RandomDistribution import socket import os from importlib import import_module import numpy as np from pyNN.utility import get_script_args, init_logging, normalized_filename simulator_name = get_script_args(1)[0] sim = import_module("pyNN.%s" % simulator_name) init_logging(None, debug=True) seed = 764756387 rng = NumpyRNG(seed=seed, parallel_safe=True) tstop = 1000.0 # ms input_rate = 100.0 # Hz cell_params = {'tau_refrac': 2.0, # ms 'v_thresh': -50.0, # mV 'tau_syn_E': 2.0, # ms 'tau_syn_I': 2.0, # ms 'tau_m': RandomDistribution('uniform', low=18.0, high=22.0, rng=rng) } n_record = 3 node = sim.setup(timestep=0.025, min_delay=1.0, max_delay=1.0, debug=True, quit_on_end=False) print("Process with rank %d running on %s" % (node, socket.gethostname())) print("[%d] Creating populations" % node) n_spikes = int(2 * tstop * input_rate / 1000.0) spike_times = np.add.accumulate(rng.next(n_spikes, 'exponential', {'beta': 1000.0 / input_rate})) input_population = sim.Population(10, sim.SpikeSourceArray(spike_times=spike_times), label="input") output_population = sim.Population(20, sim.IF_curr_exp(**cell_params), label="output") print("[%d] input_population cells: %s" % (node, input_population.local_cells)) print("[%d] output_population cells: %s" % (node, output_population.local_cells)) print("tau_m =", output_population.get('tau_m')) print("[%d] Connecting populations" % node) connector = sim.FixedProbabilityConnector(0.5, rng=rng) syn = sim.StaticSynapse(weight=1.0) projection = sim.Projection(input_population, output_population, connector, syn) filename = normalized_filename("Results", "simpleRandomNetwork", "conn", simulator_name, sim.num_processes()) projection.save('connections', filename) input_population.record('spikes') output_population.record('spikes') output_population.sample(n_record, rng).record('v') print("[%d] Running simulation" % node) sim.run(tstop) print("[%d] Writing spikes and Vm to disk" % node) filename = normalized_filename("Results", "simpleRandomNetwork_output", "pkl", simulator_name, sim.num_processes()) output_population.write_data(filename, annotations={'script_name': __file__}) ##input_population.write_data('%s_input.h5' % file_stem) spike_counts = output_population.get_spike_counts() for id in sorted(spike_counts): print(id, spike_counts[id]) print("[%d] Finishing" % node) sim.end() print("[%d] Done" % node) PyNN-0.10.0/examples/simpleRandomNetwork_csa.py000066400000000000000000000043231415343567000214500ustar00rootroot00000000000000""" Simple network with a 1D population of poisson spike sources projecting to a 2D population of IF_curr_exp neurons. Andrew Davison, UNIC, CNRS August 2006, November 2009 """ from pyNN.random import NumpyRNG import socket import os import csa import numpy as np from pyNN.utility import get_script_args, Timer simulator_name = get_script_args(1)[0] exec("from pyNN.%s import *" % simulator_name) timer = Timer() seed = 764756387 tstop = 1000.0 # ms input_rate = 100.0 # Hz cell_params = {'tau_refrac': 2.0, # ms 'v_thresh': -50.0, # mV 'tau_syn_E': 2.0, # ms 'tau_syn_I': 2.0} # ms n_record = 5 node = setup(timestep=0.025, min_delay=1.0, max_delay=10.0, debug=True, quit_on_end=False) print("Process with rank %d running on %s" % (node, socket.gethostname())) rng = NumpyRNG(seed=seed, parallel_safe=True) print("[%d] Creating populations" % node) n_spikes = int(2 * tstop * input_rate / 1000.0) spike_times = np.add.accumulate(rng.next(n_spikes, 'exponential', {'beta': 1000.0 / input_rate}, mask_local=False)) input_population = Population(100, SpikeSourceArray(spike_times=spike_times), label="input") output_population = Population(10, IF_curr_exp(**cell_params), label="output") print("[%d] input_population cells: %s" % (node, input_population.local_cells)) print("[%d] output_population cells: %s" % (node, output_population.local_cells)) print("[%d] Connecting populations" % node) timer.start() connector = CSAConnector(csa.random(0.5)) syn = StaticSynapse(weight=0.1) projection = Projection(input_population, output_population, connector, syn) print(connector.describe(), timer.elapsedTime()) file_stem = "Results/simpleRandomNetwork_csa_np%d_%s" % (num_processes(), simulator_name) projection.save('all', '%s.conn' % file_stem) input_population.record('spikes') output_population.record('spikes') output_population.sample(n_record, rng).record('v') print("[%d] Running simulation" % node) run(tstop) print("[%d] Writing spikes and Vm to disk" % node) output_population.write_data('%s_output.pkl' % file_stem) #input_population.write_data('%s_input.pkl' % file_stem) print("[%d] Finishing" % node) end() print("[%d] Done" % node) PyNN-0.10.0/examples/simple_STDP.py000066400000000000000000000200201415343567000167310ustar00rootroot00000000000000# encoding: utf8 """ A very simple example of using STDP. A single postsynaptic neuron fires at a constant rate. We connect several presynaptic neurons to it, each of which fires spikes with a fixed time lag or time advance with respect to the postsynaptic neuron. The weights of these connections are very small, so they will not significantly affect the firing times of the post-synaptic neuron. We plot the amount of potentiation or depression of each synapse as a function of the time difference. Usage: python simple_STDP.py [-h] [--plot-figure] [--debug DEBUG] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file --fit-curve Calculate the best-fit curve to the weight-delta_t measurements --debug DEBUG Print debugging information """ from math import exp import numpy as np import neo from quantities import ms from pyNN.utility import get_simulator, init_logging, normalized_filename from pyNN.utility.plotting import DataTable from pyNN.parameters import Sequence # === Parameters ============================================================ firing_period = 100.0 # (ms) interval between spikes cell_parameters = { "tau_m": 10.0, # (ms) "v_thresh": -50.0, # (mV) "v_reset": -60.0, # (mV) "v_rest": -60.0, # (mV) "cm": 1.0, # (nF) "tau_refrac": firing_period / 2, # (ms) long refractory period to prevent bursting } n = 60 # number of synapses / number of presynaptic neurons delta_t = 1.0 # (ms) time difference between the firing times of neighbouring neurons t_stop = 10 * firing_period + n * delta_t delay = 3.0 # (ms) synaptic time delay # === Configure the simulator =============================================== sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file", {"action": "store_true"}), ("--fit-curve", "Calculate the best-fit curve to the weight-delta_t measurements", {"action": "store_true"}), ("--dendritic-delay-fraction", "What fraction of the total transmission delay is due to dendritic propagation", {"default": 1}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(timestep=0.01, min_delay=delay, max_delay=delay) # === Build the network ===================================================== def build_spike_sequences(period, duration, n, delta_t): """ Return a spike time generator for `n` neurons (spike sources), where all neurons fire with the same period, but neighbouring neurons have a relative firing time difference of `delta_t`. """ def spike_time_gen(i): """Spike time generator. `i` should be an array of indices.""" return [Sequence(np.arange(period + j * delta_t, duration, period)) for j in (i - n // 2)] return spike_time_gen spike_sequence_generator = build_spike_sequences(firing_period, t_stop, n, delta_t) # presynaptic population p1 = sim.Population(n, sim.SpikeSourceArray(spike_times=spike_sequence_generator), label="presynaptic") # single postsynaptic neuron p2 = sim.Population(1, sim.IF_cond_exp(**cell_parameters), initial_values={"v": cell_parameters["v_reset"]}, label="postsynaptic") # drive to the postsynaptic neuron, ensuring it fires at exact multiples of the firing period p3 = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(firing_period - delay, t_stop, firing_period)), label="driver") # we set the initial weights to be very small, to avoid perturbing the firing times of the # postsynaptic neurons stdp_model = sim.STDPMechanism( timing_dependence=sim.SpikePairRule(tau_plus=20.0, tau_minus=20.0, A_plus=0.01, A_minus=0.012), weight_dependence=sim.AdditiveWeightDependence(w_min=0, w_max=0.0000001), weight=0.00000005, delay=delay, dendritic_delay_fraction=float(options.dendritic_delay_fraction)) connections = sim.Projection(p1, p2, sim.AllToAllConnector(), stdp_model) # the connection weight from the driver neuron is very strong, to ensure the # postsynaptic neuron fires at the correct times driver_connection = sim.Projection(p3, p2, sim.OneToOneConnector(), sim.StaticSynapse(weight=10.0, delay=delay)) # == Instrument the network ================================================= p1.record('spikes') p2.record(['spikes', 'v']) class WeightRecorder(object): """ Recording of weights is not yet built in to PyNN, so therefore we need to construct a callback object, which reads the current weights from the projection at regular intervals. """ def __init__(self, sampling_interval, projection): self.interval = sampling_interval self.projection = projection self._weights = [] def __call__(self, t): self._weights.append(self.projection.get('weight', format='list', with_address=False)) return t + self.interval def get_weights(self): signal = neo.AnalogSignal(self._weights, units='nA', sampling_period=self.interval * ms, name="weight", array_annotations={"channel_index": np.arange(len(self._weights[0]))}) return signal weight_recorder = WeightRecorder(sampling_interval=1.0, projection=connections) # === Run the simulation ===================================================== sim.run(t_stop, callbacks=[weight_recorder]) # === Save the results, optionally plot a figure ============================= filename = normalized_filename("Results", "simple_stdp", "pkl", options.simulator) p2.write_data(filename, annotations={'script_name': __file__}) presynaptic_data = p1.get_data().segments[0] postsynaptic_data = p2.get_data().segments[0] print("Post-synaptic spike times: %s" % postsynaptic_data.spiketrains[0]) weights = weight_recorder.get_weights() final_weights = np.array(weights[-1]) deltas = delta_t * np.arange(n // 2, -n // 2, -1) print("Final weights: %s" % final_weights) plasticity_data = DataTable(deltas, final_weights) if options.fit_curve: def double_exponential(t, t0, w0, wp, wn, tau): return w0 + np.where(t >= t0, wp * np.exp(-(t - t0) / tau), wn * np.exp((t - t0) / tau)) p0 = (-1.0, 5e-8, 1e-8, -1.2e-8, 20.0) popt, pcov = plasticity_data.fit_curve(double_exponential, p0, ftol=1e-10) print("Best fit parameters: t0={0}, w0={1}, wp={2}, wn={3}, tau={4}".format(*popt)) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel, DataTable figure_filename = filename.replace("pkl", "png") Figure( # raster plot of the presynaptic neuron spike times Panel(presynaptic_data.spiketrains, yticks=True, markersize=0.2, xlim=(0, t_stop)), # membrane potential of the postsynaptic neuron Panel(postsynaptic_data.filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[p2.label], yticks=True, xlim=(0, t_stop)), # evolution of the synaptic weights with time Panel(weights, xticks=True, yticks=True, xlabel="Time (ms)", legend=False, xlim=(0, t_stop)), # scatterplot of the final weight of each synapse against the relative # timing of pre- and postsynaptic spikes for that synapse Panel(plasticity_data, xticks=True, yticks=True, xlim=(-n / 2 * delta_t, n / 2 * delta_t), ylim=(0.9 * final_weights.min(), 1.1 * final_weights.max()), xlabel="t_post - t_pre (ms)", ylabel="Final weight (nA)", show_fit=options.fit_curve), title="Simple STDP example", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/small_network.py000066400000000000000000000067631415343567000175110ustar00rootroot00000000000000# encoding: utf-8 """ Small network created with the Population and Projection classes Usage: small_network.py [-h] [--plot-figure] [--debug DEBUG] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure plot the simulation results to a file --debug DEBUG print debugging information """ import numpy as np from pyNN.utility import get_simulator, init_logging, normalized_filename from pyNN.parameters import Sequence from pyNN.random import RandomDistribution as rnd, NumpyRNG sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) # === Define parameters ======================================================== n = 20 # Number of cells w = 0.002 # synaptic weight (µS) cell_params = { 'tau_m' : 20.0, # (ms) 'tau_syn_E' : 2.0, # (ms) 'tau_syn_I' : 4.0, # (ms) 'e_rev_E' : 0.0, # (mV) 'e_rev_I' : -70.0, # (mV) 'tau_refrac' : 2.0, # (ms) 'v_rest' : -60.0, # (mV) 'v_reset' : -70.0, # (mV) 'v_thresh' : -50.0, # (mV) 'cm' : 0.5} # (nF) dt = 0.1 # (ms) syn_delay = 1.0 # (ms) input_rate = 50.0 # (Hz) simtime = 1000.0 # (ms) seed = 945645645 # === Build the network ======================================================== sim.setup(timestep=dt, max_delay=syn_delay) rng = NumpyRNG(seed=seed) cells = sim.Population(n, sim.IF_cond_alpha(**cell_params), initial_values={'v': rnd('uniform', (-60.0, -50.0), rng)}, label="cells") number = int(2 * simtime * input_rate / 1000.0) np.random.seed(26278342) def generate_spike_times(i): gen = lambda: Sequence(np.add.accumulate(dt + np.random.exponential(1000.0 / input_rate, size=number))) if hasattr(i, "__len__"): return [gen() for j in i] else: return gen() assert generate_spike_times(0).max() > simtime spike_source = sim.Population(n, sim.SpikeSourceArray(spike_times=generate_spike_times)) spike_source.record('spikes') cells.record('spikes') cells[0:2].record(('v', 'gsyn_exc')) syn = sim.StaticSynapse(weight=w, delay=syn_delay) input_conns = sim.Projection(spike_source, cells, sim.FixedProbabilityConnector(0.5), syn) # === Run simulation =========================================================== sim.run(simtime) filename = normalized_filename("Results", "small_network", "pkl", options.simulator, sim.num_processes()) cells.write_data(filename, annotations={'script_name': __file__}) print("Mean firing rate: ", cells.mean_spike_count() * 1000.0 / simtime, "Hz") if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") data = cells.get_data().segments[0] vm = data.filter(name="v")[0] gsyn = data.filter(name="gsyn_exc")[0] Figure( Panel(vm, ylabel="Membrane potential (mV)"), Panel(gsyn, ylabel="Synaptic conductance (uS)"), Panel(data.spiketrains, xlabel="Time (ms)", xticks=True), annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/specific_network.py000066400000000000000000000051341415343567000201550ustar00rootroot00000000000000""" Small network created with the Population and Projection classes and the FromListConnector Andrew Davison, UNIC, CNRS April 2013 """ import numpy as np from pyNN.utility import get_script_args, init_logging, normalized_filename init_logging(None, debug=True) simulator_name = get_script_args(1)[0] exec("from pyNN.%s import *" % simulator_name) from pyNN.parameters import Sequence # === Define parameters ======================================================== n = 5 # Number of cells w = 0.5 # synaptic weight (nA) cell_params = { 'tau_m' : 20.0, # (ms) 'tau_syn_E' : 2.0, # (ms) 'tau_syn_I' : 4.0, # (ms) 'tau_refrac' : 2.0, # (ms) 'v_rest' : 0.0, # (mV) 'v_reset' : 0.0, # (mV) 'v_thresh' : 20.0, # (mV) 'cm' : 0.5} # (nF) dt = 0.1 # (ms) syn_delay = 1.0 # (ms) input_rate = 50.0 # (Hz) simtime = 1000.0 # (ms) # === Build the network ======================================================== setup(timestep=dt, min_delay=syn_delay, max_delay=syn_delay) cells = Population(n, IF_curr_alpha(**cell_params), initial_values={'v': 0.0}, label="cells") number = int(2 * simtime * input_rate / 1000.0) np.random.seed(26278342) def generate_spike_times(i): gen = lambda: Sequence(np.add.accumulate(dt + np.random.exponential(1000.0 / input_rate, size=number))) if hasattr(i, "__len__"): return [gen() for j in i] else: return gen() assert generate_spike_times(0).max() > simtime spike_source = Population(n, SpikeSourceArray(spike_times=generate_spike_times)) spike_source.record('spikes') cells.record('spikes') cells[0:1].record('v') connector = FromListConnector([ (0, 1, w, syn_delay), (0, 2, w, syn_delay), (0, 4, w, syn_delay), (1, 0, w, syn_delay), (1, 1, w, syn_delay), (1, 3, w, syn_delay), (1, 4, w, syn_delay), (2, 3, w, syn_delay), (3, 0, w, syn_delay), (3, 2, w, syn_delay), (4, 2, w, syn_delay), ]) input_conns = Projection(spike_source, cells, connector, StaticSynapse()) # === Run simulation =========================================================== run(simtime) #spike_source.write_data("Results/small_network_input_np%d_%s.pkl" % (num_processes(), simulator_name)) filename = normalized_filename("Results", "specific_network", "pkl", simulator_name, num_processes()) cells.write_data(filename, annotations={'script_name': __file__}) print("Mean firing rate: ", cells.mean_spike_count() * 1000.0 / simtime, "Hz") # === Clean up and quit ======================================================== end() PyNN-0.10.0/examples/stdp_network.py000066400000000000000000000072031415343567000173410ustar00rootroot00000000000000""" Network of integrate-and-fire neurons with distance-dependent connectivity and STDP. """ from pyNN.utility import get_simulator sim, options = get_simulator() from pyNN import space n_exc = 80 n_inh = 20 n_stim = 20 cell_parameters = { 'tau_m' : 20.0, 'tau_syn_E': 2.0, 'tau_syn_I': 5.0, 'v_rest': -65.0, 'v_reset' : -70.0, 'v_thresh': -50.0, 'cm': 1.0, 'tau_refrac': 2.0, 'e_rev_E': 0.0, 'e_rev_I': -70.0, } grid_parameters = { 'aspect_ratio': 1, 'dx': 50.0, 'dy': 50.0, 'fill_order': 'random' } stimulation_parameters = { 'rate': 100.0, 'duration': 50.0 } connectivity_parameters = { 'gaussian': {'d_expression': 'exp(-d**2/1e4)'}, 'global': {'p_connect': 0.1}, 'input': {'n': 10}, } synaptic_parameters = { 'excitatory': { 'timing_dependence': {'tau_plus': 20.0, 'tau_minus': 20.0, 'A_plus': 0.01, 'A_minus': 0.012}, 'weight_dependence': {'w_min': 0, 'w_max': 0.04}, 'weight': 0.01, 'delay': '0.1+0.001*d'}, 'inhibitory': {'weight': 0.05, 'delay': '0.1+0.001*d'}, 'input': {'weight': 0.01, 'delay': 0.1}, } sim.setup() all_cells = sim.Population(n_exc + n_inh, sim.IF_cond_exp(**cell_parameters), structure=space.Grid2D(**grid_parameters), label="All Cells") exc_cells = all_cells[:n_exc]; exc_cells.label = "Excitatory cells" inh_cells = all_cells[n_exc:]; inh_cells.label = "Inhibitory cells" ext_stim = sim.Population(n_stim, sim.SpikeSourcePoisson(**stimulation_parameters), label="External Poisson stimulation") stdp_mechanism = sim.STDPMechanism( timing_dependence=sim.SpikePairRule(**synaptic_parameters['excitatory']['timing_dependence']), weight_dependence=sim.AdditiveWeightDependence(**synaptic_parameters['excitatory']['weight_dependence']), weight=synaptic_parameters['excitatory']['weight'], delay=synaptic_parameters['excitatory']['delay']) gaussian_connectivity = sim.DistanceDependentProbabilityConnector( **connectivity_parameters['gaussian']) global_connectivity = sim.FixedProbabilityConnector( **connectivity_parameters['global']) input_connectivity = sim.FixedNumberPostConnector( **connectivity_parameters['input']) exc_connections = sim.Projection(exc_cells, all_cells, gaussian_connectivity, receptor_type='excitatory', synapse_type=stdp_mechanism, label='Excitatory connections') inh_connections = sim.Projection(inh_cells, all_cells, global_connectivity, receptor_type='inhibitory', synapse_type=sim.StaticSynapse(**synaptic_parameters['inhibitory']), label='Inhibitory connections') stim_connections = sim.Projection(ext_stim, all_cells, input_connectivity, receptor_type='excitatory', synapse_type=sim.StaticSynapse(**synaptic_parameters['input']), label='Input connections') print(__doc__) print("The network consists of:\n") print(all_cells.describe()) print(exc_cells.describe()) print(inh_cells.describe()) print(ext_stim.describe()) print("connected as follows:\n") print(exc_connections.describe()) print(inh_connections.describe()) print(stim_connections.describe()) sim.end() PyNN-0.10.0/examples/stochastic_deterministic_comparison.py000066400000000000000000000112341415343567000241360ustar00rootroot00000000000000# encoding: utf-8 """ Example of facilitating and depressing synapses in deterministic and stochastic versions """ import matplotlib matplotlib.use('Agg') import numpy as np import neo from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(quit_on_end=False) # === Build and instrument the network ======================================= spike_times = np.hstack((np.arange(10, 100, 10), np.arange(250, 350, 10))) spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=spike_times)) connector = sim.AllToAllConnector() depressing = dict(U=0.8, tau_rec=100.0, tau_facil=0.0, weight=0.01, delay=0.5) facilitating = dict(U=0.04, tau_rec=50.0, tau_facil=200.0, weight=0.01, delay=0.5) synapse_types = { 'depressing, deterministic': sim.TsodyksMarkramSynapse(**depressing), 'depressing, stochastic': sim.StochasticTsodyksMarkramSynapse(**depressing), 'facilitating, deterministic': sim.TsodyksMarkramSynapse(**facilitating), 'facilitating, stochastic': sim.StochasticTsodyksMarkramSynapse(**facilitating), } populations = {} projections = {} for label in synapse_types: populations[label] = sim.Population(1000, sim.IF_cond_exp(e_rev_I=-75, tau_syn_I=4.3), label=label) populations[label].record('gsyn_inh') projections[label] = sim.Projection(spike_source, populations[label], connector, receptor_type='inhibitory', synapse_type=synapse_types[label]) spike_source.record('spikes') # === Run the simulation ===================================================== sim.run(400.0) # === Save the results, optionally plot a figure ============================= for label, p in populations.items(): filename = normalized_filename("Results", "stochastic_comparison_%s" % label, "pkl", options.simulator) p.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel #figure_filename = normalized_filename("Results", "stochastic_comparison", # "png", options.simulator) figure_filename = "Results/stochastic_comparison_{}.png".format(options.simulator) data = {} for label in synapse_types: data[label] = populations[label].get_data().segments[0] if 'stochastic' in label: gsyn = data[label].filter(name='gsyn_inh')[0] gsyn_mean = neo.AnalogSignal(gsyn.mean(axis=1).reshape(-1, 1), sampling_rate=gsyn.sampling_rate, array_annotations={"channel_index": np.array([0])}, name = 'gsyn_inh_mean') data[label].analogsignals.append(gsyn_mean) def make_panel(population, label): return Panel(population.get_data().segments[0].filter(name='gsyn_inh')[0], data_labels=[label], yticks=True) panels = [ Panel(data['depressing, deterministic'].filter(name='gsyn_inh')[0][:, 0], data_labels=['depressing, deterministic'], yticks=True, ylim=[0, 0.008]), Panel(data['depressing, stochastic'].filter(name='gsyn_inh_mean')[0], data_labels=['depressing, stochastic mean'], yticks=True, ylim=[0, 0.008]), Panel(data['facilitating, deterministic'].filter(name='gsyn_inh')[0][:, 0], data_labels=['facilitating, deterministic'], yticks=True, ylim=[0, 0.002]), Panel(data['facilitating, stochastic'].filter(name='gsyn_inh_mean')[0], data_labels=['facilitating, stochastic mean'], yticks=True, ylim=[0, 0.002]), ] # add ylabel to top panel in each group panels[0].options.update(ylabel=u'Synaptic conductance (µS)') ##panels[len(synapse_types)].options.update(ylabel='Membrane potential (mV)') # add xticks and xlabel to final panel panels[-1].options.update(xticks=True, xlabel="Time (ms)") Figure(*panels, title="Example of facilitating and depressing synapses in deterministic and stochastic versions", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/stochastic_synapses.py000066400000000000000000000055151415343567000207130ustar00rootroot00000000000000# encoding: utf-8 """ Example of simple stochastic synapses """ import matplotlib matplotlib.use('Agg') import numpy as np from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(quit_on_end=False) # === Build and instrument the network ======================================= spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) connector = sim.AllToAllConnector() synapse_types = { 'static': sim.StaticSynapse(weight=0.01, delay=0.5), 'stochastic': sim.SimpleStochasticSynapse(p=0.5, weight=0.02, delay=0.5) } populations = {} projections = {} for label in 'static', 'stochastic': populations[label] = sim.Population(1, sim.IF_cond_exp(), label=label) populations[label].record(['v', 'gsyn_inh']) projections[label] = sim.Projection(spike_source, populations[label], connector, receptor_type='inhibitory', synapse_type=synapse_types[label]) spike_source.record('spikes') # === Run the simulation ===================================================== sim.run(200.0) # === Save the results, optionally plot a figure ============================= for label, p in populations.items(): filename = normalized_filename("Results", "stochastic_synapses_%s" % label, "pkl", options.simulator) p.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = normalized_filename("Results", "stochastic_synapses_", "png", options.simulator) panels = [] for variable in ('gsyn_inh', 'v'): for population in populations.values(): panels.append( Panel(population.get_data().segments[0].filter(name=variable)[0], data_labels=[population.label], yticks=True), ) # add ylabel to top panel in each group panels[0].options.update(ylabel=u'Synaptic conductance (µS)') panels[3].options.update(ylabel='Membrane potential (mV)') # add xticks and xlabel to final panel panels[-1].options.update(xticks=True, xlabel="Time (ms)") Figure(*panels, title="Example of simple stochastic synapses", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/stochastic_tsodyksmarkram.py000066400000000000000000000075031415343567000221200ustar00rootroot00000000000000# encoding: utf-8 """ Example of depressing and facilitating synapses Usage: stochastic_tsodyksmarkram.py [-h] [--plot-figure] [--debug DEBUG] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. --debug DEBUG Print debugging information """ import matplotlib matplotlib.use('Agg') import numpy as np from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(quit_on_end=False) # === Build and instrument the network ======================================= spike_times = np.hstack((np.arange(10, 100, 10), np.arange(250, 350, 10))) spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=spike_times)) connector = sim.AllToAllConnector() depressing = dict(U=0.8, tau_rec=100.0, tau_facil=0.0, weight=0.01, delay=0.5) facilitating = dict(U=0.04, tau_rec=50.0, tau_facil=200.0, weight=0.01, delay=0.5) synapse_types = { 'depressing, deterministic': sim.TsodyksMarkramSynapse(**depressing), 'depressing, stochastic': sim.StochasticTsodyksMarkramSynapse(**depressing), 'facilitating, deterministic': sim.TsodyksMarkramSynapse(**facilitating), 'facilitating, stochastic': sim.StochasticTsodyksMarkramSynapse(**facilitating), } populations = {} projections = {} for label in synapse_types: populations[label] = sim.Population(3, sim.IF_cond_exp(e_rev_I=-75, tau_syn_I=[1.2, 6.7, 4.3]), label=label) populations[label].record(['v', 'gsyn_inh']) projections[label] = sim.Projection(spike_source, populations[label], connector, receptor_type='inhibitory', synapse_type=synapse_types[label]) spike_source.record('spikes') # === Run the simulation ===================================================== sim.run(400.0) # === Save the results, optionally plot a figure ============================= for label, p in populations.items(): filename = normalized_filename("Results", "stochastic_tsodyksmarkram_%s" % label, "pkl", options.simulator) p.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel #figure_filename = normalized_filename("Results", "stochastic_tsodyksmarkram", # "png", options.simulator) figure_filename = "Results/stochastic_tsodyksmarkram_{}.png".format(options.simulator) panels = [] for variable in ('gsyn_inh',): # 'v'): for population in sorted(populations.values(), key=lambda p: p.label): panels.append( Panel(population.get_data().segments[0].filter(name=variable)[0], data_labels=[population.label], yticks=True), ) # add ylabel to top panel in each group panels[0].options.update(ylabel=u'Synaptic conductance (µS)') ##panels[len(synapse_types)].options.update(ylabel='Membrane potential (mV)') # add xticks and xlabel to final panel panels[-1].options.update(xticks=True, xlabel="Time (ms)") Figure(*panels, title="Example of facilitating and depressing synapses in deterministic and stochastic versions", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/synaptic_input.py000066400000000000000000000120711415343567000176660ustar00rootroot00000000000000""" A demonstration of the responses of different standard neuron models to synaptic input. This should show that for the current-based synapses, the size of the excitatory post-synaptic potential (EPSP) is constant, whereas for the conductance-based synapses it depends on the value of the membrane potential. Usage: python synaptic_input.py [-h] [--plot-figure] [--debug] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. --debug Print debugging information """ from quantities import ms from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(timestep=0.01, min_delay=1.0) # === Build and instrument the network ======================================= # for each cell type we create two neurons, one of which we depolarize with # injected current cuba_exp = sim.Population(2, sim.IF_curr_exp(tau_m=10.0, i_offset=[0.0, 1.0]), initial_values={"v": [-65, -55]}, label="Exponential, current-based") cuba_alpha = sim.Population(2, sim.IF_curr_alpha(tau_m=10.0, i_offset=[0.0, 1.0]), initial_values={"v": [-65, -55]}, label="Alpha, current-based") coba_exp = sim.Population(2, sim.IF_cond_exp(tau_m=10.0, i_offset=[0.0, 1.0]), initial_values={"v": [-65, -55]}, label="Exponential, conductance-based") coba_alpha = sim.Population(2, sim.IF_cond_alpha(tau_m=10.0, i_offset=[0.0, 1.0]), initial_values={"v": [-65, -55]}, label="Alpha, conductance-based") v_step = sim.Population(2, sim.Izhikevich(i_offset=[0.0, 0.002]), initial_values={"v": [-70, -67], "u": [-14, -13.4]}, label="Izhikevich") # we next create a spike source, which will emit spikes at the specified times spike_times = [25, 50, 80, 90] stimulus = sim.Population(1, sim.SpikeSourceArray(spike_times=spike_times), label="Input spikes") # now we connect the spike source to each of the neuron populations, with differing synaptic weights all_neurons = cuba_exp + cuba_alpha + coba_exp + coba_alpha + v_step connections = [sim.Projection(stimulus, population, connector=sim.AllToAllConnector(), synapse_type=sim.StaticSynapse(weight=w, delay=2.0), receptor_type="excitatory") for population, w in zip(all_neurons.populations, [1.6, 4.0, 0.03, 0.12, 1.0])] # finally, we set up recording of the membrane potential all_neurons.record('v') # === Run the simulation ===================================================== sim.run(100.0) # === Calculate the height of the first EPSP ================================= print("Height of first EPSP:") for population in all_neurons.populations: # retrieve the recorded data vm = population.get_data().segments[0].filter(name='v')[0] # take the data between the first and second incoming spikes vm12 = vm.time_slice(spike_times[0] * ms, spike_times[1] * ms) # calculate and print the EPSP height for channel in (0, 1): v_init = vm12[:, channel][0] height = vm12[:, channel].max() - v_init print(" {:<30} at {}: {}".format(population.label, v_init, height)) # === Save the results, optionally plot a figure ============================= filename = normalized_filename("Results", "synaptic_input", "pkl", options.simulator) all_neurons.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = filename.replace("pkl", "png") Figure( Panel(cuba_exp.get_data().segments[0].filter(name='v')[0], ylabel="Membrane potential (mV)", data_labels=[cuba_exp.label], yticks=True, ylim=(-66, -50)), Panel(cuba_alpha.get_data().segments[0].filter(name='v')[0], data_labels=[cuba_alpha.label], yticks=True, ylim=(-66, -50)), Panel(coba_exp.get_data().segments[0].filter(name='v')[0], data_labels=[coba_exp.label], yticks=True, ylim=(-66, -50)), Panel(coba_alpha.get_data().segments[0].filter(name='v')[0], data_labels=[coba_alpha.label], yticks=True, ylim=(-66, -50)), Panel(v_step.get_data().segments[0].filter(name='v')[0], xticks=True, xlabel="Time (ms)", data_labels=[v_step.label], yticks=True, ylim=(-71, -65)), title="Responses of standard neuron models to synaptic input", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/tools/000077500000000000000000000000001415343567000154025ustar00rootroot00000000000000PyNN-0.10.0/examples/tools/VAbenchmark_graphs.py000066400000000000000000000166431415343567000215130ustar00rootroot00000000000000""" Plot graphs showing the results of running the VAbenchmarks.py script. Usage: VAbenchmark_graphs.py [-h] [-s SORT] [-o OUTPUT_FILE] [-a ANNOTATION] datafile [datafile ...] positional arguments: datafile a list of data files in a Neo-supported format optional arguments: -h, --help show this help message and exit -s SORT, --sort SORT field to sort by (default='simulator') -o OUTPUT_FILE, --output-file OUTPUT_FILE output filename -a ANNOTATION, --annotation ANNOTATION additional annotation (optional) """ import argparse from collections import defaultdict import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from quantities import mV from neo.io import get_io def plot_signal(panel, signal, index, colour='b', linewidth='1', label='', fake_aps=False, hide_axis_labels=False): label = "%s (Neuron %d)" % (label, signal.array_annotations["channel_index"][index]) if fake_aps: # add fake APs for plotting v_thresh = fake_aps spike_indices = signal >= v_thresh - 0.05 * mV signal[spike_indices] = 0.0 * mV panel.plot(signal.times, signal[:, index], colour, linewidth=linewidth, label=label) #plt.setp(plt.gca().get_xticklabels(), visible=False) if not hide_axis_labels: panel.set_xlabel("time (%s)" % signal.times.units._dimensionality.string) panel.set_ylabel("%s (%s)" % (signal.name, signal.units._dimensionality.string)) def plot_hist(panel, hist, bins, width, xlabel=None, ylabel=None, label=None, xticks=None, xticklabels=None, xmin=None, ymax=None): if xlabel: panel.set_xlabel(xlabel) if ylabel: panel.set_ylabel(ylabel) for t, n in zip(bins[:-1], hist): panel.bar(t, n, width=width, color=None) if xmin: panel.set_xlim(xmin=xmin) if ymax: panel.set_ylim(ymax=ymax) if xticks is not None: panel.set_xticks(xticks) if xticklabels: panel.set_xticklabels(xticklabels) panel.text(0.8, 0.8, label, transform=panel.transAxes) def plot_vm_traces(panel, segment, label, hide_axis_labels=False): for array in segment.analogsignals: sorted_channels = sorted(array.array_annotations["channel_index"]) for j in range(2): i = array.array_annotations["channel_index"].tolist().index(j) print("plotting '%s' for %s" % (array.name, label)) col = 'rbgmck'[j % 6] plot_signal(panel, array, i, colour=col, linewidth=1, label=label, fake_aps=-50 * mV, hide_axis_labels=hide_axis_labels) panel.set_title(label) def plot_spiketrains(panel, segment, label, hide_axis_labels=False): print("plotting spikes for %s" % label) for spiketrain in segment.spiketrains: y = np.ones_like(spiketrain) * spiketrain.annotations['source_id'] panel.plot(spiketrain, y, '.', markersize=0.2) if not hide_axis_labels: panel.set_ylabel(segment.name) #plt.setp(plt.gca().get_xticklabels(), visible=False) def plot_isi_hist(panel, segment, label, hide_axis_labels=False): print("plotting ISI histogram (%s)" % label) bin_width = 0.2 bins_log = np.arange(0, 8, 0.2) bins = np.exp(bins_log) all_isis = np.concatenate([np.diff(np.array(st)) for st in segment.spiketrains]) isihist, bins = np.histogram(all_isis, bins) xlabel = "Inter-spike interval (ms)" ylabel = "n in bin" if hide_axis_labels: xlabel = None ylabel = None plot_hist(panel, isihist, bins_log, bin_width, label=label, xlabel=xlabel, xticks=np.log([10, 100, 1000]), xticklabels=['10', '100', '1000'], xmin=np.log(2), ylabel=ylabel) def plot_cvisi_hist(panel, segment, label, hide_axis_labels=False): print("plotting CV(ISI) histogram (%s)" % label) def cv_isi(spiketrain): isi = np.diff(np.array(spiketrain)) return np.std(isi) / np.mean(isi) cvs = np.fromiter((cv_isi(st) for st in segment.spiketrains if st.size > 2), dtype=float) bin_width = 0.1 bins = np.arange(0, 2, bin_width) cvhist, bins = np.histogram(cvs[~np.isnan(cvs)], bins) xlabel = "CV(ISI)" ylabel = "n in bin" if hide_axis_labels: xlabel = None ylabel = None plot_hist(panel, cvhist, bins, bin_width, label=label, xlabel=xlabel, xticks=np.arange(0, 2, 0.5), ylabel=ylabel) def sort_by_annotation(name, objects): sorted_objects = defaultdict(list) for obj in objects: sorted_objects[obj.annotations[name]].append(obj) return sorted_objects def plot(datafiles, output_file, sort_by='simulator', annotation=None): blocks = [get_io(datafile).read_block() for datafile in datafiles] # note: Neo needs a pretty printer that is not tied to IPython # for block in blocks: # print(block.describe()) script_name = blocks[0].annotations['script_name'] for block in blocks[1:]: assert block.annotations['script_name'] == script_name fig_settings = { # pass these in a configuration file? 'lines.linewidth': 0.5, 'axes.linewidth': 0.5, 'axes.labelsize': 'small', 'legend.fontsize': 'small', 'font.size': 8, 'savefig.dpi': 200, } plt.rcParams.update(fig_settings) CM = 1 / 2.54 plt.figure(1, figsize=(15 * CM * len(blocks), 20 * CM)) gs = gridspec.GridSpec(4, 2 * len(blocks), hspace=0.25, wspace=0.25) sorted_blocks = sort_by_annotation(sort_by, blocks) hide_axis_labels = False for k, (label, block_list) in enumerate(sorted_blocks.items()): segments = {} for block in block_list: for name in ("exc", "inh"): if name in block.name.lower(): segments[name] = block.segments[0] # Plot membrane potential traces plot_vm_traces(plt.subplot(gs[0, 2 * k:2 * k + 2]), segments['exc'], label, hide_axis_labels) # Plot spike rasters plot_spiketrains(plt.subplot(gs[1, 2 * k:2 * k + 2]), segments['exc'], label, hide_axis_labels) # Inter-spike-interval histograms # Histograms of coefficients of variation of ISI plot_isi_hist(plt.subplot(gs[2, 2 * k]), segments['exc'], 'exc', hide_axis_labels) plot_cvisi_hist(plt.subplot(gs[3, 2 * k]), segments['exc'], 'exc', hide_axis_labels) hide_axis_labels = True plot_isi_hist(plt.subplot(gs[2, 2 * k + 1]), segments['inh'], 'inh', hide_axis_labels) plot_cvisi_hist(plt.subplot(gs[3, 2 * k + 1]), segments['inh'], 'inh', hide_axis_labels) plt.savefig(output_file) def main(): parser = argparse.ArgumentParser() parser.add_argument("datafiles", metavar="datafile", nargs="+", help="a list of data files in a Neo-supported format") parser.add_argument("-s", "--sort", default="simulator", help="field to sort by (default='%(default)s' - also try 'mpi_processes')") parser.add_argument("-o", "--output-file", default="output.png", help="output filename") parser.add_argument("-a", "--annotation", help="additional annotation (optional)") args = parser.parse_args() plot(args.datafiles, output_file=args.output_file, sort_by=args.sort, annotation=args.annotation) if __name__ == "__main__": main() PyNN-0.10.0/examples/tools/comparison_plot.py000066400000000000000000000054631415343567000211740ustar00rootroot00000000000000""" A fairly generic script to produce plots that compare two or more Neo data sets. Usage: python comparison_plot.py , , etc. """ import argparse import os from datetime import datetime import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import numpy as np import neo from neo.io import get_io from pyNN.utility.plotting import comparison_plot def variable_names(segment): return set(signal.name for signal in segment.analogsignals) def plot_signal(panel, signal, index, colour='b', linewidth='1', label=''): label = "%s (Neuron %d)" % (label, signal.array_annotations["channel_index"][index]) panel.plot(signal.times, signal[:, index], colour, linewidth=linewidth, label=label) panel.set_ylabel("%s (%s)" % (signal.name, signal.units._dimensionality.string)) plt.setp(plt.gca().get_xticklabels(), visible=False) def plot(datafiles, output_file, extra_annotation=None): print(datafiles) print(output_file) # load data blocks = [get_io(datafile).read_block() for datafile in datafiles] # note: Neo needs a pretty printer that is not tied to IPython # for block in blocks: # print(block.describe()) # for now take only the first segment segments = [block.segments[0] for block in blocks] labels = [block.annotations['simulator'] for block in blocks] # build annotations script_name = blocks[0].annotations.get('script_name', '') if script_name: for block in blocks[1:]: assert block.annotations['script_name'] == script_name # also consider adding metadata to PNG file - see http://stackoverflow.com/questions/10532614/can-matplotlib-add-metadata-to-saved-figures context = ["Generated by: %s" % __file__, "Working directory: %s" % os.getcwd(), "Timestamp: %s" % datetime.now().strftime("%Y-%m-%d %H:%M:%S%z"), "Output file: %s" % output_file, "Input file(s): %s" % "\n ".join(datafiles)] if extra_annotation: context.append(extra_annotation) annotations = "\n".join(context) # create and save plot fig = comparison_plot(segments, labels, title=script_name, annotations=annotations) fig.save(output_file) def main(): parser = argparse.ArgumentParser() parser.add_argument("datafiles", metavar="datafile", nargs="+", help="a list of data files in a Neo-supported format") parser.add_argument("-o", "--output-file", default="output.png", help="output filename") parser.add_argument("-a", "--annotation", help="additional annotation (optional)") args = parser.parse_args() plot(args.datafiles, output_file=args.output_file, extra_annotation=args.annotation) if __name__ == "__main__": main() PyNN-0.10.0/examples/tools/plot_results.py000066400000000000000000000044431415343567000205200ustar00rootroot00000000000000""" Plot graphs for the example scripts in this directory. Run with: python plot_results.py where is the first part of the example script name, without the ".py" e.g. python plot_results.py IF_curr_exp """ from pprint import pprint import sys import os import matplotlib.pyplot as plt import numpy as np import warnings import glob from pyNN.recording import get_io plt.ion() # .rcParams['interactive'] = True example = sys.argv[1] blocks = {} for simulator in 'MOCK', 'NEST', 'NEURON', 'Brian2': pattern = "Results/%s_*%s*.*" % (example, simulator.lower()) datafiles = glob.glob(pattern) if datafiles: for datafile in datafiles: base = os.path.basename(datafile) root = base[:base.find(simulator.lower()) - 1] if root not in blocks: blocks[root] = {} blocks[root][simulator] = get_io(datafile).read_block() else: print("No data found for pattern %s" % pattern) print("-" * 79) print(example) pprint(blocks) if len(blocks) > 0: for name in blocks: plt.figure() lw = 2 * len(blocks[name]) - 1 for simulator, block in blocks[name].items(): vm = block.segments[0].filter(name="v")[0] plt.plot(vm.times, vm[:, 0], label=simulator, linewidth=lw) lw -= 2 plt.legend() plt.title(name) plt.xlabel("Time (ms)") plt.ylabel("Vm (mV)") plt.savefig("Results/%s.png" % name) print("Results/%s.png" % name) if list(blocks[name].values())[0].segments[0].filter(name="gsyn_exc"): plt.figure() lw = 2 * len(blocks[name]) - 1 for simulator, block in blocks[name].items(): g_exc = block.segments[0].filter(name="gsyn_exc")[0] g_inh = block.segments[0].filter(name="gsyn_inh")[0] plt.plot(g_exc.times, g_exc[:, 0], label=simulator + "(exc)", linewidth=lw) plt.plot(g_inh.times, g_inh[:, 0], label=simulator + "(inh)", linewidth=lw) lw -= 2 plt.legend() plt.title(name) plt.xlabel("Time (ms)") plt.ylabel("Synaptic conductance (nS)") plt.savefig("Results/%s_gsyn.png" % name) print("Results/%s_gsyn.png" % name) PyNN-0.10.0/examples/tools/run_all_examples.py000066400000000000000000000051271415343567000213130ustar00rootroot00000000000000#!/usr/bin/env python import subprocess import glob import os import sys default_simulators = ['MOCK', 'NEST', 'NEURON', 'Brian2'] simulator_names = sys.argv[1:] if len(simulator_names) > 0: for name in simulator_names: if name not in default_simulators: print("Simulator must be one of: " + ", ".join(default_simulators)) sys.exit(1) else: simulator_names = default_simulators simulators = [] for simulator in simulator_names: sim = simulator.lower() try: exec("import pyNN.%s" % sim) simulators.append(sim) except ImportError: pass exclude = { 'MOCK': ["nineml_neuron.py", "nineml_brunel.py"], 'NEURON': ["nineml_neuron.py"], 'NEST': ["nineml_neuron.py", "nineml_brunel.py"], 'Brian2': ["nineml_neuron.py", "nineml_brunel.py", "multiquantal_synapses.py", "random_numbers.py", "gif_neuron.py", "stochastic_tsodyksmarkram.py", "stochastic_deterministic_comparison.py", "stochastic_synapses.py", "varying_poisson.py"] } extra_args = { "VAbenchmarks.py": "CUBA", "VAbenchmarks2.py": "CUBA", "VAbenchmarks2-csa.py": "CUBA", "VAbenchmarks3.py": "CUBA", "nineml_brunel.py": "SR" } if not os.path.exists("Results"): os.mkdir("Results") for simulator in simulator_names: if simulator.lower() in simulators: print("\n\n\n================== Running examples with %s =================\n" % simulator) for script in glob.glob("../*.py"): script_name = os.path.basename(script) if script_name not in exclude[simulator]: cmd = "%s %s %s" % (sys.executable, script, simulator.lower()) if script_name in extra_args: cmd += " " + extra_args[script_name] print(cmd, end='') sys.stdout.flush() logfile = open("Results/%s_%s.log" % (os.path.basename(script), simulator), 'w') p = subprocess.Popen(cmd, shell=True, stdout=logfile, stderr=subprocess.PIPE, close_fds=True) retval = p.wait() print(retval == 0 and " - ok" or " - fail") else: print("\n\n\n================== %s not available =================\n" % simulator) print("\n\n\n================== Plotting results =================\n") for script in glob.glob("../*.py"): cmd = "%s plot_results.py %s" % (sys.executable, os.path.basename(script)[:-3]) print(cmd) p = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) p.wait() PyNN-0.10.0/examples/tsodyksmarkram.py000066400000000000000000000067161415343567000177010ustar00rootroot00000000000000# encoding: utf-8 """ Example of depressing and facilitating synapses Usage: tsodyksmarkram.py [-h] [--plot-figure] [--debug DEBUG] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. --debug DEBUG Print debugging information """ import numpy as np from pyNN.utility import get_simulator, init_logging, normalized_filename # === Configure the simulator ================================================ sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"}), ("--debug", "Print debugging information")) if options.debug: init_logging(None, debug=True) sim.setup(quit_on_end=False) # === Build and instrument the network ======================================= spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) connector = sim.AllToAllConnector() synapse_types = { 'static': sim.StaticSynapse(weight=0.01, delay=0.5), 'depressing': sim.TsodyksMarkramSynapse(U=0.5, tau_rec=800.0, tau_facil=0.0, weight=0.01, delay=0.5), 'facilitating': sim.TsodyksMarkramSynapse(U=0.04, tau_rec=100.0, tau_facil=1000.0, weight=0.01, delay=0.5), } populations = {} projections = {} for label in 'static', 'depressing', 'facilitating': populations[label] = sim.Population(3, sim.IF_cond_exp(e_rev_I=-75, tau_syn_I=[1.2, 6.7, 4.3]), label=label) populations[label].record(['v', 'gsyn_inh']) projections[label] = sim.Projection(spike_source, populations[label], connector, receptor_type='inhibitory', synapse_type=synapse_types[label]) spike_source.record('spikes') # === Run the simulation ===================================================== sim.run(200.0) # === Save the results, optionally plot a figure ============================= for label, p in populations.items(): filename = normalized_filename("Results", "tsodyksmarkram_%s" % label, "pkl", options.simulator) p.write_data(filename, annotations={'script_name': __file__}) if options.plot_figure: from pyNN.utility.plotting import Figure, Panel figure_filename = normalized_filename("Results", "tsodyksmarkram", "png", options.simulator) panels = [] for variable in ('gsyn_inh', 'v'): for population in populations.values(): panels.append( Panel(population.get_data().segments[0].filter(name=variable)[0], data_labels=[population.label], yticks=True), ) # add ylabel to top panel in each group panels[0].options.update(ylabel=u'Synaptic conductance (µS)') panels[3].options.update(ylabel='Membrane potential (mV)') # add xticks and xlabel to final panel panels[-1].options.update(xticks=True, xlabel="Time (ms)") Figure(*panels, title="Example of static, facilitating and depressing synapses", annotations="Simulated with %s" % options.simulator.upper() ).save(figure_filename) print(figure_filename) # === Clean up and quit ======================================================== sim.end() PyNN-0.10.0/examples/update_spike_source_array.py000066400000000000000000000070311415343567000220500ustar00rootroot00000000000000""" A demonstration of the use of callbacks to update the spike times in a SpikeSourceArray. Usage: update_spike_source_array.py [-h] [--plot-figure] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. """ import numpy as np from pyNN.utility import get_simulator, normalized_filename, ProgressBar from pyNN.utility.plotting import Figure, Panel from pyNN.parameters import Sequence sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"})) rate_increment = 20 interval = 200 class SetRate(object): """ A callback which changes the firing rate of a population of spike sources at a fixed interval. """ def __init__(self, population, rate_generator, update_interval=20.0): assert isinstance(population.celltype, sim.SpikeSourceArray) self.population = population self.update_interval = update_interval self.rate_generator = rate_generator def __call__(self, t): try: rate = next(rate_generator) if rate > 0: isi = 1000.0/rate times = t + np.arange(0, self.update_interval, isi) # here each neuron fires with the same isi, # but there is a phase offset between neurons spike_times = [ Sequence(times + phase * isi) for phase in self.population.annotations["phase"] ] else: spike_times = [] self.population.set(spike_times=spike_times) except StopIteration: pass return t + self.update_interval class MyProgressBar(object): """ A callback which draws a progress bar in the terminal. """ def __init__(self, interval, t_stop): self.interval = interval self.t_stop = t_stop self.pb = ProgressBar(width=int(t_stop / interval), char=".") def __call__(self, t): self.pb(t / self.t_stop) return t + self.interval sim.setup() # === Create a population of poisson processes =============================== p = sim.Population(50, sim.SpikeSourceArray()) p.annotate(phase=np.random.uniform(0, 1, size=p.size)) p.record('spikes') # === Run the simulation, with two callback functions ======================== rate_generator = iter(range(0, 100, rate_increment)) sim.run(1000, callbacks=[MyProgressBar(10.0, 1000.0), SetRate(p, rate_generator, interval)]) # === Retrieve recorded data, and count the spikes in each interval ========== data = p.get_data().segments[0] all_spikes = np.hstack([st.magnitude for st in data.spiketrains]) spike_counts = [((all_spikes >= x) & (all_spikes < x + interval)).sum() for x in range(0, 1000, interval)] expected_spike_counts = [p.size * rate * interval / 1000.0 for rate in range(0, 100, rate_increment)] print("\nActual spike counts: {}".format(spike_counts)) print("Expected mean spike counts: {}".format(expected_spike_counts)) if options.plot_figure: Figure( Panel(data.spiketrains, xlabel="Time (ms)", xticks=True, markersize=0.5), title="Incrementally updated SpikeSourceArrays", annotations="Simulated with %s" % options.simulator.upper() ).save(normalized_filename("Results", "update_spike_source_array", "png", options.simulator)) sim.end() PyNN-0.10.0/examples/varying_poisson.py000066400000000000000000000057001415343567000200470ustar00rootroot00000000000000""" A demonstration of the use of callbacks to vary the rate of a SpikeSourcePoisson. Every 200 ms, the Poisson firing rate is increased by 20 spikes/s Usage: varying_poisson.py [-h] [--plot-figure] simulator positional arguments: simulator neuron, nest, brian or another backend simulator optional arguments: -h, --help show this help message and exit --plot-figure Plot the simulation results to a file. """ import numpy as np from pyNN.utility import get_simulator, normalized_filename, ProgressBar from pyNN.utility.plotting import Figure, Panel sim, options = get_simulator(("--plot-figure", "Plot the simulation results to a file.", {"action": "store_true"})) rate_increment = 20 interval = 200 class SetRate(object): """ A callback which changes the firing rate of a population of poisson processes at a fixed interval. """ def __init__(self, population, rate_generator, interval=20.0): assert isinstance(population.celltype, sim.SpikeSourcePoisson) self.population = population self.interval = interval self.rate_generator = rate_generator def __call__(self, t): try: self.population.set(rate=next(rate_generator)) except StopIteration: pass return t + self.interval class MyProgressBar(object): """ A callback which draws a progress bar in the terminal. """ def __init__(self, interval, t_stop): self.interval = interval self.t_stop = t_stop self.pb = ProgressBar(width=int(t_stop / interval), char=".") def __call__(self, t): self.pb(t / self.t_stop) return t + self.interval sim.setup() # === Create a population of poisson processes =============================== p = sim.Population(50, sim.SpikeSourcePoisson()) p.record('spikes') # === Run the simulation, with two callback functions ======================== rate_generator = iter(range(0, 100, rate_increment)) sim.run(1000, callbacks=[MyProgressBar(10.0, 1000.0), SetRate(p, rate_generator, interval)]) # === Retrieve recorded data, and count the spikes in each interval ========== data = p.get_data().segments[0] all_spikes = np.hstack([st.magnitude for st in data.spiketrains]) spike_counts = [((all_spikes >= x) & (all_spikes < x + interval)).sum() for x in range(0, 1000, interval)] expected_spike_counts = [p.size * rate * interval / 1000.0 for rate in range(0, 100, rate_increment)] print("\nActual spike counts: {}".format(spike_counts)) print("Expected mean spike counts: {}".format(expected_spike_counts)) if options.plot_figure: Figure( Panel(data.spiketrains, xlabel="Time (ms)", xticks=True), title="Time varying Poisson spike trains", annotations="Simulated with %s" % options.simulator.upper() ).save(normalized_filename("Results", "varying_poisson", "png", options.simulator)) sim.end() PyNN-0.10.0/pyNN/000077500000000000000000000000001415343567000133105ustar00rootroot00000000000000PyNN-0.10.0/pyNN/__init__.py000066400000000000000000000052121415343567000154210ustar00rootroot00000000000000""" PyNN (pronounced 'pine') is a Python package for simulator-independent specification of neuronal network models. In other words, you can write the code for a model once, using the PyNN API, and then run it without modification on any simulator that PyNN supports. To use PyNN, import the particular simulator module you wish to use, e.g. import pyNN.neuron as sim all subsequent code in the `sim` namespace will then have the same behaviour independent of simulator. Functions for simulation set-up and control: setup() run() run_until() reset() end() get_time_step() get_current_time() get_min_delay() get_max_delay() rank() num_processes() list_standard_models() Functions for creating, connecting, modifying and recording from neurons (low-level interface): create() connect() set() record() initialize() Classes for creating, connecting, modifying and recording from neurons (high-level interface): Population Projection Space Structures: Line, Grid2D, Grid3D, RandomStructure Connectors: AllToAllConnector, OneToOneConnector, FixedProbabilityConnector, DistanceDependentProbabilityConnector, FixedNumberPreConnector, FixedNumberPostConnector, FromListConnector, FromFileConnector, CSAConnector, ArrayConnector, IndexBasedConnector Standard cell types: IF_curr_exp, IF_curr_alpha, IF_cond_exp, IF_cond_alpha, IF_cond_exp_gsfa_grr, IF_facets_hardware1, HH_cond_exp, EIF_cond_alpha_isfa_ista, EIF_cond_exp_isfa_ista, SpikeSourcePoisson, SpikeSourceArray, SpikeSourceInhGamma (not all cell types are available for all simulator backends). Standard synapse types: StaticSynapse, TsodyksMarkramSynapse, STDPMechanism, AdditiveWeightDependence, MultiplicativeWeightDependence, AdditivePotentiationMultiplicativeDepression, GutigWeightDependence, SpikePairRule (not all combinations area available for all simulator backends). Current injection: DCSource, ACSource, StepCurrentSource, NoisyCurrentSource. File types: StandardTextFile, PickleFile, NumpyBinaryFile, HDF5ArrayFile Available simulator modules: nest neuron brian Other modules: utility random space :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ __version__ = '0.10.0' __all__ = ["common", "random", "nest", "neuron", "brian2", "recording", "errors", "space", "descriptions", "standardmodels", "parameters", "core", "serialization"] PyNN-0.10.0/pyNN/brian2/000077500000000000000000000000001415343567000144655ustar00rootroot00000000000000PyNN-0.10.0/pyNN/brian2/__init__.py000066400000000000000000000051451415343567000166030ustar00rootroot00000000000000""" Brian2 implementation of the PyNN API. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import brian2 from pyNN import common, space from pyNN.common.control import DEFAULT_MAX_DELAY, DEFAULT_TIMESTEP, DEFAULT_MIN_DELAY from pyNN.connectors import * from pyNN.brian2 import simulator from pyNN.brian2.standardmodels.cells import * from pyNN.brian2.standardmodels.synapses import * from pyNN.brian2.standardmodels.electrodes import * from pyNN.brian2.populations import Population, PopulationView, Assembly from pyNN.brian2.projections import Projection from pyNN.recording import get_io logger = logging.getLogger("PyNN") def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" return [obj.__name__ for obj in globals().values() if isinstance(obj, type) and issubclass(obj, StandardCellType)] def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): """ Should be called at the very beginning of a script. extra_params contains any keyword arguments that are required by a given simulator but not by others. """ max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) common.setup(timestep, min_delay, **extra_params) simulator.state.clear() simulator.state.dt = timestep # move to common.setup? simulator.state.min_delay = min_delay simulator.state.max_delay = max_delay simulator.state.mpi_rank = 0 simulator.state.num_processes = 1 simulator.state.network.add( NetworkOperation(update_currents, when="start", clock=simulator.state.network.clock) ) return rank() def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: io = get_io(filename) population.write_data(io, variables) simulator.state.write_on_end = [] # should have common implementation of end() run, run_until = common.build_run(simulator) run_for = run reset = common.build_reset(simulator) initialize = common.initialize get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) record = common.build_record(simulator) def record_v(source, filename): return record(['v'], source, filename) def record_gsyn(source, filename): return record(['gsyn_exc', 'gsyn_inh'], source, filename) PyNN-0.10.0/pyNN/brian2/cells.py000066400000000000000000000325161415343567000161500ustar00rootroot00000000000000""" Definition of cell classes for the brian2 module. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import brian2 from pyNN.parameters import Sequence, simplify from pyNN.core import is_listlike from pyNN import errors from pyNN.brian2 import simulator mV = brian2.mV ms = brian2.ms nA = brian2.nA uS = brian2.uS Hz = brian2.Hz nF = brian2.nF ampere = brian2.amp second = brian2.second def _new_property(obj_hierarchy, attr_name, units): """ Return a new property, mapping attr_name to obj_hierarchy.attr_name. For example, suppose that an object of class A has an attribute b which itself has an attribute c which itself has an attribute d. Then placing e = _new_property('b.c', 'd') in the class definition of A makes A.e an alias for A.b.c.d """ def set(self, value): if obj_hierarchy: obj = reduce(getattr, [self] + obj_hierarchy.split('.')) else: obj = self setattr(obj, attr_name, value * units) def get(self): if obj_hierarchy: obj = reduce(getattr, [self] + obj_hierarchy.split('.')) # ( getattr, ...) else: obj = self return getattr(obj, attr_name) / units return property(fset=set, fget=get) class BaseNeuronGroup(brian2.NeuronGroup): def __init__(self, n, equations, threshold, reset=-20 * mV, refractory=0 * ms, method=None, **parameters): if "tau_refrac" in parameters: if parameters["tau_refrac"].min() != parameters["tau_refrac"].max(): raise Exception("Non-homogeneous refractory period not yet supported") refractory = parameters["tau_refrac"].min() if method is None: method = ('exact', 'euler', 'heun') # Brian 2 default brian2.NeuronGroup.__init__(self, n, model=equations, threshold=threshold, reset=reset, refractory=refractory, method=method, clock=simulator.state.network.clock) for name, value in parameters.items(): if not hasattr(self, name): self.add_attribute(name) else: setattr(self, name, value) # self._S0 = self._S[:, 0] # store parameter values in case of reset. # TODO: update this when parameters are modified # TODO: Brian2 does not have _S0a self.add_attribute('initial_values') self.initial_values = {} def _get_tau_refrac(self): return self._refractory def _set_tau_refrac(self, value): self._refractory = simplify(value) tau_refrac = property(fget=_get_tau_refrac, fset=_set_tau_refrac) def initialize(self): for variable, values in self.initial_values.items(): setattr(self, variable, values) class ThresholdNeuronGroup(BaseNeuronGroup): def __init__(self, n, equations, **parameters): threshold = 'v > v_thresh' reset = 'v = v_reset' equations += '''v_thresh : volt (constant) v_reset : volt (constant)''' BaseNeuronGroup.__init__(self, n, equations, threshold, reset=reset, **parameters) class BiophysicalNeuronGroup(BaseNeuronGroup): def __init__(self, n, equations, **parameters): threshold = 'v > -40*mV' refractory = False reset = None BaseNeuronGroup.__init__(self, n, equations, threshold=threshold, reset=reset, refractory=refractory, **parameters) # implicit=True, compile=False, class AdaptiveReset(object): def __init__(self, Vr=-70.7 * mV, b=0.0805 * nA): self.Vr = Vr self.b = b def __call__(self, P, spikes): P.v[spikes] = self.Vr[spikes] P.w[spikes] += self.b[spikes] class AdaptiveNeuronGroup(BaseNeuronGroup): def __init__(self, n, equations, **parameters): #threshold = 'v >= {}*mV'.format(parameters["v_thresh"][0]*1000) thresh = parameters["v_thresh"][0] Vcut = thresh + parameters["delta_T"][0] * 5 threshold = 'v > {}*mV'.format(Vcut*1000) self._resetvalue = parameters.pop('v_reset')[0] self._bvalue = parameters.pop('b')[0] # period = simplify(parameters['tau_refrac'])*1000 ##### problem here with the refractory self._refracvalue = parameters.pop('tau_refrac')[0] reset = 'v = {}*mV; w+={}* amp'.format(self._resetvalue*10**3, self._bvalue) # refractory=None refractory = self._refracvalue # refractory=period # parameters['tau_refrac']=parameters['tau_refrac']/ms #refractory = 0*ms BaseNeuronGroup.__init__(self, n, equations, threshold=threshold, reset=reset, refractory=refractory, method="rk2", **parameters) @property def v_reset(self): return self._resetvalue @property def b(self): return self._bvalue @property def tau_refrac(self): return self._refractory @tau_refrac.setter def tau_refrac(self, tau_refrac_value): #self._refracvalue = tau_refrac_value * ms #brian2.NeuronGroup.__setattr__(self, 'tau_refrac', tau_refrac_value) self._refractory = tau_refrac_value @v_reset.setter def v_reset(self, resetvalue): #self._resetvalue = resetvalue * mV self.event_codes['spike'] = 'v = {}*mV'.format(resetvalue) class AdaptiveReset2(object): def __init__(self, v_reset, q_r, q_s): self.v_reset = v_reset self.q_r = q_r self.q_s = q_s def __call__(self, P, spikes): P.v[spikes] = self.v_reset[spikes] P.g_r[spikes] += self.q_r[spikes] P.g_s[spikes] += self.q_s[spikes] class AdaptiveNeuronGroup2(BaseNeuronGroup): def __init__(self, n, equations, **parameters): threshold = 'v >= {}*mV'.format(parameters["v_thresh"][0]*1000) self._resetvalue = parameters.pop('v_reset')[0] self._q_rvalue = parameters.pop('q_r')[0] * 10**9 self._q_svalue = parameters.pop('q_s')[0] * 10**9 self._refracvalue = parameters.pop('tau_refrac')[0] reset = 'v = {}*mV; g_r+= {}*nS; g_s+={}*nS'.format( self._resetvalue*1000, self._q_rvalue, self._q_svalue) refractory = self._refracvalue ''' threshold = brian2.SimpleFunThreshold(self.check_threshold) period = simplify(parameters['tau_refrac']) assert not hasattr(period, "__len__"), "Brian2 does not support heterogenerous refractory periods with CustomRefractoriness" reset = brian2.SimpleCustomRefractoriness( AdaptiveReset2(parameters.pop('v_reset'), parameters.pop('q_r'), parameters.pop('q_s')), period=period * second) refractory = None ''' BaseNeuronGroup.__init__(self, n, equations, threshold, reset=reset, refractory=refractory, method="rk2", **parameters) @property def v_reset(self): return self._resetvalue @property def q_r(self): return self._q_rvalue @property def q_s(self): return self._q_svalue @property def tau_refrac(self): return self._refractory @tau_refrac.setter def tau_refrac(self, tau_refrac_value): #self._refracvalue = tau_refrac_value * ms #brian2.NeuronGroup.__setattr__(self, 'tau_refrac', tau_refrac_value) self._refractory = tau_refrac_value @v_reset.setter def v_reset(self, resetvalue): #self._resetvalue = resetvalue * mV self.event_codes['spike'] = 'v = {}*mV'.format(resetvalue) ''' tau_refrac = _new_property('', '_refractory_array', ms) v_reset = _new_property('_resetfun.resetfun', 'v_reset', mV) q_r = _new_property('_resetfun.resetfun', 'q_r', nA) q_s = _new_property('_resetfun.resetfun', 'q_s', nA) def check_threshold(self, v): return v >= self.v_thresh ''' # The below can be replaced by # reset = '''v = v_reset # u += d''' class IzhikevichReset(object): def __init__(self, Vr=-65 * mV, d=0.2 * mV / ms): self.Vr = Vr self.d = d def __call__(self, P, spikes): P.v[spikes] = self.Vr[spikes] P.u[spikes] += self.d[spikes] class IzhikevichNeuronGroup(BaseNeuronGroup): def __init__(self, n, equations, **parameters): #threshold = brian2.SimpleFunThreshold(self.check_threshold) #threshold = 'v >= {}*mV'.format(parameters["v_thresh"][0]*1000) threshold = 'v >= 30*mV' self._resetvalue = parameters.pop('v_reset')[0] self._dvalue = parameters.pop('d')[0] reset = 'v = {}*mV; u+={}*mV/ms'.format(self._resetvalue*1000, self._dvalue) ''' reset = brian2.SimpleCustomRefractoriness( IzhikevichReset(parameters['v_reset'], parameters['d']), period=0 * ms) ''' refractory = 0 * ms BaseNeuronGroup.__init__(self, n, equations, threshold=threshold, reset=reset, refractory=refractory, **parameters) #self._variable_refractory_time = True #self._refractory_variable = None #self._S0 = self._S[:, 0] #v_reset = _new_property('_resetfun.resetfun', 'Vr', mV) #b = _new_property('_resetfun.resetfun', 'b', nA) @property def v_reset(self): return self._resetvalue @property def d(self): return self._dvalue @v_reset.setter def v_reset(self, resetvalue): #self._resetvalue = resetvalue * mV self.event_codes['spike'] = 'v = {}*mV'.format(resetvalue) class PoissonGroup(brian2.PoissonGroup): def __init__(self, n, equations, **parameters): self.start_time = simplify(parameters["start_time"]) self.firing_rate = parameters["firing_rate"] self.duration = simplify(parameters["duration"]) brian2.PoissonGroup.__init__(self, n, rates=self.firing_rate, clock=simulator.state.network.clock) if is_listlike(self.start_time): self.variables.add_array('start_time', size=n, dimensions=second.dim) else: self.variables.add_constant('start_time', value=float( self.start_time), dimensions=second.dim) if is_listlike(self.duration) or is_listlike(self.start_time): self.variables.add_array('end_time', size=n, dimensions=second.dim) self.end_time = self.start_time + self.duration else: self.variables.add_constant('end_time', value=float( self.start_time + self.duration), dimensions=second.dim) self.events = {'spike': '(t >= start_time) and (t <= end_time) and (rand() < rates * dt)'} def initialize(self): pass class SpikeGeneratorGroup(brian2.SpikeGeneratorGroup): def __init__(self, n, equations, spike_time_sequences=None): """ Note that `equations` is not used: it is simply for compatibility with other NeuronGroup subclasses. """ assert spike_time_sequences.size == n self._check_spike_times(spike_time_sequences) indices, times = self._convert_sequences_to_arrays(spike_time_sequences) brian2.SpikeGeneratorGroup.__init__(self, n, indices=indices, times=times) def _convert_sequences_to_arrays(self, spike_time_sequences): times = np.concatenate([seq.value for seq in spike_time_sequences]) indices = np.concatenate([i * np.ones(seq.value.size) for i, seq in enumerate(spike_time_sequences)]) return indices, times * second # todo: try to push the multiplication by seconds back into the translation step. # note that the scaling from ms to seconds does take place during translation def _get_spike_time_sequences(self): # todo: might be faster using array operations values = [list() for i in range(self.N)] for i, t in zip(self.neuron_index, self.spike_time): values[i].append(t) return np.array([Sequence(times) for times in values], dtype=Sequence) def _set_spike_time_sequences(self, spike_time_sequences, mask=None): if mask is not None: existing_times = self._get_spike_time_sequences() existing_times[mask] = spike_time_sequences spike_time_sequences = existing_times self._check_spike_times(spike_time_sequences) indices, times = self._convert_sequences_to_arrays(spike_time_sequences) self.set_spikes(indices, times) spike_time_sequences = property(fget=_get_spike_time_sequences, fset=_set_spike_time_sequences) def _check_spike_times(self, spike_time_sequences): for seq in spike_time_sequences: if np.any(seq.value[:-1] > seq.value[1:]): raise errors.InvalidParameterValueError( "Spike times given to SpikeSourceArray must be in increasing order") def initialize(self): pass PyNN-0.10.0/pyNN/brian2/populations.py000066400000000000000000000111561415343567000174200ustar00rootroot00000000000000""" """ import numpy as np from pyNN import common from pyNN.standardmodels import StandardCellType from pyNN.parameters import ParameterSpace, simplify from . import simulator from .recording import Recorder import numpy as np from brian2.units.fundamentalunits import Quantity #from brian2.units import * #from quantities import * from brian2.core.variables import VariableView import brian2 #from brian2.groups.neurongroup import * ms = brian2.ms mV = brian2.mV class Assembly(common.Assembly): _simulator = simulator class PopulationView(common.PopulationView): _assembly_class = Assembly _simulator = simulator def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: value = getattr(self.brian2_group, name) if hasattr(value, "shape") and value.shape: value = value[self.mask] parameter_dict[name] = simplify(value) return ParameterSpace(parameter_dict, shape=(self.size,)) def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" parameter_space.evaluate(simplify=False) for name, value in parameter_space.items(): if name == "spike_time_sequences": self.brian2_group._set_spike_time_sequences(value, self.mask) elif name == "tau_refrac": # cannot be heterogeneous self.tau_refrac = value else: getattr(self.brian2_group, name)[self.mask] = value def _set_initial_value_array(self, variable, initial_values): raise NotImplementedError def _get_view(self, selector, label=None): return PopulationView(self, selector, label) @property def brian2_group(self): return self.parent.brian2_group class Population(common.Population): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def _create_cells(self): id_range = np.arange(simulator.state.id_counter, simulator.state.id_counter + self.size) self.all_cells = np.array([simulator.ID(id) for id in id_range], dtype=simulator.ID) # all cells are local. This doesn't seem very efficient. self._mask_local = np.ones((self.size,), bool) if isinstance(self.celltype, StandardCellType): parameter_space = self.celltype.native_parameters else: parameter_space = self.celltype.parameter_space parameter_space.shape = (self.size,) parameter_space.evaluate(simplify=False) self.brian2_group = self.celltype.brian2_model(self.size, self.celltype.eqs, **parameter_space) for id in self.all_cells: id.parent = self simulator.state.id_counter += self.size simulator.state.network.add(self.brian2_group) def _set_initial_value_array(self, variable, value): D = self.celltype.state_variable_translations[variable] pname = D['translated_name'] if callable(D['forward_transform']): pval = D['forward_transform'](value) # (value) else: pval = eval(D['forward_transform'], globals(), {variable: value}) pval = pval.evaluate(simplify=False) self.brian2_group.initial_values[pname] = pval self.brian2_group.initialize() def _get_view(self, selector, label=None): return PopulationView(self, selector, label) def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: value = getattr(self.brian2_group, name) if hasattr(value, "shape") and value.shape != (): value = value[:] parameter_dict[name] = value return ParameterSpace(parameter_dict, shape=(self.size,)) def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" parameter_space.evaluate(simplify=False) for name, value in parameter_space.items(): if (name == "tau_refrac"): value = simplify(value) self.brian2_group.tau_refrac = value elif (name == "v_reset"): value = simplify(value) self.brian2_group.v_reset = value else: setattr(self.brian2_group, name, value) PyNN-0.10.0/pyNN/brian2/projections.py000066400000000000000000000327711415343567000174100ustar00rootroot00000000000000# encoding: utf-8 """ """ import logging from itertools import chain from collections import defaultdict import numpy as np import brian2 from brian2 import uS, nA, mV, ms, second from pyNN import common from pyNN.standardmodels.synapses import TsodyksMarkramSynapse from pyNN.core import is_listlike from pyNN.parameters import ParameterSpace, simplify from pyNN.space import Space from . import simulator from .standardmodels.synapses import StaticSynapse logger = logging.getLogger("PyNN") class Connection(common.Connection): """ Store an individual plastic connection and information about it. Provide an interface that allows access to the connection's weight, delay and other attributes. """ def __init__(self, projection, i_group, j_group, index): self.projection = projection self.i_group = i_group self.j_group = j_group self.index = index self._syn_obj = self.projection._brian2_synapses[self.i_group][self.j_group] def _get(self, attr_name): value = getattr(self._syn_obj, attr_name)[self.index] native_ps = ParameterSpace({attr_name: value}, shape=(1,)) ps = self.projection.synapse_type.reverse_translate(native_ps) ps.evaluate() return ps[attr_name] def _set(self, attr_name, value): ps = ParameterSpace({attr_name: value}, shape=( 1,), schema=self.projection.synapse_type.get_schema()) native_ps = self.projection.synapse_type.translate(ps) native_ps.evaluate() getattr(self._syn_obj, attr_name)[self.index] = native_ps[attr_name] def _set_weight(self, w): self._set("weight", w) def _get_weight(self): """Synaptic weight in nA or µS.""" return self._get("weight") def _set_delay(self, d): self._set("delay", d) def _get_delay(self): """Synaptic delay in ms.""" return self._get("delay") weight = property(_get_weight, _set_weight) delay = property(_get_delay, _set_delay) def as_tuple(self, *attribute_names): # should return indices, not IDs for source and target return tuple([getattr(self, name) for name in attribute_names]) def basic_units(units): # todo: implement this properly so it works with any units if units == mV: return "volt" if units == uS: return "siemens" if units == nA: return "ampere" raise Exception("Can't handle units '{}'".format(units)) class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator _static_synapse_class = StaticSynapse def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, receptor_type=None, space=Space(), label=None): common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) self._n_connections = 0 # create one Synapses object per pre-post population pair # there will be multiple such pairs if either `presynaptic_population` # or `postsynaptic_population` is an Assembly. if isinstance(self.pre, common.Assembly): presynaptic_populations = self.pre.populations else: presynaptic_populations = [self.pre] if isinstance(self.post, common.Assembly): postsynaptic_populations = self.post.populations assert self.post._homogeneous_synapses, "Inhomogeneous assemblies not yet supported" else: postsynaptic_populations = [self.post] self._brian2_synapses = defaultdict(dict) for i, pre in enumerate(presynaptic_populations): for j, post in enumerate(postsynaptic_populations): # complete the synapse type equations according to the # post-synaptic response type psv = post.celltype.post_synaptic_variables[self.receptor_type] if hasattr(post.celltype, "voltage_based_synapses") and post.celltype.voltage_based_synapses: weight_units = mV else: weight_units = post.celltype.conductance_based and uS or nA self.synapse_type._set_target_type(weight_units) equation_context = {"syn_var": psv, "weight_units": basic_units(weight_units)} pre_eqns = self.synapse_type.pre % equation_context if self.synapse_type.post: post_eqns = self.synapse_type.post % equation_context else: post_eqns = None #  units are being transformed for exemple from amp to A model = self.synapse_type.eqs % equation_context # create the brian2 Synapses object. syn_obj = brian2.Synapses(pre.brian2_group, post.brian2_group, model=model, on_pre=pre_eqns, on_post=post_eqns, clock=simulator.state.network.clock, multisynaptic_index='synapse_number') # code_namespace={"exp": np.exp}) self._brian2_synapses[i][j] = syn_obj simulator.state.network.add(syn_obj) # connect the populations connector.connect(self) # special-case: the Tsodyks-Markram short-term plasticity model takes # a parameter value from the post-synaptic response model if isinstance(self.synapse_type, TsodyksMarkramSynapse): self._set_tau_syn_for_tsodyks_markram() def __len__(self): return self._n_connections @property def connections(self): """ Returns an iterator over local connections in this projection, as `Connection` objects. """ return (Connection(self, i_group, j_group, i) for i_group in range(len(self._brian2_synapses)) for j_group in range(len(self._brian2_synapses[i_group])) for i in range(len(self._brian2_synapses[i_group][j_group])) ) def _partition(self, indices): """ partition indices, in case of Assemblies """ if isinstance(self.pre, common.Assembly): boundaries = np.cumsum([0] + [p.size for p in self.pre.populations]) assert indices.max() < boundaries[-1] partitions = np.split(indices, np.searchsorted( indices, boundaries[1:-1])) - boundaries[:-1] for i_group, local_indices in enumerate(partitions): if isinstance(self.pre.populations[i_group], common.PopulationView): partitions[i_group] = self.pre.populations[i_group].index_in_grandparent( local_indices) elif isinstance(self.pre, common.PopulationView): partitions = [self.pre.index_in_grandparent(indices)] else: partitions = [indices] return partitions def _localize_index(self, index): """determine which group the postsynaptic index belongs to """ if isinstance(self.post, common.Assembly): boundaries = np.cumsum([0] + [p.size for p in self.post.populations]) j = np.searchsorted(boundaries, index, side='right') - 1 local_index = index - boundaries[j] if isinstance(self.post.populations[j], common.PopulationView): return j, self.post.populations[j].index_in_grandparent(local_index) else: return j, local_index elif isinstance(self.post, common.PopulationView): return 0, self.post.index_in_grandparent(index) else: return 0, index def _convergent_connect(self, presynaptic_indices, postsynaptic_index, **connection_parameters): connection_parameters.pop("dendritic_delay_fraction", None) # TODO: need to to handle this presynaptic_index_partitions = self._partition(presynaptic_indices) j_group, j = self._localize_index(postsynaptic_index) # specify which connections exist for i_group, i in enumerate(presynaptic_index_partitions): if i.size > 0: self._brian2_synapses[i_group][j_group].connect(i=i, j=j) # "[i, j] self._n_connections += i.size # set connection parameters for name, value in chain(connection_parameters.items(), self.synapse_type.initial_conditions.items()): if name == 'delay': scale = self._simulator.state.dt * ms value /= scale # ensure delays are rounded to the value = np.round(value) * scale # nearest time step, rather than truncated for i_group, i in enumerate(presynaptic_index_partitions): if i.size > 0: brian2_var = getattr(self._brian2_synapses[i_group][j_group], name) if is_listlike(value): for ii, v in zip(i, value): brian2_var[ii, j] = v else: for ii in i: try: brian2_var[ii, j] = value except TypeError as err: if "read-only" in str(err): logger.info( "Cannot set synaptic initial value for variable {}".format(name)) else: raise # brian2_var[i, j] = value # doesn't work with multiple connections between a given neuron pair. Need to understand the internals of Synapses and SynapticVariable better def _set_attributes(self, connection_parameters): if isinstance(self.post, common.Assembly) or isinstance(self.pre, common.Assembly): raise NotImplementedError syn_obj = self._brian2_synapses[0][0] connection_parameters.evaluate() # inefficient: would be better to evaluate using mask for name, value in connection_parameters.items(): creation_order_sorted_value = value[syn_obj.i[:], syn_obj.j[:]] setattr(syn_obj, name, creation_order_sorted_value) def _get_attributes_as_arrays(self, attribute_names, multiple_synapses='sum'): if isinstance(self.post, common.Assembly) or isinstance(self.pre, common.Assembly): raise NotImplementedError values = [] syn_obj = self._brian2_synapses[0][0] nan_mask = np.full((self.pre.size, self.post.size), True) iarr, jarr = syn_obj.i[:], syn_obj.j[:] nan_mask[iarr, jarr] = False multi_synapse_aggregation_map = { 'sum': (np.add.at, 0.0), 'min': (np.minimum.at, np.inf), 'max': (np.maximum.at, -np.inf) } for name in attribute_names: value = getattr(syn_obj, name)[:] # should really use the translated name native_ps = ParameterSpace({name: value}, shape=value.shape) ps = self.synapse_type.reverse_translate(native_ps) ps.evaluate() if multiple_synapses in multi_synapse_aggregation_map: aggregation_func, dummy_val = multi_synapse_aggregation_map[multiple_synapses] array_val = np.full((self.pre.size, self.post.size), dummy_val) aggregation_func(array_val, (syn_obj.i[:], syn_obj.j[:]), ps[name]) array_val[nan_mask] = np.nan else: raise NotImplementedError values.append(array_val) return values def _get_attributes_as_list(self, attribute_names): if isinstance(self.post, common.Assembly) or isinstance(self.pre, common.Assembly): raise NotImplementedError values = [] syn_obj = self._brian2_synapses[0][0] for name in attribute_names: if name == "presynaptic_index": value = syn_obj.i[:] # _indices.synaptic_pre.get_value() if hasattr(self.pre, "parent"): # map index in parent onto index in view value = self.pre.index_from_parent_index(value) elif name == "postsynaptic_index": value = syn_obj.j[:] # _indices.synaptic_post.get_value() if hasattr(self.post, "parent"): # map index in parent onto index in view value = self.post.index_from_parent_index(value) else: value = getattr(syn_obj, name)[:] # should really use the translated name native_ps = ParameterSpace({name: value}, shape=value.shape) # this whole "get attributes" thing needs refactoring in all backends to properly use translation ps = self.synapse_type.reverse_translate(native_ps) ps.evaluate() value = ps[name] values.append(value) a = np.array(values) return [tuple(x) for x in a.T] def _set_tau_syn_for_tsodyks_markram(self): if isinstance(self.post, common.Assembly) or isinstance(self.pre, common.Assembly): raise NotImplementedError tau_syn_var = self.synapse_type.tau_syn_var[self.receptor_type] self._brian2_synapses[0][0].tau_syn = self.post.get( tau_syn_var)[self._brian2_synapses[0][0].j] * brian2.ms PyNN-0.10.0/pyNN/brian2/recording.py000066400000000000000000000116171415343567000170210ustar00rootroot00000000000000""" :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from collections import defaultdict import numpy as np import quantities as pq import brian2 from pyNN.core import is_listlike from pyNN import recording from . import simulator mV = brian2.mV ms = brian2.ms uS = brian2.uS pq.uS = pq.UnitQuantity('microsiemens', 1e-6 * pq.S, 'uS') pq.nS = pq.UnitQuantity('nanosiemens', 1e-9 * pq.S, 'nS') logger = logging.getLogger("PyNN") class Recorder(recording.Recorder): """Encapsulates data and functions related to recording model variables.""" _simulator = simulator def __init__(self, population=None, file=None): __doc__ = recording.Recorder.__doc__ recording.Recorder.__init__(self, population, file) self._devices = {} # defer creation until first call of run() def _create_device(self, group, variable): """Create a Brian2 recording device.""" # Brian2 records in the 'start' scheduling slot by default if variable == 'spikes': self._devices[variable] = brian2.SpikeMonitor(group, record=self.recorded) else: varname = self.population.celltype.state_variable_translations[variable]['translated_name'] neurons_to_record = np.sort(np.fromiter( self.recorded[variable], dtype=int)) - self.population.first_id self._devices[variable] = brian2.StateMonitor(group, varname, record=neurons_to_record, when='end', dt=self.sampling_interval * ms) simulator.state.network.add(self._devices[variable]) def _record(self, variable, new_ids, sampling_interval=None): """Add the cells in `new_ids` to the set of recorded cells.""" self.sampling_interval = sampling_interval or self._simulator.state.dt def _finalize(self): for variable in self.recorded: if variable not in self._devices: self._create_device(self.population.brian2_group, variable) logger.debug("recording %s from %s" % (variable, self.recorded[variable])) def _reset(self): """Clear the list of cells to record.""" self._devices = {} for device in self._devices.values(): del device def _clear_simulator(self): """Delete all recorded data, but retain the list of cells to record from.""" # for variable, device in self._devices.items(): # group = device.source # self._create_device(group, variable) # del device for device in self._devices.values(): device.resize(0) def _get_spiketimes(self, id, clear=False): if is_listlike(id): all_spiketimes = {} for cell_id in id: i = cell_id - self.population.first_id spiky = self._devices['spikes'].spike_trains() all_spiketimes[cell_id] = spiky[i] / ms return all_spiketimes else: i = id - self.population.first_id spiky = self._devices['spikes'].spike_trains() return spiky[i] / ms def _get_all_signals(self, variable, ids, clear=False): # need to filter according to ids # check that the requested ids have indeed been recorded if not set(ids).issubset(self.recorded[variable]): raise Exception("You are requesting data from neurons that have not been recorded") device = self._devices[variable] varname = self.population.celltype.state_variable_translations[variable]['translated_name'] if len(ids) == len(self.recorded[variable]): values = getattr(device, varname).T else: raise NotImplementedError # todo - construct a mask to get only the desired signals values = self.population.celltype.state_variable_translations[variable]['reverse_transform']( values) # because we use `when='end'`, need to add the value at the beginning of the run tmp = np.empty((values.shape[0] + 1, values.shape[1])) tmp[1:, :] = values population_mask = self.population.id_to_index(ids) tmp[0, :] = self.population.initial_values[variable][population_mask] values = tmp if clear: self._devices[variable].resize(0) return values def _local_count(self, variable, filter_ids=None): N = {} filtered_ids = self.filter_recorded(variable, filter_ids) padding = self.population.first_id indices = np.fromiter(filtered_ids, dtype=int) - padding spiky = self._devices['spikes'].spike_trains() for i, id in zip(indices, filtered_ids): #N[id] = len(self._devices['spikes'].spiketimes[i]) N[id] = len(spiky[i]) return N PyNN-0.10.0/pyNN/brian2/simulator.py000066400000000000000000000070531415343567000170630ustar00rootroot00000000000000# encoding: utf-8 """ Implementation of the "low-level" functionality used by the common implementation of the API, for the Brian2 simulator. Classes and attributes usable by the common implementation: Classes: ID Connection Attributes: state -- an instance of the _State class. All other functions and classes are private, and should not be used by other modules. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import brian2 import numpy as np from pyNN import common from pyNN.parameters import simplify name = "Brian2" logger = logging.getLogger("PyNN") ms = brian2.ms class ID(int, common.IDMixin): def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) class State(common.control.BaseState): def __init__(self): common.control.BaseState.__init__(self) self.mpi_rank = 0 self.num_processes = 1 self._min_delay = 'auto' self.network = None self.clear() def run(self, simtime): for recorder in self.recorders: recorder._finalize() if not self.running: assert self.network.clock.t == 0 * ms self.network.store("before-first-run") # todo: handle the situation where new Populations or Projections are # created after the first run and then "reset" is called self.running = True self.network.run(simtime * ms) def run_until(self, tstop): self.run(tstop - self.t) def clear(self): self.recorders = set([]) self.id_counter = 0 self.current_sources = [] self.segment_counter = -1 if self.network: for item in self.network.sorted_objects: del item del self.network self.network = brian2.Network() self.network.clock = brian2.Clock(0.1 * ms) self.running = False self.reset() def reset(self): """Reset the state of the current network to time t = 0.""" if self.running: self.network.restore("before-first-run") self.running = False self.t_start = 0 self.segment_counter += 1 def _get_dt(self): if self.network.clock is None: raise Exception("Simulation timestep not yet set. Need to call setup()") return float(self.network.clock.dt / ms) def _set_dt(self, timestep): logger.debug("Setting timestep to %s", timestep) # if self.network.clock is None or timestep != self._get_dt(): # self.network.clock = brian2.Clock(dt=timestep*ms) self.network.clock.dt = timestep * ms dt = property(fget=_get_dt, fset=_set_dt) @property def t(self): return float(self.network.clock.t / ms) def _get_min_delay(self): if self._min_delay == 'auto': min_delay = np.inf for item in self.network.sorted_objects: if isinstance(item, brian2.Synapses): matrix = np.asarray(item.delay) * 10000 min_delay = min(min_delay, matrix.min()) if np.isinf(min_delay): self._min_delay = self.dt else: self._min_delay = min_delay * self.dt # Synapses.delay is an integer, the number of time steps return self._min_delay def _set_min_delay(self, delay): self._min_delay = delay min_delay = property(fget=_get_min_delay, fset=_set_min_delay) state = State() PyNN-0.10.0/pyNN/brian2/standardmodels/000077500000000000000000000000001415343567000174715ustar00rootroot00000000000000PyNN-0.10.0/pyNN/brian2/standardmodels/__init__.py000066400000000000000000000000001415343567000215700ustar00rootroot00000000000000PyNN-0.10.0/pyNN/brian2/standardmodels/cells.py000066400000000000000000000453561415343567000211620ustar00rootroot00000000000000# encoding: utf-8 """ Standard cells for the Brian2 module. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from copy import deepcopy import brian2 from brian2 import mV, ms, nF, nA, uS, Hz, nS from pyNN.standardmodels import cells, build_translations from ..cells import (ThresholdNeuronGroup, SpikeGeneratorGroup, PoissonGroup, BiophysicalNeuronGroup, AdaptiveNeuronGroup, AdaptiveNeuronGroup2, IzhikevichNeuronGroup) import logging logger = logging.getLogger("PyNN") leaky_iaf = brian2.Equations(''' dv/dt = (v_rest-v)/tau_m + (i_syn + i_offset + i_inj)/c_m : volt (unless refractory) tau_m : second c_m : farad v_rest : volt i_offset : amp i_inj : amp ''') # give v_thresh a different name adexp_iaf = brian2.Equations(''' dv/dt = (delta_T*gL*exp((-v_thresh + v)/delta_T) + I + gL*(v_rest - v) - w )/ c_m : volt (unless refractory) dw/dt = (a*(v-v_rest) - w)/tau_w : amp gL = c_m / tau_m : siemens I = i_syn + i_inj + i_offset : amp a : siemens tau_m : second tau_w : second c_m : farad v_rest : volt v_spike : volt v_thresh : volt delta_T : volt i_offset : amp i_inj : amp ''') # g_r, g_s should be in uS for PyNN unit system consistency adapt_iaf = brian2.Equations(''' dv/dt = (v_rest-v)/tau_m + (-g_r*(v-E_r) - g_s*(v-E_s) + i_syn + i_offset + i_inj)/c_m : volt (unless refractory) dg_s/dt = -g_s/tau_s : siemens (unless refractory) dg_r/dt = -g_r/tau_r : siemens (unless refractory) tau_m : second tau_s : second tau_r : second c_m : farad v_rest : volt i_offset : amp i_inj : amp E_r : volt E_s : volt ''') conductance_based_exponential_synapses = brian2.Equations(''' dge/dt = -ge/tau_syn_e : siemens dgi/dt = -gi/tau_syn_i : siemens i_syn = ge*(e_rev_e - v) + gi*(e_rev_i - v) : amp tau_syn_e : second tau_syn_i : second e_rev_e : volt e_rev_i : volt ''') conductance_based_alpha_synapses = brian2.Equations(''' dge/dt = (2.7182818284590451*ye-ge)/tau_syn_e : siemens dye/dt = -ye/tau_syn_e : siemens dgi/dt = (2.7182818284590451*yi-gi)/tau_syn_i : siemens dyi/dt = -yi/tau_syn_i : siemens i_syn = ge*(e_rev_e - v) + gi*(e_rev_i - v) : amp tau_syn_e : second tau_syn_i : second e_rev_e : volt e_rev_i : volt ''') current_based_exponential_synapses = brian2.Equations(''' die/dt = -ie/tau_syn_e : amp dii/dt = -ii/tau_syn_i : amp i_syn = ie + ii : amp tau_syn_e : second tau_syn_i : second ''') current_based_alpha_synapses = brian2.Equations(''' die/dt = (2.7182818284590451*ye-ie)/tau_syn_e : amp dye/dt = -ye/tau_syn_e : amp dii/dt = (2.7182818284590451*yi-ii)/tau_syn_e : amp dyi/dt = -yi/tau_syn_e : amp i_syn = ie + ii : amp tau_syn_e : second tau_syn_i : second ''') leaky_iaf_translations = build_translations( ('v_rest', 'v_rest', lambda **p: p["v_rest"] * mV, lambda **p: p["v_rest"] / mV), ('v_reset', 'v_reset', lambda **p: p["v_reset"] * mV, lambda **p: p["v_reset"] / mV), ('cm', 'c_m', lambda **p: p["cm"] * nF, lambda **p: p["c_m"] / nF), ('tau_m', 'tau_m', lambda **p: p["tau_m"] * ms, lambda **p: p["tau_m"] / ms), ###p["tau_m"] * ms, p["tau_m"] /nF ('tau_refrac', 'tau_refrac', lambda **p: p["tau_refrac"] * ms, lambda **p: p["tau_refrac"] / ms), ('v_thresh', 'v_thresh', lambda **p: p["v_thresh"] * mV, lambda **p: p["v_thresh"] / mV), ('i_offset', 'i_offset', lambda **p: p["i_offset"] * nA, lambda **p: p["i_offset"] / nA)) adexp_iaf_translations = build_translations( ('v_rest', 'v_rest', lambda **p: p["v_rest"] * mV, lambda **p: p["v_rest"] / mV), ('v_reset', 'v_reset', lambda **p: p["v_reset"] * mV, lambda **p: p["v_reset"] / mV), ('cm', 'c_m', lambda **p: p["cm"] * nF, lambda **p: p["c_m"] / nF), ('tau_m', 'tau_m', lambda **p: p["tau_m"] * ms, lambda **p: p["tau_m"] / ms), ('tau_refrac', 'tau_refrac', lambda **p: p["tau_refrac"] * ms, lambda **p: p["tau_refrac"] / ms), ('v_thresh', 'v_thresh', lambda **p: p["v_thresh"] * mV, lambda **p: p["v_thresh"] / mV), ('i_offset', 'i_offset', lambda **p: p["i_offset"] * nA, lambda **p: p["i_offset"] / nA), ('a', 'a', lambda **p: p["a"] * nS, lambda **p: p["a"] / nS), ('b', 'b', lambda **p: p["b"] * nA, lambda **p: p["b"] / nA), ('delta_T', 'delta_T', lambda **p: p["delta_T"] * mV, lambda **p: p["delta_T"] / mV), ('tau_w', 'tau_w', lambda **p: p["tau_w"] * ms, lambda **p: p["tau_w"] / ms), ('v_spike', 'v_spike', lambda **p: p["v_spike"] * mV, lambda **p: p["v_spike"] / mV)) adapt_iaf_translations = build_translations( ('v_rest', 'v_rest', lambda **p: p["v_rest"] * mV, lambda **p: p["v_rest"] / mV), ('v_reset', 'v_reset', lambda **p: p["v_reset"] * mV, lambda **p: p["v_reset"] / mV), ('cm', 'c_m', lambda **p: p["cm"] * nF, lambda **p: p["c_m"] / nF), ('tau_m', 'tau_m', lambda **p: p["tau_m"] * ms, lambda **p: p["tau_m"] / ms), ('tau_refrac', 'tau_refrac', lambda **p: p["tau_refrac"] * ms, lambda **p: p["tau_refrac"] / ms), ('v_thresh', 'v_thresh', lambda **p: p["v_thresh"] * mV, lambda **p: p["v_thresh"] / mV), ('i_offset', 'i_offset', lambda **p: p["i_offset"] * nA, lambda **p: p["i_offset"] / nA), ('tau_sfa', 'tau_s', lambda **p: p["tau_sfa"] * ms, lambda **p: p["tau_s"] / ms), ('e_rev_sfa', 'E_s', lambda **p: p["e_rev_sfa"] * mV, lambda **p: p["E_s"] / mV), ('q_sfa', 'q_s', lambda **p: p["q_sfa"] * nS, lambda **p: p["q_s"] / nS), # should we uS for consistency of PyNN unit system? ('tau_rr', 'tau_r', lambda **p: p["tau_rr"] * ms, lambda **p: p["tau_r"] / ms), ('e_rev_rr', 'E_r', lambda **p: p["e_rev_rr"] * mV, lambda **p: p["E_r"] / mV), ('q_rr', 'q_r', lambda **p: p["q_rr"] * nS, lambda **p: p["q_r"] / nS)) conductance_based_synapse_translations = build_translations( ('tau_syn_E', 'tau_syn_e', lambda **p: p["tau_syn_E"] * ms, lambda **p: p["tau_syn_e"] / ms), ('tau_syn_I', 'tau_syn_i', lambda **p: p["tau_syn_I"] * ms, lambda **p: p["tau_syn_i"] / ms), ('e_rev_E', 'e_rev_e', lambda **p: p["e_rev_E"] * mV, lambda **p: p["e_rev_e"] / mV), ('e_rev_I', 'e_rev_i', lambda **p: p["e_rev_I"] * mV, lambda **p: p["e_rev_i"] / mV)) current_based_synapse_translations = build_translations( ('tau_syn_E', 'tau_syn_e', lambda **p: p["tau_syn_E"] * ms, lambda **p: p["tau_syn_e"] / ms), ('tau_syn_I', 'tau_syn_i', lambda **p: p["tau_syn_I"] * ms, lambda **p: p["tau_syn_i"] / ms)) conductance_based_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), ('gsyn_exc', 'ge', lambda p: p * uS, lambda p: p/ uS), ('gsyn_inh', 'gi', lambda p: p * uS, lambda p: p/ uS)) current_based_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), #### change p by p["v"] ('isyn_exc', 'ie', lambda p: p * nA, lambda p: p/ nA), ('isyn_inh', 'ii', lambda p: p * nA, lambda p: p/ nA)) class IF_curr_alpha(cells.IF_curr_alpha): __doc__ = cells.IF_curr_alpha.__doc__ eqs = leaky_iaf + current_based_alpha_synapses translations = deepcopy(leaky_iaf_translations) translations.update(current_based_synapse_translations) state_variable_translations = current_based_variable_translations post_synaptic_variables = {'excitatory': 'ye', 'inhibitory': 'yi'} brian2_model = ThresholdNeuronGroup class IF_curr_exp(cells.IF_curr_exp): __doc__ = cells.IF_curr_exp.__doc__ eqs = leaky_iaf + current_based_exponential_synapses translations = deepcopy(leaky_iaf_translations) translations.update(current_based_synapse_translations) state_variable_translations = current_based_variable_translations post_synaptic_variables = {'excitatory': 'ie', 'inhibitory': 'ii'} brian2_model = ThresholdNeuronGroup class IF_cond_alpha(cells.IF_cond_alpha): __doc__ = cells.IF_cond_alpha.__doc__ eqs = leaky_iaf + conductance_based_alpha_synapses translations = deepcopy(leaky_iaf_translations) translations.update(conductance_based_synapse_translations) state_variable_translations = conductance_based_variable_translations post_synaptic_variables = {'excitatory': 'ye', 'inhibitory': 'yi'} brian2_model = ThresholdNeuronGroup class IF_cond_exp(cells.IF_cond_exp): __doc__ = cells.IF_cond_exp.__doc__ eqs = leaky_iaf + conductance_based_exponential_synapses translations = deepcopy(leaky_iaf_translations) translations.update(conductance_based_synapse_translations) state_variable_translations = conductance_based_variable_translations post_synaptic_variables = {'excitatory': 'ge', 'inhibitory': 'gi'} brian2_model = ThresholdNeuronGroup class EIF_cond_exp_isfa_ista(cells.EIF_cond_exp_isfa_ista): __doc__ = cells.EIF_cond_exp_isfa_ista.__doc__ eqs = adexp_iaf + conductance_based_exponential_synapses translations = deepcopy(adexp_iaf_translations) translations.update(conductance_based_synapse_translations) state_variable_translations = build_translations( ('v', 'v',lambda p: p * mV, lambda p: p/ mV), ('w', 'w', lambda p: p * nA, lambda p: p/ nA), ('gsyn_exc', 'ge',lambda p: p * uS, lambda p: p/ uS), ('gsyn_inh', 'gi', lambda p: p * uS, lambda p: p/ uS)) post_synaptic_variables = {'excitatory': 'ge', 'inhibitory': 'gi'} brian2_model = AdaptiveNeuronGroup class EIF_cond_alpha_isfa_ista(cells.EIF_cond_alpha_isfa_ista): __doc__ = cells.EIF_cond_alpha_isfa_ista.__doc__ eqs = adexp_iaf + conductance_based_alpha_synapses translations = deepcopy(adexp_iaf_translations) translations.update(conductance_based_synapse_translations) state_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), ('w', 'w', lambda p: p * nA, lambda p: p/ nA), ('gsyn_exc', 'ge', lambda p: p * uS, lambda p: p/ uS), ('gsyn_inh', 'gi', lambda p: p * uS, lambda p: p/ uS)) post_synaptic_variables = {'excitatory': 'ge', 'inhibitory': 'gi'} brian2_model = AdaptiveNeuronGroup class IF_cond_exp_gsfa_grr(cells.IF_cond_exp_gsfa_grr): eqs = adapt_iaf + conductance_based_alpha_synapses translations = deepcopy(adapt_iaf_translations) translations.update(conductance_based_synapse_translations) state_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), ('g_s', 'g_s', lambda p: p * uS, lambda p: p/ uS), # should be uS - needs changed for all back-ends ('g_r', 'g_r', lambda p: p * uS, lambda p: p/ uS), ('gsyn_exc', 'ge', lambda p: p * uS, lambda p: p/ uS), ('gsyn_inh', 'gi', lambda p: p * uS, lambda p: p/ uS)) post_synaptic_variables = {'excitatory': 'ge', 'inhibitory': 'gi'} brian2_model = AdaptiveNeuronGroup2 class HH_cond_exp(cells.HH_cond_exp): __doc__ = cells.HH_cond_exp.__doc__ translations = build_translations( ('gbar_Na', 'gbar_Na', lambda **p: p["gbar_Na"] * uS, lambda **p: p["gbar_Na"] / uS), ('gbar_K', 'gbar_K', lambda **p: p["gbar_K"] * uS, lambda **p: p["gbar_K"] / uS), ('g_leak', 'g_leak', lambda **p: p["g_leak"] * uS, lambda **p: p["g_leak"] / uS), ('cm', 'c_m', lambda **p: p["cm"] * nF, lambda **p: p["c_m"] / nF), ('v_offset', 'v_offset', lambda **p: p["v_offset"] * mV, lambda **p: p["v_offset"] / mV), ('e_rev_Na', 'e_rev_Na', lambda **p: p["e_rev_Na"] * mV, lambda **p: p["e_rev_Na"] / mV), ('e_rev_K', 'e_rev_K', lambda **p: p["e_rev_K"] * mV, lambda **p: p["e_rev_K"] / mV), ('e_rev_leak', 'e_rev_leak', lambda **p: p["e_rev_leak"] * mV, lambda **p: p["e_rev_leak"] / mV), ('e_rev_E', 'e_rev_e', lambda **p: p["e_rev_E"] * mV, lambda **p: p["e_rev_e"] / mV), ('e_rev_I', 'e_rev_i', lambda **p: p["e_rev_I"] * mV, lambda **p: p["e_rev_i"] / mV), ('tau_syn_E', 'tau_syn_e', lambda **p: p["tau_syn_E"] * ms, lambda **p: p["tau_syn_e"] / ms), ('tau_syn_I', 'tau_syn_i', lambda **p: p["tau_syn_I"] * ms, lambda **p: p["tau_syn_i"] / ms), ('i_offset', 'i_offset', lambda **p: p["i_offset"] * nA, lambda **p: p["i_offset"] / nA)) eqs = brian2.Equations(''' dv/dt = (g_leak*(e_rev_leak-v) - gbar_Na*(m*m*m)*h*(v-e_rev_Na) - gbar_K*(n*n*n*n)*(v-e_rev_K) + i_syn + i_offset + i_inj)/c_m : volt dm/dt = (alpham*(1-m)-betam*m) : 1 dn/dt = (alphan*(1-n)-betan*n) : 1 dh/dt = (alphah*(1-h)-betah*h) : 1 alpham = (0.32/mV)*(13*mV-v+v_offset)/(exp((13*mV-v+v_offset)/(4*mV))-1.)/ms : Hz betam = (0.28/mV)*(v-v_offset-40*mV)/(exp((v-v_offset-40*mV)/(5*mV))-1)/ms : Hz alphah = 0.128*exp((17*mV-v+v_offset)/(18*mV))/ms : Hz betah = 4./(1+exp((40*mV-v+v_offset)/(5*mV)))/ms : Hz alphan = (0.032/mV)*(15*mV-v+v_offset)/(exp((15*mV-v+v_offset)/(5*mV))-1.)/ms : Hz betan = .5*exp((10*mV-v+v_offset)/(40*mV))/ms : Hz e_rev_Na : volt e_rev_K : volt e_rev_leak : volt gbar_Na : siemens gbar_K : siemens g_leak : siemens v_offset : volt c_m : farad i_offset : amp i_inj : amp ''') + conductance_based_exponential_synapses recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh', 'm','n','h'] post_synaptic_variables = {'excitatory': 'ge', 'inhibitory': 'gi'} state_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), ('gsyn_exc', 'ge', lambda p: p * uS, lambda p: p/ uS), ('gsyn_inh', 'gi', lambda p: p * uS, lambda p: p/ uS), ('h', 'h', lambda p: p *1, lambda p: p*1), ('m', 'm', lambda p: p *1, lambda p: p*1), ('n', 'n', lambda p: p*1 , lambda p: p*1)) brian2_model = BiophysicalNeuronGroup class Izhikevich(cells.Izhikevich): __doc__ = cells.Izhikevich.__doc__ translations = build_translations( ('a', 'a', lambda **p: p["a"] *(1/ms) , lambda **p: p["a"] / (1/ms)), ('b', 'b', lambda **p: p["b"] *(1/ms) , lambda **p: p["b"] / (1/ms)), ('c', 'v_reset', lambda **p: p["c"] * mV, lambda **p: p["v_reset"] / mV), ('d', 'd', lambda **p: p["d"] *(mV/ms) , lambda **p: p["d"] / (mV/ms)), ('i_offset', 'i_offset', lambda **p: p["i_offset"] * nA, lambda **p: p["i_offset"] / nA)) ### dv/dt = (0.04/ms/mV)*v*v ->>>> (0.04/ms/mV)*v**2 eqs = brian2.Equations(''' dv/dt = (0.04/ms/mV)*v*v + (5/ms)*v + 140*mV/ms - u + (i_offset + i_inj)/pF : volt (unless refractory) du/dt = a*(b*v-u) : volt/second (unless refractory) a : 1/second b : 1/second v_reset : volt d : volt/second i_offset : amp i_inj : amp ''') post_synaptic_variables = {'excitatory': 'v', 'inhibitory': 'v'} state_variable_translations = build_translations( ('v', 'v', lambda p: p * mV, lambda p: p/ mV), ('u', 'u', lambda p: p * (mV/ms), lambda p: p/ (mV/ms))) brian2_model = IzhikevichNeuronGroup class SpikeSourcePoisson(cells.SpikeSourcePoisson): __doc__ = cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('rate', 'firing_rate', lambda **p: p["rate"] * Hz, lambda **p: p["firing_rate"] / Hz), ('start', 'start_time', lambda **p: p["start"] * ms, lambda **p: p["start_time"] / ms), ('duration', 'duration', lambda **p: p["duration"] * ms, lambda **p: p["duration"] / ms), ) eqs = None brian2_model = PoissonGroup class SpikeSourceArray(cells.SpikeSourceArray): __doc__ = cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'spike_time_sequences', ms), ) eqs = None brian2_model = SpikeGeneratorGroup PyNN-0.10.0/pyNN/brian2/standardmodels/electrodes.py000066400000000000000000000240401415343567000221740ustar00rootroot00000000000000""" Current source classes for the brian2 module. Classes: DCSource -- a single pulse of current of constant amplitude. StepCurrentSource -- a step-wise time-varying current. ACSource -- a sine modulated current. NoisyCurrentSource -- a Gaussian whitish noise current. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import numpy as np import brian2 from brian2 import ms, second, nA, amp, Hz, NetworkOperation, amp as ampere from pyNN.standardmodels import electrodes, build_translations, StandardCurrentSource from pyNN.parameters import ParameterSpace, Sequence from pyNN.brian2 import simulator logger = logging.getLogger("PyNN") def update_currents(): for current_source in simulator.state.current_sources: current_source._update_current() class Brian2CurrentSource(StandardCurrentSource): """Base class for a source of current to be injected into a neuron.""" def __init__(self, **parameters): super(StandardCurrentSource, self).__init__(**parameters) self.cell_list = [] self.indices = [] self.prev_amp_dict = {} self.running = False simulator.state.current_sources.append(self) parameter_space = ParameterSpace(self.default_parameters, self.get_schema(), shape=(1)) parameter_space.update(**parameters) parameter_space = self.translate(parameter_space) self.set_native_parameters(parameter_space) def _check_step_times(self, times, amplitudes, resolution): # change resolution from ms to s; as per brian2 convention resolution = resolution*1e-3 # ensure that all time stamps are non-negative if not (times >= 0.0).all(): raise ValueError("Step current cannot accept negative timestamps.") # ensure that times provided are of strictly increasing magnitudes dt_times = np.diff(times) if not all(dt_times > 0.0): raise ValueError("Step current timestamps should be monotonically increasing.") # map timestamps to actual simulation time instants based on specified dt times = self._round_timestamp(times, resolution) # remove duplicate timestamps, and corresponding amplitudes, after mapping step_times = [] step_amplitudes = [] for ts0, amp0, ts1 in zip(times, amplitudes, times[1:]): if ts0 != ts1: step_times.append(ts0) step_amplitudes.append(amp0) step_times.append(times[-1]) step_amplitudes.append(amplitudes[-1]) return step_times, step_amplitudes def set_native_parameters(self, parameters): parameters.evaluate(simplify=True) for name, value in parameters.items(): if name == "amplitudes": # key used only by StepCurrentSource step_times = parameters["times"].value step_amplitudes = parameters["amplitudes"].value step_times, step_amplitudes = self._check_step_times( step_times, step_amplitudes, simulator.state.dt) parameters["times"].value = step_times parameters["amplitudes"].value = step_amplitudes if isinstance(value, Sequence): value = value.value object.__setattr__(self, name, value) self._reset() def _reset(self): # self.i reset to 0 only at the start of a new run; not for continuation of existing runs if not hasattr(self, 'running') or self.running == False: self.i = 0 self.running = True if self._is_computed: self._generate() def inject_into(self, cell_list): __doc__ = StandardCurrentSource.inject_into.__doc__ for cell in cell_list: if not cell.celltype.injectable: raise TypeError("Can't inject current into a spike source.") self.cell_list.extend(cell_list) for cell in cell_list: cell_idx = cell.parent.id_to_index(cell) self.indices.extend([cell_idx]) self.prev_amp_dict[cell_idx] = 0.0 def _update_current(self): # check if current timestamp is within dt/2 of target time; Brian2 uses seconds as unit of time if self.running and abs(simulator.state.t - self.times[self.i] * 1e3) < (simulator.state.dt/2.0): for cell, idx in zip(self.cell_list, self.indices): if not self._is_playable: cell.parent.brian2_group.i_inj[idx] += ( self.amplitudes[self.i] - self.prev_amp_dict[idx]) * ampere self.prev_amp_dict[idx] = self.amplitudes[self.i] else: amp_val = self._compute(self.times[self.i]) self.amplitudes = np.append(self.amplitudes, amp_val) cell.parent.brian2_group.i_inj[idx] += (amp_val - self.prev_amp_dict[idx]) * ampere self.prev_amp_dict[idx] = amp_val # * ampere self.i += 1 if self.i >= len(self.times): self.running = False if self._is_playable: # ensure that currents are set to 0 after t_stop for cell, idx in zip(self.cell_list, self.indices): cell.parent.brian2_group.i_inj[idx] -= self.prev_amp_dict[idx] * ampere def record(self): pass def _get_data(self): def find_nearest(array, value): array = np.asarray(array) return (np.abs(array - value)).argmin() len_t = int(round((simulator.state.t * 1e-3) / (simulator.state.dt * 1e-3))) + 1 times = np.array([(i * simulator.state.dt * 1e-3) for i in range(len_t)]) amps = np.array([0.0] * len_t) for idx, [t1, t2] in enumerate(zip(self.times, self.times[1:])): if t2 < simulator.state.t * 1e-3: idx1 = find_nearest(times, t1) idx2 = find_nearest(times, t2) amps[idx1:idx2] = [self.amplitudes[idx]] * len(amps[idx1:idx2]) if idx == len(self.times)-2: if not self._is_playable and not self._is_computed: amps[idx2:] = [self.amplitudes[idx+1]] * len(amps[idx2:]) else: if t1 < simulator.state.t * 1e-3: idx1 = find_nearest(times, t1) amps[idx1:] = [self.amplitudes[idx]] * len(amps[idx1:]) break return (times * second / ms, amps * amp / nA) class StepCurrentSource(Brian2CurrentSource, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitudes', nA), ('times', 'times', ms) ) _is_computed = False _is_playable = False class ACSource(Brian2CurrentSource, electrodes.ACSource): __doc__ = electrodes.ACSource.__doc__ translations = build_translations( ('amplitude', 'amplitude', nA), ('start', 'start', ms), ('stop', 'stop', ms), ('frequency', 'frequency', Hz), ('offset', 'offset', nA), ('phase', 'phase', 1) ) _is_computed = True _is_playable = True def __init__(self, **parameters): Brian2CurrentSource.__init__(self, **parameters) self._generate() def _generate(self): # Note: Brian2 uses seconds as unit of time temp_num_t = int(round(((self.stop + simulator.state.dt * 1e-3) - self.start) / (simulator.state.dt * 1e-3))) self.times = np.array([self.start + (i * simulator.state.dt * 1e-3) for i in range(temp_num_t)]) self.amplitudes = np.zeros(0) def _compute(self, time): # Note: Brian2 uses seconds as unit of time; frequency is specified in Hz; thus no conversion required return self.offset + self.amplitude * np.sin((time-self.start) * 2 * np.pi * self.frequency + 2 * np.pi * self.phase / 360) class DCSource(Brian2CurrentSource, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude', nA), ('start', 'start', ms), ('stop', 'stop', ms) ) _is_computed = True _is_playable = False def __init__(self, **parameters): Brian2CurrentSource.__init__(self, **parameters) self._generate() def _generate(self): if self.start == 0: self.times = [self.start, self.stop] self.amplitudes = [self.amplitude, 0.0] else: self.times = [0.0, self.start, self.stop] self.amplitudes = [0.0, self.amplitude, 0.0] # ensures proper handling of changes in parameters on the fly if self.start < simulator.state.t*1e-3 < self.stop: self.times.insert(-1, simulator.state.t*1e-3) self.amplitudes.insert(-1, self.amplitude) if (self.start == 0 and self.i == 2) or (self.start != 0 and self.i == 3): self.i -= 1 class NoisyCurrentSource(Brian2CurrentSource, electrodes.NoisyCurrentSource): __doc__ = electrodes.NoisyCurrentSource.__doc__ translations = build_translations( ('mean', 'mean', nA), ('start', 'start', ms), ('stop', 'stop', ms), ('stdev', 'stdev', nA), ('dt', 'dt', ms) ) _is_computed = True _is_playable = True def __init__(self, **parameters): Brian2CurrentSource.__init__(self, **parameters) self._generate() def _generate(self): temp_num_t = int(round((self.stop - self.start) / max(self.dt, simulator.state.dt * 1e-3))) self.times = np.array( [self.start + (i * max(self.dt, simulator.state.dt * 1e-3)) for i in range(temp_num_t)]) self.times = np.append(self.times, self.stop) self.amplitudes = np.zeros(0) def _compute(self, time): return self.mean + self.stdev * np.random.randn() PyNN-0.10.0/pyNN/brian2/standardmodels/synapses.py000066400000000000000000000211171415343567000217120ustar00rootroot00000000000000# encoding: utf-8 """ Standard cells for the Brian2 module. :copyright: Copyright 2006-2016 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from brian2 import ms from pyNN.standardmodels import synapses, build_translations from ..simulator import state logger = logging.getLogger("PyNN") class StaticSynapse(synapses.StaticSynapse): __doc__ = synapses.StaticSynapse.__doc__ eqs = """weight : %(weight_units)s""" pre = "%(syn_var)s += weight" post = None initial_conditions = {} def __init__(self, **parameters): super(StaticSynapse, self).__init__(**parameters) # we have to define the translations on a per-instance basis because # they depend on whether the synapses are current-, conductance- or voltage-based. self.translations = build_translations( ('weight', 'weight'), ('delay', 'delay', lambda **P: P["delay"] * ms, lambda **P: P["delay"] / ms)) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def _set_target_type(self, weight_units): self.translations["weight"]["forward_transform"] = lambda **P: P["weight"] * weight_units self.translations["weight"]["reverse_transform"] = lambda **P: P["weight"] / weight_units class TsodyksMarkramSynapse(synapses.TsodyksMarkramSynapse): __doc__ = synapses.TsodyksMarkramSynapse.__doc__ translations = build_translations( ('weight', 'weight'), ('delay', 'delay', lambda **P: P["delay"] * ms, lambda **P: P["delay"] / ms), ('U', 'U'), ('tau_rec', 'tau_rec', lambda **P: P["tau_rec"] * ms, lambda **P: P["tau_rec"] / ms), ('tau_facil', 'tau_facil', lambda **P: P["tau_facil"] * ms, lambda **P: P["tau_facil"] / ms), ('tau_syn' , 'tau_syn', lambda **P: P["tau_syn"] * ms, lambda **P: P["tau_syn"] / ms) ) eqs = '''weight : %(weight_units)s U : 1 tau_syn : second tau_rec : second tau_facil : second dz/dt = z/tau_rec : 1 (event-driven) dy/dt = -y/tau_syn : 1 (event-driven) du/dt = -u/tau_facil : 1 (event-driven) x=1-y-z : 1 ''' pre = ''' u += U*(1-u) u = int(u > U)*U + int(u <= U)*u %(syn_var)s += weight*x*u y += x*u ''' post = None initial_conditions = {"u": 0.0, "x": 1.0, "y": 0.0, "z": 0.0} tau_syn_var = {"excitatory": "tau_syn_E", "inhibitory": "tau_syn_I"} def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def _set_target_type(self, weight_units): self.translations["weight"]["forward_transform"] = lambda **P: P["weight"] * weight_units self.translations["weight"]["reverse_transform"] = lambda **P: P["weight"] / weight_units class STDPMechanism(synapses.STDPMechanism): __doc__ = synapses.STDPMechanism.__doc__ base_translations = build_translations( ('weight', 'weight'), ('delay', 'delay', lambda **P: P["delay"] * ms, lambda **P: P["delay"] / ms), ('dendritic_delay_fraction', 'dendritic_delay_fraction', 1) ) eqs = """weight : %(weight_units)s tau_plus : second tau_minus : second w_max : %(weight_units)s w_min : %(weight_units)s A_plus : 1 A_minus : 1 dP/dt = -P/tau_plus : 1 (event-driven) dM/dt = -M/tau_minus : 1 (event-driven)""" # to be split among component parts pre = """ P += A_plus weight = weight + w_max * M weight = int(weight >= w_min)*weight + int(weight < w_min)*w_min %(syn_var)s += weight """ post = """ M -= A_minus weight = weight + w_max * P weight = int(weight > w_max)*w_max + int(weight <= w_max)*weight """ # for consistency with NEST, the synaptic variable is only updated on a pre-synaptic spike initial_conditions = {"M": 0.0, "P": 0.0} def __init__(self, timing_dependence=None, weight_dependence=None, voltage_dependence=None, dendritic_delay_fraction=1.0, weight=0.0, delay=None): if dendritic_delay_fraction != 0: raise ValueError("The pyNN.brian2 backend does not currently support " "dendritic delays: for the purpose of STDP calculations " "all delays are assumed to be axonal.") # could perhaps support axonal delays using parrot neurons? super(STDPMechanism, self).__init__(timing_dependence, weight_dependence, voltage_dependence, dendritic_delay_fraction, weight, delay) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def _set_target_type(self, weight_units): self.translations["weight"]["forward_transform"] = lambda **P: P["weight"] * weight_units self.translations["weight"]["reverse_transform"] = lambda **P: P["weight"] / weight_units self.weight_dependence._set_target_type(weight_units) class AdditiveWeightDependence(synapses.AdditiveWeightDependence): __doc__ = synapses.AdditiveWeightDependence.__doc__ translations = build_translations( ('w_max', 'w_max'), ('w_min', 'w_min'), ) def _set_target_type(self, weight_units): self.translations["w_max"]["forward_transform"] = lambda **P: P["w_max"] * weight_units self.translations["w_max"]["reverse_transform"] = lambda **P: P["w_max"] / weight_units self.translations["w_min"]["forward_transform"] = lambda **P: P["w_min"] * weight_units self.translations["w_min"]["reverse_transform"] = lambda **P: P["w_min"] / weight_units class MultiplicativeWeightDependence(synapses.MultiplicativeWeightDependence): __doc__ = synapses.MultiplicativeWeightDependence.__doc__ translations = build_translations( ('w_max', 'w_max'), ('w_min', 'w_min'), ) def _set_target_type(self, weight_units): self.translations["w_max"]["forward_transform"] = lambda **P: P["w_max"] * weight_units self.translations["w_max"]["reverse_transform"] = lambda **P: P["w_max"] / weight_units self.translations["w_min"]["forward_transform"] = lambda **P: P["w_min"] * weight_units self.translations["w_min"]["reverse_transform"] = lambda **P: P["w_min"] / weight_units class AdditivePotentiationMultiplicativeDepression(synapses.AdditivePotentiationMultiplicativeDepression): __doc__ = synapses.AdditivePotentiationMultiplicativeDepression.__doc__ translations = build_translations( ('w_max', 'w_max'), ('w_min', 'w_min'), ) def _set_target_type(self, weight_units): self.translations["w_max"]["forward_transform"] = lambda **P: P["w_max"] * weight_units self.translations["w_max"]["reverse_transform"] = lambda **P: P["w_max"] / weight_units self.translations["w_min"]["forward_transform"] = lambda **P: P["w_min"] * weight_units self.translations["w_min"]["reverse_transform"] = lambda **P: P["w_min"] / weight_units class GutigWeightDependence(synapses.GutigWeightDependence): __doc__ = synapses.GutigWeightDependence.__doc__ translations = build_translations( ('w_max', 'w_max'), ('w_min', 'w_min'), ('mu_plus', 'mu_plus'), ('mu_minus', 'mu_minus'), ) def _set_target_type(self, weight_units): self.translations["w_max"]["forward_transform"] = lambda **P: P["w_max"] * weight_units self.translations["w_max"]["reverse_transform"] = lambda **P: P["w_max"] / weight_units self.translations["w_min"]["forward_transform"] = lambda **P: P["w_min"] * weight_units self.translations["w_min"]["reverse_transform"] = lambda **P: P["w_min"] / weight_units class SpikePairRule(synapses.SpikePairRule): __doc__ = synapses.SpikePairRule.__doc__ translations = build_translations( ('A_plus', 'A_plus'), ('A_minus', 'A_minus'), ('tau_plus', 'tau_plus', lambda **P: P["tau_plus"] * ms, lambda **P: P["tau_plus"] / ms), ('tau_minus', 'tau_minus', lambda **P: P["tau_minus"] * ms, lambda **P: P["tau_minus"] / ms), ) PyNN-0.10.0/pyNN/common/000077500000000000000000000000001415343567000146005ustar00rootroot00000000000000PyNN-0.10.0/pyNN/common/__init__.py000066400000000000000000000025741415343567000167210ustar00rootroot00000000000000# encoding: utf-8 """ Defines a backend-independent, partial implementation of the PyNN API Backend simulator modules are not required to use any of the code herein, provided they provide the correct interface, but it is suggested that they use as much as is consistent with good performance (optimisations may require overriding some of the default definitions given here). Utility functions and classes: is_conductance() check_weight() Base classes to be sub-classed by individual backends: IDMixin Population PopulationView Assembly Projection Function-factories to generate backend-specific API functions: build_reset() build_state_queries() build_create() build_connect() build_record() Common implementation of API functions: set() initialize() Function skeletons to be extended by backends: setup() end() run() Global constants: DEFAULT_MAX_DELAY DEFAULT_TIMESTEP DEFAULT_MIN_DELAY :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from .populations import IDMixin, BasePopulation, Population, PopulationView, Assembly, is_conductance from .projections import Projection, Connection from .procedural_api import build_create, build_connect, set, build_record, initialize from .control import setup, end, build_run, build_reset, build_state_queries PyNN-0.10.0/pyNN/common/control.py000066400000000000000000000136671415343567000166470ustar00rootroot00000000000000# encoding: utf-8 """ Common implementation of functions for simulation set-up and control This module contains: * partial implementations of API functions which can be reused by backend-specific implementations (in some cases only the docstring is intended to be reused) * function factories for generating backend-specific API functions. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ DEFAULT_MAX_DELAY = 'auto' DEFAULT_TIMESTEP = 0.1 DEFAULT_MIN_DELAY = 'auto' assert 'simulator' not in locals() class BaseState(object): """Base class for simulator _State classes.""" def __init__(self): """Initialize the simulator.""" self.running = False self.t_start = 0 # a list of (population, variable, filename) combinations that should be written to file on end() self.write_on_end = [] self.recorders = set([]) def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): """ Initialises/reinitialises the simulator. Any existing network structure is destroyed. `timestep`, `min_delay` and `max_delay` should all be in milliseconds. `extra_params` contains any keyword arguments that are required by a given simulator but not by others. """ max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) invalid_extra_params = ('mindelay', 'maxdelay', 'dt', 'time_step') for param in invalid_extra_params: if param in extra_params: raise Exception("%s is not a valid argument for setup()" % param) if min_delay != 'auto': if max_delay != 'auto' and min_delay > max_delay: raise Exception("min_delay has to be less than or equal to max_delay.") if min_delay < timestep: raise Exception("min_delay (%g) must be greater than timestep (%g)" % (min_delay, timestep)) def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" raise NotImplementedError def build_run(simulator): def run_until(time_point, callbacks=None): """ Advance the simulation until `time_point` (in ms). `callbacks` is an optional list of callables, each of which should accept the current time as an argument, and return the next time it wishes to be called. ``run_until()`` and ``run()`` may be combined freely. See the documentation of the ``run()`` function for further information. """ now = simulator.state.t if time_point - now < -simulator.state.dt / 2.0: # allow for floating point error raise ValueError("Time %g is in the past (current time %g)" % (time_point, now)) if callbacks: callback_events = [(callback(simulator.state.t), callback) for callback in callbacks] while simulator.state.t + 1e-9 < time_point: callback_events.sort(key=lambda cbe: cbe[0], reverse=True) next, callback = callback_events.pop() # collapse multiple events that happen within the same timestep active_callbacks = [callback] while len(callback_events) > 0 and\ abs(next - callback_events[-1][0]) < simulator.state.dt: active_callbacks.append(callback_events.pop()[1]) next = min(next, time_point) simulator.state.run_until(next) callback_events.extend((callback(simulator.state.t), callback) for callback in active_callbacks) else: simulator.state.run_until(time_point) return simulator.state.t def run(simtime, callbacks=None): """ Advance the simulation by `simtime` ms. `callbacks` is an optional list of callables, each of which should accept the current time as an argument, and return the next time it wishes to be called. ``run()`` may be called multiple times during a simulation. In between calls to ``run()`` it is possible to retrieve data and modify neuron/synapse parameters. Some backends allow modification of the network structure. ``run(x + y)`` is equivalent to ``run(x)`` followed by ``run(y)``. If you wish to reset the simulation state to the initial conditions (time ``t = 0``), use the ``reset()`` function. """ return run_until(simulator.state.t + simtime, callbacks) return run, run_until def build_reset(simulator): def reset(annotations={}): """ Reset the time to zero, neuron membrane potentials and synaptic weights to their initial values, and begin a new Segment for recorded data. The network structure is not changed, nor are neuron/synapse parameters, nor the specification of which neurons to record from. """ for recorder in simulator.state.recorders: recorder.store_to_cache(annotations) simulator.state.reset() return reset def build_state_queries(simulator): def get_current_time(): """Return the current time in the simulation (in milliseconds).""" return simulator.state.t def get_time_step(): """Return the integration time step (in milliseconds).""" return simulator.state.dt def get_min_delay(): """Return the minimum allowed synaptic delay (in milliseconds).""" return simulator.state.min_delay def get_max_delay(): """Return the maximum allowed synaptic delay (in milliseconds).""" return simulator.state.max_delay def num_processes(): """Return the number of MPI processes.""" return simulator.state.num_processes def rank(): """Return the MPI rank of the current node.""" return simulator.state.mpi_rank return get_current_time, get_time_step, get_min_delay, get_max_delay, num_processes, rank PyNN-0.10.0/pyNN/common/populations.py000066400000000000000000001743571415343567000175500ustar00rootroot00000000000000# encoding: utf-8 """ Common implementation of ID, Population, PopulationView and Assembly classes. These base classes should be sub-classed by the backend-specific classes. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import logging import operator from itertools import chain from functools import reduce from collections import defaultdict from pyNN import random, recording, errors, standardmodels, core, space, descriptions from pyNN.models import BaseCellType from pyNN.parameters import ParameterSpace, LazyArray, simplify as simplify_parameter_array from pyNN.recording import files deprecated = core.deprecated logger = logging.getLogger("PyNN") def is_conductance(target_cell): """ Returns True if the target cell uses conductance-based synapses, False if it uses current-based synapses, and None if the synapse-basis cannot be determined. """ if hasattr(target_cell, 'local') and target_cell.local and hasattr(target_cell, 'celltype'): is_conductance = target_cell.celltype.conductance_based else: is_conductance = None return is_conductance class IDMixin(object): """ Instead of storing ids as integers, we store them as ID objects, which allows a syntax like: p[3,4].tau_m = 20.0 where p is a Population object. """ # Simulator ID classes should inherit both from the base type of the ID # (e.g., int or long) and from IDMixin. def __getattr__(self, name): if name == "parent": raise Exception("parent is not set") elif name == "set": errmsg = "For individual cells, set values using the parameter name directly, " \ "e.g. population[0].tau_m = 20.0, or use 'set' on a population view, " \ "e.g. population[0:1].set(tau_m=20.0)" raise AttributeError(errmsg) try: val = self.get_parameters()[name] except KeyError: raise errors.NonExistentParameterError(name, self.celltype.__class__.__name__, self.celltype.get_parameter_names()) return val def __setattr__(self, name, value): if name == "parent": object.__setattr__(self, name, value) elif self.celltype.has_parameter(name): self.set_parameters(**{name: value}) else: object.__setattr__(self, name, value) def set_parameters(self, **parameters): """ Set cell parameters, given as a sequence of parameter=value arguments. """ # if some of the parameters are computed from the values of other # parameters, need to get and translate all parameters if self.local: self.as_view().set(**parameters) else: raise errors.NotLocalError( "Cannot set parameters for a cell that does not exist on this node.") def get_parameters(self): """Return a dict of all cell parameters.""" if self.local: parameter_names = self.celltype.get_parameter_names() return dict((k, v) for k, v in zip(parameter_names, self.as_view().get(parameter_names))) else: raise errors.NotLocalError( "Cannot obtain parameters for a cell that does not exist on this node.") @property def celltype(self): return self.parent.celltype @property def is_standard_cell(self): return isinstance(self.celltype, standardmodels.StandardCellType) def _set_position(self, pos): """ Set the cell position in 3D space. Cell positions are stored in an array in the parent Population. """ assert isinstance(pos, (tuple, np.ndarray)) assert len(pos) == 3 self.parent._set_cell_position(self, pos) def _get_position(self): """ Return the cell position in 3D space. Cell positions are stored in an array in the parent Population, if any, or within the ID object otherwise. Positions are generated the first time they are requested and then cached. """ return self.parent._get_cell_position(self) position = property(_get_position, _set_position) @property def local(self): return self.parent.is_local(self) def inject(self, current_source): """Inject current from a current source object into the cell.""" current_source.inject_into([self]) def get_initial_value(self, variable): """Get the initial value of a state variable of the cell.""" return self.parent._get_cell_initial_value(self, variable) def set_initial_value(self, variable, value): """Set the initial value of a state variable of the cell.""" self.parent._set_cell_initial_value(self, variable, value) def as_view(self): """Return a PopulationView containing just this cell.""" index = self.parent.id_to_index(self) return self.parent[index:index + 1] class BasePopulation(object): _record_filter = None def __getitem__(self, index): """ Return either a single cell (ID object) from the Population, if `index` is an integer, or a subset of the cells (PopulationView object), if `index` is a slice or array. Note that __getitem__ is called when using [] access, e.g. p = Population(...) p[2] is equivalent to p.__getitem__(2). p[3:6] is equivalent to p.__getitem__(slice(3, 6)) """ if isinstance(index, (int, np.integer)): return self.all_cells[index] elif isinstance(index, (slice, list, np.ndarray)): return self._get_view(index) elif isinstance(index, tuple): return self._get_view(list(index)) else: raise TypeError( "indices must be integers, slices, lists, arrays or tuples, not %s" % type(index).__name__) def __len__(self): """Return the total number of cells in the population (all nodes).""" return self.size @property def local_size(self): """Return the number of cells in the population on the local MPI node""" return len(self.local_cells) # would self._mask_local.sum() be faster? def __iter__(self): """Iterator over cell ids on the local node.""" return iter(self.local_cells) @property def conductance_based(self): """ Indicates whether the post-synaptic response is modelled as a change in conductance or a change in current. """ return self.celltype.conductance_based @property def receptor_types(self): return self.celltype.receptor_types def is_local(self, id): """ Indicates whether the cell with the given ID exists on the local MPI node. """ assert id.parent is self index = self.id_to_index(id) return self._mask_local[index] def all(self): """Iterator over cell ids on all MPI nodes.""" return iter(self.all_cells) def __add__(self, other): """ A Population/PopulationView can be added to another Population, PopulationView or Assembly, returning an Assembly. """ assert isinstance(other, BasePopulation) return self._assembly_class(self, other) def _get_cell_position(self, id): index = self.id_to_index(id) return self.positions[:, index] def _set_cell_position(self, id, pos): index = self.id_to_index(id) self.positions[:, index] = pos @property def position_generator(self): # "generator" is a misleading name, has no yield statement def gen(i): return self.positions.T[i] return gen def _get_cell_initial_value(self, id, variable): if variable in self.initial_values: assert isinstance(self.initial_values[variable], LazyArray) index = self.id_to_local_index(id) return self.initial_values[variable][index] else: logger.warning( "Variable '{}' is not in initial values, returning 0.0".format(variable)) return 0.0 def _set_cell_initial_value(self, id, variable, value): assert isinstance(self.initial_values[variable], LazyArray) index = self.id_to_local_index(id) self.initial_values[variable][index] = value def nearest(self, position): """Return the neuron closest to the specified position.""" # doesn't always work correctly if a position is equidistant between # two neurons, i.e. 0.5 should be rounded up, but it isn't always. # also doesn't take account of periodic boundary conditions pos = np.array([position] * self.positions.shape[1]).transpose() dist_arr = (self.positions - pos)**2 distances = dist_arr.sum(axis=0) nearest = distances.argmin() return self[nearest] def sample(self, n, rng=None): """ Randomly sample `n` cells from the Population, and return a PopulationView object. """ assert isinstance(n, int) if not rng: rng = random.NumpyRNG() indices = rng.permutation(np.arange(len(self), dtype=np.int))[0:n] logger.debug("The %d cells selected have indices %s" % (n, indices)) logger.debug("%s.sample(%s)", self.label, n) return self._get_view(indices) def get(self, parameter_names, gather=False, simplify=True): """ Get the values of the given parameters for every local cell in the population, or, if gather=True, for all cells in the population. Values will be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). """ # if all the cells have the same value for a parameter, should # we return just the number, rather than an array? if isinstance(parameter_names, str): parameter_names = (parameter_names,) return_list = False else: return_list = True if isinstance(self.celltype, standardmodels.StandardCellType): if any(name in self.celltype.computed_parameters() for name in parameter_names): native_names = self.celltype.get_native_names() # need all parameters in order to calculate values else: native_names = self.celltype.get_native_names(*parameter_names) native_parameter_space = self._get_parameters(*native_names) parameter_space = self.celltype.reverse_translate(native_parameter_space) else: parameter_space = self._get_parameters(*parameter_names) # what if parameter space is homogeneous on some nodes but not on others? parameter_space.evaluate(simplify=simplify) # this also causes problems if the population size matches the number of MPI nodes parameters = dict(parameter_space.items()) if gather == True and self._simulator.state.num_processes > 1: # seems inefficient to do it in a loop - should do as single operation for name in parameter_names: values = parameters[name] if isinstance(values, np.ndarray): all_values = {self._simulator.state.mpi_rank: values.tolist()} local_indices = np.arange(self.size,)[self._mask_local].tolist() all_indices = {self._simulator.state.mpi_rank: local_indices} all_values = recording.gather_dict(all_values) all_indices = recording.gather_dict(all_indices) if self._simulator.state.mpi_rank == 0: values = reduce(operator.add, all_values.values()) indices = reduce(operator.add, all_indices.values()) idx = np.argsort(indices) values = np.array(values)[idx] parameters[name] = values try: values = [parameters[name] for name in parameter_names] except KeyError as err: raise errors.NonExistentParameterError("%s. Valid parameters for %s are: %s" % ( err, self.celltype, self.celltype.get_parameter_names())) if return_list: return values else: assert len(parameter_names) == 1 return values[0] def set(self, **parameters): """ Set one or more parameters for every cell in the population. Values passed to set() may be: (1) single values (2) RandomDistribution objects (3) lists/arrays of values of the same size as the population (4) mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single value. Here, a "single value" may be either a single number or a list/array of numbers (e.g. for spike times). Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Examples:: p.set(tau_m=20.0, v_rest=-65). p.set(spike_times=[0.3, 0.7, 0.9, 1.4]) p.set(cm=rand_distr, tau_m=lambda i: 10 + i/10.0) """ # TODO: add example using of function of (x,y,z) and Population.position_generator if self.local_size > 0: if (isinstance(self.celltype, standardmodels.StandardCellType) and any(name in self.celltype.computed_parameters() for name in parameters) and not isinstance(self.celltype, standardmodels.cells.SpikeSourceArray)): # the last condition above is a bit of hack to avoid calling expand() unecessarily # need to get existing parameter space of models so we can perform calculations native_names = self.celltype.get_native_names() parameter_space = self.celltype.reverse_translate( self._get_parameters(*native_names)) if self.local_size != self.size: parameter_space.expand((self.size,), self._mask_local) parameter_space.update(**parameters) else: parameter_space = ParameterSpace(parameters, self.celltype.get_schema(), (self.size,), self.celltype.__class__) if isinstance(self.celltype, standardmodels.StandardCellType): parameter_space = self.celltype.translate(parameter_space) assert parameter_space.shape == (self.size,), "{} != {}".format( parameter_space.shape, self.size) self._set_parameters(parameter_space) @deprecated("set(parametername=value_array)") def tset(self, parametername, value_array): """ 'Topographic' set. Set the value of parametername to the values in value_array, which must have the same dimensions as the Population. """ self.set(**{parametername: value_array}) @deprecated("set(parametername=rand_distr)") def rset(self, parametername, rand_distr): """ 'Random' set. Set the value of parametername to a value taken from rand_distr, which should be a RandomDistribution object. """ # Note that we generate enough random numbers for all cells on all nodes # but use only those relevant to this node. This ensures that the # sequence of random numbers does not depend on the number of nodes, # provided that the same rng with the same seed is used on each node. self.set(**{parametername: rand_distr}) def initialize(self, **initial_values): """ Set initial values of state variables, e.g. the membrane potential. Values passed to initialize() may be: (1) single numeric values (all neurons set to the same value) (2) RandomDistribution objects (3) lists/arrays of numbers of the same size as the population (4) mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single number. Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Examples:: p.initialize(v=-70.0) p.initialize(v=rand_distr, gsyn_exc=0.0) p.initialize(v=lambda i: -65 + i/10.0) """ for variable, value in initial_values.items(): logger.debug("In Population '%s', initialising %s to %s" % (self.label, variable, value)) initial_value = LazyArray(value, shape=(self.size,), dtype=float) self._set_initial_value_array(variable, initial_value) self.initial_values[variable] = initial_value def find_units(self, variable): """ Returns units of the specified variable or parameter, as a string. Works for all the recordable variables and neuron parameters of all standard models. """ return self.celltype.units[variable] def annotate(self, **annotations): self.annotations.update(annotations) def can_record(self, variable): """Determine whether `variable` can be recorded from this population.""" return self.celltype.can_record(variable) @property def injectable(self): return self.celltype.injectable def record(self, variables, to_file=None, sampling_interval=None): """ Record the specified variable or variables for all cells in the Population or view. `variables` may be either a single variable name or a list of variable names. For a given celltype class, `celltype.recordable` contains a list of variables that can be recorded for that celltype. If specified, `to_file` should be either a filename or a Neo IO instance and `write_data()` will be automatically called when `end()` is called. `sampling_interval` should be a value in milliseconds, and an integer multiple of the simulation timestep. """ if variables is None: # reset the list of things to record # note that if record(None) is called on a view of a population # recording will be reset for the entire population, not just the view self.recorder.reset() else: logger.debug("%s.record('%s')", self.label, variables) if self._record_filter is None: self.recorder.record(variables, self.all_cells, sampling_interval) else: self.recorder.record(variables, self._record_filter, sampling_interval) if isinstance(to_file, str): self.recorder.file = to_file self._simulator.state.write_on_end.append((self, variables, self.recorder.file)) @deprecated("record('v')") def record_v(self, to_file=True): """ Record the membrane potential for all cells in the Population. """ self.record('v', to_file) @deprecated("record(['gsyn_exc', 'gsyn_inh'])") def record_gsyn(self, to_file=True): """ Record synaptic conductances for all cells in the Population. """ self.record(['gsyn_exc', 'gsyn_inh'], to_file) def write_data(self, io, variables='all', gather=True, clear=False, annotations=None): """ Write recorded data to file, using one of the file formats supported by Neo. `io`: a Neo IO instance `variables`: either a single variable name or a list of variable names. Variables must have been previously recorded, otherwise an Exception will be raised. For parallel simulators, if `gather` is True, all data will be gathered to the master node and a single output file created there. Otherwise, a file will be written on each node, containing only data from the cells simulated on that node. If `clear` is True, recorded data will be deleted from the `Population`. `annotations` should be a dict containing simple data types such as numbers and strings. The contents will be written into the output data file as metadata. """ logger.debug("Population %s is writing %s to %s [gather=%s, clear=%s]" % ( self.label, variables, io, gather, clear)) self.recorder.write(variables, io, gather, self._record_filter, clear=clear, annotations=annotations) def get_data(self, variables='all', gather=True, clear=False): """ Return a Neo `Block` containing the data (spikes, state variables) recorded from the Population. `variables` - either a single variable name or a list of variable names Variables must have been previously recorded, otherwise an Exception will be raised. For parallel simulators, if `gather` is True, all data will be gathered to all nodes and the Neo `Block` will contain data from all nodes. Otherwise, the Neo `Block` will contain only data from the cells simulated on the local node. If `clear` is True, recorded data will be deleted from the `Population`. """ return self.recorder.get(variables, gather, self._record_filter, clear) @deprecated("write_data(file, 'spikes')") def printSpikes(self, file, gather=True, compatible_output=True): self.write_data(file, 'spikes', gather) @deprecated("get_data('spikes')") def getSpikes(self, gather=True, compatible_output=True): return self.get_data('spikes', gather) @deprecated("write_data(file, 'v')") def print_v(self, file, gather=True, compatible_output=True): self.write_data(file, 'v', gather) @deprecated("get_data('v')") def get_v(self, gather=True, compatible_output=True): return self.get_data('v', gather) @deprecated("write_data(file, ['gsyn_exc', 'gsyn_inh'])") def print_gsyn(self, file, gather=True, compatible_output=True): self.write_data(file, ['gsyn_exc', 'gsyn_inh'], gather) @deprecated("get_data(['gsyn_exc', 'gsyn_inh'])") def get_gsyn(self, gather=True, compatible_output=True): return self.get_data(['gsyn_exc', 'gsyn_inh'], gather) def get_spike_counts(self, gather=True): """ Returns a dict containing the number of spikes for each neuron. The dict keys are neuron IDs, not indices. """ # arguably, we should use indices return self.recorder.count('spikes', gather, self._record_filter) @deprecated("mean_spike_count()") def meanSpikeCount(self, gather=True): return self.mean_spike_count(gather) def mean_spike_count(self, gather=True): """ Returns the mean number of spikes per neuron. """ spike_counts = self.get_spike_counts(gather) total_spikes = sum(spike_counts.values()) if self._simulator.state.mpi_rank == 0 or not gather: # should maybe use allgather, and get the numbers on all nodes if len(spike_counts) > 0: return float(total_spikes) / len(spike_counts) else: return 0 else: return np.nan def inject(self, current_source): """ Connect a current source to all cells in the Population. """ if not self.celltype.injectable: raise TypeError("Can't inject current into a spike source.") current_source.inject_into(self) # name should be consistent with saving/writing data, i.e. save_data() and save_positions() or write_data() and write_positions() def save_positions(self, file): """ Save positions to file. The output format is ``index x y z`` """ if isinstance(file, str): file = recording.files.StandardTextFile(file, mode='w') cells = self.all_cells result = np.empty((len(cells), 4)) result[:, 0] = np.array([self.id_to_index(id) for id in cells]) result[:, 1:4] = self.positions.T if self._simulator.state.mpi_rank == 0: file.write(result, {'population': self.label}) file.close() class Population(BasePopulation): """ A group of neurons all of the same type. "Population" is used as a generic term intended to include layers, columns, nuclei, etc., of cells. Arguments: `size`: number of cells in the Population. For backwards-compatibility, `size` may also be a tuple giving the dimensions of a grid, e.g. ``size=(10,10)`` is equivalent to ``size=100`` with ``structure=Grid2D()``. `cellclass`: a cell type (a class inheriting from :class:`pyNN.models.BaseCellType`). `cellparams`: a dict, or other mapping, containing parameters, which is passed to the neuron model constructor. `structure`: a :class:`pyNN.space.Structure` instance, used to specify the positions of neurons in space. `initial_values`: a dict, or other mapping, containing initial values for the neuron state variables. `label`: a name for the population. One will be auto-generated if this is not supplied. """ _nPop = 0 def __init__(self, size, cellclass, cellparams=None, structure=None, initial_values={}, label=None): """ Create a population of neurons all of the same type. """ if not hasattr(self, "_simulator"): errmsg = "`common.Population` should not be instantiated directly. " \ "You should import Population from a PyNN backend module, " \ "e.g. pyNN.nest or pyNN.neuron" raise Exception(errmsg) if not isinstance(size, (int, np.integer)): # also allow a single integer, for a 1D population assert isinstance( size, tuple), "`size` must be an integer or a tuple of ints. You have supplied a %s" % type(size) # check the things inside are ints for e in size: assert isinstance( e, int), "`size` must be an integer or a tuple of ints. Element '%s' is not an int" % str(e) assert structure is None, "If you specify `size` as a tuple you may not specify structure." if len(size) == 1: structure = space.Line() elif len(size) == 2: nx, ny = size structure = space.Grid2D(nx / float(ny)) elif len(size) == 3: nx, ny, nz = size structure = space.Grid3D(nx / float(ny), nx / float(nz)) else: raise Exception( "A maximum of 3 dimensions is allowed. What do you think this is, string theory?") # NEST doesn't like np.int, so to be safe we cast to Python int size = int(reduce(operator.mul, size)) self.size = size self.label = label or 'population%d' % Population._nPop self._structure = structure or space.Line() self._positions = None self._is_sorted = True if isinstance(cellclass, BaseCellType): self.celltype = cellclass assert cellparams is None # cellparams being retained for backwards compatibility, but use is deprecated elif issubclass(cellclass, BaseCellType): self.celltype = cellclass(**cellparams) # emit deprecation warning else: raise TypeError( "cellclass must be an instance or subclass of BaseCellType, not a %s" % type(cellclass)) self.annotations = {} self.recorder = self._recorder_class(self) # Build the arrays of cell ids # Cells on the local node are represented as ID objects, other cells by integers # All are stored in a single numpy array for easy lookup by address # The local cells are also stored in a list, for easy iteration self._create_cells() self.first_id = self.all_cells[0] self.last_id = self.all_cells[-1] self.initial_values = {} all_initial_values = self.celltype.default_initial_values.copy() all_initial_values.update(initial_values) self.initialize(**all_initial_values) Population._nPop += 1 def __repr__(self): return "Population(%d, %r, structure=%r, label=%r)" % (self.size, self.celltype, self.structure, self.label) @property def local_cells(self): """ An array containing cell ids for the local node. """ return self.all_cells[self._mask_local] def id_to_index(self, id): """ Given the ID(s) of cell(s) in the Population, return its (their) index (order in the Population). >>> assert p.id_to_index(p[5]) == 5 """ if not np.iterable(id): if not self.first_id <= id <= self.last_id: raise ValueError("id should be in the range [%d,%d], actually %d" % ( self.first_id, self.last_id, id)) return int(id - self.first_id) # this assumes ids are consecutive else: if isinstance(id, PopulationView): id = id.all_cells id = np.array(id) if (self.first_id > id.min()) or (self.last_id < id.max()): raise ValueError("ids should be in the range [%d,%d], actually [%d, %d]" % ( self.first_id, self.last_id, id.min(), id.max())) return (id - self.first_id).astype(np.int) # this assumes ids are consecutive def id_to_local_index(self, id): """ Given the ID(s) of cell(s) in the Population, return its (their) index (order in the Population), counting only cells on the local MPI node. """ if self._simulator.state.num_processes > 1: return self.local_cells.tolist().index(id) # probably very slow # return np.nonzero(self.local_cells == id)[0][0] # possibly faster? # another idea - get global index, use idx-sum(mask_local[:idx])? else: return self.id_to_index(id) def _get_structure(self): """The spatial structure of the Population.""" return self._structure def _set_structure(self, structure): assert isinstance(structure, space.BaseStructure) if self._structure is None or structure != self._structure: self._positions = None # setting a new structure invalidates previously calculated positions self._structure = structure structure = property(fget=_get_structure, fset=_set_structure) # arguably structure should be read-only, i.e. it is not possible to change it after Population creation def _get_positions(self): """ Try to return self._positions. If it does not exist, create it and then return it. """ if self._positions is None: self._positions = self.structure.generate_positions(self.size) assert self._positions.shape == (3, self.size) return self._positions def _set_positions(self, pos_array): assert isinstance(pos_array, np.ndarray) assert pos_array.shape == (3, self.size), "%s != %s" % (pos_array.shape, (3, self.size)) self._positions = pos_array.copy() # take a copy in case pos_array is changed later self._structure = None # explicitly setting positions destroys any previous structure positions = property(_get_positions, _set_positions, doc="""A 3xN array (where N is the number of neurons in the Population) giving the x,y,z coordinates of all the neurons (soma, in the case of non-point models).""") def describe(self, template='population_default.txt', engine='default'): """ Returns a human-readable description of the population. The output may be customized by specifying a different template together with an associated template engine (see :mod:`pyNN.descriptions`). If template is None, then a dictionary containing the template context will be returned. """ context = { "label": self.label, "celltype": self.celltype.describe(template=None), "structure": None, "size": self.size, "size_local": len(self.local_cells), "first_id": self.first_id, "last_id": self.last_id, } context.update(self.annotations) if len(self.local_cells) > 0: first_id = self.local_cells[0] context.update({ "local_first_id": first_id, "cell_parameters": {} # first_id.get_parameters(), }) if self.structure: context["structure"] = self.structure.describe(template=None) return descriptions.render(engine, template, context) class PopulationView(BasePopulation): """ A view of a subset of neurons within a Population. In most ways, Populations and PopulationViews have the same behaviour, i.e. they can be recorded, connected with Projections, etc. It should be noted that any changes to neurons in a PopulationView will be reflected in the parent Population and vice versa. It is possible to have views of views. Arguments: selector: a slice or numpy mask array. The mask array should either be a boolean array of the same size as the parent, or an integer array containing cell indices, i.e. if p.size == 5:: PopulationView(p, array([False, False, True, False, True])) PopulationView(p, array([2,4])) PopulationView(p, slice(2,5,2)) will all create the same view. """ def __init__(self, parent, selector, label=None): """ Create a view of a subset of neurons within a parent Population or PopulationView. """ if not hasattr(self, "_simulator"): errmsg = "`common.PopulationView` should not be instantiated directly. " \ "You should import PopulationView from a PyNN backend module, " \ "e.g. pyNN.nest or pyNN.neuron" raise Exception(errmsg) self.parent = parent self.mask = selector # later we can have fancier selectors, for now we just have numpy masks # maybe just redefine __getattr__ instead of the following... self.celltype = self.parent.celltype # If the mask is a slice, IDs will be consecutives without duplication. # If not, then we need to remove duplicated IDs if not isinstance(self.mask, slice): if isinstance(self.mask, list): self.mask = np.array(self.mask) if self.mask.dtype is np.dtype('bool'): if len(self.mask) != len(self.parent): raise Exception("Boolean masks should have the size of Parent Population") self.mask = np.arange(len(self.parent))[self.mask] else: if len(np.unique(self.mask)) != len(self.mask): logging.warning( "PopulationView can contain only once each ID, duplicated IDs are removed") self.mask = np.unique(self.mask) self.mask.sort() # needed by NEST. Maybe emit a warning or exception if mask is not already ordered? self.all_cells = self.parent.all_cells[self.mask] idx = np.argsort(self.all_cells) self._is_sorted = np.all(idx == np.arange(len(self.all_cells))) self.size = len(self.all_cells) self.label = label or "view of '%s' with size %s" % (parent.label, self.size) self._mask_local = self.parent._mask_local[self.mask] self.local_cells = self.all_cells[self._mask_local] # only works if we assume all_cells is sorted, otherwise could use min() self.first_id = np.min(self.all_cells) self.last_id = np.max(self.all_cells) self.annotations = {} self.recorder = self.parent.recorder self._record_filter = self.all_cells def __repr__(self): return "PopulationView(parent=%r, selector=%r, label=%r)" % (self.parent, self.mask, self.label) @property def initial_values(self): # this is going to be complex - if we keep initial_values as a dict, # need to return a dict-like object that takes account of self.mask raise NotImplementedError @property def structure(self): """The spatial structure of the parent Population.""" return self.parent.structure # should we allow setting structure for a PopulationView? Maybe if the # parent has some kind of CompositeStructure? @property def positions(self): # make positions N,3 instead of 3,N to avoid all this transposing? return self.parent.positions.T[self.mask].T def id_to_index(self, id): """ Given the ID(s) of cell(s) in the PopulationView, return its/their index/indices (order in the PopulationView). >>> assert pv.id_to_index(pv[3]) == 3 """ if not np.iterable(id): if self._is_sorted: if id not in self.all_cells: raise IndexError("ID %s not present in the View" % id) return np.searchsorted(self.all_cells, id) else: result = np.where(self.all_cells == id)[0] if len(result) == 0: raise IndexError("ID %s not present in the View" % id) else: return result else: if self._is_sorted: return np.searchsorted(self.all_cells, id) else: result = np.array([], dtype=np.int) for item in id: data = np.where(self.all_cells == item)[0] if len(data) == 0: raise IndexError("ID %s not present in the View" % item) elif len(data) > 1: raise Exception("ID %s is duplicated in the View" % item) else: result = np.append(result, data) return result @property def grandparent(self): """ Returns the parent Population at the root of the tree (since the immediate parent may itself be a PopulationView). The name "grandparent" is of course a little misleading, as it could be just the parent, or the great, great, great, ..., grandparent. """ if hasattr(self.parent, "parent"): return self.parent.grandparent else: return self.parent def index_in_grandparent(self, indices): """ Given an array of indices, return the indices in the parent population at the root of the tree. """ indices_in_parent = np.arange(self.parent.size)[self.mask][indices] if hasattr(self.parent, "parent"): return self.parent.index_in_grandparent(indices_in_parent) else: return indices_in_parent def index_from_parent_index(self, indices): """ Given an index(indices) in the parent population, return the index(indices) within this view. """ # todo: add check that all indices correspond to cells that are in this view if isinstance(self.mask, slice): start = self.mask.start or 0 step = self.mask.step or 1 return (indices - start) / step else: if isinstance(indices, int): return np.nonzero(self.mask == indices)[0][0] elif isinstance(indices, np.ndarray): # Lots of ways to do this. Some profiling is in order. # - https://stackoverflow.com/questions/16992713/translate-every-element-in-numpy-array-according-to-key # - https://stackoverflow.com/questions/3403973/fast-replacement-of-values-in-a-numpy-array # - https://stackoverflow.com/questions/13572448/replace-values-of-a-numpy-index-array-with-values-of-a-list parent_indices = self.mask # assert mask is sorted view_indices = np.arange(self.size) index = np.digitize(indices, parent_indices, right=True) return view_indices[index] else: raise ValueError("indices must be an integer or an array of integers") def __eq__(self, other): """ Determine whether two views are the same. """ return not self.__ne__(other) def __ne__(self, other): """ Determine whether two views are different. """ # We can't use the self.mask, as different masks can select the same cells # (e.g. slices vs arrays), therefore we have to use self.all_cells if isinstance(other, PopulationView): return self.parent != other.parent or not np.array_equal(self.all_cells, other.all_cells) elif isinstance(other, Population): return self.parent != other or not np.array_equal(self.all_cells, other.all_cells) else: return True def describe(self, template='populationview_default.txt', engine='default'): """ Returns a human-readable description of the population view. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = {"label": self.label, "parent": self.parent.label, "mask": self.mask, "size": self.size} context.update(self.annotations) return descriptions.render(engine, template, context) class Assembly(object): """ A group of neurons, may be heterogeneous, in contrast to a Population where all the neurons are of the same type. Arguments: populations: Populations or PopulationViews kwargs: May contain a keyword argument 'label' """ _count = 0 def __init__(self, *populations, **kwargs): """ Create an Assembly of Populations and/or PopulationViews. """ if not hasattr(self, "_simulator"): errmsg = "`common.Assembly` should not be instantiated directly. " \ "You should import Assembly from a PyNN backend module, " \ "e.g. pyNN.nest or pyNN.neuron" raise Exception(errmsg) if kwargs: assert list(kwargs.keys()) == ['label'] self.populations = [] for p in populations: self._insert(p) self.label = kwargs.get('label', 'assembly%d' % Assembly._count) assert isinstance(self.label, str), "label must be a string" self.annotations = {} Assembly._count += 1 def __repr__(self): return "Assembly(*%r, label=%r)" % (self.populations, self.label) def _insert(self, element): if not isinstance(element, BasePopulation): raise TypeError("argument is a %s, not a Population." % type(element).__name__) if isinstance(element, PopulationView): if not element.parent in self.populations: double = False for p in self.populations: data = np.concatenate((p.all_cells, element.all_cells)) if len(np.unique(data)) != len(p.all_cells) + len(element.all_cells): logging.warning( 'Adding a PopulationView to an Assembly containing elements already present is not posible') double = True # Should we automatically remove duplicated IDs ? break if not double: self.populations.append(element) else: logging.warning( 'Adding a PopulationView to an Assembly when parent Population is there is not possible') elif isinstance(element, BasePopulation): if not element in self.populations: self.populations.append(element) else: logging.warning('Adding a Population twice in an Assembly is not possible') @property def local_cells(self): result = self.populations[0].local_cells for p in self.populations[1:]: result = np.concatenate((result, p.local_cells)) return result @property def all_cells(self): result = self.populations[0].all_cells for p in self.populations[1:]: result = np.concatenate((result, p.all_cells)) return result def all(self): """Iterator over cell ids on all nodes.""" return iter(self.all_cells) @property def _is_sorted(self): idx = np.argsort(self.all_cells) return np.all(idx == np.arange(len(self.all_cells))) @property def _homogeneous_synapses(self): cb = [p.celltype.conductance_based for p in self.populations] return all(cb) or not any(cb) @property def conductance_based(self): """ `True` if the post-synaptic response is modelled as a change in conductance, `False` if a change in current. """ return all(p.celltype.conductance_based for p in self.populations) @property def receptor_types(self): """ Return a list of receptor types that are common to all populations within the assembly. """ rts = self.populations[0].celltype.receptor_types if len(self.populations) > 1: rts = set(rts) for p in self.populations[1:]: rts = rts.intersection(set(p.celltype.receptor_types)) return list(rts) def find_units(self, variable): """ Returns units of the specified variable or parameter, as a string. Works for all the recordable variables and neuron parameters of all standard models. """ units = set(p.find_units(variable) for p in self.populations) if len(units) > 1: raise ValueError("Inconsistent units") return units @property def _mask_local(self): result = self.populations[0]._mask_local for p in self.populations[1:]: result = np.concatenate((result, p._mask_local)) return result @property def first_id(self): return np.min(self.all_cells) @property def last_id(self): return np.max(self.all_cells) def id_to_index(self, id): """ Given the ID(s) of cell(s) in the Assembly, return its (their) index (order in the Assembly):: >>> assert p.id_to_index(p[5]) == 5 >>> assert p.id_to_index(p.index([1, 2, 3])) == [1, 2, 3] """ all_cells = self.all_cells if not np.iterable(id): if self._is_sorted: return np.searchsorted(all_cells, id) else: result = np.where(all_cells == id)[0] if len(result) == 0: raise IndexError("ID %s not present in the View" % id) else: return result else: if self._is_sorted: return np.searchsorted(all_cells, id) else: result = np.array([], dtype=np.int) for item in id: data = np.where(all_cells == item)[0] if len(data) == 0: raise IndexError("ID %s not present in the Assembly" % item) elif len(data) > 1: raise Exception("ID %s is duplicated in the Assembly" % item) else: result = np.append(result, data) return result @property def positions(self): result = self.populations[0].positions for p in self.populations[1:]: result = np.hstack((result, p.positions)) return result @property def size(self): return sum(p.size for p in self.populations) def __iter__(self): """ Iterator over cells in all populations within the Assembly, for cells on the local MPI node. """ iterators = [iter(p) for p in self.populations] return chain(*iterators) def __len__(self): """Return the total number of cells in the population (all nodes).""" return self.size def __getitem__(self, index): """ Where `index` is an integer, return an ID. Where `index` is a slice, tuple, list or numpy array, return a new Assembly consisting of appropriate populations and (possibly newly created) population views. """ count = 0 boundaries = [0] for p in self.populations: count += p.size boundaries.append(count) boundaries = np.array(boundaries, dtype=int) if isinstance(index, (int, np.integer)): # return an ID pindex = boundaries[1:].searchsorted(index, side='right') return self.populations[pindex][index - boundaries[pindex]] elif isinstance(index, (slice, tuple, list, np.ndarray)): if isinstance(index, slice) or (hasattr(index, "dtype") and index.dtype == bool): indices = np.arange(self.size)[index] else: indices = np.array(index) pindices = boundaries[1:].searchsorted(indices, side='right') views = [self.populations[i][indices[pindices == i] - boundaries[i]] for i in np.unique(pindices)] return self.__class__(*views) else: raise TypeError("indices must be integers, slices, lists, arrays, not %s" % type(index).__name__) def __add__(self, other): """ An Assembly may be added to a Population, PopulationView or Assembly with the '+' operator, returning a new Assembly, e.g.:: a2 = a1 + p """ if isinstance(other, BasePopulation): return self.__class__(*(self.populations + [other])) elif isinstance(other, Assembly): return self.__class__(*(self.populations + other.populations)) else: raise TypeError("can only add a Population or another Assembly to an Assembly") def __iadd__(self, other): """ A Population, PopulationView or Assembly may be added to an existing Assembly using the '+=' operator, e.g.:: a += p """ if isinstance(other, BasePopulation): self._insert(other) elif isinstance(other, Assembly): for p in other.populations: self._insert(p) else: raise TypeError("can only add a Population or another Assembly to an Assembly") return self def sample(self, n, rng=None): """ Randomly sample `n` cells from the Assembly, and return a Assembly object. """ assert isinstance(n, int) if not rng: rng = random.NumpyRNG() indices = rng.permutation(np.arange(len(self), dtype=np.int))[0:n] logger.debug("The %d cells recorded have indices %s" % (n, indices)) logger.debug("%s.sample(%s)", self.label, n) return self[indices] def initialize(self, **initial_values): """ Set the initial values of the state variables of the neurons in this assembly. """ for p in self.populations: p.initialize(**initial_values) def get(self, parameter_names, gather=False, simplify=True): """ Get the values of the given parameters for every local cell in the Assembly, or, if gather=True, for all cells in the Assembly. """ if isinstance(parameter_names, str): parameter_names = (parameter_names,) return_list = False else: return_list = True parameters = defaultdict(list) for p in self.populations: population_values = p.get(parameter_names, gather, simplify=False) for name, arr in zip(parameter_names, population_values): parameters[name].append(arr) for name, value_list in parameters.items(): parameters[name] = np.hstack(value_list) if simplify: parameters[name] = simplify_parameter_array(parameters[name]) values = [parameters[name] for name in parameter_names] if return_list: return values else: assert len(parameter_names) == 1 return values[0] def set(self, **parameters): """ Set one or more parameters for every cell in the Assembly. Values passed to set() may be: (1) single values (2) RandomDistribution objects (3) mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single value. Here, a "single value" may be either a single number or a list/array of numbers (e.g. for spike times). """ for p in self.populations: p.set(**parameters) @deprecated("set(parametername=rand_distr)") def rset(self, parametername, rand_distr): self.set(parametername=rand_distr) def record(self, variables, to_file=None, sampling_interval=None): """ Record the specified variable or variables for all cells in the Assembly. `variables` may be either a single variable name or a list of variable names. For a given celltype class, `celltype.recordable` contains a list of variables that can be recorded for that celltype. If specified, `to_file` should be either a filename or a Neo IO instance and `write_data()` will be automatically called when `end()` is called. """ for p in self.populations: p.record(variables, to_file, sampling_interval) @deprecated("record('v')") def record_v(self, to_file=True): """Record the membrane potential from all cells in the Assembly.""" self.record('v', to_file) @deprecated("record(['gsyn_exc', 'gsyn_inh'])") def record_gsyn(self, to_file=True): """Record synaptic conductances from all cells in the Assembly.""" self.record(['gsyn_exc', 'gsyn_inh'], to_file) def get_population(self, label): """ Return the Population/PopulationView from within the Assembly that has the given label. If no such Population exists, raise KeyError. """ for p in self.populations: if label == p.label: return p raise KeyError("Assembly does not contain a population with the label %s" % label) def save_positions(self, file): """ Save positions to file. The output format is id x y z """ if isinstance(file, str): file = files.StandardTextFile(file, mode='w') cells = self.all_cells result = np.empty((len(cells), 4)) result[:, 0] = np.array([self.id_to_index(id) for id in cells]) result[:, 1:4] = self.positions.T if self._simulator.state.mpi_rank == 0: file.write(result, {'assembly': self.label}) file.close() @property def position_generator(self): def gen(i): return self.positions[:, i] return gen def get_data(self, variables='all', gather=True, clear=False, annotations=None): """ Return a Neo `Block` containing the data (spikes, state variables) recorded from the Assembly. `variables` - either a single variable name or a list of variable names Variables must have been previously recorded, otherwise an Exception will be raised. For parallel simulators, if `gather` is True, all data will be gathered to all nodes and the Neo `Block` will contain data from all nodes. Otherwise, the Neo `Block` will contain only data from the cells simulated on the local node. If `clear` is True, recorded data will be deleted from the `Assembly`. """ name = self.label description = self.describe() blocks = [p.get_data(variables, gather, clear) for p in self.populations] # adjust channel_ids to match assembly channel indices offset = 0 for block, p in zip(blocks, self.populations): for segment in block.segments: for signal_array in segment.analogsignals: signal_array.array_annotations["channel_index"] += offset offset += p.size for i, block in enumerate(blocks): logger.debug("%d: %s", i, block.name) for j, segment in enumerate(block.segments): logger.debug(" %d: %s", j, segment.name) for arr in segment.analogsignals: logger.debug(" %s %s", arr.shape, arr.name) merged_block = blocks[0] for block in blocks[1:]: merged_block.merge(block) merged_block.name = name merged_block.description = description if annotations: merged_block.annotate(**annotations) return merged_block @deprecated("get_data('spikes')") def getSpikes(self, gather=True, compatible_output=True): return self.get_data('spikes', gather) @deprecated("get_data('v')") def get_v(self, gather=True, compatible_output=True): return self.get_data('v', gather) @deprecated("get_data(['gsyn_exc', 'gsyn_inh'])") def get_gsyn(self, gather=True, compatible_output=True): return self.get_data(['gsyn_exc', 'gsyn_inh'], gather) def mean_spike_count(self, gather=True): """ Returns the mean number of spikes per neuron. """ spike_counts = self.get_spike_counts() total_spikes = sum(spike_counts.values()) if self._simulator.state.mpi_rank == 0 or not gather: # should maybe use allgather, and get the numbers on all nodes return float(total_spikes) / len(spike_counts) else: return np.nan def get_spike_counts(self, gather=True): """ Returns the number of spikes for each neuron. """ try: spike_counts = self.populations[0].recorder.count( 'spikes', gather, self.populations[0]._record_filter) except errors.NothingToWriteError: spike_counts = {} for p in self.populations[1:]: try: spike_counts.update(p.recorder.count('spikes', gather, p._record_filter)) except errors.NothingToWriteError: pass return spike_counts def write_data(self, io, variables='all', gather=True, clear=False, annotations=None): """ Write recorded data to file, using one of the file formats supported by Neo. `io`: a Neo IO instance `variables`: either a single variable name or a list of variable names. Variables must have been previously recorded, otherwise an Exception will be raised. For parallel simulators, if `gather` is True, all data will be gathered to the master node and a single output file created there. Otherwise, a file will be written on each node, containing only data from the cells simulated on that node. If `clear` is True, recorded data will be deleted from the `Population`. """ if isinstance(io, str): io = recording.get_io(io) if gather is False and self._simulator.state.num_processes > 1: io.filename += '.%d' % self._simulator.state.mpi_rank logger.debug("Recorder is writing '%s' to file '%s' with gather=%s" % ( variables, io.filename, gather)) data = self.get_data(variables, gather, clear, annotations) if self._simulator.state.mpi_rank == 0 or gather is False: logger.debug("Writing data to file %s" % io) io.write(data) @deprecated("write_data(file, 'spikes')") def printSpikes(self, file, gather=True, compatible_output=True): self.write_data(file, 'spikes', gather) @deprecated("write_data(file, 'v')") def print_v(self, file, gather=True, compatible_output=True): self.write_data(file, 'v', gather) @deprecated("write_data(['gsyn_exc', 'gsyn_inh'])") def print_gsyn(self, file, gather=True, compatible_output=True): self.write_data(file, ['gsyn_exc', 'gsyn_inh'], gather) def inject(self, current_source): """ Connect a current source to all cells in the Assembly. """ for p in self.populations: current_source.inject_into(p) @property def injectable(self): return all(p.injectable for p in self.populations) def describe(self, template='assembly_default.txt', engine='default'): """ Returns a human-readable description of the assembly. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = {"label": self.label, "populations": [p.describe(template=None) for p in self.populations]} return descriptions.render(engine, template, context) def get_annotations(self, annotation_keys, simplify=True): """ Get the values of the given annotations for each population in the Assembly. """ if isinstance(annotation_keys, str): annotation_keys = (annotation_keys,) annotations = defaultdict(list) for key in annotation_keys: is_array_annotation = False for p in self.populations: annotation = p.annotations[key] annotations[key].append(annotation) is_array_annotation = isinstance(annotation, np.ndarray) if is_array_annotation: annotations[key] = np.hstack(annotations[key]) if simplify: annotations[key] = simplify_parameter_array(np.array(annotations[key])) return annotations PyNN-0.10.0/pyNN/common/procedural_api.py000066400000000000000000000101011415343567000201340ustar00rootroot00000000000000# encoding: utf-8 """ Alternative, procedural API for creating, connecting and recording from individual neurons :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from .populations import IDMixin, BasePopulation, Assembly def build_create(population_class): def create(cellclass, cellparams=None, n=1): """ Create `n` cells all of the same type. Returns a Population object. """ return population_class(n, cellclass, cellparams=cellparams) return create def build_connect(projection_class, connector_class, static_synapse_class): def connect(pre, post, weight=0.0, delay=None, receptor_type=None, p=1, rng=None): """ Connect a source of spikes to a synaptic target. `source` and `target` can both be individual cells or populations/ assemblies of cells, in which case all possible connections are made with probability `p`, using either the random number generator supplied, or the default RNG otherwise. Weights should be in nA or µS. """ if isinstance(pre, IDMixin): pre = pre.as_view() if isinstance(post, IDMixin): post = post.as_view() connector = connector_class(p_connect=p, rng=rng) synapse = static_synapse_class(weight=weight, delay=delay) return projection_class(pre, post, connector, receptor_type=receptor_type, synapse_type=synapse) return connect def set(cells, **parameters): """ Set one or more parameters for every cell in a population, view or assembly. Values passed to set() may be: (1) single values (2) RandomDistribution objects (3) mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single value. Here, a "single value" may be either a single number or a list/array of numbers (e.g. for spike times). Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). """ if not isinstance(cells, (BasePopulation, Assembly)): errmsg = "For individual cells, set values using the parameter name directly, " \ "e.g. population[0].tau_m = 20.0, or use 'set' on a population view, " \ "e.g. set(population[0:1], tau_m=20.0)" raise AttributeError(errmsg) cells.set(**parameters) def build_record(simulator): def record(variables, source, filename, sampling_interval=None, annotations=None): """ Record variables to a file. source can be an individual cell, a Population, PopulationView or Assembly. """ # would actually like to be able to record to an array and choose later # whether to write to a file. if not isinstance(source, (BasePopulation, Assembly)): if isinstance(source, (IDMixin)): source = source.as_view() source.record(variables, to_file=filename, sampling_interval=sampling_interval) if annotations: source.annotate(**annotations) return record def initialize(cells, **initial_values): """ Set initial values of state variables, e.g. the membrane potential. Values passed to initialize() may be: (1) single numeric values (all neurons set to the same value) (2) RandomDistribution objects (3) lists/arrays of numbers of the same size as the population (4) mapping functions, where a mapping function accepts a single argument (the cell index) and returns a single number. Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Examples:: initialize(cells, v=-70.0) initialize(cells, v=rand_distr, gsyn_exc=0.0) initialize(cells, v=lambda i: -65 + i/10.0) """ assert isinstance(cells, (BasePopulation, Assembly)), type(cells) cells.initialize(**initial_values) PyNN-0.10.0/pyNN/common/projections.py000066400000000000000000000560211415343567000175150ustar00rootroot00000000000000# encoding: utf-8 """ Common implementation of the Projection class, to be sub-classed by backend-specific Projection classes. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from functools import reduce import numpy as np import logging import operator from copy import deepcopy from warnings import warn from pyNN import recording, errors, models, core, descriptions from pyNN.parameters import ParameterSpace, LazyArray from pyNN.space import Space from pyNN.standardmodels import StandardSynapseType from pyNN.connectors import Connector from .populations import BasePopulation, Assembly logger = logging.getLogger("PyNN") deprecated = core.deprecated class Projection(object): """ A container for all the connections of a given type (same synapse type and plasticity mechanisms) between two populations, together with methods to set the parameters of those connections, including the parameters of plasticity mechanisms. Arguments: `presynaptic_neurons` and `postsynaptic_neurons`: Population, PopulationView or Assembly objects. `source`: string specifying which attribute of the presynaptic cell signals action potentials. This is only needed for multicompartmental cells with branching axons or dendrodendritic synapses. All standard cells have a single source, and this is the default. `receptor_type`: string specifying which synaptic receptor_type type on the postsynaptic cell to connect to. For standard cells, this can be 'excitatory' or 'inhibitory'. For non-standard cells, it could be 'NMDA', etc. If receptor_type is not given, the default values of 'excitatory' is used. `connector`: a Connector object, encapsulating the algorithm to use for connecting the neurons. `synapse_type`: a SynapseType object specifying which synaptic connection mechanisms to use. `space`: TO DOCUMENT """ _nProj = 0 MULTI_SYNAPSE_OPERATIONS = { 'last': lambda a, b: b, 'first': lambda a, b: a, 'sum': operator.iadd, 'min': min, 'max': max } def __init__(self, presynaptic_neurons, postsynaptic_neurons, connector, synapse_type=None, source=None, receptor_type=None, space=Space(), label=None): """ Create a new projection, connecting the pre- and post-synaptic neurons. """ if not hasattr(self, "_simulator"): errmsg = "`common.Projection` should not be instantiated directly. " \ "You should import Projection from a PyNN backend module, " \ "e.g. pyNN.nest or pyNN.neuron" raise Exception(errmsg) for prefix, pop in zip(("pre", "post"), (presynaptic_neurons, postsynaptic_neurons)): if not isinstance(pop, (BasePopulation, Assembly)): raise errors.ConnectionError( "%ssynaptic_neurons must be a Population, PopulationView or Assembly, not a %s" % (prefix, type(pop))) if isinstance(postsynaptic_neurons, Assembly): if not postsynaptic_neurons._homogeneous_synapses: raise errors.ConnectionError( 'Projection to an Assembly object can be made only with homogeneous synapses types') self.pre = presynaptic_neurons # } these really self.source = source # } should be self.post = postsynaptic_neurons # } read-only self.label = label self.space = space if not isinstance(connector, Connector): raise TypeError( "The connector argument should be an instance of a subclass of Connector. " f"The argument provided was of type '{type(connector).__name__}'." ) self._connector = connector self.synapse_type = synapse_type or self._static_synapse_class() if not isinstance(self.synapse_type, models.BaseSynapseType): raise TypeError( "The synapse_type argument should be an instance of a subclass of BaseSynapseType. " f"The argument provided was of type '{type(synapse_type).__name__}'" ) self.receptor_type = receptor_type if self.receptor_type in ("default", None): self._guess_receptor_type() if self.receptor_type not in postsynaptic_neurons.receptor_types: valid_types = postsynaptic_neurons.receptor_types assert len(valid_types) > 0 errmsg = "User gave receptor_types=%s, receptor_types must be one of: '%s'" raise errors.ConnectionError(errmsg % (self.receptor_type, "', '".join(valid_types))) if label is None: if self.pre.label and self.post.label: self.label = u"%s→%s" % (self.pre.label, self.post.label) self.initial_values = {} self.annotations = {} Projection._nProj += 1 def _guess_receptor_type(self): """ If the receptor_type is not specified, we follow the convention that the first element in the list of available post-synaptic receptor types is the default for excitatory synapses and the second element is the default for inhibitory synapses. """ if len(self.post.receptor_types) > 1: ps = deepcopy(self.synapse_type.parameter_space) ps = self._handle_distance_expressions(ps) weights = ps["weight"] if weights.shape is None: weights.shape = self.shape try: wl = weights[self.pre.size - 1, self.post.size - 1] if wl >= 0: self.receptor_type = self.post.receptor_types[0] else: self.receptor_type = self.post.receptor_types[1] except TypeError: # for example, if using a native RNG with no Python interface warn("Unable to guess receptor type") self.receptor_type = self.post.receptor_types[0] else: self.receptor_type = self.post.receptor_types[0] def __len__(self): """Return the total number of local connections.""" raise NotImplementedError def size(self, gather=True): """ Return the total number of connections. - only local connections, if gather is False, - all connections, if gather is True (default) """ if gather and self._simulator.state.num_processes > 1: n = len(self) return recording.mpi_sum(n) else: return len(self) @property def shape(self): return (self.pre.size, self.post.size) def __repr__(self): return 'Projection("%s")' % self.label def __getitem__(self, i): """Return the *i*th connection within the Projection.""" raise NotImplementedError def __iter__(self): """Return an iterator over all connections on the local MPI node.""" for i in range(len(self)): yield self[i] # --- Methods for setting connection parameters --------------------------- def set(self, **attributes): """ Set connection attributes for all connections on the local MPI node. Attribute names may be 'weight', 'delay', or the name of any parameter of a synapse dynamics model (e.g. 'U' for TsodyksMarkramSynapse). Each attribute value may be: (1) a single number (2) a RandomDistribution object (3) a 2D array with the same dimensions as the connectivity matrix (as returned by `get(format='array')` (4) a mapping function, which accepts a single float argument (the distance between pre- and post-synaptic cells) and returns a single value. Weights should be in nA for current-based and µS for conductance-based synapses. Delays should be in milliseconds. Note that where a projection contains multiple connections between a given pair of neurons, all these connections will be set to the same value. """ # should perhaps add a "distribute" argument, for symmetry with "gather" in get() # Note: we have removed the option: # "a list/1D array of the same length as the number of local connections" # because it was proving tricky to implement and was holding up the release. # The plan is to add this option back at a later date. attributes = self._value_list_to_array(attributes) parameter_space = ParameterSpace(attributes, self.synapse_type.get_schema(), (self.pre.size, self.post.size)) parameter_space = self._handle_distance_expressions(parameter_space) if isinstance(self.synapse_type, StandardSynapseType): parameter_space = self.synapse_type.translate(parameter_space) self._set_attributes(parameter_space) def initialize(self, **initial_values): """ Set initial values of state variables of synaptic plasticity models. Values passed to initialize() may be: (1) single numeric values (all neurons set to the same value) (2) RandomDistribution objects (3) a 2D array with the same dimensions as the connectivity matrix (as returned by `get(format='array')` (4) a mapping function, which accepts a single float argument (the distance between pre- and post-synaptic cells) and returns a single value. Values should be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). Example:: prj.initialize(u=-70.0) """ for variable, value in initial_values.items(): logger.debug("In Projection '%s', initialising %s to %s" % (self.label, variable, value)) initial_value = LazyArray(value, shape=(self.size,), dtype=float) self._set_initial_value_array(variable, initial_value) self.initial_values[variable] = initial_value def _value_list_to_array(self, attributes): """Convert a list of connection parameters/attributes to a 2D array.""" connection_mask = ~np.isnan(self.get('weight', format='array', gather='all')) for name, value in attributes.items(): if isinstance(value, list) or (isinstance(value, np.ndarray) and value.ndim == 1): array_value = np.nan * np.ones(self.shape) array_value[connection_mask] = value attributes[name] = array_value return attributes def _handle_distance_expressions(self, parameter_space): # also index-based expressions for name, map in parameter_space.items(): if callable(map.base_value): if isinstance(map.base_value, core.IndexBasedExpression): map.base_value.projection = self parameter_space[name] = map else: # Assumes map is a function of distance position_generators = (self.pre.position_generator, self.post.position_generator) distance_map = LazyArray(self.space.distance_generator(*position_generators), shape=self.shape) parameter_space[name] = map(distance_map) return parameter_space @deprecated("set(weight=w)") def setWeights(self, w): self.set(weight=w) @deprecated("set(weight=rand_distr)") def randomizeWeights(self, rand_distr): self.set(weight=rand_distr) @deprecated("set(delay=d)") def setDelays(self, d): self.set(delay=d) @deprecated("set(delay=rand_distr)") def randomizeDelays(self, rand_distr): self.set(delay=rand_distr) @deprecated("set(parameter_name=value)") def setSynapseDynamics(self, parameter_name, value): self.set(parameter_name=value) @deprecated("set(name=rand_distr)") def randomizeSynapseDynamics(self, parameter_name, rand_distr): self.set(parameter_name=rand_distr) # --- Methods for writing/reading information to/from file. --------------- def get(self, attribute_names, format, gather=True, with_address=True, multiple_synapses='sum'): """ Get the values of a given attribute (weight or delay) for all connections in this Projection. `attribute_names`: name of the attributes whose values are wanted, or a list of such names. `format`: "list" or "array". `gather`: If True, node 0 gets connection information from all MPI nodes, other nodes get information only from connections that exist in this node. If 'all', all nodes will receive connection information from all other nodes. If False, all nodes get only information about local connections. With list format, returns a list of tuples. By default, each tuple contains the indices of the pre- and post-synaptic cell followed by the attribute values in the order given in `attribute_names`. Example:: >>> prj.get(["weight", "delay"], format="list")[:5] [(0.0, 0.0, 0.3401892507507171, 0.1), (0.0, 1.0, 0.7990713166233654, 0.30000000000000004), (0.0, 2.0, 0.6180841812877726, 0.5), (0.0, 3.0, 0.6758149775627305, 0.7000000000000001), (0.0, 4.0, 0.7166906726862953, 0.9)] If `with_address` is set to False, then the tuples will contain only the attribute values, not the cell indices. With array format, returns a tuple of 2D NumPy arrays, one for each name in `attribute_names`. The array element X_ij contains the attribute value for the connection from the ith neuron in the pre- synaptic Population to the jth neuron in the post-synaptic Population, if a single such connection exists. If there are no such connections, X_ij will be NaN. Example:: >>> weights, delays = prj.get(["weight", "delay"], format="array") >>> weights array([[ 0.66210438, nan, 0.10744555, 0.54557088], [ 0.3676134 , nan, 0.41463193, nan], [ 0.57434871, 0.4329354 , 0.58482943, 0.42863916]]) If there are multiple such connections, the action to take is controlled by the `multiple_synapses` argument, which must be one of {'last', 'first', 'sum', 'min', 'max'}. Values will be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). """ if isinstance(attribute_names, str): attribute_names = (attribute_names,) return_single = True else: return_single = False if isinstance(self.synapse_type, StandardSynapseType): attribute_names = self.synapse_type.get_native_names(*attribute_names) if format == 'list': names = list(attribute_names) if with_address: names = ["presynaptic_index", "postsynaptic_index"] + names values = self._get_attributes_as_list(names) if gather and self._simulator.state.num_processes > 1: all_values = {self._simulator.state.mpi_rank: values} all_values = recording.gather_dict(all_values, all=(gather == 'all')) if gather == 'all' or self._simulator.state.mpi_rank == 0: values = reduce(operator.add, all_values.values()) if not with_address and return_single: values = [val[0] for val in values] return values elif format == 'array': if multiple_synapses not in Projection.MULTI_SYNAPSE_OPERATIONS: raise ValueError("`multiple_synapses` argument must be one of {}".format( list(Projection.MULTI_SYNAPSE_OPERATIONS))) if gather and self._simulator.state.num_processes > 1: # Node 0 is the only one creating a full connection matrix, and returning it (saving memory) # Slaves nodes are returning list of connections, so this may be inconsistent... names = list(attribute_names) names = ["presynaptic_index", "postsynaptic_index"] + names values = self._get_attributes_as_list(names) all_values = {self._simulator.state.mpi_rank: values} all_values = recording.gather_dict(all_values, all=(gather == 'all')) if gather == 'all' or self._simulator.state.mpi_rank == 0: tmp_values = reduce(operator.add, all_values.values()) values = self._get_attributes_as_arrays(attribute_names, multiple_synapses=multiple_synapses) tmp_values = np.array(tmp_values) for i in range(len(values)): values[i][tmp_values[:, 0].astype( int), tmp_values[:, 1].astype(int)] = tmp_values[:, 2 + i] else: values = self._get_attributes_as_arrays(attribute_names, multiple_synapses=multiple_synapses) if return_single: if gather == 'all' or self._simulator.state.mpi_rank == 0: assert len(values) == 1, values return values[0] else: return values else: raise Exception("format must be 'list' or 'array'") def _get_attributes_as_list(self, names): return [c.as_tuple(*names) for c in self.connections] def _get_attributes_as_arrays(self, names, multiple_synapses='sum'): multi_synapse_operation = Projection.MULTI_SYNAPSE_OPERATIONS[multiple_synapses] all_values = [] for attribute_name in names: values = np.nan * np.ones((self.pre.size, self.post.size)) if attribute_name[-1] == "s": # weights --> weight, delays --> delay attribute_name = attribute_name[:-1] for c in self.connections: value = getattr(c, attribute_name) addr = (c.presynaptic_index, c.postsynaptic_index) if np.isnan(values[addr]): values[addr] = value else: values[addr] = multi_synapse_operation(values[addr], value) all_values.append(values) return all_values @deprecated("get('weight', format, gather)") def getWeights(self, format='list', gather=True): return self.get('weight', format, gather, with_address=False) @deprecated("get('delay', format, gather)") def getDelays(self, format='list', gather=True): return self.get('delay', format, gather, with_address=False) @deprecated("get(parameter_name, format, gather)") def getSynapseDynamics(self, parameter_name, format='list', gather=True): return self.get(parameter_name, format, gather, with_address=False) def save(self, attribute_names, file, format='list', gather=True, with_address=True): """ Print synaptic attributes (weights, delays, etc.) to file. In the array format, zeros are printed for non-existent connections. Values will be expressed in the standard PyNN units (i.e. millivolts, nanoamps, milliseconds, microsiemens, nanofarads, event per second). """ if attribute_names in ('all', 'connections'): attribute_names = self.synapse_type.get_parameter_names() if isinstance(file, str): file = recording.files.StandardTextFile(file, mode='wb') all_values = self.get(attribute_names, format=format, gather=gather, with_address=with_address) if format == 'array': all_values = [np.where(np.isnan(values), 0.0, values) for values in all_values] if self._simulator.state.mpi_rank == 0: metadata = {"columns": attribute_names} if with_address: metadata["columns"] = ["i", "j"] + list(metadata["columns"]) file.write(all_values, metadata) file.close() @deprecated("save('all', file, format='list', gather=gather)") def saveConnections(self, file, gather=True, compatible_output=True): self.save('all', file, format='list', gather=gather) @deprecated("save('weight', file, format, gather)") def printWeights(self, file, format='list', gather=True): self.save('weight', file, format, gather) @deprecated("save('delay', file, format, gather)") def printDelays(self, file, format='list', gather=True): """ Print synaptic weights to file. In the array format, zeros are printed for non-existent connections. """ self.save('delay', file, format, gather) @deprecated("np.histogram()") def weightHistogram(self, min=None, max=None, nbins=10): """ Return a histogram of synaptic weights. If min and max are not given, the minimum and maximum weights are calculated automatically. """ weights = np.array(self.get('weight', format='list', gather=True, with_address=False)) if min is None: min = weights.min() if max is None: max = weights.max() bins = np.linspace(min, max, nbins + 1) return np.histogram(weights, bins) # returns n, bins def annotate(self, **annotations): self.annotations.update(annotations) def describe(self, template='projection_default.txt', engine='default'): """ Returns a human-readable description of the projection. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = { "label": self.label, "pre": self.pre.describe(template=None), "post": self.post.describe(template=None), "source": self.source, "receptor_type": self.receptor_type, "size_local": len(self), "size": self.size(gather=True), "connector": self._connector.describe(template=None), "plasticity": None, } if self.synapse_type: context.update(plasticity=self.synapse_type.describe(template=None)) return descriptions.render(engine, template, context) class Connection(object): """ Store an individual plastic connection and information about it. Provide an interface that allows access to the connection's weight, delay and other attributes. """ pass PyNN-0.10.0/pyNN/connectors.py000066400000000000000000001371561415343567000160540ustar00rootroot00000000000000""" Defines a common implementation of the built-in PyNN Connector classes. Simulator modules may use these directly, or may implement their own versions for improved performance. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.random import RandomDistribution, AbstractRNG, NumpyRNG, get_mpi_config from pyNN.core import IndexBasedExpression from pyNN import errors, descriptions from pyNN.recording import files from pyNN.parameters import LazyArray from pyNN.standardmodels import StandardSynapseType from pyNN.common import Population import numpy as np from itertools import repeat import logging from copy import copy, deepcopy from lazyarray import arccos, arcsin, arctan, arctan2, ceil, cos, cosh, exp, \ fabs, floor, fmod, hypot, ldexp, log, log10, modf, power, \ sin, sinh, sqrt, tan, tanh, maximum, minimum from numpy import e, pi try: import csa haveCSA = True except ImportError: haveCSA = False logger = logging.getLogger("PyNN") def _get_rng(rng): if isinstance(rng, AbstractRNG): return rng elif rng is None: return NumpyRNG(seed=151985012) else: raise Exception("rng must be either None, or a subclass of pyNN.random.AbstractRNG") class Connector(object): """ Base class for connectors. All connector sub-classes have the following optional keyword arguments: `safe`: if True, check that weights and delays have valid values. If False, this check is skipped. `callback`: a function that will be called with the fractional progress of the connection routine. An example would be `progress_bar.set_level`. """ def __init__(self, safe=True, callback=None): """ docstring needed """ self.safe = safe self.callback = callback if callback is not None: assert callable(callback) def connect(self, projection): raise NotImplementedError() def get_parameters(self): P = {} for name in self.parameter_names: P[name] = getattr(self, name) return P def _generate_distance_map(self, projection): position_generators = (projection.pre.position_generator, projection.post.position_generator) return LazyArray(projection.space.distance_generator(*position_generators), shape=projection.shape) def _parameters_from_synapse_type(self, projection, distance_map=None): """ Obtain the parameters to be used for the connections from the projection's `synapse_type` attribute. Each parameter value is a `LazyArray`. """ if distance_map is None: distance_map = self._generate_distance_map(projection) parameter_space = projection.synapse_type.native_parameters # TODO: in the documentation, we claim that a parameter value can be # a list or 1D array of the same length as the number of connections. # We do not currently handle this scenario, although it is only # really useful for fixed-number connectors anyway. # Probably the best solution is to remove the parameter at this stage, # then set it after the connections have already been created. parameter_space.shape = (projection.pre.size, projection.post.size) for name, map in parameter_space.items(): if callable(map.base_value): if isinstance(map.base_value, IndexBasedExpression): # Assumes map is a function of index and hence requires the projection to # determine its value. It and its index function are copied so as to be able # to set the projection without altering the connector, which would perhaps # not be expected from the 'connect' call. new_map = copy(map) new_map.base_value = copy(map.base_value) new_map.base_value.projection = projection parameter_space[name] = new_map else: # Assumes map is a function of distance parameter_space[name] = map(distance_map) return parameter_space def describe(self, template='connector_default.txt', engine='default'): """ Returns a human-readable description of the connection method. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = {'name': self.__class__.__name__, 'parameters': self.get_parameters()} return descriptions.render(engine, template, context) class MapConnector(Connector): """ Abstract base class for Connectors based on connection maps, where a map is a 2D lazy array containing either the (boolean) connectivity matrix (aka adjacency matrix, connection set mask, etc.) or the values of a synaptic connection parameter. """ def _standard_connect(self, projection, connection_map_generator, distance_map=None): """ `connection_map_generator` should be a function or other callable, with one optional argument `mask`, which returns an iterable. The iterable should produce one element per post-synaptic neuron. Each element should be either: (i) a boolean array, indicating which of the pre-synaptic neurons should be connected to, (ii) an integer array indicating the same thing using indices, (iii) or a single boolean, meaning connect to all/none. The `mask` argument, a boolean array, can be used to limit processing to just neurons which exist on the local MPI node. todo: explain the argument `distance_map`. """ column_indices = np.arange(projection.post.size) postsynaptic_indices = projection.post.id_to_index(projection.post.all_cells) if (projection.synapse_type.native_parameters.parallel_safe or hasattr(self, "rng") and self.rng.parallel_safe): # If any of the synapse parameters are based on parallel-safe random number generators, # we need to iterate over all post-synaptic cells, so we can generate then # throw away the random numbers for the non-local nodes. logger.debug("Parallel-safe iteration.") components = ( column_indices, postsynaptic_indices, projection.post._mask_local, connection_map_generator()) else: # Otherwise, we only need to iterate over local post-synaptic cells. mask = projection.post._mask_local components = ( column_indices[mask], postsynaptic_indices[mask], repeat(True), connection_map_generator(mask)) parameter_space = self._parameters_from_synapse_type(projection, distance_map) # Loop over columns of the connection_map array (equivalent to looping over post-synaptic neurons) for count, (col, postsynaptic_index, local, source_mask) in enumerate(zip(*components)): # `col`: column index # `postsynaptic_index`: index of the post-synaptic neuron # `local`: boolean - does the post-synaptic neuron exist on this MPI node # `source_mask`: boolean numpy array, indicating which of the pre-synaptic neurons should be connected to, # or a single boolean, meaning connect to all/none of the pre-synaptic neurons # It can also be an array of addresses _proceed = False if source_mask is True or source_mask.any(): _proceed = True elif type(source_mask) == np.ndarray: if source_mask.dtype == bool: if source_mask.any(): _proceed = True elif len(source_mask) > 0: _proceed = True if _proceed: # Convert from boolean to integer mask, if necessary if source_mask is True: source_mask = np.arange(projection.pre.size, dtype=int) elif source_mask.dtype == bool: source_mask = source_mask.nonzero()[0] # Evaluate the lazy arrays containing the synaptic parameters connection_parameters = {} for name, map in parameter_space.items(): if map.is_homogeneous: connection_parameters[name] = map.evaluate(simplify=True) else: connection_parameters[name] = map[source_mask, col] # Check that parameter values are valid if self.safe: # it might be cheaper to do the weight and delay check before evaluating the larray, # however this is challenging to do if the base value is a function or if there are # a lot of operations, so for simplicity we do the check after evaluation syn = projection.synapse_type if hasattr(syn, "parameter_checks"): #raise Exception(f"{connection_parameters} {syn.parameter_checks}") for parameter_name, check in syn.parameter_checks.items(): native_parameter_name = syn.translations[parameter_name]["translated_name"] # note that for delays we should also apply units scaling to the check values # since this currently only affects Brian we can probably handle that separately # (for weights the checks are all based on zero) if native_parameter_name in connection_parameters: check(connection_parameters[native_parameter_name], projection) if local: # Connect the neurons #logger.debug("Connecting to %d from %s" % (postsynaptic_index, source_mask)) projection._convergent_connect( source_mask, postsynaptic_index, **connection_parameters) if self.callback: self.callback(count / projection.post.local_size) def _connect_with_map(self, projection, connection_map, distance_map=None): """ Create connections according to a connection map. Arguments: `projection`: the `Projection` that is being created. `connection_map`: a boolean `LazyArray` of the same shape as `projection`, representing the connectivity matrix. `distance_map`: TODO """ logger.debug("Connecting %s using a connection map" % projection.label) self._standard_connect(projection, connection_map.by_column, distance_map) def _get_connection_map_no_self_connections(self, projection): if (isinstance(projection.pre, Population) and isinstance(projection.post, Population) and projection.pre == projection.post): # special case, expected to be faster than the default, below connection_map = LazyArray(lambda i, j: i != j, shape=projection.shape) else: # this could be optimized by checking parent or component populations # but should handle both views and assemblies a = np.broadcast_to(projection.pre.all_cells, (projection.post.size, projection.pre.size)).T b = projection.post.all_cells connection_map = LazyArray(a != b, shape=projection.shape) return connection_map def _get_connection_map_no_mutual_connections(self, projection): if (isinstance(projection.pre, Population) and isinstance(projection.post, Population) and projection.pre == projection.post): connection_map = LazyArray(lambda i, j: i > j, shape=projection.shape) else: raise NotImplementedError("todo") return connection_map class AllToAllConnector(MapConnector): """ Connects all cells in the presynaptic population to all cells in the postsynaptic population. Takes any of the standard :class:`Connector` optional arguments and, in addition: `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. """ parameter_names = ('allow_self_connections',) def __init__(self, allow_self_connections=True, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert isinstance(allow_self_connections, bool) self.allow_self_connections = allow_self_connections def connect(self, projection): if not self.allow_self_connections: connection_map = self._get_connection_map_no_self_connections(projection) elif self.allow_self_connections == 'NoMutual': connection_map = self._get_connection_map_no_mutual_connections(projection) else: connection_map = LazyArray(True, shape=projection.shape) self._connect_with_map(projection, connection_map) class FixedProbabilityConnector(MapConnector): """ For each pair of pre-post cells, the connection probability is constant. Takes any of the standard :class:`Connector` optional arguments and, in addition: `p_connect`: a float between zero and one. Each potential connection is created with this probability. `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `rng`: an :class:`RNG` instance used to evaluate whether connections exist """ parameter_names = ('allow_self_connections', 'p_connect') def __init__(self, p_connect, allow_self_connections=True, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' self.allow_self_connections = allow_self_connections self.p_connect = float(p_connect) assert 0 <= self.p_connect self.rng = _get_rng(rng) def connect(self, projection): random_map = LazyArray(RandomDistribution('uniform', (0, 1), rng=self.rng), projection.shape) connection_map = random_map < self.p_connect if not self.allow_self_connections: mask = self._get_connection_map_no_self_connections(projection) connection_map *= mask elif self.allow_self_connections == 'NoMutual': mask = self._get_connection_map_no_mutual_connections(projection) connection_map *= mask self._connect_with_map(projection, connection_map) class DistanceDependentProbabilityConnector(MapConnector): """ For each pair of pre-post cells, the connection probability depends on distance. Takes any of the standard :class:`Connector` optional arguments and, in addition: `d_expression`: the right-hand side of a valid Python expression for probability, involving 'd', e.g. "exp(-abs(d))", or "d<3" `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `rng`: an :class:`RNG` instance used to evaluate whether connections exist """ parameter_names = ('allow_self_connections', 'd_expression') def __init__(self, d_expression, allow_self_connections=True, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert isinstance(d_expression, str) or callable(d_expression) assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' try: if isinstance(d_expression, str): d = 0 assert 0 <= eval(d_expression), eval(d_expression) d = 1e12 assert 0 <= eval(d_expression), eval(d_expression) except ZeroDivisionError as err: raise ZeroDivisionError("Error in the distance expression %s. %s" % (d_expression, err)) self.d_expression = d_expression self.allow_self_connections = allow_self_connections self.distance_function = eval("lambda d: %s" % self.d_expression) self.rng = _get_rng(rng) def connect(self, projection): distance_map = self._generate_distance_map(projection) probability_map = self.distance_function(distance_map) random_map = LazyArray(RandomDistribution('uniform', (0, 1), rng=self.rng), projection.shape) connection_map = random_map < probability_map if not self.allow_self_connections: mask = self._get_connection_map_no_self_connections(projection) connection_map *= mask elif self.allow_self_connections == 'NoMutual': mask = self._get_connection_map_no_mutual_connections(projection) connection_map *= mask self._connect_with_map(projection, connection_map, distance_map) class IndexBasedProbabilityConnector(MapConnector): """ For each pair of pre-post cells, the connection probability depends on an arbitrary functions that takes the indices of the pre and post populations. Takes any of the standard :class:`Connector` optional arguments and, in addition: `index_expression`: a function that takes the two cell indices as inputs and calculates the probability matrix from it. `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `rng`: an :class:`RNG` instance used to evaluate whether connections exist """ parameter_names = ('allow_self_connections', 'index_expression') def __init__(self, index_expression, allow_self_connections=True, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert callable(index_expression) assert isinstance(index_expression, IndexBasedExpression) assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' self.index_expression = index_expression self.allow_self_connections = allow_self_connections self.rng = _get_rng(rng) def connect(self, projection): # The index function is copied so as to avoid the connector being altered by the "connect" # function, which is probably unexpected behaviour. index_expression = copy(self.index_expression) index_expression.projection = projection probability_map = LazyArray(index_expression, projection.shape) random_map = LazyArray(RandomDistribution('uniform', (0, 1), rng=self.rng), projection.shape) connection_map = random_map < probability_map if not self.allow_self_connections: mask = self._get_connection_map_no_self_connections(projection) connection_map *= mask elif self.allow_self_connections == 'NoMutual': mask = self._get_connection_map_no_mutual_connections(projection) connection_map *= mask self._connect_with_map(projection, connection_map) class DisplacementDependentProbabilityConnector(IndexBasedProbabilityConnector): class DisplacementExpression(IndexBasedExpression): """ A displacement based expression function used to determine the connection probability and the value of variable connection parameters of a projection """ def __init__(self, disp_function): """ `disp_function`: a function that takes a 3xN numpy position matrix and maps each row (displacement) to a probability between 0 and 1 """ self._disp_function = disp_function def __call__(self, i, j): disp = (self.projection.post.positions.T[j] - self.projection.pre.positions.T[i]).T return self._disp_function(disp) def __init__(self, disp_function, allow_self_connections=True, rng=None, safe=True, callback=None): super(DisplacementDependentProbabilityConnector, self).__init__( self.DisplacementExpression(disp_function), allow_self_connections=allow_self_connections, rng=rng, callback=callback) class FromListConnector(Connector): """ Make connections according to a list. Arguments: `conn_list`: a list of tuples, one tuple for each connection. Each tuple should contain: `(pre_idx, post_idx, p1, p2, ..., pn)` where `pre_idx` is the index (i.e. order in the Population, not the ID) of the presynaptic neuron, `post_idx` is the index of the postsynaptic neuron, and p1, p2, etc. are the synaptic parameters (e.g. weight, delay, plasticity parameters). `column_names`: the names of the parameters p1, p2, etc. If not provided, it is assumed the parameters are 'weight', 'delay' (for backwards compatibility). This should be specified using a tuple. `safe`: if True, check that weights and delays have valid values. If False, this check is skipped. `callback`: if True, display a progress bar on the terminal. """ parameter_names = ('conn_list',) def __init__(self, conn_list, column_names=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe=safe, callback=callback) self.conn_list = np.array(conn_list) if len(conn_list) > 0: n_columns = self.conn_list.shape[1] if column_names is None: if n_columns == 2: self.column_names = () elif n_columns == 4: self.column_names = ('weight', 'delay') else: raise TypeError("Argument 'column_names' is required.") else: self.column_names = column_names if n_columns != len(self.column_names) + 2: raise ValueError("connection list has %d parameter columns, but %d column names provided." % ( n_columns - 2, len(self.column_names))) else: self.column_names = () def connect(self, projection): """Connect-up a Projection.""" logger.debug("conn_list (original) = \n%s", self.conn_list) synapse_parameter_names = projection.synapse_type.get_parameter_names() for name in self.column_names: if name not in synapse_parameter_names: raise ValueError("%s is not a valid parameter for %s" % ( name, projection.synapse_type.__class__.__name__)) if self.conn_list.size == 0: return if np.any(self.conn_list[:, 0] >= projection.pre.size): raise errors.ConnectionError("source index out of range") # need to do some profiling, to figure out the best way to do this: # - order of sorting/filtering by local # - use np.unique, or just do in1d(self.conn_list)? idx = np.argsort(self.conn_list[:, 1]) targets = np.unique(self.conn_list[:, 1]).astype(int) local = np.in1d(targets, np.arange(projection.post.size)[projection.post._mask_local], assume_unique=True) local_targets = targets[local] self.conn_list = self.conn_list[idx] left = np.searchsorted(self.conn_list[:, 1], local_targets, 'left') right = np.searchsorted(self.conn_list[:, 1], local_targets, 'right') logger.debug("idx = %s", idx) logger.debug("targets = %s", targets) logger.debug("local_targets = %s", local_targets) logger.debug("conn_list (sorted by target) = \n%s", self.conn_list) logger.debug("left = %s", left) logger.debug("right = %s", right) for tgt, l, r in zip(local_targets, left, right): sources = self.conn_list[l:r, 0].astype(int) connection_parameters = deepcopy(projection.synapse_type.parameter_space) connection_parameters.shape = (r - l,) for col, name in enumerate(self.column_names, 2): connection_parameters.update(**{name: self.conn_list[l:r, col]}) if isinstance(projection.synapse_type, StandardSynapseType): connection_parameters = projection.synapse_type.translate( connection_parameters) connection_parameters.evaluate() projection._convergent_connect(sources, tgt, **connection_parameters) class FromFileConnector(FromListConnector): """ Make connections according to a list read from a file. Arguments: `file`: either an open file object or the filename of a file containing a list of connections, in the format required by `FromListConnector`. Column headers, if included in the file, must be specified using a list or tuple, e.g.:: # columns = ["i", "j", "weight", "delay", "U", "tau_rec"] Note that the header requires `#` at the beginning of the line. `distributed`: if this is True, then each node will read connections from a file called `filename.x`, where `x` is the MPI rank. This speeds up loading connections for distributed simulations. `safe`: if True, check that weights and delays have valid values. If False, this check is skipped. `callback`: if True, display a progress bar on the terminal. """ parameter_names = ('file', 'distributed') def __init__(self, file, distributed=False, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe=safe, callback=callback) if isinstance(file, str): file = files.StandardTextFile(file, mode='r') self.file = file self.distributed = distributed def connect(self, projection): """Connect-up a Projection.""" if self.distributed: self.file.rename("%s.%d" % (self.file.name, projection._simulator.state.mpi_rank)) self.column_names = self.file.get_metadata().get('columns', ('weight', 'delay')) for ignore in "ij": if ignore in self.column_names: self.column_names.remove(ignore) self.conn_list = self.file.read() FromListConnector.connect(self, projection) class FixedNumberConnector(MapConnector): # base class - should not be instantiated parameter_names = ('allow_self_connections', 'n') def __init__(self, n, allow_self_connections=True, with_replacement=False, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' self.allow_self_connections = allow_self_connections self.with_replacement = with_replacement self.n = n if isinstance(n, int): assert n >= 0 elif isinstance(n, RandomDistribution): # weak check that the random distribution is ok assert np.all(np.array(n.next(100)) >= 0), "the random distribution produces negative numbers" else: raise TypeError("n must be an integer or a RandomDistribution object") self.rng = _get_rng(rng) def _rng_uniform_int_exclude(self, n, size, exclude): res = self.rng.next(n, 'uniform_int', {"low": 0, "high": size}, mask=None) logger.debug("RNG0 res=%s" % res) idx = np.where(res == exclude)[0] logger.debug("RNG1 exclude=%d, res=%s idx=%s" % (exclude, res, idx)) while idx.size > 0: redrawn = self.rng.next(idx.size, 'uniform_int', {"low": 0, "high": size}, mask=None) res[idx] = redrawn idx = idx[np.where(res == exclude)[0]] logger.debug("RNG2 exclude=%d redrawn=%s res=%s idx=%s" % (exclude, redrawn, res, idx)) return res class FixedNumberPostConnector(FixedNumberConnector): """ Each pre-synaptic neuron is connected to exactly `n` post-synaptic neurons chosen at random. The sampling behaviour is controlled by the `with_replacement` argument. "With replacement" means that each post-synaptic neuron is chosen from the entire population. There is always therefore a possibility of multiple connections between a given pair of neurons. "Without replacement" means that once a neuron has been selected, it cannot be selected again until the entire population has been selected. This means that if `n` is less than the size of the post-synaptic population, there are no multiple connections. If `n` is greater than the size of the post- synaptic population, all possible single connections are made before starting to add duplicate connections. Takes any of the standard :class:`Connector` optional arguments and, in addition: `n`: either a positive integer, or a `RandomDistribution` that produces positive integers. If `n` is a `RandomDistribution`, then the number of post-synaptic neurons is drawn from this distribution for each pre-synaptic neuron. `with_replacement`: if True, the selection of neurons to connect is made from the entire population. If False, once a neuron is selected it cannot be selected again until the entire population has been connected. `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `rng`: an :class:`RNG` instance used to evaluate which potential connections are created. """ def _get_num_post(self): if isinstance(self.n, int): n_post = self.n else: n_post = self.n.next() return n_post def connect(self, projection): connections = [[] for i in range(projection.post.size)] for source_index in range(projection.pre.size): n = self._get_num_post() if self.with_replacement: if not self.allow_self_connections and projection.pre == projection.post: targets = self._rng_uniform_int_exclude(n, projection.post.size, source_index) else: targets = self.rng.next( n, 'uniform_int', {"low": 0, "high": projection.post.size}, mask=None) else: all_cells = np.arange(projection.post.size) if not self.allow_self_connections and projection.pre == projection.post: all_cells = all_cells[all_cells != source_index] full_sets = n // all_cells.size remainder = n % all_cells.size target_sets = [] if full_sets > 0: target_sets = [all_cells] * full_sets if remainder > 0: target_sets.append(self.rng.permutation(all_cells)[:remainder]) targets = np.hstack(target_sets) assert targets.size == n for target_index in targets: connections[target_index].append(source_index) def build_source_masks(mask=None): if mask is None: return [np.array(x) for x in connections] else: return [np.array(x) for x in np.array(connections)[mask]] self._standard_connect(projection, build_source_masks) class FixedNumberPreConnector(FixedNumberConnector): """ Each post-synaptic neuron is connected to exactly `n` pre-synaptic neurons chosen at random. The sampling behaviour is controlled by the `with_replacement` argument. "With replacement" means that each pre-synaptic neuron is chosen from the entire population. There is always therefore a possibility of multiple connections between a given pair of neurons. "Without replacement" means that once a neuron has been selected, it cannot be selected again until the entire population has been selected. This means that if `n` is less than the size of the pre-synaptic population, there are no multiple connections. If `n` is greater than the size of the pre- synaptic population, all possible single connections are made before starting to add duplicate connections. Takes any of the standard :class:`Connector` optional arguments and, in addition: `n`: either a positive integer, or a `RandomDistribution` that produces positive integers. If `n` is a `RandomDistribution`, then the number of pre-synaptic neurons is drawn from this distribution for each post-synaptic neuron. `with_replacement`: if True, the selection of neurons to connect is made from the entire population. If False, once a neuron is selected it cannot be selected again until the entire population has been connected. `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `rng`: an :class:`RNG` instance used to evaluate which potential connections are created. """ def _get_num_pre(self, size, mask=None): if isinstance(self.n, int): if mask is None: n_pre = repeat(self.n, size) else: n_pre = repeat(self.n, mask.sum()) else: if mask is None: n_pre = self.n.next(size) else: if self.n.rng.parallel_safe: n_pre = self.n.next(size)[mask] else: n_pre = self.n.next(mask.sum()) return n_pre def connect(self, projection): if self.with_replacement: if self.allow_self_connections or projection.pre != projection.post: def build_source_masks(mask=None): n_pre = self._get_num_pre(projection.post.size, mask) for n in n_pre: sources = self.rng.next( n, 'uniform_int', {"low": 0, "high": projection.pre.size}, mask=None) assert sources.size == n yield sources else: def build_source_masks(mask=None): n_pre = self._get_num_pre(projection.post.size, mask) if self.rng.parallel_safe or mask is None: for i, n in enumerate(n_pre): sources = self._rng_uniform_int_exclude(n, projection.pre.size, i) assert sources.size == n yield sources else: # TODO: use mask to obtain indices i raise NotImplementedError( "allow_self_connections=False currently requires a parallel safe RNG.") else: if self.allow_self_connections or projection.pre != projection.post: def build_source_masks(mask=None): # where n > projection.pre.size, first all pre-synaptic cells # are connected one or more times, then the remainder # are chosen randomly n_pre = self._get_num_pre(projection.post.size, mask) all_cells = np.arange(projection.pre.size) for n in n_pre: full_sets = n // projection.pre.size remainder = n % projection.pre.size source_sets = [] if full_sets > 0: source_sets = [all_cells] * full_sets if remainder > 0: source_sets.append(self.rng.permutation(all_cells)[:remainder]) sources = np.hstack(source_sets) assert sources.size == n yield sources else: def build_source_masks(mask=None): # where n > projection.pre.size, first all pre-synaptic cells # are connected one or more times, then the remainder # are chosen randomly n_pre = self._get_num_pre(projection.post.size, mask) all_cells = np.arange(projection.pre.size) if self.rng.parallel_safe or mask is None: for i, n in enumerate(n_pre): full_sets = n // (projection.pre.size - 1) remainder = n % (projection.pre.size - 1) allowed_cells = all_cells[all_cells != i] source_sets = [] if full_sets > 0: source_sets = [allowed_cells] * full_sets if remainder > 0: source_sets.append(self.rng.permutation(allowed_cells)[:remainder]) sources = np.hstack(source_sets) assert sources.size == n yield sources else: raise NotImplementedError( "allow_self_connections=False currently requires a parallel safe RNG.") self._standard_connect(projection, build_source_masks) class OneToOneConnector(MapConnector): """ Where the pre- and postsynaptic populations have the same size, connect cell *i* in the presynaptic population to cell *i* in the postsynaptic population for all *i*. Takes any of the standard :class:`Connector` optional arguments. """ parameter_names = tuple() def connect(self, projection): """Connect-up a Projection.""" connection_map = LazyArray(lambda i, j: i == j, shape=projection.shape) self._connect_with_map(projection, connection_map) class SmallWorldConnector(Connector): """ Connect cells so as to create a small-world network. Takes any of the standard :class:`Connector` optional arguments and, in addition: `degree`: the region length where nodes will be connected locally. `rewiring`: the probability of rewiring each edge. `allow_self_connections`: if the connector is used to connect a Population to itself, this flag determines whether a neuron is allowed to connect to itself, or only to other neurons in the Population. `n_connections`: if specified, the number of efferent synaptic connections per neuron. `rng`: an :class:`RNG` instance used to evaluate which connections are created. """ parameter_names = ('allow_self_connections', 'degree', 'rewiring', 'n_connections') def __init__(self, degree, rewiring, allow_self_connections=True, n_connections=None, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert 0 <= rewiring <= 1 assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' self.rewiring = rewiring self.d_expression = "d < %g" % degree self.allow_self_connections = allow_self_connections self.n_connections = n_connections self.rng = _get_rng(rng) def connect(self, projection): """Connect-up a Projection.""" raise NotImplementedError class CSAConnector(MapConnector): """ Use the Connection Set Algebra (Djurfeldt, 2012) to connect cells. Takes any of the standard :class:`Connector` optional arguments and, in addition: `cset`: a connection set object. """ parameter_names = ('cset',) if haveCSA: def __init__(self, cset, safe=True, callback=None): """ """ Connector.__init__(self, safe=safe, callback=callback) self.cset = cset arity = csa.arity(cset) assert arity in (0, 2), 'must specify mask or connection-set with arity 0 or 2' else: def __init__(self, cset, safe=True, callback=None): raise RuntimeError("CSAConnector not available---couldn't import csa module") def connect(self, projection): """Connect-up a Projection.""" # Cut out finite part c = csa.cross((0, projection.pre.size - 1), (0, projection.post.size - 1)) * \ self.cset # can't we cut out just the columns we want? if csa.arity(self.cset) == 2: # Connection-set with arity 2 for (i, j, weight, delay) in c: projection._convergent_connect( [projection.pre[i]], projection.post[j], weight, delay) elif csa.arity(self.cset) == 0: # inefficient implementation as a starting point connection_map = np.zeros((projection.pre.size, projection.post.size), dtype=bool) for addr in c: connection_map[addr] = True self._connect_with_map(projection, LazyArray(connection_map)) else: raise NotImplementedError class CloneConnector(MapConnector): """ Connects cells with the same connectivity pattern as a previous projection. """ parameter_names = ('reference_projection',) def __init__(self, reference_projection, safe=True, callback=None): """ Create a new CloneConnector. `reference_projection` -- the projection to clone the connectivity pattern from """ MapConnector.__init__(self, safe, callback=callback) self.reference_projection = reference_projection def connect(self, projection): if (projection.pre != self.reference_projection.pre or projection.post != self.reference_projection.post): raise errors.ConnectionError("Pre and post populations must match between reference ({0}" " and {1}) and clone projections ({2} and {3}) for " "CloneConnector" .format(self.reference_projection.pre, self.reference_projection.post, projection.pre, projection.post)) connection_map = LazyArray(~np.isnan(self.reference_projection.get(['weight'], 'array', gather='all')[0])) self._connect_with_map(projection, connection_map) class ArrayConnector(MapConnector): """ Provide an explicit boolean connection matrix, with shape (m, n) where m is the size of the presynaptic population and n that of the postsynaptic population. """ parameter_names = ('array',) def __init__(self, array, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) self.array = array def connect(self, projection): connection_map = LazyArray(self.array, projection.shape) self._connect_with_map(projection, connection_map) class FixedTotalNumberConnector(FixedNumberConnector): parameter_names = ('allow_self_connections', 'n') def __init__(self, n, allow_self_connections=True, with_replacement=True, rng=None, safe=True, callback=None): """ Create a new connector. """ Connector.__init__(self, safe, callback) assert isinstance(allow_self_connections, bool) or allow_self_connections == 'NoMutual' self.allow_self_connections = allow_self_connections self.with_replacement = with_replacement self.n = n if isinstance(n, int): assert n >= 0 elif isinstance(n, RandomDistribution): # weak check that the random distribution is ok assert np.all(np.array(n.next(100)) >= 0), "the random distribution produces negative numbers" else: raise TypeError("n must be an integer or a RandomDistribution object") self.rng = _get_rng(rng) def connect(self, projection): # This implementation is not "parallel safe" for random numbers. # todo: support the `parallel_safe` flag. # Determine number of processes and current rank rank = projection._simulator.state.mpi_rank num_processes = projection._simulator.state.num_processes # Assume that targets are equally distributed over processes targets_per_process = int(len(projection.post) / num_processes) # Calculate the number of synapses on each process bino = RandomDistribution('binomial', [self.n, targets_per_process / len(projection.post)], rng=self.rng) num_conns_on_vp = np.zeros(num_processes, dtype=int) sum_dist = 0 sum_partitions = 0 for k in range(num_processes): p_local = targets_per_process / (len(projection.post) - sum_dist) bino.parameters['p'] = p_local bino.parameters['n'] = self.n - sum_partitions num_conns_on_vp[k] = bino.next() sum_dist += targets_per_process sum_partitions += num_conns_on_vp[k] # Draw random sources and targets connections = [[] for i in range(projection.post.size)] possible_targets = np.arange(projection.post.size)[projection.post._mask_local] for i in range(num_conns_on_vp[rank]): source_index = self.rng.next(1, 'uniform_int', {"low": 0, "high": projection.pre.size}, mask=None)[0] target_index = self.rng.choice(possible_targets, size=1)[0] connections[target_index].append(source_index) def build_source_masks(mask=None): if mask is None: return [np.array(x) for x in connections] else: return [np.array(x) for x in np.array(connections)[mask]] self._standard_connect(projection, build_source_masks) PyNN-0.10.0/pyNN/core.py000066400000000000000000000041061415343567000146130ustar00rootroot00000000000000""" Assorted utility classes and functions. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import warnings import numpy as np def is_listlike(obj): """ Check whether an object (a) can be converted into an array/list *and* has a length. This excludes iterators, for example. Maybe need to split into different functions, as don't always need length. """ return ( isinstance(obj, (list, tuple, set)) or (isinstance(obj, np.ndarray) and obj.ndim > 0) ) class deprecated(object): """ Decorator to mark functions/methods as deprecated. Emits a warning when function is called and suggests a replacement. """ def __init__(self, replacement=''): self.replacement = replacement def __call__(self, func): def new_func(*args, **kwargs): msg = "%s() is deprecated, and will be removed in a future release." % func.__name__ if self.replacement: msg += " Use %s instead." % self.replacement warnings.warn(msg, category=DeprecationWarning) return func(*args, **kwargs) new_func.__name__ = func.__name__ new_func.__doc__ = "*Deprecated*. Use ``%s`` instead." % self.replacement new_func.__dict__.update(func.__dict__) return new_func def reraise(exception, message): args = list(exception.args) args[0] += message exception.args = args raise def ezip(*args): for items in zip(*args): yield items[0], items[1:] class IndexBasedExpression(object): """ Abstract base class for general expressions that use the cell indices and projection class to determine their value instead of just the the distance between the cells """ @property def projection(self): try: return self._projection except AttributeError: return None @projection.setter def projection(self, projection): self._projection = projection def __call__(self, i, j): raise NotImplementedError PyNN-0.10.0/pyNN/descriptions/000077500000000000000000000000001415343567000160165ustar00rootroot00000000000000PyNN-0.10.0/pyNN/descriptions/__init__.py000066400000000000000000000140131415343567000201260ustar00rootroot00000000000000""" Support module for the `describe()` method of many PyNN classes. If a supported template engine is available on the Python path, PyNN will use this engine to produce the output from `describe()`. As a fall-back, it will use the built-in string.Template engine, but this produces much less well-formatted output, as it does not support hierarchical contexts, loops or conditionals. Currently supported engines are Cheetah Template and Jinja2, but it should be trivial to add others. If a user has a preference for a particular engine (e.g. if they are providing their own templates for `describe()`), they may set the DEFAULT_TEMPLATE_ENGINE module attribute, e.g.:: from pyNN import descriptions descriptions.DEFAULT_TEMPLATE_ENGINE = 'jinja2' :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import string import os.path DEFAULT_TEMPLATE_ENGINE = None # can be set by user TEMPLATE_ENGINES = {} def get_default_template_engine(): """ Return the default template engine class. """ default = DEFAULT_TEMPLATE_ENGINE or list(TEMPLATE_ENGINES.keys())[0] return TEMPLATE_ENGINES[default] def render(engine, template, context): """ Render the given template with the given context, using the given engine. """ if template is None: return context else: if engine == 'default': engine = get_default_template_engine() elif isinstance(engine, str): engine = TEMPLATE_ENGINES[engine] assert issubclass(engine, TemplateEngine), str(engine) return engine.render(template, context) class TemplateEngine(object): """ Base class. """ @classmethod def get_template(cls, template): """ template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/) """ raise NotImplementedError() @classmethod def render(cls, template, context): """ Render the template with the context. template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/) context should be a dict. """ raise NotImplementedError() class StringTemplateEngine(TemplateEngine): """ Interface to the built-in string.Template template engine. """ template_dir = os.path.join(os.path.dirname(__file__), 'templates', 'string') @classmethod def get_template(cls, template): """ template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/string/) """ template_path = os.path.join(cls.template_dir, template) if os.path.exists(template_path): f = open(template_path, 'r') template = f.read() f.close() return string.Template(template) @classmethod def render(cls, template, context): """ Render the template with the context. template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/string/) context should be a dict. """ template = cls.get_template(template) return template.safe_substitute(context) TEMPLATE_ENGINES['string'] = StringTemplateEngine try: import jinja2 class Jinja2TemplateEngine(TemplateEngine): """ Interface to the Jinja2 template engine. """ env = jinja2.Environment(loader=jinja2.PackageLoader( 'pyNN.descriptions', 'templates/jinja2')) @classmethod def get_template(cls, template): """ template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/jinja2/) """ assert isinstance(template, str) try: # maybe template is a file template = cls.env.get_template(template) except Exception: # interpret template as a string template = cls.env.from_string(template) return template @classmethod def render(cls, template, context): """ Render the template with the context. template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/jinja2/) context should be a dict. """ template = cls.get_template(template) return template.render(context) TEMPLATE_ENGINES['jinja2'] = Jinja2TemplateEngine except ImportError: pass # jinja2 is an optional dependency try: import Cheetah.Template class CheetahTemplateEngine(TemplateEngine): """ Interface to the Cheetah template engine. """ template_dir = os.path.join(os.path.dirname(__file__), 'templates', 'cheetah') @classmethod def get_template(cls, template): """ template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/cheetah) """ template_path = os.path.join(cls.template_dir, template) if os.path.exists(template_path): return Cheetah.Template.Template.compile(file=template_path) else: return Cheetah.Template.Template.compile(source=template) @classmethod def render(cls, template, context): """ Render the template with the context. template may be either a string containing a template or the name of a file (relative to pyNN/descriptions/templates/cheetah/) context should be a dict. """ template = cls.get_template(template)(namespaces=[context]) return template.respond() TEMPLATE_ENGINES['cheetah'] = CheetahTemplateEngine except ImportError: pass # cheetah is an optional dependency PyNN-0.10.0/pyNN/descriptions/templates/000077500000000000000000000000001415343567000200145ustar00rootroot00000000000000PyNN-0.10.0/pyNN/descriptions/templates/cheetah/000077500000000000000000000000001415343567000214155ustar00rootroot00000000000000PyNN-0.10.0/pyNN/descriptions/templates/cheetah/assembly_default.txt000066400000000000000000000002101415343567000254720ustar00rootroot00000000000000Neuronal assembly called "$label", consisting of the following populations: #for $p in $populations * $p.label #end for PyNN-0.10.0/pyNN/descriptions/templates/cheetah/modeltype_default.txt000066400000000000000000000000051415343567000256570ustar00rootroot00000000000000$namePyNN-0.10.0/pyNN/descriptions/templates/cheetah/population_default.txt000066400000000000000000000007021415343567000260530ustar00rootroot00000000000000Population "$label" #if $structure Structure : $structure.name #for $name,$value in $structure.parameters.items() $name: $value #end for #end if Local cells : $size_local Cell type : $celltype.name ID range : $first_id-$last_id #if $size_local First cell on this node: ID: $local_first_id #for $name,$value in $cell_parameters.items() $name: $value #end for #end ifPyNN-0.10.0/pyNN/descriptions/templates/cheetah/population_old.txt000066400000000000000000000007441415343567000252130ustar00rootroot00000000000000------- Population description ------- Population called $label is made of $size cells [$size_local being local] #if $structure -> Cells are arranged in a $structure #end if -> Celltype is $celltype -> ID range is $first_id-$last_id #if $n_cells_local > 0 -> Cell Parameters used for first cell on this node are: #for $name, $value in $cell_parameters.items() $name: $value #end for #else -> There are no cells on this node. #end if --- End of Population description ---- PyNN-0.10.0/pyNN/descriptions/templates/cheetah/populationview_default.txt000066400000000000000000000001321415343567000267430ustar00rootroot00000000000000PopulationView "$label" parent : "$parent" size : $size mask : $mask PyNN-0.10.0/pyNN/descriptions/templates/cheetah/projection_default.txt000066400000000000000000000005621415343567000260410ustar00rootroot00000000000000Projection "$label" from "$pre.label" ($pre.size cells) to "$post.label" ($post.size cells) Receptor type : $receptor_type Connector : $connector.name #for $name,$value in $connector.parameters.items() $name : $value #end for Synapse type : (to be reimplemented) Total connections : $size Local connections : $size_local PyNN-0.10.0/pyNN/descriptions/templates/cheetah/synapsedynamics_default.txt000066400000000000000000000001141415343567000270700ustar00rootroot00000000000000Short-term plasticity mechanism: $fast Long-term plasticity mechanism: $slowPyNN-0.10.0/pyNN/descriptions/templates/jinja2/000077500000000000000000000000001415343567000211715ustar00rootroot00000000000000PyNN-0.10.0/pyNN/descriptions/templates/jinja2/assembly_default.txt000066400000000000000000000002251415343567000252540ustar00rootroot00000000000000Neuronal assembly called "{{label}}", consisting of the following populations: {% for p in populations %} * {{p.label}} {% endfor %} PyNN-0.10.0/pyNN/descriptions/templates/jinja2/modeltype_default.txt000066400000000000000000000000101415343567000254270ustar00rootroot00000000000000{{name}}PyNN-0.10.0/pyNN/descriptions/templates/jinja2/population_default.txt000066400000000000000000000007461415343567000256370ustar00rootroot00000000000000Population "{{label}}"{%- if structure %} Structure : {{structure.name}} {%- for name,value in structure.parameters.items() %} {{name}}: {{value}}{% endfor %}{% endif -%} Local cells : {{size_local}} Cell type : {{celltype.name}} ID range : {{first_id}}-{{last_id}} {% if size_local %}First cell on this node: ID: {{local_first_id}} {% for name,value in cell_parameters.items() %}{{name}}: {{value}} {% endfor -%} {% endif %}PyNN-0.10.0/pyNN/descriptions/templates/jinja2/population_old.txt000066400000000000000000000010351415343567000247610ustar00rootroot00000000000000------- Population description ------- Population called {{label}} is made of {{size}} cells [{{size_local}} being local] {% if structure %}-> Cells are arranged in a {{structure}}{% endif %} -> Celltype is {{celltype}} -> ID range is {{first_id}}-{{last_id}} {% if n_cells_local > 0 %}-> Cell Parameters used for first cell on this node are: {% for name, value in cell_parameters %} {{name}}: {{value}} {% endfor %°} {% else %} -> There are no cells on this node. {% endif %} --- End of Population description ---- PyNN-0.10.0/pyNN/descriptions/templates/jinja2/populationview_default.txt000066400000000000000000000001461415343567000265240ustar00rootroot00000000000000PopulationView "{{label}}" parent : "{{parent}}" size : {{size}} mask : {{mask}} PyNN-0.10.0/pyNN/descriptions/templates/jinja2/projection_default.txt000066400000000000000000000022331415343567000256120ustar00rootroot00000000000000Projection "{{label}}" from "{{pre.label}}" ({{pre.size}} cells) to "{{post.label}}" ({{post.size}} cells) Target : {{target}} Connector : {{connector.name}} {%- for name,value in connector.parameters.items() %} {{name}} : {{value}}{% endfor %} Weights : {{connector.weights}} Delays : {{connector.delays}} Plasticity : {% if plasticity %} Short-term : {{plasticity.fast}} Long-term : {% if plasticity.slow %} Timing-dependence : {{plasticity.slow.timing_dependence.name}} {%- for name,value in plasticity.slow.timing_dependence.parameters.items() %} {{name}} : {{value}}{% endfor %} Weight-dependence : {{plasticity.slow.weight_dependence.name}} {%- for name,value in plasticity.slow.weight_dependence.parameters.items() %} {{name}} : {{value}}{% endfor %} Voltage-dependence : {{plasticity.slow.voltage_dependence}} Dendritic delay fraction : {{plasticity.slow.dendritic_delay_fraction}}{% endif %}{% else %}None{% endif %} Total connections : {{size}} Local connections : {{size_local}} PyNN-0.10.0/pyNN/descriptions/templates/jinja2/synapsedynamics_default.txt000066400000000000000000000001221415343567000266430ustar00rootroot00000000000000Short-term plasticity mechanism: {{fast}} Long-term plasticity mechanism: {{slow}}PyNN-0.10.0/pyNN/descriptions/templates/string/000077500000000000000000000000001415343567000213225ustar00rootroot00000000000000PyNN-0.10.0/pyNN/descriptions/templates/string/assembly_default.txt000066400000000000000000000001361415343567000254060ustar00rootroot00000000000000Neuronal assembly called "$label", consisting of the following populations: $populations PyNN-0.10.0/pyNN/descriptions/templates/string/modeltype_default.txt000066400000000000000000000000051415343567000255640ustar00rootroot00000000000000$namePyNN-0.10.0/pyNN/descriptions/templates/string/population_default.txt000066400000000000000000000003421415343567000257600ustar00rootroot00000000000000Population "$label" Structure : $structure Local cells : $size_local Cell type : $celltype.name ID range : $first_id-$last_id First cell on this node: ID: $local_first_id $cell_parametersPyNN-0.10.0/pyNN/descriptions/templates/string/populationview_default.txt000066400000000000000000000001321415343567000266500ustar00rootroot00000000000000PopulationView "$label" parent : "$parent" size : $size mask : $mask PyNN-0.10.0/pyNN/descriptions/templates/string/projection_default.txt000066400000000000000000000003061415343567000257420ustar00rootroot00000000000000Projection "$label" From: $pre To: $post Target : $target Connector : $connector Plasticity : $plasticity Total connections : $size Local connections : $size_local PyNN-0.10.0/pyNN/descriptions/templates/string/synapsedynamics_default.txt000066400000000000000000000001141415343567000267750ustar00rootroot00000000000000Short-term plasticity mechanism: $fast Long-term plasticity mechanism: $slowPyNN-0.10.0/pyNN/errors.py000066400000000000000000000050221415343567000151750ustar00rootroot00000000000000# encoding: utf-8 """ Defines exceptions for the PyNN API InvalidParameterValueError NonExistentParameterError InvalidDimensionsError ConnectionError InvalidModelError RoundingWarning NothingToWriteError InvalidWeightError NotLocalError RecordingError :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ class InvalidParameterValueError(ValueError): """Inappropriate parameter value""" pass class NonExistentParameterError(KeyError): """ Model parameter does not exist. """ def __init__(self, parameter_name, model_name, valid_parameter_names=['unknown']): Exception.__init__(self) self.parameter_name = parameter_name self.model_name = model_name self.valid_parameter_names = sorted(valid_parameter_names) def __str__(self): return "%s (valid parameters for %s are: %s)" % (self.parameter_name, self.model_name, ", ".join(self.valid_parameter_names)) class InvalidDimensionsError(ValueError): """Argument has inappropriate shape/dimensions.""" pass class ConnectionError(Exception): """Attempt to create an invalid connection or access a non-existent connection.""" pass class InvalidModelError(Exception): """Attempt to use a non-existent model type.""" pass class NoModelAvailableError(Exception): """The simulator does not implement the requested model.""" pass class RoundingWarning(Warning): """The argument has been rounded to a lower level of precision by the simulator.""" pass class NothingToWriteError(IOError): """There is no data available to write.""" pass class InvalidWeightError(ValueError): """Invalid value for the synaptic weight.""" pass class NotLocalError(Exception): """Attempt to access a cell or connection that does not exist on this node (but exists elsewhere).""" pass class RecordingError(Exception): # subclass AttributeError? """Attempt to record a variable that does not exist for this cell type.""" def __init__(self, variable, cell_type): self.variable = variable self.cell_type = cell_type def __str__(self): msg = "Cannot record %s from cell type %s. Available variables are %s" return msg % (self.variable, self.cell_type.__class__.__name__, ",".join(self.cell_type.recordable)) PyNN-0.10.0/pyNN/hardware/000077500000000000000000000000001415343567000151055ustar00rootroot00000000000000PyNN-0.10.0/pyNN/hardware/__init__.py000066400000000000000000000010661415343567000172210ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ hardware implementation of the PyNN API. It includes the submodules that stand on another directory. This solution is a clean way to make the submodules (brainscales, etc...) be indeed submodules of hardware, even if they don't stand on the same directory :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from auxiliary import get_path_to_analog_hardware_backend, import_all_submodules __path__.append(get_path_to_analog_hardware_backend()) import_all_submodules(__path__) PyNN-0.10.0/pyNN/hardware/auxiliary.py000066400000000000000000000052601415343567000174710ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ auxiliary functions to look for the hardware backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pkgutil import iter_modules from os import environ, path # ============================================================================== # getters # ============================================================================== def get_symap2ic_path(): if 'SYMAP2IC_PATH' not in environ: raise ImportError( """ symap2ic software is not available! - is symap2ic installed? - is environment variable SYMAP2IC_PATH set?""") symap2ic_path = environ['SYMAP2IC_PATH'] if not path.exists(symap2ic_path): raise ImportError( """ SYMAP2IC_PATH = %s SYMAP2IC_PATH points to a non existing directory""" % symap2ic_path) return symap2ic_path def get_pynn_hw_path(): hardware_path = environ['PYNN_HW_PATH'] if not path.exists(hardware_path): raise Exception( """ PYNN_HW_PATH = %s You are using PYNN_HW_PATH to point to the PyNN hardware backend. But, PYNN_HW_PATH points to a non existing directory""" % hardware_path) return hardware_path def get_hardware_path(symap2ic_path): hardware_path = path.join(symap2ic_path, "components/pynnhw/src/hardware") if not path.exists(hardware_path): raise Exception( """ hardware_path = %s It should point to the PyNN hardware backend But, hardware_path points to a non existing directory""" % hardware_path) return hardware_path # ============================================================================== # Utility functions # ============================================================================== def import_module(version="brainscales"): __import__("brainscales", globals(), locals(), [], -1) # ============================================================================== # Functions called by __init__.py # ============================================================================== def get_path_to_analog_hardware_backend(): symap2ic_path = get_symap2ic_path() if 'PYNN_HW_PATH' in environ: hardware_path = get_pynn_hw_path() else: hardware_path = get_hardware_path(symap2ic_path) return hardware_path def import_all_submodules(module_path): for importer, module_name, ispkg in iter_modules(module_path): if ispkg is True: import_module(version=module_name) print("Linked: submodule hardware.%s" % module_name) PyNN-0.10.0/pyNN/mock/000077500000000000000000000000001415343567000142415ustar00rootroot00000000000000PyNN-0.10.0/pyNN/mock/__init__.py000066400000000000000000000045421415343567000163570ustar00rootroot00000000000000""" Mock implementation of the PyNN API, for testing and documentation purposes. This simulator implements the PyNN API, but generates random data rather than really running simulations. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from pyNN import common from pyNN.common.control import DEFAULT_MAX_DELAY, DEFAULT_TIMESTEP, DEFAULT_MIN_DELAY from pyNN.connectors import * from pyNN.recording import * from . import simulator from .standardmodels import * from .populations import Population, PopulationView, Assembly from .projections import Projection from neo.io import get_io logger = logging.getLogger("PyNN") def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" return [obj.__name__ for obj in globals().values() if isinstance(obj, type) and issubclass(obj, StandardCellType)] def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) common.setup(timestep, min_delay, **extra_params) simulator.state.clear() simulator.state.dt = timestep # move to common.setup? simulator.state.min_delay = min_delay simulator.state.max_delay = max_delay simulator.state.mpi_rank = extra_params.get('rank', 0) simulator.state.num_processes = extra_params.get('num_processes', 1) return rank() def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: io = get_io(filename) population.write_data(io, variables) simulator.state.write_on_end = [] # should have common implementation of end() run, run_until = common.build_run(simulator) run_for = run reset = common.build_reset(simulator) initialize = common.initialize get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) record = common.build_record(simulator) def record_v(source, filename): return record(['v'], source, filename) def record_gsyn(source, filename): return record(['gsyn_exc', 'gsyn_inh'], source, filename) PyNN-0.10.0/pyNN/mock/populations.py000066400000000000000000000063371415343567000172010ustar00rootroot00000000000000import numpy as np from pyNN import common, errors from pyNN.standardmodels import StandardCellType from pyNN.parameters import ParameterSpace, simplify from . import simulator from .recording import Recorder class Assembly(common.Assembly): _simulator = simulator class PopulationView(common.PopulationView): _assembly_class = Assembly _simulator = simulator def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: value = self.parent._parameters[name] if isinstance(value, np.ndarray): value = value[self.mask] parameter_dict[name] = simplify(value) return ParameterSpace(parameter_dict, shape=(self.size,)) # or local size? def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" for name, value in parameter_space.items(): try: self.parent._parameters[name][self.mask] = value.evaluate(simplify=True) except ValueError as err: raise errors.InvalidParameterValueError(f"{name} should not be of type {type(value)}") def _set_initial_value_array(self, variable, initial_values): pass def _get_view(self, selector, label=None): return PopulationView(self, selector, label) class Population(common.Population): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def _create_cells(self): id_range = np.arange(simulator.state.id_counter, simulator.state.id_counter + self.size) self.all_cells = np.array([simulator.ID(id) for id in id_range], dtype=simulator.ID) def is_local(id): return (id % simulator.state.num_processes) == simulator.state.mpi_rank self._mask_local = is_local(self.all_cells) if isinstance(self.celltype, StandardCellType): parameter_space = self.celltype.native_parameters else: parameter_space = self.celltype.parameter_space parameter_space.shape = (self.size,) parameter_space.evaluate(mask=self._mask_local, simplify=False) self._parameters = parameter_space.as_dict() for id in self.all_cells: id.parent = self simulator.state.id_counter += self.size def _set_initial_value_array(self, variable, initial_values): pass def _get_view(self, selector, label=None): return PopulationView(self, selector, label) def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: parameter_dict[name] = simplify(self._parameters[name]) return ParameterSpace(parameter_dict, shape=(self.local_size,)) def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" parameter_space.evaluate(simplify=False, mask=self._mask_local) for name, value in parameter_space.items(): self._parameters[name] = value PyNN-0.10.0/pyNN/mock/projections.py000066400000000000000000000040361415343567000171550ustar00rootroot00000000000000from itertools import repeat from pyNN import common from pyNN.core import ezip from pyNN.space import Space from . import simulator class Connection(common.Connection): """ Store an individual plastic connection and information about it. Provide an interface that allows access to the connection's weight, delay and other attributes. """ def __init__(self, pre, post, **attributes): self.presynaptic_index = pre self.postsynaptic_index = post for name, value in attributes.items(): setattr(self, name, value) def as_tuple(self, *attribute_names): # should return indices, not IDs for source and target return tuple([getattr(self, name) for name in attribute_names]) class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source=None, receptor_type=None, space=Space(), label=None): common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) # Create connections self.connections = [] connector.connect(self) def __len__(self): return len(self.connections) def set(self, **attributes): raise NotImplementedError def _convergent_connect(self, presynaptic_indices, postsynaptic_index, **connection_parameters): for name, value in connection_parameters.items(): if isinstance(value, float): connection_parameters[name] = repeat(value) for pre_idx, other in ezip(presynaptic_indices, *connection_parameters.values()): other_attributes = dict(zip(connection_parameters.keys(), other)) self.connections.append( Connection(pre_idx, postsynaptic_index, **other_attributes) ) PyNN-0.10.0/pyNN/mock/recording.py000066400000000000000000000023551415343567000165740ustar00rootroot00000000000000import numpy as np from pyNN import recording from . import simulator class Recorder(recording.Recorder): _simulator = simulator def _record(self, variable, new_ids, sampling_interval=None): pass def _get_spiketimes(self, id, clear=False): if hasattr(id, "__len__"): spks = {} for i in id: spks[i] = np.array([i, i + 5], dtype=float) % self._simulator.state.t return spks else: return np.array([id, id + 5], dtype=float) % self._simulator.state.t def _get_all_signals(self, variable, ids, clear=False): # assuming not using cvode, otherwise need to get times as well and use IrregularlySampledAnalogSignal n_samples = int(round(self._simulator.state.t / self._simulator.state.dt)) + 1 return np.vstack([np.random.uniform(size=n_samples) for id in ids]).T def _local_count(self, variable, filter_ids=None): N = {} if variable == 'spikes': for id in self.filter_recorded(variable, filter_ids): N[int(id)] = 2 else: raise Exception("Only implemented for spikes") return N def _clear_simulator(self): pass def _reset(self): pass PyNN-0.10.0/pyNN/mock/simulator.py000066400000000000000000000017061415343567000166360ustar00rootroot00000000000000from pyNN import common name = "MockSimulator" class ID(int, common.IDMixin): def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) class State(common.control.BaseState): def __init__(self): common.control.BaseState.__init__(self) self.mpi_rank = 0 self.num_processes = 1 self.clear() self.dt = 0.1 def run(self, simtime): self.t += simtime self.running = True def run_until(self, tstop): self.t = tstop self.running = True def clear(self): self.recorders = set([]) self.id_counter = 42 self.segment_counter = -1 self.reset() def reset(self): """Reset the state of the current network to time t = 0.""" self.running = False self.t = 0 self.t_start = 0 self.segment_counter += 1 state = State() PyNN-0.10.0/pyNN/mock/standardmodels.py000066400000000000000000000223721415343567000176250ustar00rootroot00000000000000# encoding: utf-8 """ Standard cells for the mock module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import cells, synapses, electrodes, build_translations, StandardCurrentSource from .simulator import state import logging logger = logging.getLogger("PyNN") class IF_curr_alpha(cells.IF_curr_alpha): __doc__ = cells.IF_curr_alpha.__doc__ translations = build_translations( # should add some computed/scaled parameters ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ) class IF_curr_exp(cells.IF_curr_exp): __doc__ = cells.IF_curr_exp.__doc__ translations = build_translations( # should add some computed/scaled parameters ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'T_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ) class IF_cond_alpha(cells.IF_cond_alpha): __doc__ = cells.IF_cond_alpha.__doc__ translations = build_translations( ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I') ) class IF_cond_exp(cells.IF_cond_exp): __doc__ = cells.IF_cond_exp.__doc__ translations = build_translations( ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I') ) class IF_facets_hardware1(cells.IF_facets_hardware1): __doc__ = cells.IF_facets_hardware1.__doc__ class HH_cond_exp(cells.HH_cond_exp): __doc__ = cells.HH_cond_exp.__doc__ translations = build_translations( ('gbar_Na', 'GBAR_NA'), ('gbar_K', 'GBAR_K'), ('g_leak', 'G_LEAK'), ('cm', 'CM'), ('v_offset', 'V_OFFSET'), ('e_rev_Na', 'E_REV_NA'), ('e_rev_K', 'E_REV_K'), ('e_rev_leak', 'E_REV_LEAK'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('i_offset', 'I_OFFSET'), ) class IF_cond_exp_gsfa_grr(cells.IF_cond_exp_gsfa_grr): __doc__ = cells.IF_cond_exp_gsfa_grr.__doc__ class SpikeSourcePoisson(cells.SpikeSourcePoisson): __doc__ = cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('start', 'START'), ('rate', 'INTERVAL', "1000.0/rate", "1000.0/INTERVAL"), ('duration', 'DURATION'), ) class SpikeSourceArray(cells.SpikeSourceArray): __doc__ = cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'SPIKE_TIMES'), ) class EIF_cond_alpha_isfa_ista(cells.EIF_cond_alpha_isfa_ista): __doc__ = cells.EIF_cond_alpha_isfa_ista.__doc__ translations = build_translations( ('cm', 'CM'), ('tau_refrac', 'TAU_REFRAC'), ('v_spike', 'V_SPIKE'), ('v_reset', 'V_RESET'), ('v_rest', 'V_REST'), ('tau_m', 'TAU_M'), ('i_offset', 'I_OFFSET'), ('a', 'A'), ('b', 'B'), ('delta_T', 'DELTA_T'), ('tau_w', 'TAU_W'), ('v_thresh', 'V_THRESH'), ('e_rev_E', 'E_REV_E'), ('tau_syn_E', 'TAU_SYN_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_I', 'TAU_SYN_I'), ) class EIF_cond_exp_isfa_ista(cells.EIF_cond_exp_isfa_ista): __doc__ = cells.EIF_cond_exp_isfa_ista.__doc__ translations = build_translations( ('cm', 'CM'), ('tau_refrac', 'TAU_REFRAC'), ('v_spike', 'V_SPIKE'), ('v_reset', 'V_RESET'), ('v_rest', 'V_REST'), ('tau_m', 'TAU_M'), ('i_offset', 'I_OFFSET'), ('a', 'A'), ('b', 'B'), ('delta_T', 'DELTA_T'), ('tau_w', 'TAU_W'), ('v_thresh', 'V_THRESH'), ('e_rev_E', 'E_REV_E'), ('tau_syn_E', 'TAU_SYN_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_I', 'TAU_SYN_I'), ) class Izhikevich(cells.Izhikevich): __doc__ = cells.Izhikevich.__doc__ translations = build_translations( ('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('i_offset', 'I_e'), ) standard_receptor_type = True receptor_scale = 1e-3 # synaptic weight is in mV, so need to undo usual weight scaling class MockCurrentSource(object): def inject_into(self, cells): __doc__ = StandardCurrentSource.inject_into.__doc__ pass class DCSource(MockCurrentSource, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop') ) class StepCurrentSource(MockCurrentSource, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitudes'), ('times', 'times') ) class ACSource(MockCurrentSource, electrodes.ACSource): __doc__ = electrodes.ACSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop'), ('frequency', 'frequency'), ('offset', 'offset'), ('phase', 'phase') ) class NoisyCurrentSource(MockCurrentSource, electrodes.NoisyCurrentSource): translations = build_translations( ('mean', 'mean'), ('start', 'start'), ('stop', 'stop'), ('stdev', 'stdev'), ('dt', 'dt') ) class StaticSynapse(synapses.StaticSynapse): __doc__ = synapses.StaticSynapse.__doc__ translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY'), ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d class TsodyksMarkramSynapse(synapses.TsodyksMarkramSynapse): __doc__ = synapses.TsodyksMarkramSynapse.__doc__ translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY'), ('U', 'UU'), ('tau_rec', 'TAU_REC'), ('tau_facil', 'TAU_FACIL'), ('u0', 'U0'), ('x0', 'X'), ('y0', 'Y') ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d class STDPMechanism(synapses.STDPMechanism): __doc__ = synapses.STDPMechanism.__doc__ base_translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY'), ('dendritic_delay_fraction', 'dendritic_delay_fraction') ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d class AdditiveWeightDependence(synapses.AdditiveWeightDependence): __doc__ = synapses.AdditiveWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) class MultiplicativeWeightDependence(synapses.MultiplicativeWeightDependence): __doc__ = synapses.MultiplicativeWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) class AdditivePotentiationMultiplicativeDepression(synapses.AdditivePotentiationMultiplicativeDepression): __doc__ = synapses.AdditivePotentiationMultiplicativeDepression.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) class GutigWeightDependence(synapses.GutigWeightDependence): __doc__ = synapses.GutigWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ('mu_plus', 'muLTP'), ('mu_minus', 'muLTD'), ) class SpikePairRule(synapses.SpikePairRule): __doc__ = synapses.SpikePairRule.__doc__ translations = build_translations( ('tau_plus', 'tauLTP'), ('tau_minus', 'tauLTD'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) PyNN-0.10.0/pyNN/models.py000066400000000000000000000076621415343567000151600ustar00rootroot00000000000000""" Base classes for cell and synapse models, whether "standard" (cross-simulator) or "native" (restricted to an individual simulator). :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import descriptions from pyNN.parameters import ParameterSpace class BaseModelType(object): """Base class for standard and native cell and synapse model classes.""" default_parameters = {} default_initial_values = {} parameter_checks = {} def __init__(self, **parameters): """ `parameters` should be a mapping object, e.g. a dict """ self.parameter_space = ParameterSpace(self.default_parameters, self.get_schema(), shape=None) if parameters: self.parameter_space.update(**parameters) def __repr__(self): # should really include the parameters explicitly, to be unambiguous return "%s()" % self.__class__.__name__ @classmethod def has_parameter(cls, name): """Does this model have a parameter with the given name?""" return name in cls.default_parameters @classmethod def get_parameter_names(cls): """Return the names of the parameters of this model.""" return list(cls.default_parameters.keys()) def get_schema(self): """ Returns the model schema: i.e. a mapping of parameter names to allowed parameter types. """ return dict((name, type(value)) for name, value in self.default_parameters.items()) def describe(self, template='modeltype_default.txt', engine='default'): """ Returns a human-readable description of the cell or synapse type. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = { "name": self.__class__.__name__, "default_parameters": self.default_parameters, "default_initial_values": self.default_initial_values, "parameters": self.parameter_space._parameters, # should add a describe() method to ParameterSpace } return descriptions.render(engine, template, context) class BaseCellType(BaseModelType): """Base class for cell model classes.""" recordable = [] receptor_types = [] conductance_based = True # override for cells with current-based synapses injectable = True # override for spike sources def can_record(self, variable): return variable in self.recordable class BaseCurrentSource(BaseModelType): """Base class for current source model classes.""" pass class BaseSynapseType(BaseModelType): """Base class for synapse model classes.""" # override to specify a non-standard connection type (i.e. GapJunctions) connection_type = None has_presynaptic_components = False # override for synapses that include an active presynaptic components def __init__(self, **parameters): """ `parameters` should be a mapping object, e.g. a dict """ all_parameters = self.default_parameters.copy() if parameters: all_parameters.update(**parameters) try: if all_parameters['delay'] is None: all_parameters['delay'] = self._get_minimum_delay() if all_parameters['weight'] is None: all_parameters['weight'] = 0. except KeyError as e: if e.args[0] != 'delay': # ElectricalSynapses don't have delays raise e self.parameter_space = ParameterSpace(all_parameters, self.get_schema(), shape=None) PyNN-0.10.0/pyNN/multisim.py000066400000000000000000000064401415343567000155310ustar00rootroot00000000000000""" A small framework to make it easier to run the same model on multiple simulators. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from multiprocessing import Process, Queue def run_simulation(network_model, sim, parameters, input_queue, output_queue): """ Build the model defined in the class `network_model`, with parameters `parameters`, and then consume tasks from `input_queue` until receiving the command 'STOP'. """ print("Running simulation with %s" % sim.__name__) network = network_model(sim, parameters) print("Network constructed with %s." % sim.__name__) for obj_name, attr, args, kwargs in iter(input_queue.get, 'STOP'): print("%s processing command %s.%s(%s, %s)" % (sim.__name__, obj_name, attr, args, kwargs)) obj = eval(obj_name) result = getattr(obj, attr)(*args, **kwargs) output_queue.put(result) print("Simulation with %s complete" % sim.__name__) class MultiSim(object): """ Interface that runs a network model on different simulators, with each simulation in a separate process. """ def __init__(self, sim_list, network_model, parameters): """ Build the model defined in the class `network_model`, with parameters `parameters`, for each of the simulator modules specified in `sim_list`. The `network_model` constructor takes arguments `sim` and `parameters`. """ self.processes = {} self.task_queues = {} self.result_queues = {} for sim in sim_list: task_queue = Queue() result_queue = Queue() p = Process(target=run_simulation, args=(network_model, sim, parameters, task_queue, result_queue)) p.start() self.processes[sim.__name__] = p self.task_queues[sim.__name__] = task_queue self.result_queues[sim.__name__] = result_queue def __iter__(self): return self.processes.values() def __getattr__(self, name): """ Assumes `name` is a method of the `network_model` model. Return a function that runs `net.name()` for all the simulators. """ def iterate_over_nets(*args, **kwargs): retvals = {} for sim_name in self.processes: self.task_queues[sim_name].put(('network', name, args, kwargs)) for sim_name in self.processes: retvals[sim_name] = self.result_queues[sim_name].get() return retvals return iterate_over_nets def run(self, simtime, steps=1): # , *callbacks): """ Run the model for a time `simtime` in all simulators. The run may be broken into a number of steps (each of equal duration). #Any functions in `callbacks` will be called after each step. """ dt = float(simtime) / steps for i in range(steps): for sim_name in self.processes: self.task_queues[sim_name].put(('sim', 'run', [dt], {})) for sim_name in self.processes: t = self.result_queues[sim_name].get() def end(self): for sim_name in self.processes: self.task_queues[sim_name].put('STOP') self.processes[sim_name].join() PyNN-0.10.0/pyNN/nest/000077500000000000000000000000001415343567000142615ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nest/__init__.py000066400000000000000000000160541415343567000164000ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ NEST v3 implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import warnings try: import tables # due to freeze when importing nest before tables except ImportError: pass import nest from . import simulator from pyNN import common, recording, errors, space, __doc__ from pyNN.common.control import DEFAULT_MAX_DELAY, DEFAULT_TIMESTEP, DEFAULT_MIN_DELAY # if recording.MPI and (nest.Rank() != recording.mpi_comm.rank): # raise Exception("MPI not working properly. Please make sure you import pyNN.nest before pyNN.random.") import shutil import logging from pyNN.nest.cells import NativeCellType, native_cell_type from pyNN.nest.electrodes import NativeElectrodeType, native_electrode_type from pyNN.nest.synapses import NativeSynapseType, native_synapse_type from pyNN.nest.standardmodels.cells import * from pyNN.nest.connectors import * from pyNN.nest.standardmodels.synapses import * from pyNN.nest.standardmodels.electrodes import * from pyNN.nest.recording import * from pyNN.random import NumpyRNG, GSLRNG from pyNN.nest.random import NativeRNG from pyNN.space import Space from pyNN.standardmodels import StandardCellType from pyNN.nest.populations import Population, PopulationView, Assembly from pyNN.nest.projections import Projection logger = logging.getLogger("PyNN") # ============================================================================== # Utility functions # ============================================================================== def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" standard_cell_types = [obj for obj in globals().values() if isinstance( obj, type) and issubclass(obj, StandardCellType) and obj is not StandardCellType] for cell_class in standard_cell_types: try: create(cell_class()) except Exception as e: print("Warning: %s is defined, but produces the following error: %s" % (cell_class.__name__, e)) standard_cell_types.remove(cell_class) return [obj.__name__ for obj in standard_cell_types] def _discrepancy_due_to_rounding(parameters, output_values): """NEST rounds delays to the time step.""" if 'delay' not in parameters: return False else: # the logic here is not the clearest, the aim was to keep # _set_connection() as simple as possible, but it might be better to # refactor the whole thing. input_delay = parameters['delay'] if hasattr(output_values, "__len__"): output_delay = output_values[parameters.keys().index('delay')] else: output_delay = output_values return abs(input_delay - output_delay) < get_time_step() # ============================================================================== # Functions for simulation set-up and control # ============================================================================== def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): """ Should be called at the very beginning of a script. `extra_params` contains any keyword arguments that are required by a given simulator but not by others. NEST-specific extra_params: `spike_precision`: should be "off_grid" (default) or "on_grid" `verbosity`: one of: "all", "info", "deprecated", "warning", "error", "fatal" `recording_precision`: number of decimal places (OR SIGNIFICANT FIGURES?) in recorded data `threads`: number of threads to use `grng_seed`: one seed for the global random number generator of NEST `rng_seeds`: a list of seeds, one for each thread on each MPI process `rng_seeds_seed`: a single seed that will be used to generate random values for `rng_seeds` `t_flush`: extra time to run the simulation after using reset() to ensure the previous run does not influence the new one """ max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) common.setup(timestep, min_delay, **extra_params) simulator.state.clear() for key in ("threads", "verbosity", "spike_precision", "recording_precision"): if key in extra_params: setattr(simulator.state, key, extra_params[key]) # set kernel RNG seeds simulator.state.num_threads = extra_params.get('threads') or 1 if 'grng_seed' in extra_params: warnings.warn("The setup argument 'grng_seed' is now 'rng_seed'") simulator.state.rng_seed = extra_params['grng_seed'] if 'rng_seeds' in extra_params: warnings.warn("The setup argument 'rng_seeds' is no longer available. Taking the first value for the global seed.") simulator.state.rng_seed = extra_params['rng_seeds'][0] if 'rng_seeds_seed' in extra_params: warnings.warn("The setup argument 'rng_seeds_seed' is now 'rng_seed'") simulator.state.rng_seed = extra_params['rng_seeds_seed'] else: simulator.state.rng_seed = extra_params.get('rng_seed', 42) if "t_flush" in extra_params: # see https://github.com/nest/nest-simulator/issues/1618 simulator.state.t_flush = extra_params["t_flush"] # set resolution simulator.state.dt = timestep # Set min_delay and max_delay simulator.state.set_delays(min_delay, max_delay) nest.SetDefaults('spike_generator', {'precise_times': True}) return rank() def end(): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: logger.debug("%s%s --> %s" % (population.label, variables, filename)) io = recording.get_io(filename) population.write_data(io, variables) for tempdir in simulator.state.tempdirs: shutil.rmtree(tempdir) simulator.state.tempdirs = [] simulator.state.write_on_end = [] run, run_until = common.build_run(simulator) run_for = run reset = common.build_reset(simulator) initialize = common.initialize # ============================================================================== # Functions returning information about the simulation state # ============================================================================== get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) # ============================================================================== # Low-level API for creating, connecting and recording from individual neurons # ============================================================================== create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) set = common.set record = common.build_record(simulator) def record_v(source, filename): return record(['v'], source, filename) def record_gsyn(source, filename): return record(['gsyn_exc', 'gsyn_inh'], source, filename) # ============================================================================== PyNN-0.10.0/pyNN/nest/cells.py000066400000000000000000000065701415343567000157450ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Definition of NativeCellType class for NEST. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import warnings import numpy as np import nest from pyNN.models import BaseCellType from pyNN.parameters import Sequence from . import conversion UNITS_MAP = { 'spikes': 'ms', 'V_m': 'mV', 'I_syn_ex': 'pA', 'I_syn_in': 'pA' } def get_defaults(model_name): valid_types = (int, float, Sequence, np.ndarray) defaults = nest.GetDefaults(model_name) variables = defaults.get('recordables', []) ignore = ['archiver_length', 'available', 'Ca', 'capacity', 'elementsize', 'frozen', 'instantiations', 'local', 'model', 'needs_prelim_update', 'recordables', 'state', 't_spike', 'tau_minus', 'tau_minus_triplet', 'thread', 'vp', 'receptor_types', 'events', 'global_id', 'element_type', 'type', 'type_id', 'has_connections', 'n_synapses', 'thread_local_id', 'node_uses_wfr', 'supports_precise_spikes', 'synaptic_elements', 'y_0', 'y_1', 'allow_offgrid_spikes', 'shift_now_spikes', 'post_trace'] default_params = {} default_initial_values = {} for name, value in defaults.items(): if name in variables: default_initial_values[name] = value elif name not in ignore: if isinstance(value, valid_types): default_params[name] = conversion.make_pynn_compatible(value) else: warnings.warn("Ignoring parameter '%s' since PyNN does not support %s" % (name, type(value))) return default_params, default_initial_values def get_receptor_types(model_name): return list(nest.GetDefaults(model_name).get("receptor_types", ('excitatory', 'inhibitory'))) def get_recordables(model_name): try: return [name for name in nest.GetDefaults(model_name, "recordables")] except nest.NESTError as err: return [] def native_cell_type(model_name): """ Return a new NativeCellType subclass. """ assert isinstance(model_name, str) default_parameters, default_initial_values = get_defaults(model_name) receptor_types = get_receptor_types(model_name) recordable = get_recordables(model_name) + ['spikes'] element_type = nest.GetDefaults(model_name, 'element_type') return type(model_name, (NativeCellType,), {'nest_model': model_name, 'default_parameters': default_parameters, 'default_initial_values': default_initial_values, 'receptor_types': receptor_types, 'injectable': ("V_m" in default_initial_values), 'recordable': recordable, 'units': dict(((var, UNITS_MAP.get(var, 'unknown')) for var in recordable)), 'standard_receptor_type': (receptor_types == ['excitatory', 'inhibitory']), 'nest_name': {"on_grid": model_name, "off_grid": model_name}, 'conductance_based': ("g" in (s[0] for s in recordable)), 'always_local': element_type == 'stimulator', 'uses_parrot': element_type == 'stimulator' }) class NativeCellType(BaseCellType): def get_receptor_type(self, name): return nest.GetDefaults(self.nest_model)["receptor_types"][name] PyNN-0.10.0/pyNN/nest/connectors.py000066400000000000000000000214441415343567000170150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Connection method classes for NEST. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from warnings import warn import nest try: import csa haveCSA = True except ImportError: haveCSA = False from pyNN import random from pyNN.connectors import (Connector, AllToAllConnector, FixedProbabilityConnector, OneToOneConnector, FixedNumberPreConnector, FixedNumberPostConnector, DistanceDependentProbabilityConnector, DisplacementDependentProbabilityConnector, IndexBasedProbabilityConnector, SmallWorldConnector, FromListConnector, FromFileConnector, CloneConnector, ArrayConnector, FixedTotalNumberConnector, CSAConnector as DefaultCSAConnector) from .random import NativeRNG logger = logging.getLogger("PyNN") class CSAConnector(DefaultCSAConnector): """ Use the Connection-Set Algebra (Djurfeldt, 2012) to connect cells. This is an optimized variant of CSAConnector, which iterates the connection-set on the C++ level in NEST. See Djurfeldt et al. (2014) doi:10.3389/fninf.2014.00043 for more details about the new interface and a comparison between this and PyNN's native CSAConnector. Takes any of the standard :class:`Connector` optional arguments and, in addition: `cset`: a connection set object. """ def connect(self, projection): if nest.ll_api.sli_func("statusdict/have_libneurosim ::"): return self.cg_connect(projection) else: warn("Note: using the default CSAConnector. To use the accelerated version for NEST,\n" "Please re-compile NEST using --with-libneurosim=PATH") return super(CSAConnector, self).connect(projection) def cg_connect(self, projection): """Connect-up a Projection using the Connection Generator interface""" presynaptic_cells = projection.pre.all_cells.astype('int64') postsynaptic_cells = projection.post.all_cells.astype('int64') if csa.arity(self.cset) == 2: param_map = {'weight': 0, 'delay': 1} nest.CGConnect(presynaptic_cells, postsynaptic_cells, self.cset, param_map, projection.nest_synapse_model) else: nest.CGConnect(presynaptic_cells, postsynaptic_cells, self.cset, model=projection.nest_synapse_model) # reset the caching of the connection list, since this will have to be recalculated projection._connections = None projection._sources.extend(presynaptic_cells) class NESTConnectorMixin(object): def synapse_parameters(self, projection): params = {'synapse_model': projection.nest_synapse_model} parameter_space = self._parameters_from_synapse_type(projection, distance_map=None) for name, value in parameter_space.items(): if name in ('tau_minus', 'dendritic_delay_fraction', 'w_min_always_zero_in_NEST'): continue if isinstance(value.base_value, random.RandomDistribution): # Random Distribution specified if isinstance(value.base_value.rng, NativeRNG): logger.warning( "Random values will be created inside NEST with NEST's own RNGs") # todo: re-enable support for clipped and clipped_to_boundary params[name] = value.evaluate().as_nest_object() else: value.shape = (projection.pre.size, projection.post.size) params[name] = value.evaluate() else: # explicit values given if value.is_homogeneous: params[name] = value.evaluate(simplify=True) elif value.shape: # If parameter is given as an array or function params[name] = value.evaluate().flatten() else: value.shape = (1, 1) # If parameter is given as a single number. Checking of the dimensions should be done in NEST params[name] = float(value.evaluate()) if name == "weight" and projection.receptor_type == 'inhibitory' and self.post.conductance_based: # NEST wants negative values for inhibitory weights, even if these are conductances params[name] *= -1 return params class FixedProbabilityConnector(FixedProbabilityConnector, NESTConnectorMixin): def connect(self, projection): if projection.synapse_type.native_parameters.has_native_rngs or isinstance(self.rng, NativeRNG): return self.native_connect(projection) else: return super(FixedProbabilityConnector, self).connect(projection) def native_connect(self, projection): syn_params = self.synapse_parameters(projection) rule_params = {'allow_autapses': self.allow_self_connections, 'allow_multapses': False, 'rule': 'pairwise_bernoulli', 'p': self.p_connect} projection._connect(rule_params, syn_params) class AllToAllConnector(AllToAllConnector, NESTConnectorMixin): def connect(self, projection): # or projection.synapse_type.native_parameters.non_random: TODO if projection.synapse_type.native_parameters.has_native_rngs: return self.native_connect(projection) else: return super(AllToAllConnector, self).connect(projection) def native_connect(self, projection): syn_params = self.synapse_parameters(projection) rule_params = {'allow_autapses': self.allow_self_connections, 'allow_multapses': False, 'rule': 'all_to_all'} projection._connect(rule_params, syn_params) # class OneToOneConnector(): # # def __init__(self, allow_self_connections=True, with_replacement=True, safe=True, # callback=None): # self.allow_self_connections = allow_self_connections # self.with_replacement = with_replacement # # def connect(self, projection): # syn_params = projection.synapse_parameters() # rule_params = {'autapses': self.allow_self_connections, # 'multapses': self.with_replacement, # 'rule': 'one_to_one'} # # projection._connect(rule_params, syn_params) # # # class FixedNumberPreConnector(): # # def __init__(self, n, allow_self_connections=True, with_replacement=True, safe=True, # callback=None, rng=None): # self.allow_self_connections = allow_self_connections # self.with_replacement = with_replacement # self.n = n # # def connect(self, projection): # syn_params = projection.synapse_parameters() # rule_params = {'autapses': self.allow_self_connections, # 'multapses': self.with_replacement, # 'rule': 'fixed_indegree', # 'indegree': self.n } # # projection._connect(rule_params, syn_params) # # # class FixedNumberPostConnector(): # # def __init__(self, n, allow_self_connections=True, with_replacement=True, safe=True, # callback=None, rng=None): # self.allow_self_connections = allow_self_connections # self.with_replacement = with_replacement # self.n = n # # def connect(self, projection): # syn_params = projection.synapse_parameters() # rule_params = {'autapses': self.allow_self_connections, # 'multapses': self.with_replacement, # 'rule': 'fixed_outdegree', # 'outdegree': self.n } # # projection._connect(rule_params, syn_params) # # # class FixedTotalNumberConnector(): # # def __init__(self, n, allow_self_connections=True, with_replacement=True, safe=True, # callback=None): # self.allow_self_connections = allow_self_connections # self.with_replacement = with_replacement # self.n = n # # def connect(self, projection): # syn_params = projection.synapse_parameters() # rule_params = {'autapses': self.allow_self_connections, # 'multapses': self.with_replacement, # 'rule': 'fixed_total_number', # 'N': self.n # } # projection._connect(rule_params, syn_params) PyNN-0.10.0/pyNN/nest/conversion.py000066400000000000000000000047651415343567000170340ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Conversion functions to NEST-compatible data types. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np from pyNN.parameters import Sequence def make_sli_compatible_single(value): if isinstance(value, Sequence): return_value = value.value elif isinstance(value, np.ndarray): if value.dtype == object and isinstance(value[0], Sequence): # check if the shape of the array is something other than (1,) # to my knowledge nest cannot handle that assert value.shape == (1,), "NEST expects 1 dimensional arrays" return_value = value[0].value elif value.shape == (1,): # for nest.SetDefaults, there is a difference between an (1,)-array # and a scalar value return_value = value[0] else: return_value = value else: return_value = value # nest does not understand numpy boolean values if isinstance(return_value, np.bool_): return_value = bool(return_value) return return_value def make_sli_compatible(container): """ Makes sure container only contains datatypes understood by the nest kernel. container can be scalar, a list or a dict. """ compatible = None if isinstance(container, list): compatible = [] for value in container: compatible.append(make_sli_compatible_single(value)) elif isinstance(container, dict): compatible = {} for k, v in container.items(): compatible[k] = make_sli_compatible_single(v) else: compatible = make_sli_compatible_single(container) return compatible def make_pynn_compatible_single(value): # check if parameter is non-scalar if isinstance(value, np.ndarray): return Sequence(value) else: return value def make_pynn_compatible(container): """ Make sure that all entries in container do not confuse pyNN. container can be scalar, a list or a dict. """ compatible = None if isinstance(container, list): compatible = [] for value in container: compatible.append(make_pynn_compatible_single(value)) elif isinstance(container, dict): compatible = {} for k, v in container.items(): compatible[k] = make_pynn_compatible_single(v) else: compatible = make_pynn_compatible_single(container) return compatible PyNN-0.10.0/pyNN/nest/electrodes.py000066400000000000000000000103041415343567000167620ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Definition of NativeElectrodeType class for NEST. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import nest from pyNN.common import Population, PopulationView, Assembly from pyNN.parameters import ParameterSpace from pyNN.nest.simulator import state from pyNN.nest.cells import get_defaults from pyNN.models import BaseCurrentSource from .conversion import make_sli_compatible class NestCurrentSource(BaseCurrentSource): """Base class for a nest source of current to be injected into a neuron.""" def __init__(self, **parameters): self._device = nest.Create(self.nest_name) self.cell_list = [] self.parameter_space = ParameterSpace(self.default_parameters, self.get_schema(), shape=(1,)) if parameters: self.parameter_space.update(**parameters) self.min_delay = state.min_delay self.timestep = state.dt # NoisyCurrentSource has a parameter called "dt", so use "timestep" here state.current_sources.append(self) def inject_into(self, cells): for id in cells: if id.local and not id.celltype.injectable: raise TypeError("Can't inject current into a spike source.") if isinstance(cells, (Population, PopulationView, Assembly)): self.cell_list = [cell for cell in cells] else: self.cell_list = cells nest.Connect(self._device, self.cell_list, syn_spec={"delay": state.min_delay}) def _reset(self): # after a reset, need to adjust parameters for time offset updated_params = {} for name, value in self.parameter_space.items(): if name in ("start", "stop", "amplitude_times"): updated_params[name] = value if updated_params: state.set_status(self._device, updated_params) def _delay_correction(self, value): """ A change in a device requires a min_delay to take effect at the target """ corrected = value - self.min_delay # set negative times to zero if isinstance(value, np.ndarray): corrected = np.where(corrected > 0, corrected, 0.0) else: corrected = max(corrected, 0.0) return corrected def record(self): self.i_multimeter = nest.Create( 'multimeter', params={'record_from': ['I'], 'interval': state.dt}) nest.Connect(self.i_multimeter, self._device) def _get_data(self): events = nest.GetStatus(self.i_multimeter)[0]['events'] # Similar to recording.py: NEST does not record values at # the zeroth time step, so we add them here. t_arr = np.insert(np.array(events['times']), 0, 0.0) i_arr = np.insert(np.array(events['I']/1000.0), 0, 0.0) # NEST and pyNN have different concepts of current initiation times # To keep this consistent across simulators, we will have current # initiating at the electrode at t_start and effect on cell at next dt # This requires padding min_delay equivalent period with 0's pad_length = int(self.min_delay/self.timestep) i_arr = np.insert(i_arr[:-pad_length], 0, [0]*pad_length) return t_arr, i_arr def native_electrode_type(model_name): """ Return a new NativeElectrodeType subclass. """ assert isinstance(model_name, str) default_parameters, default_initial_values = get_defaults(model_name) return type(model_name, (NativeElectrodeType,), {'nest_name': model_name, 'default_parameters': default_parameters, 'default_initial_values': default_initial_values, }) # Should be usable with any NEST current generator class NativeElectrodeType(NestCurrentSource): _is_computed = True _is_playable = True def __init__(self, **parameters): NestCurrentSource.__init__(self, **parameters) self.parameter_space.evaluate(simplify=True) state.set_status(self._device, make_sli_compatible(self.parameter_space.as_dict())) PyNN-0.10.0/pyNN/nest/extensions/000077500000000000000000000000001415343567000164605ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nest/extensions/CMakeLists.txt000066400000000000000000000210401415343567000212150ustar00rootroot00000000000000# :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. # :license: CeCILL, see LICENSE for details. cmake_minimum_required( VERSION 2.8.12 ) # This CMakeLists.txt is configured to build your external module for NEST. # # The configuration requires a compiled and installed NEST; if `nest-config` is # not in the PATH, please specify the absolute path with `-Dwith-nest=...`. # # For more informations on how to extend and use your module see: # https://nest.github.io/nest-simulator/extension_modules # 1) Name your module here, i.e. add later with -Dexternal-modules=my: set( SHORT_NAME pynn ) # the complete module name is here: set( MODULE_NAME pynn_extensions ) # 2) Add all your sources here set( MODULE_SOURCES pynn_extensions.h pynn_extensions.cpp simple_stochastic_synapse.h stochastic_stp_synapse.h stochastic_stp_synapse_impl.h ) # 3) We require a header name like this: set( MODULE_HEADER ${MODULE_NAME}.h ) # containing the class description of the class extending the SLIModule # 4) Specify your module version set( MODULE_VERSION_MAJOR 1 ) set( MODULE_VERSION_MINOR 0 ) set( MODULE_VERSION "${MODULE_VERSION_MAJOR}.${MODULE_VERSION_MINOR}" ) # 5) Leave the rest as is. All files in `sli` will be installed to # `share/nest/sli/`, so that NEST will find the during initialization. # Leave the call to "project(...)" for after the compiler is determined. # Set the `nest-config` executable to use during configuration. set( with-nest OFF CACHE STRING "Specify the `nest-config` executable." ) # If it is not set, look for a `nest-config` in the PATH. if ( NOT with-nest ) # try find the program ourselves find_program( NEST_CONFIG NAMES nest-config ) if ( NEST_CONFIG STREQUAL "NEST_CONFIG-NOTFOUND" ) message( FATAL_ERROR "Cannot find the program `nest-config`. Specify via -Dwith-nest=... ." ) endif () else () set( NEST_CONFIG ${with-nest} ) endif () # Use `nest-config` to get the compile and installation options used with the # NEST installation. # Get the compiler that was used for NEST. execute_process( COMMAND ${NEST_CONFIG} --compiler RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_COMPILER OUTPUT_STRIP_TRAILING_WHITESPACE ) # One check on first execution, if `nest-config` is working. if ( NOT RES_VAR EQUAL 0 ) message( FATAL_ERROR "Cannot run `${NEST_CONFIG}`. Please specify correct `nest-config` via -Dwith-nest=... " ) endif () # Setting the compiler has to happen before the call to "project(...)" function. set( CMAKE_CXX_COMPILER "${NEST_COMPILER}" ) project( ${MODULE_NAME} CXX ) # Get the install prefix. execute_process( COMMAND ${NEST_CONFIG} --prefix RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_PREFIX OUTPUT_STRIP_TRAILING_WHITESPACE ) # Use the `NEST_PREFIX` as `CMAKE_INSTALL_PREFIX`. set( CMAKE_INSTALL_PREFIX "${NEST_PREFIX}" CACHE STRING "Install path prefix, prepended onto install directories." FORCE ) # Get the CXXFLAGS. execute_process( COMMAND ${NEST_CONFIG} --cflags RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_CXXFLAGS OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the Includes. execute_process( COMMAND ${NEST_CONFIG} --includes RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_INCLUDES OUTPUT_STRIP_TRAILING_WHITESPACE ) if ( NEST_INCLUDES ) # make a cmake list string( REPLACE " " ";" NEST_INCLUDES_LIST "${NEST_INCLUDES}" ) foreach ( inc_complete ${NEST_INCLUDES_LIST} ) # if it is actually a -Iincludedir if ( "${inc_complete}" MATCHES "^-I.*" ) # get the directory string( REGEX REPLACE "^-I(.*)" "\\1" inc "${inc_complete}" ) # and check whether it is a directory if ( IS_DIRECTORY "${inc}" ) include_directories( "${inc}" ) endif () endif () endforeach () endif () # Get, if NEST is build as a (mostly) static application. If yes, also only build # static library. execute_process( COMMAND ${NEST_CONFIG} --static-libraries RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_STATIC_LIB OUTPUT_STRIP_TRAILING_WHITESPACE ) if ( NEST_STATIC_LIB ) set( BUILD_SHARED_LIBS OFF ) else () set( BUILD_SHARED_LIBS ON ) endif () # Get all linked libraries. execute_process( COMMAND ${NEST_CONFIG} --libs RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_LIBS OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the data install dir. execute_process( COMMAND ${NEST_CONFIG} --datadir RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_DATADIR OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the documentation install dir. execute_process( COMMAND ${NEST_CONFIG} --docdir RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_DOCDIR OUTPUT_STRIP_TRAILING_WHITESPACE ) # Get the library install dir. execute_process( COMMAND ${NEST_CONFIG} --libdir RESULT_VARIABLE RES_VAR OUTPUT_VARIABLE NEST_LIBDIR OUTPUT_STRIP_TRAILING_WHITESPACE ) # on OS X set( CMAKE_MACOSX_RPATH ON ) # Install all stuff to NEST's install directories. set( CMAKE_INSTALL_LIBDIR ${NEST_LIBDIR}/nest CACHE STRING "object code libraries (lib/nest or lib64/nest or lib//nest on Debian)" FORCE ) set( CMAKE_INSTALL_DOCDIR ${NEST_DOCDIR} CACHE STRING "documentation root (DATAROOTDIR/doc/nest)" FORCE ) set( CMAKE_INSTALL_DATADIR ${NEST_DATADIR} CACHE STRING "read-only architecture-independent data (DATAROOTDIR/nest)" FORCE ) include( GNUInstallDirs ) # CPack stuff. Required for target `dist`. set( CPACK_GENERATOR TGZ ) set( CPACK_SOURCE_GENERATOR TGZ ) set( CPACK_PACKAGE_DESCRIPTION_SUMMARY "NEST Module ${MODULE_NAME}" ) set( CPACK_PACKAGE_VENDOR "NEST Initiative (http://www.nest-initiative.org/)" ) set( CPACK_PACKAGE_VERSION_MAJOR ${MODULE_VERSION_MAJOR} ) set( CPACK_PACKAGE_VERSION_MINOR ${MODULE_VERSION_MINOR} ) set( CPACK_PACKAGE_VERSION ${MODULE_VERSION} ) set( CPACK_SOURCE_IGNORE_FILES "\\\\.gitignore" "\\\\.git/" "\\\\.travis\\\\.yml" # if we have in source builds "/build/" "/_CPack_Packages/" "CMakeFiles/" "cmake_install\\\\.cmake" "Makefile.*" "CMakeCache\\\\.txt" "CPackConfig\\\\.cmake" "CPackSourceConfig\\\\.cmake" ) set( CPACK_SOURCE_PACKAGE_FILE_NAME ${MODULE_NAME} ) set( CPACK_PACKAGE_INSTALL_DIRECTORY "${MODULE_NAME} ${MODULE_VERSION}" ) include( CPack ) # add make dist target add_custom_target( dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source # not sure about this... seems, that it will be removed before dist... # DEPENDS doc COMMENT "Creating a source distribution from ${MODULE_NAME}..." ) if ( BUILD_SHARED_LIBS ) # When building shared libraries, also create a module for loading at runtime # with the `Install` command. add_library( ${MODULE_NAME}_module MODULE ${MODULE_SOURCES} ) set_target_properties( ${MODULE_NAME}_module PROPERTIES COMPILE_FLAGS "${NEST_CXXFLAGS} -DLTX_MODULE" LINK_FLAGS "${NEST_LIBS}" PREFIX "" OUTPUT_NAME ${MODULE_NAME} ) install( TARGETS ${MODULE_NAME}_module DESTINATION ${CMAKE_INSTALL_LIBDIR} ) endif () # Build dynamic/static library for standard linking from NEST. add_library( ${MODULE_NAME}_lib ${MODULE_SOURCES} ) if ( BUILD_SHARED_LIBS ) # Dynamic libraries are initiated by a `global` variable of the `SLIModule`, # which is included, when the flag `LINKED_MODULE` is set. target_compile_definitions( ${MODULE_NAME}_lib PRIVATE -DLINKED_MODULE ) endif () set_target_properties( ${MODULE_NAME}_lib PROPERTIES COMPILE_FLAGS "${NEST_CXXFLAGS}" LINK_FLAGS "${NEST_LIBS}" OUTPUT_NAME ${MODULE_NAME} ) # Install library, header and sli init files. install( TARGETS ${MODULE_NAME}_lib DESTINATION ${CMAKE_INSTALL_LIBDIR} ) install( FILES ${MODULE_HEADER} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} ) install( DIRECTORY sli DESTINATION ${CMAKE_INSTALL_DATADIR} ) message( "" ) message( "-------------------------------------------------------" ) message( "${MODULE_NAME} Configuration Summary" ) message( "-------------------------------------------------------" ) message( "" ) message( "C++ compiler : ${CMAKE_CXX_COMPILER}" ) message( "Build static libs : ${NEST_STATIC_LIB}" ) message( "C++ compiler flags : ${CMAKE_CXX_FLAGS}" ) message( "NEST compiler flags : ${NEST_CXXFLAGS}" ) message( "NEST include dirs : ${NEST_INCLUDES}" ) message( "NEST libraries flags : ${NEST_LIBS}" ) message( "" ) message( "-------------------------------------------------------" ) message( "" ) message( "You can build and install ${MODULE_NAME} now, using" ) message( " make" ) message( " make install" ) message( "" ) message( "${MODULE_NAME} will be installed to: ${CMAKE_INSTALL_FULL_LIBDIR}" ) message( "" ) PyNN-0.10.0/pyNN/nest/extensions/pynn_extensions.cpp000066400000000000000000000073031415343567000224320ustar00rootroot00000000000000/* :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. */ #include "pynn_extensions.h" // Generated includes: #include "config.h" // include headers with your own stuff #include "simple_stochastic_synapse.h" #include "stochastic_stp_synapse.h" #include "stochastic_stp_synapse_impl.h" // Includes from nestkernel: #include "connection_manager_impl.h" #include "connector_model_impl.h" #include "dynamicloader.h" #include "exceptions.h" #include "genericmodel.h" #include "kernel_manager.h" #include "model.h" #include "model_manager_impl.h" #include "nest.h" #include "nest_impl.h" #include "nestmodule.h" #include "target_identifier.h" // Includes from sli: #include "booldatum.h" #include "integerdatum.h" #include "sliexceptions.h" #include "tokenarray.h" // -- Interface to dynamic module loader --------------------------------------- /* * There are three scenarios, in which PyNNExtensions can be loaded by NEST: * * 1) When loading your module with `Install`, the dynamic module loader must * be able to find your module. You make the module known to the loader by * defining an instance of your module class in global scope. (LTX_MODULE is * defined) This instance must have the name * * _LTX_mod * * The dynamicloader can then load modulename and search for symbol "mod" in it. * * 2) When you link the library dynamically with NEST during compilation, a new * object has to be created. In the constructor the DynamicLoaderModule will * register your module. (LINKED_MODULE is defined) * * 3) When you link the library statically with NEST during compilation, the * registration will take place in the file `static_modules.h`, which is * generated by cmake. */ #if defined( LTX_MODULE ) | defined( LINKED_MODULE ) pynn::PyNNExtensions pynn_extensions_LTX_mod; #endif // -- DynModule functions ------------------------------------------------------ pynn::PyNNExtensions::PyNNExtensions() { #ifdef LINKED_MODULE // register this module at the dynamic loader // this is needed to allow for linking in this module at compile time // all registered modules will be initialized by the main app's dynamic loader nest::DynamicLoaderModule::registerLinkedModule( this ); #endif } pynn::PyNNExtensions::~PyNNExtensions() { } const std::string pynn::PyNNExtensions::name( void ) const { return std::string( "PyNN extensions for NEST" ); // Return name of the module } const std::string pynn::PyNNExtensions::commandstring( void ) const { // Instruct the interpreter to load pynn_extensions-init.sli return std::string( "(pynn_extensions-init) run" ); } //------------------------------------------------------------------------------------- void pynn::PyNNExtensions::init( SLIInterpreter* i ) { /* Register a neuron or device model. Give node type as template argument and the name as second argument. */ /* nest::kernel().model_manager.register_node_model< pif_psc_alpha >( "pif_psc_alpha" ); */ /* Register a synapse type. Give synapse type as template argument and the name as second argument. There are two choices for the template argument: - nest::TargetIdentifierPtrRport - nest::TargetIdentifierIndex The first is the standard and you should usually stick to it. nest::TargetIdentifierIndex reduces the memory requirement of synapses even further, but limits the number of available rports. Please see Kunkel et al, Front Neurofinfom 8:78 (2014), Sec 3.3.2, for details. */ nest::register_connection_model< simple_stochastic_synapse >( "simple_stochastic_synapse" ); nest::register_connection_model< stochastic_stp_synapse >( "stochastic_stp_synapse" ); } // PyNNExtensions::init() PyNN-0.10.0/pyNN/nest/extensions/pynn_extensions.h000066400000000000000000000024441415343567000221000ustar00rootroot00000000000000/* :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. */ #ifndef PYNNEXTENSIONS_H #define PYNNEXTENSIONS_H // Includes from sli: #include "slifunction.h" #include "slimodule.h" // Put your stuff into your own namespace. namespace pynn { /** * Class defining your model. * @note For each model, you must define one such class, with a unique name. */ class PyNNExtensions : public SLIModule { public: // Interface functions ------------------------------------------ /** * @note The constructor registers the module with the dynamic loader. * Initialization proper is performed by the init() method. */ PyNNExtensions(); /** * @note The destructor does not do much in modules. */ ~PyNNExtensions(); /** * Initialize module. * @param SLIInterpreter* SLI interpreter */ void init( SLIInterpreter* ); /** * Return the name of your model. */ const std::string name( void ) const; /** * Return the name of a sli file to execute when the module is loaded. * This mechanism can be used to define SLI commands associated with your * module, in particular, set up type tries for functions you have defined. */ const std::string commandstring( void ) const; }; } // namespace pynn #endif PyNN-0.10.0/pyNN/nest/extensions/simple_stochastic_synapse.h000066400000000000000000000132551415343567000241160ustar00rootroot00000000000000/* * :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. * :license: CeCILL, see LICENSE for details. * */ #ifndef SIMPLE_STOCHASTIC_SYNAPSE_H #define SIMPLE_STOCHASTIC_SYNAPSE_H // Includes from nestkernel: #include "connection.h" /* BeginUserDocs: synapse, short-term plasticity Short description +++++++++++++++++ Synapse dropping spikes stochastically. Description +++++++++++ This synapse will deliver spikes with probability p. Parameters ++++++++++ = ==== ========================================================================================= p real probability that a spike is transmitted, default = 1.0 (i.e. spike is always transmitted) = ==== ========================================================================================= Transmits +++++++++ SpikeEvent SeeAlso +++++++ static_synapse, synapsedict EndUserDocs */ namespace pynn { template < typename targetidentifierT > class simple_stochastic_synapse : public nest::Connection< targetidentifierT > { private: double weight_; //!< Synaptic weight double p_; //!< Probability of spike transmission public: //! Type to use for representing common synapse properties typedef nest::CommonSynapseProperties CommonPropertiesType; //! Shortcut for base class typedef nest::Connection< targetidentifierT > ConnectionBase; /** * Default Constructor. * Sets default values for all parameters. Needed by GenericConnectorModel. */ simple_stochastic_synapse() : ConnectionBase() , weight_( 1.0 ) , p_( 1.0 ) { } //! Default Destructor. ~simple_stochastic_synapse() { } /** * Helper class defining which types of events can be transmitted. * * These methods are only used to test whether a certain type of connection * can be created. * * `handles_test_event()` should be added for all event types that the * synapse can transmit. The methods shall return `invalid_port_`; the * return value will be ignored. * * Since this is a synapse model dropping spikes, it is only for spikes, * therefore we only implement `handles_test_event()` only for spike * events. * * See Kunkel et al (2014), Sec 3.3.1, for background information. */ class ConnTestDummyNode : public nest::ConnTestDummyNodeBase { public: using nest::ConnTestDummyNodeBase::handles_test_event; nest::port handles_test_event( nest::SpikeEvent&, nest::rport ) { return nest::invalid_port_; } nest::port handles_test_event( nest::DSSpikeEvent&, nest::rport ) { return nest::invalid_port_; } }; /** * Check that requested connection can be created. * * This function is a boilerplate function that should be included unchanged * in all synapse models. It is called before a connection is added to check * that the connection is legal. It is a wrapper that allows us to call * the "real" `check_connection_()` method with the `ConnTestDummyNode * dummy_target;` class for this connection type. This avoids a virtual * function call for better performance. * * @param s Source node for connection * @param t Target node for connection * @param receptor_type Receptor type for connection * @param lastspike Time of most recent spike of presynaptic (sender) neuron, * not used here */ void check_connection( nest::Node& s, nest::Node& t, nest::rport receptor_type, const CommonPropertiesType& ) { ConnTestDummyNode dummy_target; ConnectionBase::check_connection_( dummy_target, s, t, receptor_type ); } /** * Send an event to the receiver of this connection. * @param e The event to send * @param t Thread * @param cp Common properties to all synapses. */ void send( nest::Event& e, nest::thread t, const CommonPropertiesType& cp ); // The following methods contain mostly fixed code to forward the // corresponding tasks to corresponding methods in the base class and the w_ // data member holding the weight. //! Store connection status information in dictionary void get_status( DictionaryDatum& d ) const; /** * Set connection status. * * @param d Dictionary with new parameter values * @param cm ConnectorModel is passed along to validate new delay values */ void set_status( const DictionaryDatum& d, nest::ConnectorModel& cm ); //! Allows efficient initialization on construction void set_weight( double w ) { weight_ = w; } }; template < typename targetidentifierT > inline void simple_stochastic_synapse< targetidentifierT >::send( nest::Event& e, nest::thread t, const CommonPropertiesType& props ) { if ( nest::get_vp_specific_rng( t )->drand() < (1 - p_) ) // drop spike return; // Even time stamp, we send the spike using the normal sending mechanism // send the spike to the target e.set_weight( weight_ ); e.set_delay_steps( ConnectionBase::get_delay_steps() ); e.set_receiver( *ConnectionBase::get_target( t ) ); e.set_rport( ConnectionBase::get_rport() ); e(); // this sends the event } template < typename targetidentifierT > void simple_stochastic_synapse< targetidentifierT >::get_status( DictionaryDatum& d ) const { ConnectionBase::get_status( d ); def< double >( d, nest::names::weight, weight_ ); def< double >( d, nest::names::p, p_ ); def< long >( d, nest::names::size_of, sizeof( *this ) ); } template < typename targetidentifierT > void simple_stochastic_synapse< targetidentifierT >::set_status( const DictionaryDatum& d, nest::ConnectorModel& cm ) { ConnectionBase::set_status( d, cm ); updateValue< double >( d, nest::names::weight, weight_ ); updateValue< double >( d, nest::names::p, p_ ); } } // namespace #endif // simple_stochastic_synapse.h PyNN-0.10.0/pyNN/nest/extensions/sli/000077500000000000000000000000001415343567000172475ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nest/extensions/sli/pynn_extensions-init.sli000066400000000000000000000004751415343567000241720ustar00rootroot00000000000000/* :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. */ /* * Initialization file for PyNNExtensions module. * Run automatically when PyNNExtensions module is loaded. */ M_DEBUG (pynn_extensions.sli) (Initializing SLI support for PyNNExtensions.) message PyNN-0.10.0/pyNN/nest/extensions/stochastic_stp_synapse.h000066400000000000000000000134501415343567000234300ustar00rootroot00000000000000/* * stochastic_stp_synapse.h * * :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. * :license: CeCILL, see LICENSE for details. * */ #ifndef STOCHASTIC_STP_SYNAPSE_H #define STOCHASTIC_STP_SYNAPSE_H // Includes from nestkernel: #include "connection.h" /* BeginUserDocs: synapse, short-term plasticity Short description +++++++++++++++++ Probabilistic synapse model with short term plasticity. Description +++++++++++ This synapse model implements synaptic short-term depression and short-term facilitation according to an algorithm developed by the Blue Brain Project. The implementation is based on quantal_stp_synapse and the NMODL file ProbGABAAB_EMS.mod from the Blue Brain Project. Parameters ++++++++++ The following parameters can be set in the status dictionary: ======= ==== ======================================================= U real Maximal fraction of available resources [0,1], default=0.5 u real release probability, default=0.5 p real probability that a vesicle is available, default = 1.0 R real recovered state {0=unrecovered, 1=recovered}, default=1 tau_rec real time constant for depression in ms, default=800 ms tau_fac real time constant for facilitation in ms, default=0 (off) t_surv real time since last evaluation of survival in ms, default=0 ======= ==== ======================================================= Transmits +++++++++ SpikeEvent SeeAlso +++++++ tsodyks2_synapse, synapsedict, quantal_stp_synapse, static_synapse EndUserDocs */ namespace pynn { template < typename targetidentifierT > class stochastic_stp_synapse : public nest::Connection< targetidentifierT > { public: typedef nest::CommonSynapseProperties CommonPropertiesType; typedef nest::Connection< targetidentifierT > ConnectionBase; /** * Default Constructor. * Sets default values for all parameters. Needed by GenericConnectorModel. */ stochastic_stp_synapse(); /** * Copy constructor to propagate common properties. */ stochastic_stp_synapse( const stochastic_stp_synapse& ); // Explicitly declare all methods inherited from the dependent base // ConnectionBase. This avoids explicit name prefixes in all places these // functions are used. Since ConnectionBase depends on the template parameter, // they are not automatically found in the base class. using ConnectionBase::get_delay_steps; using ConnectionBase::get_delay; using ConnectionBase::get_rport; using ConnectionBase::get_target; /** * Get all properties of this connection and put them into a dictionary. */ void get_status( DictionaryDatum& d ) const; /** * Set default properties of this connection from the values given in * dictionary. */ void set_status( const DictionaryDatum& d, nest::ConnectorModel& cm ); /** * Send an event to the receiver of this connection. * \param e The event to send * \param cp Common properties to all synapses (empty). */ void send( nest::Event& e, nest::thread t, const CommonPropertiesType& cp ); class ConnTestDummyNode : public nest::ConnTestDummyNodeBase { public: // Ensure proper overriding of overloaded virtual functions. // Return values from functions are ignored. using nest::ConnTestDummyNodeBase::handles_test_event; nest::port handles_test_event( nest::SpikeEvent&, nest::rport ) { return nest::invalid_port_; } }; void check_connection( nest::Node& s, nest::Node& t, nest::rport receptor_type, const CommonPropertiesType& ) { ConnTestDummyNode dummy_target; ConnectionBase::check_connection_( dummy_target, s, t, receptor_type ); } void set_weight( double w ) { weight_ = w; } private: double weight_; //!< synaptic weight double U_; //!< unit increment of a facilitating synapse (U) double u_; //!< dynamic value of probability of release double tau_rec_; //!< [ms] time constant for recovery from depression (D) double tau_fac_; //!< [ms] time constant for facilitation (F) double R_; //!< recovered state {0=unrecovered, 1=recovered} double t_surv_; //!< time since last evaluation of survival double t_lastspike_; //!< Time point of last spike emitted }; /** * Send an event to the receiver of this connection. * \param e The event to send * \param t The thread on which this connection is stored. * \param t_lastspike Time point of last spike emitted * \param cp Common properties object, containing the stochastic_stp parameters. */ template < typename targetidentifierT > inline void stochastic_stp_synapse< targetidentifierT >::send( nest::Event& e, nest::thread thr, const CommonPropertiesType& ) { double t_spike = e.get_stamp().get_ms(); // calculation of u if ( tau_fac_ > 1.0e-10 ) { u_ *= std::exp( -(t_spike - t_lastspike_) / tau_fac_ ); u_ += U_ * ( 1 - u_ ); } else { u_ = U_; } // check for recovery bool release = false; double p_surv = 0.0; // survival probability of unrecovered state if ( R_ == 0 ) { release = false; // probability of survival of unrecovered state based on Poisson recovery with rate 1/tau_rec p_surv = std::exp( -(t_spike - t_surv_) / tau_rec_ ); if ( nest::get_vp_specific_rng( thr )->drand() > p_surv ) { R_ = 1; // recovered } else { t_surv_ = t_spike; // failed to recover } } // check for release if ( R_ == 1 ) { if ( nest::get_vp_specific_rng( thr )->drand() < u_ ) { // release release = true; R_ = 0; t_surv_ = t_spike; } else { release = false; } } if ( release ) { e.set_receiver( *get_target( thr ) ); e.set_weight( weight_ ); e.set_delay_steps( get_delay_steps() ); e.set_rport( get_rport() ); e(); } t_lastspike_ = t_spike; } } // namespace #endif // STOCHASTIC_STP_SYNAPSE_H PyNN-0.10.0/pyNN/nest/extensions/stochastic_stp_synapse_impl.h000066400000000000000000000040471415343567000244530ustar00rootroot00000000000000/* * stochastic_stp_synapse_impl.h * * :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. * :license: CeCILL, see LICENSE for details. * */ #ifndef STOCHASTIC_STP_SYNAPSE_IMPL_H #define STOCHASTIC_STP_SYNAPSE_IMPL_H #include "stochastic_stp_synapse.h" // Includes from nestkernel: #include "connection.h" #include "connector_model.h" #include "nest_names.h" // Includes from sli: #include "dictutils.h" namespace pynn { template < typename targetidentifierT > stochastic_stp_synapse< targetidentifierT >::stochastic_stp_synapse() : ConnectionBase() , weight_( 1.0 ) , U_( 0.5 ) , u_( 0.0 ) , tau_rec_( 800.0 ) , tau_fac_( 10.0 ) , R_( 1.0 ) , t_surv_( 0.0 ) , t_lastspike_( 0.0 ) { } template < typename targetidentifierT > stochastic_stp_synapse< targetidentifierT >::stochastic_stp_synapse( const stochastic_stp_synapse& rhs ) : ConnectionBase( rhs ) , weight_( rhs.weight_ ) , U_( rhs.U_ ) , u_( rhs.u_ ) , tau_rec_( rhs.tau_rec_ ) , tau_fac_( rhs.tau_fac_ ) , R_( rhs.R_ ) , t_surv_( rhs.t_surv_ ) , t_lastspike_( rhs.t_lastspike_ ) { } template < typename targetidentifierT > void stochastic_stp_synapse< targetidentifierT >::get_status( DictionaryDatum& d ) const { ConnectionBase::get_status( d ); def< double >( d, nest::names::weight, weight_ ); def< double >( d, nest::names::dU, U_ ); def< double >( d, nest::names::u, u_ ); def< double >( d, nest::names::tau_rec, tau_rec_ ); def< double >( d, nest::names::tau_fac, tau_fac_ ); } template < typename targetidentifierT > void stochastic_stp_synapse< targetidentifierT >::set_status( const DictionaryDatum& d, nest::ConnectorModel& cm ) { ConnectionBase::set_status( d, cm ); updateValue< double >( d, nest::names::weight, weight_ ); updateValue< double >( d, nest::names::dU, U_ ); updateValue< double >( d, nest::names::u, u_ ); updateValue< double >( d, nest::names::tau_rec, tau_rec_ ); updateValue< double >( d, nest::names::tau_fac, tau_fac_ ); } } // of namespace pynn #endif // #ifndef STOCHASTIC_STP_SYNAPSE_IMPL_H PyNN-0.10.0/pyNN/nest/nineml.py000066400000000000000000000121631415343567000161200ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Support cell types defined in 9ML with NEST. Requires the 9ml nestbuilder script to be on the import path. Classes: NineMLCellType - base class for cell types, not used directly Functions: nineml_cell_type_from_model - return a new NineMLCellType subclass Constants: NEST_DIR - subdirectory to which NEST mechanisms will be written (TODO: not implemented) :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from pyNN.nest.cells import NativeCellType logger = logging.getLogger("PyNN") # TODO: This should go to a evironment variable, like PYNN_9ML_DIR # and then a sub-dir for nest, neuron, etc. # but default to ~/.pyNN or something to that regard. NEST_DIR = "nest_models" class NineMLCellType(NativeCellType): def __init__(self, parameters): NativeCellType.__init__(self, parameters) def nineml_celltype_from_model(name, nineml_model, synapse_components): """ Return a new NineMLCellType subclass from a NineML model. """ dct = {'nineml_model': nineml_model, 'synapse_components': synapse_components} return _nest_build_nineml_celltype(name, (NineMLCellType,), dct) class _nest_build_nineml_celltype(type): """ Metaclass for building NineMLCellType subclasses Called by nineml_celltype_from_model """ def __new__(cls, name, bases, dct): import nineml.abstraction as al from nineml.abstraction import flattening, component_modifiers import nest # Extract Parameters Back out from Dict: nineml_model = dct['nineml_model'] synapse_components = dct['synapse_components'] # Flatten the model: assert isinstance(nineml_model, al.ComponentClass) if nineml_model.is_flat(): flat_component = nineml_model else: flat_component = flattening.flatten(nineml_model, name) # Make the substitutions: flat_component.backsub_all() # flat_component.backsub_aliases() # flat_component.backsub_equations() # Close any open reduce ports: component_modifiers.ComponentModifier.close_all_reduce_ports(component=flat_component) flat_component.short_description = "Auto-generated 9ML neuron model for PyNN.nest" flat_component.long_description = "Auto-generated 9ML neuron model for PyNN.nest" # Close any open reduce ports: component_modifiers.ComponentModifier.close_all_reduce_ports(component=flat_component) # synapse ports: synapse_ports = [] for syn in synapse_components: # get recv event ports # TODO: model namespace look #syn_component = nineml_model[syn.namespace] syn_component = nineml_model.subnodes[syn.namespace] recv_event_ports = list(syn_component.query.event_recv_ports) # check there's only one if len(recv_event_ports) != 1: raise ValueError( "A synapse component has multiple recv ports. Cannot dis-ambiguate") synapse_ports.append(syn.namespace + '_' + recv_event_ports[0].name) # New: dct["combined_model"] = flat_component # TODO: Override this with user layer #default_values = ModelToSingleComponentReducer.flatten_namespace_dict( parameters ) dct["default_parameters"] = dict((p.name, 1.0) for p in flat_component.parameters) dct["default_initial_values"] = dict((s.name, 0.0) for s in flat_component.state_variables) dct["synapse_types"] = [syn.namespace for syn in synapse_components] dct["standard_receptor_type"] = (dct["synapse_types"] == ('excitatory', 'inhibitory')) dct["injectable"] = True # need to determine this. How?? dct["conductance_based"] = True # how to determine this?? dct["model_name"] = name dct["nest_model"] = name # Recording from bindings: dct["recordable"] = [port.name for port in flat_component.analog_ports] + ['spikes', 'regime'] # TODO bindings -> alias and support recording of them in nest template #+ [binding.name for binding in flat_component.bindings] dct["weight_variables"] = dict([(syn.namespace, syn.namespace + '_' + syn.weight_connector) for syn in synapse_components]) logger.debug("Creating class '%s' with bases %s and dictionary %s" % (name, bases, dct)) # TODO: UL configuration of initial regime. initial_regime = flat_component.regimes_map.keys()[0] from nestbuilder import NestFileBuilder nfb = NestFileBuilder(nest_classname=name, component=flat_component, synapse_ports=synapse_ports, initial_regime=initial_regime, initial_values=dct["default_initial_values"], default_values=dct["default_parameters"], ) nfb.compile_files() nest.Install('mymodule') return type.__new__(cls, name, bases, dct) PyNN-0.10.0/pyNN/nest/populations.py000066400000000000000000000233251415343567000172150ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ NEST v2 implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import nest import logging from pyNN import common, errors from pyNN.parameters import ArrayParameter, Sequence, ParameterSpace, simplify, LazyArray from pyNN.random import RandomDistribution from pyNN.standardmodels import StandardCellType from . import simulator from .recording import Recorder, VARIABLE_MAP logger = logging.getLogger("PyNN") class PopulationMixin(object): def _get_view(self, selector, label=None): return PopulationView(self, selector, label) def _set_parameters(self, parameter_space): """ parameter_space should contain native parameters """ param_dict = _build_params(parameter_space, np.where(self._mask_local)[0]) if hasattr(self.celltype, "uses_parrot") and self.celltype.uses_parrot: ids = self.node_collection_source[self._mask_local] else: ids = self.node_collection[self._mask_local] simulator.state.set_status(ids, param_dict) def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ if hasattr(self.celltype, "uses_parrot") and self.celltype.uses_parrot: ids = self.node_collection_source[self._mask_local] else: ids = self.node_collection[self._mask_local] if "spike_times" in names: parameter_dict = {"spike_times": [Sequence(value) for value in nest.GetStatus(ids, names)]} else: parameter_dict = {} for name in names: # one name at a time, since some parameter values may be tuples val = np.array(nest.GetStatus(ids, name)) if isinstance(val[0], tuple) or len(val.shape) == 2: val = np.array([ArrayParameter(v) for v in val]) val = LazyArray(simplify(val), shape=(self.local_size,), dtype=ArrayParameter) parameter_dict[name] = val else: parameter_dict[name] = simplify(val) ps = ParameterSpace(parameter_dict, shape=(self.local_size,)) return ps @property def local_node_collection(self): return self.node_collection[self._mask_local] class Assembly(common.Assembly): __doc__ = common.Assembly.__doc__ _simulator = simulator @property def local_node_collection(self): result = self.populations[0].local_node_collection for p in self.populations[1:]: result += p.local_node_collection return result @property def node_collection(self): return sum((p.node_collection for p in self.populations[1:]), start=self.populations[0].node_collection) class PopulationView(common.PopulationView, PopulationMixin): __doc__ = common.PopulationView.__doc__ _simulator = simulator _assembly_class = Assembly @property def node_collection(self): return self.parent.node_collection[self.mask] @property def node_collection_source(self): return self.parent.node_collection_source[self.mask] def _build_params(parameter_space, mask_local, size=None, extra_parameters=None): """ Return either a single parameter dict or a list of dicts, suitable for use in Create or SetStatus. """ if "UNSUPPORTED" in parameter_space.keys(): parameter_space.pop("UNSUPPORTED") if size: parameter_space.shape = (size,) if parameter_space.is_homogeneous: parameter_space.evaluate(simplify=True) cell_parameters = parameter_space.as_dict() if extra_parameters: cell_parameters.update(extra_parameters) for name, val in cell_parameters.items(): if isinstance(val, ArrayParameter): cell_parameters[name] = val.value.tolist() else: parameter_space.evaluate(mask=mask_local) cell_parameters = list(parameter_space) # may not be the most efficient way. # Might be best to set homogeneous parameters on creation, # then inhomogeneous ones using SetStatus. Need some timings. for D in cell_parameters: for name, val in D.items(): if isinstance(val, ArrayParameter): D[name] = val.value.tolist() if extra_parameters: D.update(extra_parameters) return cell_parameters class Population(common.Population, PopulationMixin): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def __init__(self, size, cellclass, cellparams=None, structure=None, initial_values={}, label=None): __doc__ = common.Population.__doc__ self._deferred_parrot_connections = False super(Population, self).__init__(size, cellclass, cellparams, structure, initial_values, label) self._simulator.state.populations.append(self) def _create_cells(self): """ Create cells in NEST using the celltype of the current Population. """ # this method should never be called more than once # perhaps should check for that nest_model = self.celltype.nest_name[simulator.state.spike_precision] if isinstance(self.celltype, StandardCellType): self.celltype.parameter_space.shape = (self.size,) # should perhaps do this on a copy? params = _build_params(self.celltype.native_parameters, None, size=self.size, extra_parameters=self.celltype.extra_parameters) else: params = _build_params(self.celltype.parameter_space, None, size=self.size) try: self.node_collection = nest.Create(nest_model, self.size, params=params) except nest.kernel.NESTError as err: if "UnknownModelName" in err.args[0] and "cond" in err.args[0]: raise errors.InvalidModelError("%s Have you compiled NEST with the GSL (Gnu Scientific Library)?" % err) if "Spike times must be sorted in non-descending order" in err.args[0]: raise errors.InvalidParameterValueError("Spike times given to SpikeSourceArray must be in increasing order") raise # errors.InvalidModelError(err) # create parrot neurons if necessary if hasattr(self.celltype, "uses_parrot") and self.celltype.uses_parrot: self.node_collection_source = self.node_collection # we put the parrots into all_cells, since this will parrot_model = simulator.state.spike_precision == "off_grid" and "parrot_neuron_ps" or "parrot_neuron" self.node_collection = nest.Create(parrot_model, self.size) # be used for connections and recording. all_cells_source # should be used for setting parameters self._deferred_parrot_connections = True # connecting up the parrot neurons is deferred until we know the value of min_delay # which could be 'auto' at this point. if self.node_collection.local is True: self._mask_local = np.array([True]) else: self._mask_local = np.array(self.node_collection.local) self.all_cells = np.array([simulator.ID(gid) for gid in self.node_collection.tolist()], simulator.ID) for gid in self.all_cells: gid.parent = self gid.node_collection = nest.NodeCollection([int(gid)]) if hasattr(self.celltype, "uses_parrot") and self.celltype.uses_parrot: for gid, source in zip(self.all_cells, self.node_collection_source.tolist()): gid.source = source def _connect_parrot_neurons(self): nest.Connect(self.node_collection_source, self.node_collection, 'one_to_one', syn_spec={'delay': simulator.state.min_delay}) self._deferred_parrot_connections = False def _reset(self): # adjust parameters that represent absolute times for the time offset after reset if hasattr(self.celltype, "uses_parrot") and self.celltype.uses_parrot: for name in self.celltype.get_native_names(): if name in ("start", "stop", "spike_times"): value = self.celltype.native_parameters[name] self._simulator.set_status(self.node_collection, name, value) def _set_initial_value_array(self, variable, value): variable = VARIABLE_MAP.get(variable, variable) if isinstance(value.base_value, RandomDistribution) and value.base_value.rng.parallel_safe: local_values = value.evaluate()[self._mask_local] else: local_values = value._partially_evaluate(self._mask_local, simplify=True) try: if self._mask_local.dtype == bool and self._mask_local.size == 1 and self._mask_local[0]: simulator.state.set_status(self.node_collection, variable, local_values) else: simulator.state.set_status(self.node_collection[self._mask_local], variable, local_values) except nest.kernel.NESTError as e: if "Unused dictionary items" in e.args[0]: logger.warning("NEST does not allow setting an initial value for %s" % variable) # should perhaps check whether value-to-be-set is the same as current value, # and raise an Exception if not, rather than just emit a warning. else: raise PyNN-0.10.0/pyNN/nest/projections.py000066400000000000000000000574111415343567000172020ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ NEST v3 implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from collections import defaultdict import numpy as np import nest import logging from pyNN import common, errors from pyNN.space import Space from pyNN.parameters import simplify from . import simulator from .standardmodels.synapses import StaticSynapse from .conversion import make_sli_compatible logger = logging.getLogger("PyNN") def listify(obj): if isinstance(obj, np.ndarray): return obj.astype(float).tolist() elif np.isscalar(obj): return float(obj) # NEST chokes on numpy's float types else: return obj def split_array_to_avoid_repeats(arr, **associated_arrays): assert arr.dtype == int n_sub_arrays = np.bincount(arr).max() split_indices = [[] for i in range(n_sub_arrays)] index_pointer = defaultdict(int) for i, element in enumerate(arr): split_index = index_pointer[element] split_indices[split_index].append(i) index_pointer[element] += 1 sub_arrays = [arr[split_index] for split_index in split_indices] sub_associated = [] for split_index in split_indices: assoc = {} for key, value in associated_arrays.items(): if isinstance(value, np.ndarray): assoc[key] = value[split_index] else: assoc[key] = value sub_associated.append(assoc) return sub_arrays, sub_associated class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator _static_synapse_class = StaticSynapse def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, receptor_type=None, space=Space(), label=None): common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) self.nest_synapse_model = self.synapse_type._get_nest_synapse_model() self.nest_synapse_label = Projection._nProj self.synapse_type._set_tau_minus(self.post.local_node_collection) self._sources = set() self._connections = None # This is used to keep track of common synapse properties self._common_synapse_properties = {} self._common_synapse_property_names = None # Create connections connector.connect(self) def __getitem__(self, i): """Return the `i`th connection on the local MPI node.""" if isinstance(i, int): if i < len(self): return simulator.Connection(self, i) else: raise IndexError("%d > %d" % (i, len(self) - 1)) elif isinstance(i, slice): if i.stop < len(self): return [simulator.Connection(self, j) for j in range(i.start, i.stop, i.step or 1)] else: raise IndexError("%d > %d" % (i.stop, len(self) - 1)) def __len__(self): """Return the number of connections on the local MPI node.""" nest_model = self.post.celltype.nest_name[self._simulator.state.spike_precision] local_nodes = nest.GetNodes({"model": nest_model}, local_only=True) local_connections = nest.GetConnections(target=local_nodes, synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label) return len(local_connections) @property def nest_connections(self): if self._connections is None or self._simulator.state.stale_connection_cache: if len(self._sources) > 0: self._connections = nest.GetConnections( nest.NodeCollection(sorted(self._sources)), synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label) else: self._connections = [] self._simulator.state.stale_connection_cache = False return self._connections @property def connections(self): """ Returns an iterator over local connections in this projection, as `Connection` objects. """ return (simulator.Connection(self, i) for i in range(len(self))) def _connect(self, rule_params, syn_params): """ Create connections by calling nest. Connect on the presynaptic and postsynaptic population with the parameters provided by params. """ if 'tsodyks' in self.nest_synapse_model: if self.receptor_type == 'inhibitory': param_name = self.post.local_cells[0].celltype.translations['tau_syn_I']['translated_name'] elif self.receptor_type == 'excitatory': param_name = self.post.local_cells[0].celltype.translations['tau_syn_E']['translated_name'] else: raise NotImplementedError() syn_params.update({'tau_psc': nest.GetStatus([self.nest_connections[0,1]], param_name)}) syn_params.update({'synapse_label': self.nest_synapse_label}) nest.Connect(self.pre.node_collection, self.post.node_collection, rule_params, syn_params) self._simulator.state.stale_connection_cache = True self._sources.update( nest.GetConnections(synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label).sources() ) def _identify_common_synapse_properties(self): """ Use the connection between the sample indices to distinguish between local and common synapse properties. """ sample_connection = nest.GetConnections( source=nest.NodeCollection([next(iter(self._sources))]), # take any source from the set synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label)[:1] local_parameters = nest.GetStatus(sample_connection)[0].keys() all_parameters = nest.GetDefaults(self.nest_synapse_model).keys() self._common_synapse_property_names = [name for name in all_parameters if name not in local_parameters] def _update_syn_params(self, syn_dict, connection_parameters): """ Update the paramaters to be passed in the nest.Connect method with the connection parameters specific to the synapse type. `syn_dict` - the dictionary to be passed to nest.Connect containing "local" parameters `connection_parameters` - a dictionary containing all parameters (local and common) """ # Set connection parameters other than weight and delay if connection_parameters: for name, value in connection_parameters.items(): if name not in self._common_synapse_property_names: value = make_sli_compatible(value) if isinstance(value, np.ndarray): syn_dict.update({name: np.array([value.tolist()])}) else: syn_dict.update({name: value}) return syn_dict def _convergent_connect(self, presynaptic_indices, postsynaptic_index, **connection_parameters): """ Connect a neuron to one or more other neurons with a static connection. `presynaptic_indices` - 1D array of presynaptic indices `postsynaptic_index` - integer - the index of the postsynaptic neuron `connection_parameters` - dict whose keys are native NEST parameter names. Values may be scalars or arrays. """ # Clean the connection parameters by removing parameters that are # used by PyNN but should not be passed to NEST connection_parameters.pop('tau_minus', None) # TODO: set tau_minus on the post-synaptic cells connection_parameters.pop('dendritic_delay_fraction', None) connection_parameters.pop('w_min_always_zero_in_NEST', None) syn_dict = { 'synapse_model': self.nest_synapse_model, 'synapse_label': self.nest_synapse_label, } # Weights require some special handling if self.receptor_type == 'inhibitory' and self.post.conductance_based: connection_parameters['weight'] *= -1 # NEST wants negative values for inhibitory weights, even if these are conductances if "stdp" in self.nest_synapse_model: syn_dict["Wmax"] = -1.2345e6 # just some very large negative value to avoid # NEST complaining about weight and Wmax having different signs # (see https://github.com/NeuralEnsemble/PyNN/issues/636) # Will be overwritten below. connection_parameters["Wmax"] *= -1 if hasattr(self.post, "celltype") and hasattr(self.post.celltype, "receptor_scale"): # this is a bit of a hack connection_parameters['weight'] *= self.post.celltype.receptor_scale # needed for the Izhikevich model # Prepare connections. NodeCollections can't have repeated values, so for some # connector types we need to split the presynaptic cells into groups that # don't have such repeats. # note that NEST needs sorted indices sort_indices = presynaptic_indices.argsort() presynaptic_indices = presynaptic_indices[sort_indices] for name, value in connection_parameters.items(): if isinstance(value, np.ndarray): connection_parameters[name] = value[sort_indices] try: presynaptic_cell_groups = [self.pre.node_collection[presynaptic_indices]] connection_parameter_groups = [connection_parameters] except ValueError as err: if "All node IDs in a NodeCollection have to be unique" in str(err): presynaptic_index_groups, connection_parameter_groups = \ split_array_to_avoid_repeats(presynaptic_indices, **connection_parameters) presynaptic_cell_groups = [self.pre.node_collection[i] for i in presynaptic_index_groups] else: raise postsynaptic_cell = self.post[postsynaptic_index] # Create connections and set parameters for presynaptic_cells, connection_parameter_group in zip(presynaptic_cell_groups, connection_parameter_groups): self._sources.update(presynaptic_cells.tolist()) try: weights = connection_parameter_group.pop('weight') delays = connection_parameter_group.pop('delay') # nest.Connect expects a 2D array if not np.isscalar(weights): weights = np.array([weights]) if not np.isscalar(delays): delays = np.array([delays]) syn_dict.update({'weight': weights, 'delay': delays}) if postsynaptic_cell.celltype.standard_receptor_type: # For Tsodyks-Markram synapses models we set the "tau_psc" parameter to match # the relevant "tau_syn" parameter from the post-synaptic neuron. if 'tsodyks' in self.nest_synapse_model: if self.receptor_type == 'inhibitory': param_name = postsynaptic_cell.celltype.translations['tau_syn_I']['translated_name'] elif self.receptor_type == 'excitatory': param_name = postsynaptic_cell.celltype.translations['tau_syn_E']['translated_name'] else: raise NotImplementedError() syn_dict["tau_psc"] = nest.GetStatus(postsynaptic_cell.node_collection, param_name)[0] else: syn_dict.update( {"receptor_type": postsynaptic_cell.celltype.get_receptor_type( self.receptor_type)}) # For parameters other than weight and delay, we need to know if they are "common" # parameters (the same for all synapses) or "local" (different synapses can have # different values), as this affects how they are set. # # To introspect which parameters are common, we need an existing connection, so # the first time we create connections we pass just the weight and delay, and set # the other parameters later. We then get the list of common parameters and cache # it so that in subsequent Connect() calls we can pass all of the local # (non-common) parameters. if self._common_synapse_property_names is None: nest.Connect(presynaptic_cells, postsynaptic_cell.node_collection, 'all_to_all', syn_dict) self._identify_common_synapse_properties() # Retrieve connections so that we can set additional # parameters using nest.SetStatus connections = nest.GetConnections(source=presynaptic_cells, target=postsynaptic_cell.node_collection, synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label) for name, value in connection_parameter_group.items(): if name not in self._common_synapse_property_names: value = make_sli_compatible(value) if isinstance(value, np.ndarray): nest.SetStatus(connections, name, value.tolist()) else: nest.SetStatus(connections, name, value) else: self._set_common_synapse_property(name, value) else: # Since we know which parameters are common, we can set the non-common # parameters directly in the nest.Connect call syn_dict = self._update_syn_params(syn_dict, connection_parameter_group) nest.Connect(presynaptic_cells, postsynaptic_cell.node_collection, 'all_to_all', syn_dict) # and then set the common parameters for name, value in connection_parameter_group.items(): if name in self._common_synapse_property_names: self._set_common_synapse_property(name, value) except nest.kernel.NESTError as e: errmsg = "%s. presynaptic_cells=%s, postsynaptic_cell=%s, weights=%s, delays=%s, synapse model='%s'" % ( e, presynaptic_cells, postsynaptic_cell, weights, delays, self.nest_synapse_model) raise errors.ConnectionError(errmsg) # Reset the caching of the connection list, since this will have to be recalculated self._connections = None self._simulator.state.stale_connection_cache = True def _set_attributes(self, parameter_space): if "tau_minus" in parameter_space.keys() and not parameter_space["tau_minus"].is_homogeneous: raise ValueError("tau_minus cannot be heterogeneous " "within a single Projection with NEST.") # only columns for connections that exist on this machine parameter_space.evaluate(mask=(slice(None), self.post._mask_local)) sources = nest.NodeCollection(sorted(self._sources)) if self._common_synapse_property_names is None: self._identify_common_synapse_properties() for postsynaptic_cell, connection_parameters in zip(self.post.local_cells, parameter_space.columns()): connections = nest.GetConnections(source=sources, target=postsynaptic_cell.node_collection, synapse_model=self.nest_synapse_model, synapse_label=self.nest_synapse_label) if connections: source_mask = self.pre.id_to_index(list(connections.sources())) for name, value in connection_parameters.items(): if name == "weight" and self.receptor_type == 'inhibitory' and self.post.conductance_based: value *= -1 # NEST uses negative values for inhibitory weights, even if these are conductances if name == "tau_minus": # set on the post-synaptic cell nest.SetStatus(self.post.node_collection[self.post.node_collection.local], {"tau_minus": simplify(value)}) elif name not in self._common_synapse_property_names: value = make_sli_compatible(value) if len(source_mask) > 1: nest.SetStatus(connections, name, value[source_mask]) elif isinstance(value, np.ndarray): # OneToOneConnector nest.SetStatus(connections, name, value[source_mask]) else: nest.SetStatus(connections, name, value) else: self._set_common_synapse_property(name, value) def _set_common_synapse_property(self, name, value): """ Sets the common synapse property while making sure its value stays unique (i.e. it can only be set once). """ if name in self._common_synapse_properties: unequal = self._common_synapse_properties[name] != value # handle both scalars and numpy ndarray if isinstance(unequal, np.ndarray): raise_error = unequal.any() else: raise_error = unequal if raise_error: raise ValueError("{} cannot be heterogeneous " "within a single Projection. Warning: " "Projection was only partially initialized." " Please call sim.nest.reset() to reset " "your network and start over!".format(name)) if hasattr(value, "__len__"): value1 = value[0] else: value1 = value self._common_synapse_properties[name] = value1 # we delay make_sli_compatible until this late stage so that we can # distinguish "parameter is an array consisting of scalar values" # (one value per connection) from # "parameter is a scalar value containing an array" # (one value for the entire projection) # In the latter case the value is wrapped in a Sequence object, # which is removed by make_sli_compatible value2 = make_sli_compatible(value1) nest.SetDefaults(self.nest_synapse_model, name, value2) # def saveConnections(self, file, gather=True, compatible_output=True): # """ # Save connections to file in a format suitable for reading in with a # FromFileConnector. # """ # import operator # # if isinstance(file, str): # file = recording.files.StandardTextFile(file, mode='w') # # lines = nest.GetStatus(self.nest_connections, ('source', 'target', 'weight', 'delay')) # if gather == True and simulator.state.num_processes > 1: # all_lines = { simulator.state.mpi_rank: lines } # all_lines = recording.gather_dict(all_lines) # if simulator.state.mpi_rank == 0: # lines = reduce(operator.add, all_lines.values()) # elif simulator.state.num_processes > 1: # file.rename('%s.%d' % (file.name, simulator.state.mpi_rank)) # logger.debug("--- Projection[%s].__saveConnections__() ---" % self.label) # # if gather == False or simulator.state.mpi_rank == 0: # lines = np.array(lines, dtype=float) # lines[:,2] *= 0.001 # if compatible_output: # lines[:,0] = self.pre.id_to_index(lines[:,0]) # lines[:,1] = self.post.id_to_index(lines[:,1]) # file.write(lines, {'pre' : self.pre.label, 'post' : self.post.label}) # file.close() def _get_attributes_as_list(self, names): nest_names = [] for name in names: if name == 'presynaptic_index': nest_names.append('source') elif name == 'postsynaptic_index': nest_names.append('target') else: nest_names.append(name) values = nest.GetStatus(self.nest_connections, nest_names) values = np.array(values) # ought to preserve int type for source, target if 'weight' in names: # other attributes could also have scale factors - need to use translation mechanisms scale_factors = np.ones(len(names)) scale_factors[names.index('weight')] = 0.001 if self.receptor_type == 'inhibitory' and self.post.conductance_based: # NEST uses negative values for inhibitory weights, even if these are conductances scale_factors[names.index('weight')] *= -1 values *= scale_factors if 'presynaptic_index' in names: values[:, names.index('presynaptic_index')] = self.pre.id_to_index( values[:, names.index('presynaptic_index')]) if 'postsynaptic_index' in names: values[:, names.index('postsynaptic_index')] = self.post.id_to_index( values[:, names.index('postsynaptic_index')]) values = values.tolist() for i in range(len(values)): values[i] = tuple(values[i]) return values def _get_attributes_as_arrays(self, names, multiple_synapses='sum'): multi_synapse_operation = Projection.MULTI_SYNAPSE_OPERATIONS[multiple_synapses] all_values = [] for attribute_name in names: if attribute_name[-1] == "s": # weights --> weight, delays --> delay attribute_name = attribute_name[:-1] value_arr = np.nan * np.ones((self.pre.size, self.post.size)) connection_attributes = nest.GetStatus(self.nest_connections, ('source', 'target', attribute_name)) for conn in connection_attributes: # (offset is always 0,0 for connections created with connect()) src, tgt, value = conn addr = self.pre.id_to_index(src), self.post.id_to_index(tgt) if np.isnan(value_arr[addr]): value_arr[addr] = value else: value_arr[addr] = multi_synapse_operation(value_arr[addr], value) if attribute_name == 'weight': value_arr *= 0.001 if self.receptor_type == 'inhibitory' and self.post.conductance_based: value_arr *= -1 # NEST uses negative values for inhibitory weights, even if these are conductances all_values.append(value_arr) return all_values def _set_initial_value_array(self, variable, value): local_value = value.evaluate(simplify=True) nest.SetStatus(self.nest_connections, variable, local_value) PyNN-0.10.0/pyNN/nest/random.py000066400000000000000000000066731415343567000161270ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ NEST v3 implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from numbers import Real from copy import copy import nest.random from pyNN.random import NativeRNG NEST_RDEV_TYPES = ['binomial', 'binomial_clipped', 'binomial_clipped_to_boundary', 'exponential', 'exponential_clipped', 'exponential_clipped_to_boundary', 'gamma', 'gamma_clipped', 'gamma_clipped_to_boundary', 'gsl_binomial', 'lognormal', 'lognormal_clipped', 'lognormal_clipped_to_boundary', 'normal', 'normal_clipped', 'normal_clipped_to_boundary', 'poisson', 'poisson_clipped', 'poisson_clipped_to_boundary', 'uniform', 'uniform_int'] class NativeRNG(NativeRNG): """ Signals that the random numbers will be drawn by NEST's own RNGs and takes care of transforming pyNN parameters for the random distributions to NEST parameters. """ translations = { #'binomial': {'n': 'n', 'p': 'p'}, #'gamma': {'theta': 'scale', 'k': 'order'}, 'exponential': {'beta': 'lambda'}, 'lognormal': {'mu': 'mean', 'sigma': 'std'}, 'normal': {'mu': 'mean', 'sigma': 'std'}, #'normal_clipped': {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}, #'normal_clipped_to_boundary': # {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}, #'poisson': {'lambda_': 'lambda'}, 'uniform': {'low': 'min', 'high': 'max'}, #'uniform_int': {'low': 'low', 'high': 'high'}, #'vonmises': {'mu': 'mu', 'kappa': 'kappa'}, } def next(self, n=None, distribution=None, parameters=None, mask=None): # we ignore `n` and `mask_local`; they are needed for interface consistency parameter_map = self.translations[distribution] return NESTRandomDistribution(distribution, dict((parameter_map[k], v) for k, v in parameters.items())) class NESTRandomDistribution(object): scale_parameters = { 'gamma': ('scale',), 'normal': ('mean', 'std'), 'lognormal': ('mean', 'std'), 'uniform': ('min', 'max'), } def __init__(self, name, parameters): self.name = name self.parameters = parameters self.shape = None # pretend to lazyarray that I am an array def reshape(self, shape): # pretend to be an array return self def repr(self): D = {'distribution': self.name} D.update(self.parameters) return D def as_nest_object(self): cls = getattr(nest.random, self.name) return cls(**self.parameters) def __mul__(self, value): if not isinstance(value, Real): raise ValueError("Can only multiply by a number, not by a %s" % type(value)) new_parameters = copy(self.parameters) if self.name in self.scale_parameters: for parameter_name in self.scale_parameters[self.name]: print("Multiplying parameter %s by %s" % (parameter_name, value)) new_parameters[parameter_name] *= value else: raise NotImplementedError("Scaling not supported or not yet implemented for the %s distribution" % self.name) return NESTRandomDistribution(self.name, new_parameters) PyNN-0.10.0/pyNN/nest/recording.py000066400000000000000000000264561415343567000166240ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ NEST v3 implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from collections import defaultdict import numpy as np import logging import nest from pyNN import recording, errors from pyNN.nest import simulator # todo: this information should come from the cell type classes VARIABLE_MAP = {'v': 'V_m', 'gsyn_exc': 'g_ex', 'gsyn_inh': 'g_in', 'u': 'U_m', 'w': 'w', 'i_eta': 'I_stc', 'v_t': 'E_sfa'} REVERSE_VARIABLE_MAP = dict((v, k) for k, v in VARIABLE_MAP.items()) SCALE_FACTORS = {'v': 1, 'gsyn_exc': 0.001, 'gsyn_inh': 0.001, 'w': 0.001, 'i_eta': 0.001, 'v_t': 1} logger = logging.getLogger("PyNN") def _set_status(obj, parameters): """Wrapper around nest.SetStatus() to add a more informative error message.""" try: nest.SetStatus(obj, parameters) except nest.kernel.NESTError as e: raise nest.kernel.NESTError("%s. Parameter dictionary was: %s" % (e, parameters)) class RecordingDevice(object): """Base class for SpikeDetector and Multimeter""" def __init__(self, device_parameters, to_memory=True): # to be called at the end of the subclass __init__ if to_memory: self.device.record_to = "memory" else: self.device.record_to = "ascii" self._all_ids = set([]) self._connected = False self._overrun_data = None self._clean = True # might there be data in the pipeline that hasn't been delivered yet simulator.state.recording_devices.append(self) _set_status(self.device, device_parameters) def add_ids(self, new_ids): assert not self._connected self._all_ids = self._all_ids.union(new_ids) def get_data(self, variable, desired_ids, clear=False): """ Return recorded data as a dictionary containing one numpy array for each neuron, ids as keys. """ scale_factor = SCALE_FACTORS.get(variable, 1) nest_variable = VARIABLE_MAP.get(variable, variable) events = nest.GetStatus(self.device, 'events')[0] ids = events['senders'] times = events["times"] - simulator.state._time_offset if variable == "times": values = times else: # I'm hoping numpy optimises for the case where scale_factor = 1, otherwise should avoid this multiplication in that case values = events[nest_variable] * scale_factor valid_times_index = times <= simulator.state.t if clear: future_times_index = np.invert(valid_times_index) if future_times_index.any(): new_overrun_data = { "ids": ids[future_times_index], "values": values[future_times_index] } else: new_overrun_data = None if self._overrun_data: ids = np.hstack((self._overrun_data["ids"], ids)) values = np.hstack((self._overrun_data["values"], values)) self._overrun_data = new_overrun_data else: ids = ids[valid_times_index] values = values[valid_times_index] data = {} recorded_ids = set(ids) for id in recorded_ids: data[id] = [] for id, v in zip(ids, values): data[id].append(v) desired_and_existing_ids = np.intersect1d( np.array(list(recorded_ids)), np.array(desired_ids)) data = {k: data[k] for k in desired_and_existing_ids} if variable != 'times': if variable not in self._initial_values: self._initial_values[variable] = {} for id in desired_ids: initial_value = self._initial_values[variable].get(int(id), id.get_initial_value(variable)) if self._clean: # NEST does not record values at the zeroth time step, so we # add them here. data[int(id)] = [initial_value] + data.get(int(id), []) else: # The values at the zeroth time step come from a previous run, # so should be replaced if len(data[int(id)]) > 0: data[int(id)][0] = initial_value # if `get_data(..., clear=True)` is called in the middle of a simulation, the # value at the last time point will become the initial value for # the next time `get_data()` is called if clear: self._initial_values[variable][int(id)] = data[int(id)][-1] return data class SpikeDetector(RecordingDevice): """A wrapper around the NEST spike_recorder device""" def __init__(self, to_memory=True): self.device = nest.Create('spike_recorder') device_parameters = {} if not to_memory: device_parameters["precision"] = simulator.state.default_recording_precision super(SpikeDetector, self).__init__(device_parameters, to_memory) def connect_to_cells(self): assert not self._connected if len(self._all_ids) > 0: nest.Connect(nest.NodeCollection(sorted(self._all_ids)), self.device, {'rule': 'all_to_all'}, {'delay': simulator.state.min_delay}) self._connected = True def get_spiketimes(self, desired_ids, clear=False): """ Return spike times as a dictionary containing one numpy array for each neuron, ids as keys. Equivalent to `get_data('times', desired_ids)` """ return self.get_data('times', desired_ids, clear=clear) def get_spike_counts(self, desired_ids): events = nest.GetStatus(self.device, 'events')[0] N = {} for id in desired_ids: mask = events['senders'] == int(id) N[int(id)] = len(events['times'][mask]) return N class Multimeter(RecordingDevice): """A wrapper around the NEST multimeter device""" def __init__(self, to_memory=True): self.device = nest.Create('multimeter') device_parameters = { "interval": simulator.state.dt, } self._initial_values = {} super(Multimeter, self).__init__(device_parameters, to_memory) def connect_to_cells(self): assert not self._connected if len(self._all_ids) > 0: nest.Connect(self.device, nest.NodeCollection(sorted(self._all_ids)), {'rule': 'all_to_all'}, {'delay': simulator.state.min_delay}) self._connected = True @property def variables(self): return set(nest.GetStatus(self.device, 'record_from')[0]) def add_variable(self, variable): current_variables = self.variables current_variables.add(VARIABLE_MAP.get(variable, variable)) _set_status(self.device, {'record_from': list(current_variables)}) class Recorder(recording.Recorder): """Encapsulates data and functions related to recording model variables.""" _simulator = simulator scale_factors = {'spikes': 1, 'v': 1, 'w': 0.001, 'gsyn': 0.001} # units conversion def __init__(self, population, file=None): __doc__ = recording.Recorder.__doc__ self._multimeter = Multimeter() self._spike_detector = SpikeDetector() recording.Recorder.__init__(self, population, file) self.recorded_all = defaultdict(set) def record(self, variables, ids, sampling_interval=None): """ Add the cells in `ids` to the sets of recorded cells for the given variables. """ logger.debug('Recorder.record(<%d cells>)' % len(ids)) self._check_sampling_interval(sampling_interval) # for NEST we need all ids, not just local ones, otherwise simulations # sometimes hang with MPI if some nodes aren't recording anything all_ids = set(ids) local_ids = set([id for id in ids if id.local]) for variable in recording.normalize_variables_arg(variables): if not self.population.can_record(variable): raise errors.RecordingError(variable, self.population.celltype) new_ids = all_ids.difference(self.recorded_all[variable]) self.recorded[variable] = self.recorded[variable].union(local_ids) self.recorded_all[variable] = self.recorded_all[variable].union(all_ids) self._record(variable, new_ids, sampling_interval) def _record(self, variable, new_ids, sampling_interval=None): """ Add the cells in `new_ids` to the set of recorded cells for the given variable. Since a given node can only be recorded from by one multimeter (http://www.nest-initiative.org/index.php/Analog_recording_with_multimeter, 14/11/11) we record all analog variables for all requested cells. """ if variable == 'spikes': self._spike_detector.add_ids(new_ids) else: self.sampling_interval = sampling_interval self._multimeter.add_variable(variable) self._multimeter.add_ids(new_ids) def _get_sampling_interval(self): return nest.GetStatus(self._multimeter.device, "interval")[0] def _set_sampling_interval(self, value): if value is not None: nest.SetStatus(self._multimeter.device, {"interval": value}) sampling_interval = property(fget=_get_sampling_interval, fset=_set_sampling_interval) def _reset(self): """ """ simulator.state.recording_devices.remove(self._multimeter) simulator.state.recording_devices.remove(self._spike_detector) # I guess the existing devices still exist in NEST, can we delete them # or at least turn them off? # Maybe we can reset them, rather than create new ones? self._multimeter = Multimeter() self._spike_detector = SpikeDetector() def _get_spiketimes(self, ids, clear=False): # hugely inefficient - to be optimized later return self._spike_detector.get_spiketimes(ids, clear=clear) def _get_all_signals(self, variable, ids, clear=False): data = self._multimeter.get_data(variable, ids, clear=clear) if len(ids) > 0: # JACOMMENT: this is very expensive but not sure how to get rid of it return np.array([data[i] for i in ids]).T else: return np.array([]) def _local_count(self, variable, filter_ids): assert variable == 'spikes' return self._spike_detector.get_spike_counts(self.filter_recorded('spikes', filter_ids)) def _clear_simulator(self): """ Should remove all recorded data held by the simulator and, ideally, free up the memory. """ for rec in (self._spike_detector, self._multimeter): nest.SetStatus(rec.device, 'n_events', 0) rec._clean = False def store_to_cache(self, annotations=None): # we over-ride the implementation from the parent class so as to # do some reinitialisation. recording.Recorder.store_to_cache(self, annotations) self._multimeter._initial_values = {} PyNN-0.10.0/pyNN/nest/simulator.py000066400000000000000000000301561415343567000166570ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Implementation of the "low-level" functionality used by the common implementation of the API, for the NEST simulator. Classes and attributes usable by the common implementation: Classes: ID Connection Attributes: state -- a singleton instance of the _State class. All other functions and classes are private, and should not be used by other modules. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nest import logging import tempfile import warnings import numpy as np from pyNN import common from pyNN.core import reraise logger = logging.getLogger("PyNN") name = "NEST" # for use in annotating output data # The following constants contain the names of NEST model parameters that # relate to simulation time and so may need to be adjusted by a time offset. # TODO: Currently contains only parameters that occur in PyNN standard models. # We should add parameters from all models that are distributed with NEST # in case they are used with PyNN as "native" models. NEST_VARIABLES_TIME_DIMENSION = ("start", "stop") NEST_ARRAY_VARIABLES_TIME_DIMENSION = ("spike_times", "amplitude_times", "rate_times") # --- For implementation of get_time_step() and similar functions -------------- def nest_property(name, dtype): """Return a property that accesses a NEST kernel parameter""" def _get(self): return nest.GetKernelStatus(name) def _set(self, val): try: nest.SetKernelStatus({name: dtype(val)}) except nest.kernel.NESTError as e: reraise(e, "%s = %s (%s)" % (name, val, type(val))) return property(fget=_get, fset=_set) def apply_time_offset(parameters, offset): parameters_copy = {} for name, value in parameters.items(): if name in NEST_VARIABLES_TIME_DIMENSION: parameters_copy[name] = value + offset elif name in NEST_ARRAY_VARIABLES_TIME_DIMENSION: parameters_copy[name] = [v + offset for v in value] else: parameters_copy[name] = value return parameters_copy class _State(common.control.BaseState): """Represent the simulator state.""" def __init__(self): super(_State, self).__init__() try: nest.Install('pynn_extensions') self.extensions_loaded = True except nest.kernel.NESTError as err: self.extensions_loaded = False self.initialized = False self.optimize = False self.spike_precision = "off_grid" self.verbosity = "error" self._cache_num_processes = nest.GetKernelStatus()['num_processes'] # avoids blocking if only some nodes call num_processes # do the same for rank? # allow NEST to erase previously written files (defaut with all the other simulators) nest.SetKernelStatus({'overwrite_files': True}) self.tempdirs = [] self.recording_devices = [] self.populations = [] # needed for reset self.current_sources = [] self._time_offset = 0.0 self.t_flush = -1 self.stale_connection_cache = False @property def t(self): # note that we always simulate one min delay past the requested time # we round to try to reduce floating-point problems # longer-term, we should probably work with integers (in units of time step) return max(np.around(self.t_kernel - self.min_delay - self._time_offset, decimals=12), 0.0) t_kernel = nest_property("biological_time", float) dt = nest_property('resolution', float) threads = nest_property('local_num_threads', int) rng_seed = nest_property('rng_seed', int) grng_seed = nest_property('rng_seed', int) @property def min_delay(self): return nest.GetKernelStatus('min_delay') def set_delays(self, min_delay, max_delay): # this assumes we never set max_delay without also setting min_delay if min_delay != 'auto': min_delay = float(min_delay) if max_delay == 'auto': max_delay = 10.0 else: max_delay = float(max_delay) nest.SetKernelStatus({'min_delay': min_delay, 'max_delay': max_delay}) @property def max_delay(self): return nest.GetKernelStatus('max_delay') @property def num_processes(self): return self._cache_num_processes @property def mpi_rank(self): return nest.Rank() def _get_spike_precision(self): return self._spike_precision def _set_spike_precision(self, precision): if nest.off_grid_spiking and precision == "on_grid": raise ValueError("The option to use off-grid spiking cannot be turned off once enabled") if precision == 'off_grid': self.default_recording_precision = 15 elif precision == 'on_grid': self.default_recording_precision = 3 else: raise ValueError("spike_precision must be 'on_grid' or 'off_grid'") self._spike_precision = precision spike_precision = property(fget=_get_spike_precision, fset=_set_spike_precision) def _set_verbosity(self, verbosity): nest.set_verbosity('M_{}'.format(verbosity.upper())) verbosity = property(fset=_set_verbosity) def set_status(self, nodes, params, val=None): """ Wrapper around nest.SetStatus() to handle time offset """ if self._time_offset == 0.0: nest.SetStatus(nodes, params, val=val) else: if val is None: parameters = params else: parameters = {params: val} if isinstance(parameters, list): params_copy = [] for item in parameters: params_copy.append(apply_time_offset(item, self._time_offset)) else: params_copy = apply_time_offset(parameters, self._time_offset) nest.SetStatus(nodes, params_copy) def run(self, simtime): """Advance the simulation for a certain time.""" for population in self.populations: if population._deferred_parrot_connections: population._connect_parrot_neurons() for device in self.recording_devices: if not device._connected: device.connect_to_cells() device._local_files_merged = False if not self.running and simtime > 0: # we simulate past the real time by one min_delay, otherwise NEST doesn't give us all the recorded data simtime += self.min_delay self.running = True if simtime > 0: nest.Simulate(simtime) def run_until(self, tstop): self.run(tstop - self.t) def reset(self): if self.t > 0: if self.t_flush < 0: raise ValueError( "Full reset functionality is not currently available with NEST. " "If you nevertheless want to use this functionality, pass the `t_flush`" "argument to `setup()` with a suitably large value (>> 100 ms)" "then check carefully that the previous run is not influencing the " "following one." ) else: warnings.warn( "Full reset functionality is not available with NEST. " "Please check carefully that the previous run is not influencing the " "following one and, if so, increase the `t_flush` argument to `setup()`" ) self.run(self.t_flush) # get spikes and recorded data out of the system for recorder in self.recorders: recorder._clear_simulator() self._time_offset = self.t_kernel for p in self.populations: if hasattr(p.celltype, "uses_parrot") and p.celltype.uses_parrot: # 'uses_parrot' is a marker for spike sources, # which may have parameters that need to be updated # to account for time offset # TODO: need to ensure that get/set parameters also works correctly p._set_parameters(p.celltype.native_parameters) for variable, initial_value in p.initial_values.items(): p._set_initial_value_array(variable, initial_value) p._reset() for cs in self.current_sources: cs._reset() self.running = False self.segment_counter += 1 def clear(self): self.populations = [] self.current_sources = [] self.recording_devices = [] self.recorders = set() # clear the sli stack, if this is not done --> memory leak cause the stack increases nest.ll_api.sr('clear') # reset the simulation kernel nest.ResetKernel() # but this reverts some of the PyNN settings, so we have to repeat them (see NEST #716) self.spike_precision = self._spike_precision # set tempdir tempdir = tempfile.mkdtemp() self.tempdirs.append(tempdir) # append tempdir to tempdirs list nest.SetKernelStatus({'data_path': tempdir, }) self.segment_counter = -1 self.reset() # --- For implementation of access to individual neurons' parameters ----------- class ID(int, common.IDMixin): __doc__ = common.IDMixin.__doc__ def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) @property def local(self): return self.node_collection.local # --- For implementation of connect() and Connector classes -------------------- class Connection(common.Connection): """ Provide an interface that allows access to the connection's weight, delay and other attributes. """ def __init__(self, parent, index): """ Create a new connection interface. `parent` -- a Projection instance. `index` -- the index of this connection in the parent. """ self.parent = parent self.index = index def id(self): """Return a tuple of arguments for `nest.GetConnection()`. """ return self.parent.nest_connections[self.index] @property def source(self): """The ID of the pre-synaptic neuron.""" src = ID(nest.GetStatus(self.id(), 'source')[0]) src.parent = self.parent.pre return src presynaptic_cell = source @property def target(self): """The ID of the post-synaptic neuron.""" tgt = ID(nest.GetStatus(self.id(), 'target')[0]) tgt.parent = self.parent.post return tgt postsynaptic_cell = target def _set_weight(self, w): nest.SetStatus(self.id(), 'weight', w * 1000.0) def _get_weight(self): """Synaptic weight in nA or µS.""" w_nA = nest.GetStatus(self.id(), 'weight')[0] if self.parent.synapse_type == 'inhibitory' and common.is_conductance(self.target): w_nA *= -1 # NEST uses negative values for inhibitory weights, even if these are conductances return 0.001 * w_nA def _set_delay(self, d): nest.SetStatus(self.id(), 'delay', d) def _get_delay(self): """Synaptic delay in ms.""" return nest.GetStatus(self.id(), 'delay')[0] weight = property(_get_weight, _set_weight) delay = property(_get_delay, _set_delay) def generate_synapse_property(name): def _get(self): return nest.GetStatus(self.id(), name)[0] def _set(self, val): nest.SetStatus(self.id(), name, val) return property(_get, _set) setattr(Connection, 'U', generate_synapse_property('U')) setattr(Connection, 'tau_rec', generate_synapse_property('tau_rec')) setattr(Connection, 'tau_facil', generate_synapse_property('tau_fac')) setattr(Connection, 'u0', generate_synapse_property('u0')) setattr(Connection, '_tau_psc', generate_synapse_property('tau_psc')) # --- Initialization, and module attributes ------------------------------------ state = _State() # a Singleton, so only a single instance ever exists del _State PyNN-0.10.0/pyNN/nest/standardmodels/000077500000000000000000000000001415343567000172655ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nest/standardmodels/__init__.py000066400000000000000000000000001415343567000213640ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nest/standardmodels/cells.py000066400000000000000000000277231415343567000207540ustar00rootroot00000000000000""" Standard cells for nest :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import cells, build_translations from .. import simulator class IF_curr_alpha(cells.IF_curr_alpha): __doc__ = cells.IF_curr_alpha.__doc__ translations = build_translations( ('v_rest', 'E_L'), ('v_reset', 'V_reset'), ('cm', 'C_m', 1000.0), # C_m is in pF, cm in nF ('tau_m', 'tau_m'), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('v_thresh', 'V_th'), ('i_offset', 'I_e', 1000.0), # I_e is in pA, i_offset in nA ) nest_name = {"on_grid": "iaf_psc_alpha", "off_grid": "iaf_psc_alpha"} standard_receptor_type = True class IF_curr_exp(cells.IF_curr_exp): __doc__ = cells.IF_curr_exp.__doc__ translations = build_translations( ('v_rest', 'E_L'), ('v_reset', 'V_reset'), ('cm', 'C_m', 1000.0), # C_m is in pF, cm in nF ('tau_m', 'tau_m'), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('v_thresh', 'V_th'), ('i_offset', 'I_e', 1000.0), # I_e is in pA, i_offset in nA ) nest_name = {"on_grid": 'iaf_psc_exp', "off_grid": 'iaf_psc_exp_ps'} standard_receptor_type = True class IF_cond_alpha(cells.IF_cond_alpha): __doc__ = cells.IF_cond_alpha.__doc__ translations = build_translations( ('v_rest', 'E_L'), ('v_reset', 'V_reset'), ('cm', 'C_m', 1000.0), # C_m is in pF, cm in nF ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('v_thresh', 'V_th'), ('i_offset', 'I_e', 1000.0), # I_e is in pA, i_offset in nA ('e_rev_E', 'E_ex'), ('e_rev_I', 'E_in'), ) nest_name = {"on_grid": "iaf_cond_alpha", "off_grid": "iaf_cond_alpha"} standard_receptor_type = True class IF_cond_exp(cells.IF_cond_exp): __doc__ = cells.IF_cond_exp.__doc__ translations = build_translations( ('v_rest', 'E_L'), ('v_reset', 'V_reset'), ('cm', 'C_m', 1000.0), # C_m is in pF, cm in nF ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('v_thresh', 'V_th'), ('i_offset', 'I_e', 1000.0), # I_e is in pA, i_offset in nA ('e_rev_E', 'E_ex'), ('e_rev_I', 'E_in'), ) nest_name = {"on_grid": "iaf_cond_exp", "off_grid": "iaf_cond_exp"} standard_receptor_type = True class IF_cond_exp_gsfa_grr(cells.IF_cond_exp_gsfa_grr): __doc__ = cells.IF_cond_exp_gsfa_grr.__doc__ translations = build_translations( ('v_rest', 'E_L'), ('v_reset', 'V_reset'), ('cm', 'C_m', 1000.0), # C_m is in pF, cm in nF ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('v_thresh', 'V_th'), ('i_offset', 'I_e', 1000.0), # I_e is in pA, i_offset in nA ('e_rev_E', 'E_ex'), ('e_rev_I', 'E_in'), ('tau_sfa', 'tau_sfa'), ('e_rev_sfa', 'E_sfa'), ('q_sfa', 'q_sfa'), ('tau_rr', 'tau_rr'), ('e_rev_rr', 'E_rr'), ('q_rr', 'q_rr') ) nest_name = {"on_grid": "iaf_cond_exp_sfa_rr", "off_grid": "iaf_cond_exp_sfa_rr"} standard_receptor_type = True class IF_facets_hardware1(cells.IF_facets_hardware1): __doc__ = cells.IF_facets_hardware1.__doc__ # in 'iaf_cond_exp', the dimension of C_m is pF, # while in the pyNN context, cm is given in nF translations = build_translations( ('v_reset', 'V_reset'), ('v_rest', 'E_L'), ('v_thresh', 'V_th'), ('e_rev_I', 'E_in'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('g_leak', 'g_L') ) nest_name = {"on_grid": "iaf_cond_exp", "off_grid": "iaf_cond_exp"} standard_receptor_type = True extra_parameters = { 'C_m': 200.0, 't_ref': 1.0, 'E_ex': 0.0 } class HH_cond_exp(cells.HH_cond_exp): __doc__ = cells.HH_cond_exp.__doc__ translations = build_translations( ('gbar_Na', 'g_Na', 1000.0), # uS --> nS ('gbar_K', 'g_K', 1000.0), ('g_leak', 'g_L', 1000.0), ('cm', 'C_m', 1000.0), # nF --> pF ('v_offset', 'V_T'), ('e_rev_Na', 'E_Na'), ('e_rev_K', 'E_K'), ('e_rev_leak', 'E_L'), ('e_rev_E', 'E_ex'), ('e_rev_I', 'E_in'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('i_offset', 'I_e', 1000.0), # nA --> pA ) nest_name = {"on_grid": "hh_cond_exp_traub", "off_grid": "hh_cond_exp_traub"} standard_receptor_type = True class EIF_cond_alpha_isfa_ista(cells.EIF_cond_alpha_isfa_ista): __doc__ = cells.EIF_cond_alpha_isfa_ista.__doc__ translations = build_translations( ('cm', 'C_m', 1000.0), # nF -> pF ('tau_refrac', 't_ref'), ('v_spike', 'V_peak'), ('v_reset', 'V_reset'), ('v_rest', 'E_L'), ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('i_offset', 'I_e', 1000.0), # nA -> pA ('a', 'a'), ('b', 'b', 1000.0), # nA -> pA. ('delta_T', 'Delta_T'), ('tau_w', 'tau_w'), ('v_thresh', 'V_th'), ('e_rev_E', 'E_ex'), ('tau_syn_E', 'tau_syn_ex'), ('e_rev_I', 'E_in'), ('tau_syn_I', 'tau_syn_in'), ) nest_name = {"on_grid": "aeif_cond_alpha", "off_grid": "aeif_cond_alpha"} standard_receptor_type = True class SpikeSourcePoisson(cells.SpikeSourcePoisson): __doc__ = cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('rate', 'rate'), ('start', 'start'), ('duration', 'stop', "start+duration", "stop-start"), ) nest_name = {"on_grid": 'poisson_generator', "off_grid": 'poisson_generator_ps'} always_local = True uses_parrot = True extra_parameters = { 'origin': 1.0 } def unsupported(parameter_name, valid_value): def error_if_invalid(**parameters): if parameters[parameter_name].base_value != valid_value: raise NotImplementedError( "The `{}` parameter is not supported in NEST".format(parameter_name)) return valid_value return error_if_invalid class SpikeSourcePoissonRefractory(cells.SpikeSourcePoissonRefractory): __doc__ = cells.SpikeSourcePoissonRefractory.__doc__ translations = build_translations( ('rate', 'rate'), ('tau_refrac', 'dead_time'), ('start', 'UNSUPPORTED', unsupported('start', 0.0), None), ('duration', 'UNSUPPORTED', unsupported('duration', 1e10), None), ) nest_name = {"on_grid": 'ppd_sup_generator', "off_grid": 'ppd_sup_generator'} always_local = True uses_parrot = True extra_parameters = { 'n_proc': 1, 'frequency': 0.0, } class SpikeSourceGamma(cells.SpikeSourceGamma): __doc__ = cells.SpikeSourceGamma.__doc__ translations = build_translations( ('alpha', 'gamma_shape'), ('beta', 'rate', 'beta/alpha', 'gamma_shape * rate'), ('start', 'UNSUPPORTED', unsupported('start', 0.0), None), ('duration', 'UNSUPPORTED', unsupported('duration', 1e10), None), ) nest_name = {"on_grid": 'gamma_sup_generator', "off_grid": 'gamma_sup_generator'} always_local = True uses_parrot = True extra_parameters = { 'n_proc': 1 } class SpikeSourceInhGamma(cells.SpikeSourceInhGamma): __doc__ = cells.SpikeSourceInhGamma.__doc__ translations = build_translations( ('a', 'a'), ('b', 'b'), ('tbins', 'tbins'), ('start', 'start'), ('duration', 'stop', "duration+start", "stop-start"), ) nest_name = {"on_grid": 'inh_gamma_generator', "off_grid": 'inh_gamma_generator'} always_local = True uses_parrot = True extra_parameters = { 'origin': 1.0 } def adjust_spike_times_forward(spike_times): """ Since this cell type requires parrot neurons, we have to adjust the spike times to account for the transmission delay from device to parrot neuron. """ # todo: emit warning if any times become negative return spike_times - simulator.state.min_delay def adjust_spike_times_backward(spike_times): """ Since this cell type requires parrot neurons, we have to adjust the spike times to account for the transmission delay from device to parrot neuron. """ return spike_times + simulator.state.min_delay class SpikeSourceArray(cells.SpikeSourceArray): __doc__ = cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'spike_times', adjust_spike_times_forward, adjust_spike_times_backward), ) nest_name = {"on_grid": 'spike_generator', "off_grid": 'spike_generator'} uses_parrot = True always_local = True class EIF_cond_exp_isfa_ista(cells.EIF_cond_exp_isfa_ista): __doc__ = cells.EIF_cond_exp_isfa_ista.__doc__ translations = build_translations( ('cm', 'C_m', 1000.0), # nF -> pF ('tau_refrac', 't_ref'), ('v_spike', 'V_peak'), ('v_reset', 'V_reset'), ('v_rest', 'E_L'), ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('i_offset', 'I_e', 1000.0), # nA -> pA ('a', 'a'), ('b', 'b', 1000.0), # nA -> pA. ('delta_T', 'Delta_T'), ('tau_w', 'tau_w'), ('v_thresh', 'V_th'), ('e_rev_E', 'E_ex'), ('tau_syn_E', 'tau_syn_ex'), ('e_rev_I', 'E_in'), ('tau_syn_I', 'tau_syn_in'), ) nest_name = {"on_grid": "aeif_cond_exp", "off_grid": "aeif_cond_exp"} standard_receptor_type = True class Izhikevich(cells.Izhikevich): __doc__ = cells.Izhikevich.__doc__ translations = build_translations( ('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('i_offset', 'I_e', 1000.0), ) nest_name = {"on_grid": "izhikevich", "off_grid": "izhikevich"} standard_receptor_type = True receptor_scale = 1e-3 # synaptic weight is in mV, so need to undo usual weight scaling class GIF_cond_exp(cells.GIF_cond_exp): translations = build_translations( ('v_rest', 'E_L'), ('cm', 'C_m', 1000.0), # nF -> pF ('tau_m', 'g_L', "cm/tau_m*1000.0", "C_m/g_L"), ('tau_refrac', 't_ref'), ('tau_syn_E', 'tau_syn_ex'), ('tau_syn_I', 'tau_syn_in'), ('e_rev_E', 'E_ex'), ('e_rev_I', 'E_in'), ('v_reset', 'V_reset'), ('i_offset', 'I_e', 1000.0), # nA -> pA ('delta_v', 'Delta_V'), ('v_t_star', 'V_T_star'), ('lambda0', 'lambda_0'), ('tau_eta', 'tau_stc'), ('tau_gamma', 'tau_sfa'), ('a_eta', 'q_stc', 1000.0), # nA -> pA ('a_gamma', 'q_sfa'), ) nest_name = {"on_grid": "gif_cond_exp", "off_grid": "gif_cond_exp"} standard_receptor_type = True PyNN-0.10.0/pyNN/nest/standardmodels/electrodes.py000066400000000000000000000170611415343567000217750ustar00rootroot00000000000000""" Current source classes for the nest module. Classes: DCSource -- a single pulse of current of constant amplitude. StepCurrentSource -- a step-wise time-varying current. NoisyCurrentSource -- a Gaussian whitish noise current. ACSource -- a sine modulated current. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import nest from pyNN.standardmodels import electrodes, build_translations, StandardCurrentSource from pyNN.common import Population, PopulationView, Assembly from pyNN.parameters import ParameterSpace, Sequence from pyNN.nest.simulator import state from pyNN.nest.electrodes import NestCurrentSource class NestStandardCurrentSource(NestCurrentSource, StandardCurrentSource): """Base class for a nest source of current to be injected into a neuron.""" def __init__(self, **parameters): NestCurrentSource.__init__(self, **parameters) self.phase_given = 0.0 # required for PR #502 native_parameters = self.translate(self.parameter_space) self.set_native_parameters(native_parameters) def inject_into(self, cells): __doc__ = StandardCurrentSource.inject_into.__doc__ for id in cells: if id.local and not id.celltype.injectable: raise TypeError("Can't inject current into a spike source.") if isinstance(cells, (Population, PopulationView, Assembly)): self.cell_list = cells.node_collection else: self.cell_list = nest.NodeCollection(sorted(cells)) nest.Connect(self._device, self.cell_list, syn_spec={"delay": state.min_delay}) def _phase_correction(self, start, freq, phase): """ Fixes #497 (PR #502) Tweaks the value of phase supplied to NEST ACSource so as to remain consistent with other simulators """ phase_fix = ((phase*np.pi/180) - (2*np.pi*freq*start/1000)) * 180/np.pi phase_fix.shape = (1) phase_fix = phase_fix.evaluate()[0] nest.SetStatus(self._device, {'phase': phase_fix}) def _delay_correction(self, value): """ A change in a device requires a min_delay to take effect at the target """ corrected = value - self.min_delay # set negative times to zero if isinstance(value, np.ndarray): corrected = np.where(corrected > 0, corrected, 0.0) else: corrected = max(corrected, 0.0) return corrected def _phase_correction(self, start, freq, phase): """ Fixes #497 (PR #502) Tweaks the value of phase supplied to NEST ACSource so as to remain consistent with other simulators """ phase_fix = ((phase*np.pi/180) - (2*np.pi*freq*start/1000)) * 180/np.pi phase_fix.shape = (1) phase_fix = phase_fix.evaluate()[0] nest.SetStatus(self._device, {'phase': phase_fix}) def _check_step_times(self, times, amplitudes, resolution): # ensure that all time stamps are non-negative if np.min(times) < 0: raise ValueError("Step current cannot accept negative timestamps.") # ensure that times provided are of strictly increasing magnitudes if len(times) > 1 and np.min(np.diff(times)) <= 0: raise ValueError("Step current timestamps should be monotonically increasing.") # NEST specific: subtract min_delay from times (set to 0.0, if result is negative) times = self._delay_correction(times) # find the last element <= dt (we find >dt and then go one element back) # this corresponds to the first timestamp that can be used by NEST for current injection ctr = np.searchsorted(times, resolution, side="right") - 1 if ctr >= 0: times[ctr] = resolution times = times[ctr:] amplitudes = amplitudes[ctr:] # map timestamps to actual simulation time instants based on specified dt # for ind in range(len(times)): # times[ind] = self._round_timestamp(times[ind], resolution) times = self._round_timestamp(times, resolution) # remove duplicate timestamps, and corresponding amplitudes, after mapping step_times, step_indices = np.unique(times[::-1], return_index=True) step_times = step_times.tolist() step_indices = len(times)-step_indices-1 step_amplitudes = amplitudes[step_indices] # [amplitudes[i] for i in step_indices] return step_times, step_amplitudes def set_native_parameters(self, parameters): parameters.evaluate(simplify=True) for key, value in parameters.items(): if key == "amplitude_values": assert isinstance(value, Sequence) step_times = parameters["amplitude_times"].value step_amplitudes = parameters["amplitude_values"].value step_times, step_amplitudes = self._check_step_times( step_times, step_amplitudes, self.timestep) parameters["amplitude_times"].value = step_times parameters["amplitude_values"].value = step_amplitudes nest.SetStatus(self._device, {key: step_amplitudes, 'amplitude_times': step_times}) elif key in ("start", "stop"): nest.SetStatus(self._device, {key: self._delay_correction(value)}) if key == "start" and type(self).__name__ == "ACSource": self._phase_correction(self.start, self.frequency, self.phase_given) elif key == "frequency": nest.SetStatus(self._device, {key: value}) self._phase_correction(self.start, self.frequency, self.phase_given) elif key == "phase": self.phase_given = value self._phase_correction(self.start, self.frequency, self.phase_given) elif not key == "amplitude_times": nest.SetStatus(self._device, {key: value}) def get_native_parameters(self): all_params = nest.GetStatus(self._device)[0] return ParameterSpace(dict((k, v) for k, v in all_params.items() if k in self.get_native_names())) class DCSource(NestStandardCurrentSource, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude', 1000.), ('start', 'start'), ('stop', 'stop') ) nest_name = 'dc_generator' class ACSource(NestStandardCurrentSource, electrodes.ACSource): __doc__ = electrodes.ACSource.__doc__ translations = build_translations( ('amplitude', 'amplitude', 1000.), ('start', 'start'), ('stop', 'stop'), ('frequency', 'frequency'), ('offset', 'offset', 1000.), ('phase', 'phase') ) nest_name = 'ac_generator' class StepCurrentSource(NestStandardCurrentSource, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitude_values', 1000.), ('times', 'amplitude_times') ) nest_name = 'step_current_generator' class NoisyCurrentSource(NestStandardCurrentSource, electrodes.NoisyCurrentSource): __doc__ = electrodes.NoisyCurrentSource.__doc__ translations = build_translations( ('mean', 'mean', 1000.), ('start', 'start'), ('stop', 'stop'), ('stdev', 'std', 1000.), ('dt', 'dt') ) nest_name = 'noise_generator' PyNN-0.10.0/pyNN/nest/standardmodels/synapses.py000066400000000000000000000203321415343567000215040ustar00rootroot00000000000000""" Synapse Dynamics classes for nest :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nest from pyNN.standardmodels import synapses, build_translations from pyNN.nest.synapses import NESTSynapseMixin import logging from ..conversion import make_sli_compatible logger = logging.getLogger("PyNN") class StaticSynapse(synapses.StaticSynapse, NESTSynapseMixin): translations = build_translations( ('weight', 'weight', 1000.0), ('delay', 'delay') ) nest_name = 'static_synapse' class STDPMechanism(synapses.STDPMechanism, NESTSynapseMixin): """Specification of STDP models.""" base_translations = build_translations( ('weight', 'weight', 1000.0), # nA->pA, uS->nS ('delay', 'delay'), ('dendritic_delay_fraction', 'dendritic_delay_fraction') ) # will be extended by translations from timing_dependence, etc. def __init__(self, timing_dependence=None, weight_dependence=None, voltage_dependence=None, dendritic_delay_fraction=1.0, weight=0.0, delay=None): if dendritic_delay_fraction != 1: raise ValueError("NEST does not currently support axonal delays: " "for the purpose of STDP calculations all delays " "are assumed to be dendritic.") # could perhaps support axonal delays using parrot neurons? super(STDPMechanism, self).__init__(timing_dependence, weight_dependence, voltage_dependence, dendritic_delay_fraction, weight, delay) def _get_nest_synapse_model(self): base_model = self.possible_models if isinstance(base_model, set): logger.warning("Several STDP models are available for these connections:") logger.warning(", ".join(model for model in base_model)) base_model = list(base_model)[0] logger.warning("By default, %s is used" % base_model) available_models = nest.Models(mtype='synapses') if base_model not in available_models: raise ValueError("Synapse dynamics model '%s' not a valid NEST synapse model. " "Possible models in your NEST build are: %s" % (base_model, available_models)) # Defaults must be simple floats, so we use the NEST defaults # for any inhomogeneous parameters, and set the inhomogeneous values # later synapse_defaults = {} for name, value in self.native_parameters.items(): if value.is_homogeneous: value.shape = (1,) synapse_defaults[name] = value.evaluate(simplify=True) synapse_defaults.pop("dendritic_delay_fraction") synapse_defaults.pop("w_min_always_zero_in_NEST") # Tau_minus is a parameter of the post-synaptic cell, not of the connection synapse_defaults.pop("tau_minus", None) synapse_defaults = make_sli_compatible(synapse_defaults) nest.SetDefaults(base_model + '_lbl', synapse_defaults) return base_model + '_lbl' class TsodyksMarkramSynapse(synapses.TsodyksMarkramSynapse, NESTSynapseMixin): __doc__ = synapses.TsodyksMarkramSynapse.__doc__ translations = build_translations( ('weight', 'weight', 1000.0), ('delay', 'delay'), ('U', 'U'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_fac') ) nest_name = 'tsodyks_synapse' class SimpleStochasticSynapse(synapses.SimpleStochasticSynapse, NESTSynapseMixin): translations = build_translations( ('weight', 'weight', 1000.0), ('delay', 'delay'), ('p', 'p_transmit'), ) nest_name = 'bernoulli_synapse' class StochasticTsodyksMarkramSynapse(synapses.StochasticTsodyksMarkramSynapse, NESTSynapseMixin): translations = build_translations( ('weight', 'weight', 1000.0), ('delay', 'delay'), ('U', 'U'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_fac') ) nest_name = 'stochastic_stp_synapse' class MultiQuantalSynapse(synapses.MultiQuantalSynapse, NESTSynapseMixin): translations = build_translations( ('weight', 'weight', 1000.0), ('delay', 'delay'), ('U', 'U'), ('n', 'n'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_fac') ) nest_name = 'quantal_stp_synapse' class AdditiveWeightDependence(synapses.AdditiveWeightDependence): __doc__ = synapses.AdditiveWeightDependence.__doc__ translations = build_translations( ('w_max', 'Wmax', 1000.0), # unit conversion ('w_min', 'w_min_always_zero_in_NEST'), ) possible_models = set(['stdp_synapse']) # ,'stdp_synapse_hom']) extra_parameters = { 'mu_plus': 0.0, 'mu_minus': 0.0 } def __init__(self, w_min=0.0, w_max=1.0): if w_min != 0: raise Exception("Non-zero minimum weight is not supported by NEST.") synapses.AdditiveWeightDependence.__init__(self, w_min, w_max) class MultiplicativeWeightDependence(synapses.MultiplicativeWeightDependence): __doc__ = synapses.MultiplicativeWeightDependence.__doc__ translations = build_translations( ('w_max', 'Wmax', 1000.0), # unit conversion ('w_min', 'w_min_always_zero_in_NEST'), ) possible_models = set(['stdp_synapse']) # ,'stdp_synapse_hom']) extra_parameters = { 'mu_plus': 1.0, 'mu_minus': 1.0 } def __init__(self, w_min=0.0, w_max=1.0): if w_min != 0: raise Exception("Non-zero minimum weight is not supported by NEST.") synapses.MultiplicativeWeightDependence.__init__(self, w_min, w_max) class AdditivePotentiationMultiplicativeDepression(synapses.AdditivePotentiationMultiplicativeDepression): __doc__ = synapses.AdditivePotentiationMultiplicativeDepression.__doc__ translations = build_translations( ('w_max', 'Wmax', 1000.0), # unit conversion ('w_min', 'w_min_always_zero_in_NEST'), ) possible_models = set(['stdp_synapse']) # ,'stdp_synapse_hom']) extra_parameters = { 'mu_plus': 0.0, 'mu_minus': 1.0 } def __init__(self, w_min=0.0, w_max=1.0): if w_min != 0: raise Exception("Non-zero minimum weight is not supported by NEST.") synapses.AdditivePotentiationMultiplicativeDepression.__init__(self, w_min, w_max) class GutigWeightDependence(synapses.GutigWeightDependence): __doc__ = synapses.GutigWeightDependence.__doc__ translations = build_translations( ('w_max', 'Wmax', 1000.0), # unit conversion ('w_min', 'w_min_always_zero_in_NEST'), ('mu_plus', 'mu_plus'), ('mu_minus', 'mu_minus'), ) possible_models = set(['stdp_synapse']) # ,'stdp_synapse_hom']) def __init__(self, w_min=0.0, w_max=1.0, mu_plus=0.5, mu_minus=0.5): if w_min != 0: raise Exception("Non-zero minimum weight is not supported by NEST.") synapses.GutigWeightDependence.__init__(self, w_min, w_max) def _translate_A_minus_forwards(**parameters): A_minus = parameters["A_minus"] A_plus = parameters["A_plus"] if A_plus == 0: # can't divide by zero, and value of alpha has no # effect (since it will be multiplied by zero in NEST) # so we just store the provided value of A_minus alpha = A_minus # to-do: handle the case where A_plus is an array with some zero values else: alpha = A_minus / A_plus return alpha def _translate_A_minus_reverse(**parameters): alpha = parameters["alpha"] lambda_ = parameters["lambda"] if lambda_ == 0: A_minus = alpha # presumed to have been stored by _translate_A_minus_forwards else: A_minus = alpha * lambda_ return A_minus class SpikePairRule(synapses.SpikePairRule): __doc__ = synapses.SpikePairRule.__doc__ translations = build_translations( ('tau_plus', 'tau_plus'), ('tau_minus', 'tau_minus'), # defined in post-synaptic neuron ('A_plus', 'lambda'), ('A_minus', 'alpha', _translate_A_minus_forwards, _translate_A_minus_reverse), ) possible_models = set(['stdp_synapse']) # ,'stdp_synapse_hom']) PyNN-0.10.0/pyNN/nest/synapses.py000066400000000000000000000063171415343567000165070ustar00rootroot00000000000000# -*- coding: utf-8 -*- """ Definition of NativeSynapseType class for NEST :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nest from pyNN.models import BaseSynapseType from pyNN.errors import NoModelAvailableError from .simulator import state from .conversion import make_pynn_compatible, make_sli_compatible DEFAULT_TAU_MINUS = 20.0 def get_synapse_defaults(model_name): defaults = nest.GetDefaults(model_name) ignore = ['max_delay', 'min_delay', 'num_connections', 'num_connectors', 'receptor_type', 'synapsemodel', 'property_object', 'element_type', 'type', 'sizeof', 'has_delay', 'synapse_model', 'requires_symmetric', 'weight_recorder', 'init_flag', 'next_readout_time', 'synapse_id'] default_params = {} for name, value in defaults.items(): if name not in ignore: default_params[name] = value default_params['tau_minus'] = DEFAULT_TAU_MINUS return default_params class NESTSynapseMixin(object): def _get_nest_synapse_model(self): synapse_defaults = {} for name, value in self.native_parameters.items(): if value.is_homogeneous: value.shape = (1,) synapse_defaults[name] = value.evaluate(simplify=True) synapse_defaults = make_sli_compatible(synapse_defaults) synapse_defaults.pop("tau_minus", None) try: nest.SetDefaults(self.nest_name + '_lbl', synapse_defaults) except nest.lib.hl_api_exceptions.NESTError as err: if not state.extensions_loaded: raise NoModelAvailableError( "{self.__class__.__name__} is not available." "There was a problem loading NEST extensions".format(self=self) ) raise return self.nest_name + '_lbl' def _get_minimum_delay(self): return state.min_delay def _set_tau_minus(self, cells): if len(cells) > 0 and self.has_parameter('tau_minus'): native_parameters = self.native_parameters # could allow inhomogeneous values as long as each column is internally homogeneous if not native_parameters["tau_minus"].is_homogeneous: raise ValueError( "pyNN.NEST does not support tau_minus being different for different synapses") native_parameters.shape = (1,) tau_minus = native_parameters["tau_minus"].evaluate(simplify=True) nest.SetStatus(cells, {'tau_minus': tau_minus}) class NativeSynapseType(BaseSynapseType, NESTSynapseMixin): @property def native_parameters(self): return self.parameter_space def get_native_names(self, *names): return names def native_synapse_type(model_name): """ Return a new NativeSynapseType subclass. """ default_parameters = get_synapse_defaults(model_name) default_parameters = make_pynn_compatible(default_parameters) return type(model_name, (NativeSynapseType,), { 'nest_name': model_name, 'default_parameters': default_parameters, }) PyNN-0.10.0/pyNN/network.py000066400000000000000000000073501415343567000153600ustar00rootroot00000000000000""" """ import sys import inspect from itertools import chain from neo.io import get_io from pyNN.common import Population, PopulationView, Projection, Assembly class Network(object): """ docstring """ def __init__(self, *components): self._populations = set([]) self._views = set([]) self._assemblies = set([]) self._projections = set([]) self.add(*components) @property def populations(self): return frozenset(self._populations) @property def views(self): return frozenset(self._views) @property def assemblies(self): return frozenset(self._assemblies) @property def projections(self): return frozenset(self._projections) @property def sim(self): """Figure out which PyNN backend module this Network is using.""" # we assume there is only one. Could be mixed if using multiple simulators # at once. populations_module = inspect.getmodule(list(self.populations)[0].__class__) return sys.modules[".".join(populations_module.__name__.split(".")[:-1])] def count_neurons(self): return sum(population.size for population in chain(self.populations)) def count_connections(self): return sum(projection.size() for projection in chain(self.projections)) def add(self, *components): for component in components: if isinstance(component, Population): self._populations.add(component) elif isinstance(component, PopulationView): self._views.add(component) self._populations.add(component.parent) elif isinstance(component, Assembly): self._assemblies.add(component) self._populations.update(component.populations) elif isinstance(component, Projection): self._projections.add(component) # todo: check that pre and post populations/views/assemblies have been added else: raise TypeError() def get_component(self, label): for obj in chain(self.populations, self.views, self.assemblies, self.projections): if obj.label == label: return obj return None def filter(self, cell_types=None): """Return an Assembly of all components that have a cell type in the list""" if cell_types is None: raise NotImplementedError() else: if cell_types == "all": return self.sim.Assembly(*(pop for pop in self.populations if pop.celltype.injectable)) # or could use len(receptor_types) > 0 else: return self.sim.Assembly(*(pop for pop in self.populations if pop.celltype.__class__ in cell_types)) def record(self, variables, to_file=None, sampling_interval=None, include_spike_source=True): for obj in chain(self.populations, self.assemblies): if include_spike_source or obj.injectable: # spike sources are not injectable obj.record(variables, to_file=to_file, sampling_interval=sampling_interval) def get_data(self, variables='all', gather=True, clear=False, annotations=None): return [assembly.get_data(variables, gather, clear, annotations) for assembly in self.assemblies] def write_data(self, io, variables='all', gather=True, clear=False, annotations=None): if isinstance(io, str): io = get_io(io) data = self.get_data(variables, gather, clear, annotations) # if self._simulator.state.mpi_rank == 0 or gather is False: if True: # tmp. Need to handle MPI io.write(data) PyNN-0.10.0/pyNN/neuroml/000077500000000000000000000000001415343567000147715ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuroml/__init__.py000066400000000000000000000073721415343567000171130ustar00rootroot00000000000000""" Export of PyNN models to NeuroML 2 See https://github.com/NeuroML/NeuroML2/issues/73 for more details Contact Padraig Gleeson for more details :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from pyNN import common from pyNN.common.control import DEFAULT_MAX_DELAY, DEFAULT_TIMESTEP, DEFAULT_MIN_DELAY from pyNN.connectors import * from pyNN.recording import * from pyNN.neuroml import simulator from pyNN.neuroml.standardmodels import * from pyNN.neuroml.standardmodels.synapses import * from pyNN.neuroml.standardmodels.cells import * from pyNN.neuroml.standardmodels.electrodes import * from pyNN.neuroml.populations import Population, PopulationView, Assembly from pyNN.neuroml.projections import Projection from neo.io import get_io import neuroml logger = logging.getLogger("PyNN_NeuroML") save_format = 'xml' def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" return [obj.__name__ for obj in globals().values() if isinstance(obj, type) and issubclass(obj, StandardCellType)] def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): """ Set up for saving cell models and network structure to NeuroML """ common.setup(timestep, min_delay, **extra_params) simulator.state.clear() simulator.state.dt = timestep # move to common.setup? simulator.state.min_delay = min_delay simulator.state.max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) simulator.state.mpi_rank = extra_params.get('rank', 0) simulator.state.num_processes = extra_params.get('num_processes', 1) logger.debug("Creating network in NeuroML document to store structure") nml_doc = simulator._get_nml_doc(extra_params.get('reference', "PyNN_NeuroML2_Export"),reset=True) global save_format save_format = extra_params.get('save_format', "xml") # Create network net = neuroml.Network(id=nml_doc.id) nml_doc.networks.append(net) lems_sim = simulator._get_lems_sim(reset=True) lems_sim.dt = '%s'%timestep return rank() def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: io = get_io(filename) population.write_data(io, variables) simulator.state.write_on_end = [] nml_doc = simulator._get_nml_doc() import neuroml.writers as writers if save_format == 'xml': nml_file = '%s.net.nml'%nml_doc.id writers.NeuroMLWriter.write(nml_doc, nml_file) elif save_format == 'hdf5': nml_file = '%s.net.nml.h5'%nml_doc.id writers.NeuroMLHdf5Writer.write(nml_doc, nml_file) logger.info("Written NeuroML 2 file out to: "+nml_file) lems_sim = simulator._get_lems_sim() lems_sim.include_neuroml2_file("PyNN.xml", include_included=False) lems_sim.include_neuroml2_file(nml_file) lems_file = lems_sim.save_to_file() logger.info("Written LEMS file (to simulate NeuroML file) to: "+lems_file) # should have common implementation of end() run, run_until = common.build_run(simulator) run_for = run reset = common.build_reset(simulator) initialize = common.initialize get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) #set = common.set record = common.build_record(simulator) record_v = lambda source, filename: record(['v'], source, filename) record_gsyn = lambda source, filename: record(['gsyn_exc', 'gsyn_inh'], source, filename) PyNN-0.10.0/pyNN/neuroml/populations.py000066400000000000000000000124021415343567000177170ustar00rootroot00000000000000""" Export of PyNN models to NeuroML 2 Contact Padraig Gleeson for more details :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np from pyNN import common from pyNN.standardmodels import StandardCellType from pyNN.parameters import ParameterSpace, simplify from . import simulator from .recording import Recorder import logging import neuroml logger = logging.getLogger("PyNN_NeuroML") class Assembly(common.Assembly): _simulator = simulator class PopulationView(common.PopulationView): _assembly_class = Assembly _simulator = simulator def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: value = self.parent._parameters[name] if isinstance(value, np.ndarray): value = value[self.mask] parameter_dict[name] = simplify(value) return ParameterSpace(parameter_dict, shape=(self.size,)) # or local size? def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" #ps = self.parent._get_parameters(*self.celltype.get_native_names()) for name, value in parameter_space.items(): self.parent._parameters[name][self.mask] = value.evaluate(simplify=True) #ps[name][self.mask] = value.evaluate(simplify=True) #ps.evaluate(simplify=True) #self.parent._parameters = ps.as_dict() def _set_initial_value_array(self, variable, initial_values): pass def _get_view(self, selector, label=None): return PopulationView(self, selector, label) class Population(common.Population): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def __init__(self, size, cellclass, cellparams=None, structure=None, initial_values={}, label=None): super(Population, self).__init__(size, cellclass, cellparams, structure,initial_values, label) logger.debug("Created NeuroML Population: %s of size %i" % (self.label, self.size)) for cell in self.all_cells: index = self.id_to_index(cell) inst = neuroml.Instance(id=index) self.pop.instances.append(inst) x = self.positions[0][index] y = self.positions[1][index] z = self.positions[2][index] logger.debug("Creating cell at (%s, %s, %s)"%(x,y,z)) inst.location = neuroml.Location(x=x,y=y,z=z) def _create_cells(self): """Create the cells in the population""" nml_doc = simulator._get_nml_doc() net = simulator._get_main_network() cell_pynn = self.celltype.__class__.__name__ logger.debug("Creating Cell instance: %s" % (cell_pynn)) cell_id = self.celltype.add_to_nml_doc(nml_doc, self) logger.debug("Creating Population: %s of size %i" % (self.label, self.size)) self.pop = neuroml.Population(id=self.label, size = self.size, type="populationList", component=cell_id) net.populations.append(self.pop) id_range = np.arange(simulator.state.id_counter, simulator.state.id_counter + self.size) self.all_cells = np.array([simulator.ID(id) for id in id_range], dtype=simulator.ID) def is_local(id): return (id % simulator.state.num_processes) == simulator.state.mpi_rank self._mask_local = is_local(self.all_cells) if isinstance(self.celltype, StandardCellType): parameter_space = self.celltype.native_parameters else: parameter_space = self.celltype.parameter_space parameter_space.shape = (self.size,) parameter_space.evaluate(mask=self._mask_local, simplify=False) self._parameters = parameter_space.as_dict() for id in self.all_cells: id.parent = self simulator.state.id_counter += self.size def annotate(self, **annotations): print("Updating annotations: %s"%annotations) for k in annotations: self.pop.properties.append(neuroml.Property(k, annotations[k])) self.annotations.update(annotations) def _set_initial_value_array(self, variable, initial_values): pass def _get_view(self, selector, label=None): return PopulationView(self, selector, label) def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: parameter_dict[name] = simplify(self._parameters[name]) return ParameterSpace(parameter_dict, shape=(self.local_size,)) def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" #ps = self._get_parameters(*self.celltype.get_native_names()) #ps.update(**parameter_space) #ps.evaluate(simplify=True) #self._parameters = ps.as_dict() parameter_space.evaluate(simplify=False, mask=self._mask_local) for name, value in parameter_space.items(): self._parameters[name] = value PyNN-0.10.0/pyNN/neuroml/projections.py000066400000000000000000000144551415343567000177130ustar00rootroot00000000000000""" Export of PyNN models to NeuroML 2 Contact Padraig Gleeson for more details :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from itertools import repeat from pyNN import common from pyNN.core import ezip from pyNN.space import Space from . import simulator import logging import neuroml logger = logging.getLogger("PyNN_NeuroML") class Connection(common.Connection): """ Store an individual plastic connection and information about it. Provide an interface that allows access to the connection's weight, delay and other attributes. """ def __init__(self, pre_index, post_index, presynaptic_population, postsynaptic_population, projection_nml, conn_id, pre_pop_comp, post_pop_comp, **attributes): #logger.info("Creating Connection: %s -> %s (%s)" % (pre_index, post_index, projection_nml)) self.presynaptic_index = pre_index self.postsynaptic_index = post_index nml_pre_index = pre_index nml_post_index = post_index pre_pop = presynaptic_population if isinstance(presynaptic_population, common.PopulationView): pre_pop = presynaptic_population.parent nml_pre_index = presynaptic_population.index_in_grandparent([pre_index])[0] post_pop = postsynaptic_population if isinstance(postsynaptic_population, common.PopulationView): post_pop = postsynaptic_population.parent nml_post_index = postsynaptic_population.index_in_grandparent([post_index])[0] projection_nml.connection_wds.append(neuroml.ConnectionWD(id=conn_id,pre_cell_id="../%s/%i/%s"%(pre_pop.label,nml_pre_index,pre_pop_comp), post_cell_id="../%s/%i/%s"%(post_pop.label,nml_post_index,post_pop_comp), weight=attributes['WEIGHT'], delay='%sms'%attributes['DELAY'])) for name, value in attributes.items(): setattr(self, name, value) def as_tuple(self, *attribute_names): # should return indices, not IDs for source and target return tuple([getattr(self, name) for name in attribute_names]) class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source=None, receptor_type=None, space=Space(), label=None): common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) logger.debug("Creating Projection: %s -> %s (%s)" % (presynaptic_population, postsynaptic_population, synapse_type)) self.presynaptic_population = presynaptic_population self.postsynaptic_population = postsynaptic_population nml_doc = simulator._get_nml_doc() net = nml_doc.networks[0] nml_proj_id = self.label.replace(u'\u2192','__TO__') syn_id = 'syn__%s'%nml_proj_id logger.debug("Creating Synapse: %s; %s; %s" % (receptor_type, synapse_type.parameter_space, connector)) celltype = postsynaptic_population.celltype.__class__.__name__ logger.debug("Post cell: %s" % (celltype)) syn = None if receptor_type == 'inhibitory': tau_key = 'tau_syn_I' erev_key = 'e_rev_I' else: tau_key = 'tau_syn_E' erev_key = 'e_rev_E' if 'cond_exp' in celltype: syn = neuroml.ExpCondSynapse(id=syn_id) syn.__setattr__('e_rev', postsynaptic_population.celltype.parameter_space[erev_key].base_value) nml_doc.exp_cond_synapses.append(syn) if 'cond_alpha' in celltype: syn = neuroml.AlphaCondSynapse(id=syn_id) syn.__setattr__('e_rev', postsynaptic_population.celltype.parameter_space[erev_key].base_value) nml_doc.alpha_cond_synapses.append(syn) if 'curr_exp' in celltype: syn = neuroml.ExpCurrSynapse(id=syn_id) nml_doc.exp_curr_synapses.append(syn) if 'curr_alpha' in celltype: syn = neuroml.AlphaCurrSynapse(id=syn_id) nml_doc.alpha_curr_synapses.append(syn) syn.tau_syn = postsynaptic_population.celltype.parameter_space[tau_key].base_value pre_pop = presynaptic_population.label if isinstance(presynaptic_population, common.PopulationView): pre_pop = presynaptic_population.parent.label post_pop = postsynaptic_population.label if isinstance(postsynaptic_population, common.PopulationView): post_pop = postsynaptic_population.parent.label self.pre_pop_comp = '%s_%s'%(presynaptic_population.celltype.__class__.__name__, pre_pop) self.post_pop_comp = '%s_%s'%(postsynaptic_population.celltype.__class__.__name__, post_pop) logger.debug("Creating Projection: %s" % (nml_proj_id)) self.projection = neuroml.Projection(id=nml_proj_id, presynaptic_population=pre_pop, postsynaptic_population=post_pop, synapse=syn_id) net.projections.append(self.projection) ## Create connections self.connections = [] connector.connect(self) def __len__(self): return len(self.connections) def set(self, **attributes): #parameter_space = ParameterSpace raise NotImplementedError def _convergent_connect(self, presynaptic_indices, postsynaptic_index, **connection_parameters): for name, value in connection_parameters.items(): if isinstance(value, float): connection_parameters[name] = repeat(value) for pre_idx, other in ezip(presynaptic_indices, *connection_parameters.values()): other_attributes = dict(zip(connection_parameters.keys(), other)) self.connections.append( Connection(pre_idx, postsynaptic_index, self.presynaptic_population, self.postsynaptic_population, self.projection, len(self.connections), self.pre_pop_comp, self.post_pop_comp, **other_attributes) )PyNN-0.10.0/pyNN/neuroml/recording.py000066400000000000000000000071061415343567000173230ustar00rootroot00000000000000""" Export of PyNN models to NeuroML 2 Contact Padraig Gleeson for more details :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np from pyNN import recording from . import simulator import logging logger = logging.getLogger("PyNN_NeuroML") class Recorder(recording.Recorder): _simulator = simulator def __init__(self, population, file=None): super(Recorder, self).__init__(population, file=file) self.event_output_files = [] self.displays = [] self.output_files = [] def _record(self, variable, new_ids, sampling_interval=None): lems_sim = simulator._get_lems_sim() for id in new_ids: if variable == 'v': logger.debug("Recording var: %s; %s; %s"%(variable, id, id.parent)) pop_id = id.parent.label celltype = id.parent.celltype.__class__.__name__ disp_id = '%s_%s'%(pop_id,variable) of_id = 'OF_%s'%disp_id index = id.parent.id_to_index(id) if not disp_id in self.displays: lems_sim.create_display(disp_id, '%s %s'%(pop_id,variable), "-70", "10") self.displays.append(disp_id) if not of_id in self.output_files: lems_sim.create_output_file(of_id, "%s.dat"%disp_id) self.output_files.append(of_id) #quantity = "%s/%i/%s/%s"%(pop_id,index,id.celltype.__class__.__name__,variable) quantity = "%s/%i/%s_%s/%s"%(pop_id,index,celltype,pop_id,variable) lems_sim.add_line_to_display(disp_id, '%s %s: cell %s'%(pop_id,variable,id), quantity, "1mV") lems_sim.add_column_to_output_file(of_id, quantity.replace('/','_'), quantity) elif variable == 'spikes': logger.debug("Recording spike: %s; %s; %s"%(variable, id, id.parent)) pop_id = id.parent.label celltype = id.parent.celltype.__class__.__name__ index = id.parent.id_to_index(id) eof0 = 'Spikes_file_%s'%pop_id if not eof0 in self.event_output_files: lems_sim.create_event_output_file(eof0, "%s.spikes"%pop_id, format='TIME_ID') self.event_output_files.append(eof0) lems_sim.add_selection_to_event_output_file(eof0, index, "%s/%i/%s_%s"%(pop_id,index,celltype,pop_id), 'spike') def _get_spiketimes(self, id, clear=False): if hasattr(id, "__len__"): spks = {} for i in id: spks[i] = np.array([i, i + 5], dtype=float) % self._simulator.state.t return spks else: return np.array([id, id + 5], dtype=float) % self._simulator.state.t def _get_all_signals(self, variable, ids, clear=False): # assuming not using cvode, otherwise need to get times as well and use IrregularlySampledAnalogSignal n_samples = int(round(self._simulator.state.t/self._simulator.state.dt)) + 1 return np.vstack([np.random.uniform(size=n_samples) for id in ids]).T def _local_count(self, variable, filter_ids=None): N = {} if variable == 'spikes': for id in self.filter_recorded(variable, filter_ids): N[int(id)] = 2 else: raise Exception("Only implemented for spikes") return N def _clear_simulator(self): pass def _reset(self): self.displays = [] self.output_files = [] self.event_output_files = [] PyNN-0.10.0/pyNN/neuroml/simulator.py000066400000000000000000000051421415343567000173640ustar00rootroot00000000000000""" Export of PyNN models to NeuroML 2 Contact Padraig Gleeson for more details :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import common from pyNN import __version__ import logging name = "NeuroML2Converter" import neuroml import pyneuroml from pyneuroml.lems.LEMSSimulation import LEMSSimulation nml_doc = None lems_sim = None logger = logging.getLogger("PyNN_NeuroML") comment = "\n This %s file has been generated from: \n" + \ " PyNN v%s\n"%__version__ + \ " libNeuroML v%s\n"%neuroml.__version__ + \ " pyNeuroML v%s\n "%pyneuroml.__version__ def _get_nml_doc(reference="PyNN_NeuroML2_Export",reset=False): """Return the main NeuroMLDocument object being created""" global nml_doc global comment if nml_doc == None or reset: nml_doc = neuroml.NeuroMLDocument(id=reference) nml_doc.notes = comment%'NeuroML 2' return nml_doc def _get_main_network(): """Return the main NeuroML network object being created""" return _get_nml_doc().networks[0] def _get_lems_sim(reference=None,reset=False): """Return the main LEMSSimulation object being created""" global lems_sim global comment if reference == None: reference = _get_nml_doc().id if lems_sim == None or reset: # Note: values will be over written lems_sim = LEMSSimulation("Sim_%s"%reference, 100, 0.01, target=reference,comment=comment%'LEMS') return lems_sim class ID(int, common.IDMixin): def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) class State(common.control.BaseState): def __init__(self): logger.debug("State initialised!") common.control.BaseState.__init__(self) self.mpi_rank = 0 self.num_processes = 1 self.clear() self.dt = 0.1 def run(self, simtime): self.t += simtime self.running = True def run_until(self, tstop): logger.debug("run_until() called with %s"%tstop) lems_sim = _get_lems_sim() lems_sim.duration = float(tstop) self.t = tstop self.running = True def clear(self): self.recorders = set([]) self.id_counter = 42 self.segment_counter = -1 self.reset() def reset(self): """Reset the state of the current network to time t = 0.""" self.running = False self.t = 0 self.t_start = 0 self.segment_counter += 1 state = State() PyNN-0.10.0/pyNN/neuroml/standardmodels/000077500000000000000000000000001415343567000177755ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuroml/standardmodels/__init__.py000066400000000000000000000000001415343567000220740ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuroml/standardmodels/cells.py000066400000000000000000000225171415343567000214600ustar00rootroot00000000000000""" Standard cells for the NeuroML module. :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import cells, build_translations import logging from pyNN.random import RandomDistribution import neuroml logger = logging.getLogger("PyNN_NeuroML") def add_params(pynn_cell, nml_cell, set_v_init=True): """Copy the parameters set in PyNN to the NeuroML equivalent""" for param in pynn_cell.simple_parameters(): value_generator = pynn_cell.parameter_space[param].base_value #print(value_generator) # TODO: handle this.... if isinstance(value_generator, RandomDistribution): print('*'*200+'\n\nRandom element in population! Not supported!!\n\n'+'*'*200) value = value_generator.next() else: value = float(value_generator) nml_param = param # .lower() if (not 'tau_syn' in param and not 'e_rev' in param) else param logger.debug("Adding param: %s = %s as %s for cell %s"%(param, value, nml_param, nml_cell.id)) nml_cell.__setattr__(nml_param, value) if set_v_init: nml_cell.__setattr__('v_init', pynn_cell.default_initial_values['v']) logger.debug("Adding param: %s = %s as %s for cell %s"%('v_init', pynn_cell.default_initial_values['v'], 'v_init', nml_cell.id)) class IF_curr_alpha(cells.IF_curr_alpha): __doc__ = cells.IF_curr_alpha.__doc__ translations = build_translations( # should add some computed/scaled parameters ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.IF_curr_alpha(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.IF_curr_alpha.append(cell) add_params(self, cell) return cell.id class IF_curr_exp(cells.IF_curr_exp): __doc__ = cells.IF_curr_exp.__doc__ translations = build_translations( # should add some computed/scaled parameters ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'T_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.IF_curr_exp(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.IF_curr_exp.append(cell) add_params(self, cell) return cell.id class IF_cond_alpha(cells.IF_cond_alpha): __doc__ = cells.IF_cond_alpha.__doc__ translations = build_translations( ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I') ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.IF_cond_alpha(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.IF_cond_alpha.append(cell) add_params(self, cell) return cell.id class IF_cond_exp(cells.IF_cond_exp): __doc__ = cells.IF_cond_exp.__doc__ translations = build_translations( ('tau_m', 'TAU_M'), ('cm', 'CM'), ('v_rest', 'V_REST'), ('v_thresh', 'V_THRESH'), ('v_reset', 'V_RESET'), ('tau_refrac', 'TAU_REFRAC'), ('i_offset', 'I_OFFSET'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I') ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.IF_cond_exp(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.IF_cond_exp.append(cell) add_params(self, cell) return cell.id class IF_facets_hardware1(cells.IF_facets_hardware1): __doc__ = cells.IF_facets_hardware1.__doc__ class HH_cond_exp(cells.HH_cond_exp): __doc__ = cells.HH_cond_exp.__doc__ translations = build_translations( ('gbar_Na', 'GBAR_NA'), ('gbar_K', 'GBAR_K'), ('g_leak', 'G_LEAK'), ('cm', 'CM'), ('v_offset', 'V_OFFSET'), ('e_rev_Na', 'E_REV_NA'), ('e_rev_K', 'E_REV_K'), ('e_rev_leak', 'E_REV_LEAK'), ('e_rev_E', 'E_REV_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_E', 'TAU_SYN_E'), ('tau_syn_I', 'TAU_SYN_I'), ('i_offset', 'I_OFFSET'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.HH_cond_exp(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.HH_cond_exp.append(cell) add_params(self, cell) return cell.id class IF_cond_exp_gsfa_grr(cells.IF_cond_exp_gsfa_grr): __doc__ = cells.IF_cond_exp_gsfa_grr.__doc__ class SpikeSourcePoisson(cells.SpikeSourcePoisson): __doc__ = cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('start', 'START'), ('rate', 'INTERVAL', "1000.0/rate", "1000.0/INTERVAL"), ('duration', 'DURATION'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.SpikeSourcePoisson(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.SpikeSourcePoisson.append(cell) cell.start = '%sms'%self.parameter_space['start'].base_value cell.duration = '%sms'%self.parameter_space['duration'].base_value cell.rate = '%sHz'%self.parameter_space['rate'].base_value return cell.id class SpikeSourceArray(cells.SpikeSourceArray): __doc__ = cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'SPIKE_TIMES'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.SpikeArray(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) index=0 spikes = self.parameter_space['spike_times'] for spike_time in spikes.base_value.value: cell.spikes.append(neuroml.Spike(id=index, time='%sms'%spike_time)) index+=1 nml_doc.spike_arrays.append(cell) return cell.id class EIF_cond_alpha_isfa_ista(cells.EIF_cond_alpha_isfa_ista): __doc__ = cells.EIF_cond_alpha_isfa_ista.__doc__ translations = build_translations( ('cm', 'CM'), ('tau_refrac', 'TAU_REFRAC'), ('v_spike', 'V_SPIKE'), ('v_reset', 'V_RESET'), ('v_rest', 'V_REST'), ('tau_m', 'TAU_M'), ('i_offset', 'I_OFFSET'), ('a', 'A'), ('b', 'B'), ('delta_T', 'DELTA_T'), ('tau_w', 'TAU_W'), ('v_thresh', 'V_THRESH'), ('e_rev_E', 'E_REV_E'), ('tau_syn_E', 'TAU_SYN_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_I', 'TAU_SYN_I'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.EIF_cond_alpha_isfa_ista(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.EIF_cond_alpha_isfa_ista.append(cell) add_params(self, cell) return cell.id class EIF_cond_exp_isfa_ista(cells.EIF_cond_exp_isfa_ista): __doc__ = cells.EIF_cond_exp_isfa_ista.__doc__ translations = build_translations( ('cm', 'CM'), ('tau_refrac', 'TAU_REFRAC'), ('v_spike', 'V_SPIKE'), ('v_reset', 'V_RESET'), ('v_rest', 'V_REST'), ('tau_m', 'TAU_M'), ('i_offset', 'I_OFFSET'), ('a', 'A'), ('b', 'B'), ('delta_T', 'DELTA_T'), ('tau_w', 'TAU_W'), ('v_thresh', 'V_THRESH'), ('e_rev_E', 'E_REV_E'), ('tau_syn_E', 'TAU_SYN_E'), ('e_rev_I', 'E_REV_I'), ('tau_syn_I', 'TAU_SYN_I'), ) def add_to_nml_doc(self, nml_doc, population): cell = neuroml.EIF_cond_exp_isfa_ista(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.EIF_cond_exp_isfa_ista.append(cell) add_params(self, cell) return cell.id class Izhikevich(cells.Izhikevich): __doc__ = cells.Izhikevich.__doc__ translations = build_translations( ('a', 'a'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('i_offset', 'I_e'), ) standard_receptor_type = True receptor_scale = 1e-3 # synaptic weight is in mV, so need to undo usual weight scaling def add_to_nml_doc(self, nml_doc, population): cell = neuroml.Izhikevich(id="%s_%s"%(self.__class__.__name__, population.label if population else '0')) nml_doc.Izhikevich.append(cell) add_params(self, cell) return cell.id PyNN-0.10.0/pyNN/neuroml/standardmodels/electrodes.py000066400000000000000000000143761415343567000225130ustar00rootroot00000000000000""" Standard electrodes for the NeuroML module. :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import electrodes, build_translations, StandardCurrentSource from pyNN.neuroml.simulator import _get_nml_doc, _get_main_network import logging from pyNN.parameters import ParameterSpace, Sequence import neuroml logger = logging.getLogger("PyNN_NeuroML") current_sources = [] class NeuroMLCurrentSource(StandardCurrentSource): def __init__(self, **parameters): super(StandardCurrentSource, self).__init__(**parameters) global current_sources self.cell_list = [] self.indices = [] self.ind = len(current_sources) # Todo use self.indices instead... current_sources.append(self) parameter_space = ParameterSpace(self.default_parameters, self.get_schema(), shape=(1,)) parameter_space.update(**parameters) parameter_space = self.translate(parameter_space) self.set_native_parameters(parameter_space) def set_native_parameters(self, parameters): parameters.evaluate(simplify=True) for name, value in parameters.items(): if isinstance(value, Sequence): value = value.value object.__setattr__(self, name, value) def _get_input_list(self, stim_id, pop): id="Input_%s"%(stim_id) self.network = _get_main_network() for il in self.network.input_lists: if il.id ==id: return il input_list = neuroml.InputList(id=id, component=stim_id, populations=pop) self.network.input_lists.append(input_list) return input_list def inject_into(self, cells): __doc__ = StandardCurrentSource.inject_into.__doc__ logger.debug("%s injecting into: %s"%(self.__class__.__name__, cells)) self.nml_doc = _get_nml_doc() id = self.add_to_nml_doc(self.nml_doc, cells) for cell in cells: pop_id = cell.parent.label index = cell.parent.id_to_index(cell) celltype = cell.parent.celltype.__class__.__name__ logger.debug("Injecting: %s to %s (%s[%s])"%(id, cell, pop_id, index)) input_list = self._get_input_list(id, pop_id) input = neuroml.Input(id=len(input_list.input), target="../%s/%i/%s_%s"%(pop_id, index, celltype, pop_id), destination="synapses") input_list.input.append(input) def get_id_for_nml(self, cells): return "%s_%s_%s"%(self.__class__.__name__, cells.label if hasattr(cells, 'label') else self.__class__.__name__, self.ind) class DCSource(NeuroMLCurrentSource, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop') ) def add_to_nml_doc(self, nml_doc, cells): id=self.get_id_for_nml(cells) pg = neuroml.PulseGenerator(id=id, delay='%sms'%self.start, duration='%sms'%(self.stop-self.start), amplitude='%snA'%self.amplitude) found = False for pg in nml_doc.pulse_generators: if pg.id==id: found = True if not found: nml_doc.pulse_generators.append(pg) return pg.id class StepCurrentSource(NeuroMLCurrentSource, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitudes'), ('times', 'times') ) def add_to_nml_doc(self, nml_doc, cells): ci = neuroml.CompoundInput(id=self.get_id_for_nml(cells)) num_steps = len(self.amplitudes) for i in range(num_steps): next_time = 1e9 if i==num_steps-1 else self.times[i+1] ci.pulse_generators.append(neuroml.PulseGenerator(id='step_%s'%i,delay='%sms'%self.times[i],duration='%sms'%(next_time-self.times[i]),amplitude='%snA'%self.amplitudes[i])) self.nml_doc = _get_nml_doc() self.nml_doc.compound_inputs.append(ci) return ci.id class ACSource(NeuroMLCurrentSource, electrodes.ACSource): __doc__ = electrodes.ACSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop'), ('frequency', 'frequency'), ('offset', 'offset'), ('phase', 'phase') ) def add_to_nml_doc(self, nml_doc, cells): ci = neuroml.CompoundInput(id=self.get_id_for_nml(cells)) sg = neuroml.SineGenerator(id='SG_'+self.get_id_for_nml(cells), delay='%sms'%self.start, duration='%sms'%(self.stop-self.start), amplitude='%snA'%self.amplitude, period='%s s'%(1/float(self.frequency)), phase=(3.14159265 * self.phase/180)) pg = neuroml.PulseGenerator(id='PG_'+self.get_id_for_nml(cells), delay='%sms'%self.start, duration='%sms'%(self.stop-self.start), amplitude='%snA'%self.offset) ci.sine_generators.append(sg) ci.pulse_generators.append(pg) self.nml_doc = _get_nml_doc() self.nml_doc.compound_inputs.append(ci) return ci.id class NoisyCurrentSource(NeuroMLCurrentSource, electrodes.NoisyCurrentSource): translations = build_translations( ('mean', 'mean'), ('start', 'start'), ('stop', 'stop'), ('stdev', 'stdev'), ('dt', 'dt') ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() PyNN-0.10.0/pyNN/neuroml/standardmodels/synapses.py000066400000000000000000000073631415343567000222250ustar00rootroot00000000000000""" Standard synapses for the NeuroML module. :copyright: Copyright 2006-2017 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import synapses, build_translations from pyNN.neuroml.simulator import state import logging import neuroml logger = logging.getLogger("PyNN_NeuroML") class StaticSynapse(synapses.StaticSynapse): __doc__ = synapses.StaticSynapse.__doc__ translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY'), ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class TsodyksMarkramSynapse(synapses.TsodyksMarkramSynapse): __doc__ = synapses.TsodyksMarkramSynapse.__doc__ translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY'), ('U', 'UU'), ('tau_rec', 'TAU_REC'), ('tau_facil', 'TAU_FACIL'), ('u0', 'U0'), ('x0', 'X' ), ('y0', 'Y') ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class STDPMechanism(synapses.STDPMechanism): __doc__ = synapses.STDPMechanism.__doc__ base_translations = build_translations( ('weight', 'WEIGHT'), ('delay', 'DELAY') ) def _get_minimum_delay(self): d = state.min_delay if d == 'auto': d = state.dt return d def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class AdditiveWeightDependence(synapses.AdditiveWeightDependence): __doc__ = synapses.AdditiveWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class MultiplicativeWeightDependence(synapses.MultiplicativeWeightDependence): __doc__ = synapses.MultiplicativeWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class AdditivePotentiationMultiplicativeDepression(synapses.AdditivePotentiationMultiplicativeDepression): __doc__ = synapses.AdditivePotentiationMultiplicativeDepression.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class GutigWeightDependence(synapses.GutigWeightDependence): __doc__ = synapses.GutigWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ('mu_plus', 'muLTP'), ('mu_minus', 'muLTD'), ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() class SpikePairRule(synapses.SpikePairRule): __doc__ = synapses.SpikePairRule.__doc__ translations = build_translations( ('tau_plus', 'tauLTP'), ('tau_minus', 'tauLTD'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) def add_to_nml_doc(self, nml_doc, population): raise NotImplementedError() PyNN-0.10.0/pyNN/neuron/000077500000000000000000000000001415343567000146165ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuron/__init__.py000066400000000000000000000115141415343567000167310ustar00rootroot00000000000000# encoding: utf-8 """ nrnpython implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import warnings try: from mpi4py import MPI except ImportError: warnings.warn("mpi4py not available") from pyNN.random import NumpyRNG, GSLRNG from pyNN import common, core, space, __doc__ from pyNN.common.control import DEFAULT_MAX_DELAY, DEFAULT_TIMESTEP, DEFAULT_MIN_DELAY from pyNN.standardmodels import StandardCellType from pyNN.recording import get_io from pyNN.space import Space from pyNN.neuron import simulator from pyNN.neuron.random import NativeRNG from pyNN.neuron.standardmodels.cells import * from pyNN.neuron.connectors import * from pyNN.neuron.standardmodels.synapses import * from pyNN.neuron.standardmodels.electrodes import * from pyNN.neuron.populations import Population, PopulationView, Assembly from pyNN.neuron.projections import Projection from pyNN.neuron.cells import NativeCellType, IntFire1, IntFire2, IntFire4 try: from . import nineml except ImportError: pass # nineml is an optional dependency import logging logger = logging.getLogger("PyNN") # ============================================================================== # Utility functions # ============================================================================== def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" return [obj.__name__ for obj in globals().values() if (isinstance(obj, type) and issubclass(obj, StandardCellType) and obj is not StandardCellType)] # ============================================================================== # Functions for simulation set-up and control # ============================================================================== def setup(timestep=DEFAULT_TIMESTEP, min_delay=DEFAULT_MIN_DELAY, **extra_params): """ Should be called at the very beginning of a script. `extra_params` contains any keyword arguments that are required by a given simulator but not by others. NEURON specific extra_params: use_cvode - use the NEURON cvode solver. Defaults to False. Optional cvode Parameters: -> rtol - specify relative error tolerance -> atol - specify absolute error tolerance native_rng_baseseed - added to MPI.rank to form seed for SpikeSourcePoisson, etc. default_maxstep - TODO returns: MPI rank """ common.setup(timestep, min_delay, **extra_params) simulator.initializer.clear() simulator.state.clear() simulator.state.dt = timestep simulator.state.min_delay = min_delay simulator.state.max_delay = extra_params.get('max_delay', DEFAULT_MAX_DELAY) if 'use_cvode' in extra_params: simulator.state.cvode.active(int(extra_params['use_cvode'])) if 'rtol' in extra_params: simulator.state.cvode.rtol(float(extra_params['rtol'])) if 'atol' in extra_params: simulator.state.cvode.atol(float(extra_params['atol'])) if 'native_rng_baseseed' in extra_params: simulator.state.native_rng_baseseed = int(extra_params['native_rng_baseseed']) if 'default_maxstep' in extra_params: simulator.state.default_maxstep = float(extra_params['default_maxstep']) return rank() def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: io = get_io(filename) population.write_data(io, variables) simulator.state.write_on_end = [] # simulator.state.finalize() run, run_until = common.build_run(simulator) run_for = run reset = common.build_reset(simulator) initialize = common.initialize # ============================================================================== # Functions returning information about the simulation state # ============================================================================== get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) # ============================================================================== # Low-level API for creating, connecting and recording from individual neurons # ============================================================================== create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) set = common.set record = common.build_record(simulator) def record_v(source, filename): return record(['v'], source, filename) def record_gsyn(source, filename): return record(['gsyn_exc', 'gsyn_inh'], source, filename) # ============================================================================== PyNN-0.10.0/pyNN/neuron/cells.py000066400000000000000000000604751415343567000163060ustar00rootroot00000000000000# encoding: utf-8 """ Definition of cell classes for the neuron module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from math import pi from functools import reduce import numpy as np from neuron import h, nrn, hclass from pyNN import errors from pyNN.models import BaseCellType from .recording import recordable_pattern from .simulator import state logger = logging.getLogger("PyNN") def _new_property(obj_hierarchy, attr_name): """ Returns a new property, mapping attr_name to obj_hierarchy.attr_name. For example, suppose that an object of class A has an attribute b which itself has an attribute c which itself has an attribute d. Then placing e = _new_property('b.c', 'd') in the class definition of A makes A.e an alias for A.b.c.d """ def set(self, value): obj = reduce(getattr, [self] + obj_hierarchy.split('.')) setattr(obj, attr_name, value) def get(self): obj = reduce(getattr, [self] + obj_hierarchy.split('.')) return getattr(obj, attr_name) return property(fset=set, fget=get) def guess_units(variable): # works with NEURON 7.3, not with 7.1, 7.2 not tested nrn_units = h.units(variable.split('.')[-1]) pq_units = nrn_units.replace("2", "**2").replace("3", "**3") return pq_units class NativeCellType(BaseCellType): def can_record(self, variable): # crude check, could be improved return bool(recordable_pattern.match(variable)) # todo: use `guess_units` to construct "units" attribute class BaseSingleCompartmentNeuron(nrn.Section): """docstring""" def __init__(self, c_m, i_offset): # initialise Section object with 'pas' mechanism nrn.Section.__init__(self) self.seg = self(0.5) self.L = 100 self.seg.diam = 1000 / pi # gives area = 1e-3 cm2 self.source_section = self # insert current source self.stim = h.IClamp(0.5, sec=self) self.stim.delay = 0 self.stim.dur = 1e12 self.stim.amp = i_offset # for recording self.spike_times = h.Vector(0) self.traces = {} self.recording_time = 0 self.v_init = None def area(self): """Membrane area in µm²""" return pi * self.L * self.seg.diam c_m = _new_property('seg', 'cm') i_offset = _new_property('stim', 'amp') def memb_init(self): assert self.v_init is not None, "cell is a %s" % self.__class__.__name__ for seg in self: seg.v = self.v_init #self.seg.v = self.v_init def set_parameters(self, param_dict): for name in self.parameter_names: setattr(self, name, param_dict[name]) class SingleCompartmentNeuron(BaseSingleCompartmentNeuron): """Single compartment with excitatory and inhibitory synapses""" synapse_models = { 'current': {'exp': h.ExpISyn, 'alpha': h.AlphaISyn}, 'conductance': {'exp': h.ExpSyn, 'alpha': h.AlphaSyn}, } def __init__(self, syn_type, syn_shape, c_m, i_offset, tau_e, tau_i, e_e, e_i): BaseSingleCompartmentNeuron.__init__(self, c_m, i_offset) self.syn_type = syn_type self.syn_shape = syn_shape # insert synapses assert syn_type in ( 'current', 'conductance'), "syn_type must be either 'current' or 'conductance'. Actual value is %s" % syn_type assert syn_shape in ('alpha', 'exp'), "syn_type must be either 'alpha' or 'exp'" synapse_model = self.synapse_models[syn_type][syn_shape] self.esyn = synapse_model(0.5, sec=self) self.isyn = synapse_model(0.5, sec=self) @property def excitatory(self): return self.esyn @property def inhibitory(self): return self.isyn def _get_tau_e(self): return self.esyn.tau def _set_tau_e(self, value): self.esyn.tau = value tau_e = property(fget=_get_tau_e, fset=_set_tau_e) def _get_tau_i(self): return self.isyn.tau def _set_tau_i(self, value): self.isyn.tau = value tau_i = property(fget=_get_tau_i, fset=_set_tau_i) def _get_e_e(self): return self.esyn.e def _set_e_e(self, value): self.esyn.e = value e_e = property(fget=_get_e_e, fset=_set_e_e) def _get_e_i(self): return self.isyn.e def _set_e_i(self, value): self.isyn.e = value e_i = property(fget=_get_e_i, fset=_set_e_i) class LeakySingleCompartmentNeuron(SingleCompartmentNeuron): def __init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, i_offset, tau_e, tau_i, e_e, e_i): SingleCompartmentNeuron.__init__(self, syn_type, syn_shape, c_m, i_offset, tau_e, tau_i, e_e, e_i) self.insert('pas') self.v_init = v_rest # default value def __set_tau_m(self, value): # print("setting tau_m to", value, "cm =", self.seg.cm)) # cm(nF)/tau_m(ms) = G(uS) = 1e-6G(S). Divide by area (1e-3) to get factor of 1e-3 self.seg.pas.g = 1e-3 * self.seg.cm / value def __get_tau_m(self): #print("tau_m = ", 1e-3*self.seg.cm/self.seg.pas.g, "cm = ", self.seg.cm) return 1e-3 * self.seg.cm / self.seg.pas.g def __get_cm(self): #print("cm = ", self.seg.cm) return self.seg.cm def __set_cm(self, value): # when we set cm, need to change g to maintain the same value of tau_m #print("setting cm to", value) tau_m = self.tau_m self.seg.cm = value self.tau_m = tau_m v_rest = _new_property('seg.pas', 'e') tau_m = property(fget=__get_tau_m, fset=__set_tau_m) c_m = property(fget=__get_cm, fset=__set_cm) # if the property were called 'cm' # it would never get accessed as the # built-in Section.cm would always # be used first class StandardIF(LeakySingleCompartmentNeuron): """docstring""" def __init__(self, syn_type, syn_shape, tau_m=20, c_m=1.0, v_rest=-65, v_thresh=-55, t_refrac=2, i_offset=0, v_reset=None, tau_e=5, tau_i=5, e_e=0, e_i=-70): if v_reset is None: v_reset = v_rest LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, i_offset, tau_e, tau_i, e_e, e_i) # insert spike reset mechanism self.spike_reset = h.ResetRefrac(0.5, sec=self) self.spike_reset.vspike = 40 # (mV) spike height self.source = self.spike_reset self.rec = h.NetCon(self.source, None) # process arguments self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 't_refrac', # 'c_m' must come before 'tau_m' 'i_offset', 'v_reset', 'tau_e', 'tau_i'] if syn_type == 'conductance': self.parameter_names.extend(['e_e', 'e_i']) self.set_parameters(locals()) v_thresh = _new_property('spike_reset', 'vthresh') v_reset = _new_property('spike_reset', 'vreset') t_refrac = _new_property('spike_reset', 'trefrac') class BretteGerstnerIF(LeakySingleCompartmentNeuron): """docstring""" def __init__(self, syn_type, syn_shape, tau_m=20, c_m=1.0, v_rest=-65, v_thresh=-55, t_refrac=2, i_offset=0, tau_e=5, tau_i=5, e_e=0, e_i=-70, v_spike=0.0, v_reset=-70.6, A=4.0, B=0.0805, tau_w=144.0, delta=2.0): LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, i_offset, tau_e, tau_i, e_e, e_i) # insert Brette-Gerstner spike mechanism self.adexp = h.AdExpIF(0.5, sec=self) self.source = self.adexp self.rec = h.NetCon(self.source, None) self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 't_refrac', 'i_offset', 'v_reset', 'tau_e', 'tau_i', 'A', 'B', 'tau_w', 'delta', 'v_spike'] if syn_type == 'conductance': self.parameter_names.extend(['e_e', 'e_i']) self.set_parameters(locals()) self.w_init = None v_thresh = _new_property('adexp', 'vthresh') v_reset = _new_property('adexp', 'vreset') t_refrac = _new_property('adexp', 'trefrac') B = _new_property('adexp', 'b') A = _new_property('adexp', 'a') # using 'A' because for some reason, cell.a gives the error "NameError: a, the mechanism does not exist at PySec_170bb70(0.5)" tau_w = _new_property('adexp', 'tauw') delta = _new_property('adexp', 'delta') def __set_v_spike(self, value): self.adexp.vspike = value self.adexp.vpeak = value + 10.0 def __get_v_spike(self): return self.adexp.vspike v_spike = property(fget=__get_v_spike, fset=__set_v_spike) def __set_tau_m(self, value): # cm(nF)/tau_m(ms) = G(uS) = 1e-6G(S). Divide by area (1e-3) to get factor of 1e-3 self.seg.pas.g = 1e-3 * self.seg.cm / value self.adexp.GL = self.seg.pas.g * self.area() * 1e-2 # S/cm2 to uS def __get_tau_m(self): return 1e-3 * self.seg.cm / self.seg.pas.g def __set_v_rest(self, value): self.seg.pas.e = value self.adexp.EL = value def __get_v_rest(self): return self.seg.pas.e tau_m = property(fget=__get_tau_m, fset=__set_tau_m) v_rest = property(fget=__get_v_rest, fset=__set_v_rest) def get_threshold(self): if self.delta == 0: return self.adexp.vthresh else: return self.adexp.vspike def memb_init(self): assert self.v_init is not None, "cell is a %s" % self.__class__.__name__ assert self.w_init is not None for seg in self: seg.v = self.v_init self.adexp.w = self.w_init class Izhikevich_(BaseSingleCompartmentNeuron): """docstring""" def __init__(self, a_=0.02, b=0.2, c=-65.0, d=2.0, i_offset=0.0): BaseSingleCompartmentNeuron.__init__(self, 1.0, i_offset) self.L = 10 self.seg.diam = 10 / pi self.c_m = 1.0 # insert Izhikevich mechanism self.izh = h.Izhikevich(0.5, sec=self) self.source = self.izh self.rec = h.NetCon(self.seg._ref_v, None, self.get_threshold(), 0.0, 0.0, sec=self) self.excitatory = self.inhibitory = self.source self.parameter_names = ['a_', 'b', 'c', 'd', 'i_offset'] self.set_parameters(locals()) self.u_init = None a_ = _new_property('izh', 'a') b = _new_property('izh', 'b') c = _new_property('izh', 'c') d = _new_property('izh', 'd') # using 'a_' because for some reason, cell.a gives the error "NameError: a, the mechanism does not exist at PySec_170bb70(0.5)" def get_threshold(self): return self.izh.vthresh def memb_init(self): assert self.v_init is not None, "cell is a %s" % self.__class__.__name__ assert self.u_init is not None for seg in self: seg.v = self.v_init self.izh.u = self.u_init class GsfaGrrIF(StandardIF): """docstring""" def __init__(self, syn_type, syn_shape, tau_m=10.0, c_m=1.0, v_rest=-70.0, v_thresh=-57.0, t_refrac=0.1, i_offset=0.0, tau_e=1.5, tau_i=10.0, e_e=0.0, e_i=-75.0, v_spike=0.0, v_reset=-70.0, q_rr=3214.0, q_sfa=14.48, e_rr=-70.0, e_sfa=-70.0, tau_rr=1.97, tau_sfa=110.0): StandardIF.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, v_thresh, t_refrac, i_offset, v_reset, tau_e, tau_i, e_e, e_i) # insert GsfaGrr mechanism self.gsfa_grr = h.GsfaGrr(0.5, sec=self) self.v_thresh = v_thresh self.parameter_names = ['c_m', 'tau_m', 'v_rest', 'v_thresh', 'v_reset', 't_refrac', 'tau_e', 'tau_i', 'i_offset', 'e_rr', 'e_sfa', 'q_rr', 'q_sfa', 'tau_rr', 'tau_sfa'] if syn_type == 'conductance': self.parameter_names.extend(['e_e', 'e_i']) self.set_parameters(locals()) q_sfa = _new_property('gsfa_grr', 'q_s') q_rr = _new_property('gsfa_grr', 'q_r') tau_sfa = _new_property('gsfa_grr', 'tau_s') tau_rr = _new_property('gsfa_grr', 'tau_r') e_sfa = _new_property('gsfa_grr', 'E_s') e_rr = _new_property('gsfa_grr', 'E_r') def __set_v_thresh(self, value): self.spike_reset.vthresh = value # this can fail on constructor # todo: figure out why it is failing and fix in a way that does not require ignoring an Exception try: self.gsfa_grr.vthresh = value except AttributeError: pass def __get_v_thresh(self): return self.spike_reset.vthresh v_thresh = property(fget=__get_v_thresh, fset=__set_v_thresh) class SingleCompartmentTraub(SingleCompartmentNeuron): def __init__(self, syn_type, syn_shape, c_m=1.0, e_leak=-65, i_offset=0, tau_e=5, tau_i=5, e_e=0, e_i=-70, gbar_Na=20e-3, gbar_K=6e-3, g_leak=0.01e-3, ena=50, ek=-90, v_offset=-63): """ Conductances are in millisiemens (S/cm2, since A = 1e-3) """ SingleCompartmentNeuron.__init__(self, syn_type, syn_shape, c_m, i_offset, tau_e, tau_i, e_e, e_i) self.source = self.seg._ref_v self.source_section = self self.rec = h.NetCon(self.source, None, sec=self) self.insert('k_ion') self.insert('na_ion') self.insert('hh_traub') self.parameter_names = ['c_m', 'e_leak', 'i_offset', 'tau_e', 'tau_i', 'gbar_Na', 'gbar_K', 'g_leak', 'ena', 'ek', 'v_offset'] if syn_type == 'conductance': self.parameter_names.extend(['e_e', 'e_i']) self.set_parameters(locals()) self.v_init = e_leak # default value # not sure ena and ek are handled correctly e_leak = _new_property('seg.hh_traub', 'el') v_offset = _new_property('seg.hh_traub', 'vT') gbar_Na = _new_property('seg.hh_traub', 'gnabar') gbar_K = _new_property('seg.hh_traub', 'gkbar') g_leak = _new_property('seg.hh_traub', 'gl') def get_threshold(self): return 10.0 class GIFNeuron(LeakySingleCompartmentNeuron): """ to write... References: [1] Mensi, S., Naud, R., Pozzorini, C., Avermann, M., Petersen, C. C., & Gerstner, W. (2012). Parameter extraction and classification of three cortical neuron types reveals two distinct adaptation mechanisms. Journal of Neurophysiology, 107(6), 1756-1775. [2] Pozzorini, C., Mensi, S., Hagens, O., Naud, R., Koch, C., & Gerstner, W. (2015). Automated High-Throughput Characterization of Single Neurons by Means of Simplified Spiking Models. PLoS Comput Biol, 11(6), e1004275. """ def __init__(self, syn_type, syn_shape, tau_m=20, c_m=1.0, v_rest=-65, t_refrac=2, i_offset=0, v_reset=-55.0, tau_e=5, tau_i=5, e_e=0, e_i=-70, vt_star=-48.0, dV=0.5, lambda0=1.0, tau_eta=(10.0, 50.0, 250.0), a_eta=(0.2, 0.05, 0.025), tau_gamma=(5.0, 200.0, 250.0), a_gamma=(15.0, 3.0, 1.0)): LeakySingleCompartmentNeuron.__init__(self, syn_type, syn_shape, tau_m, c_m, v_rest, i_offset, tau_e, tau_i, e_e, e_i) self.gif_fun = h.GifCurrent(0.5, sec=self) self.source = self.gif_fun self.rec = h.NetCon(self.source, None) self.parameter_names = ['c_m', 'tau_m', 'v_rest', 't_refrac', 'i_offset', 'v_reset', 'tau_e', 'tau_i', 'vt_star', 'dV', 'lambda0', 'tau_eta', 'a_eta', 'tau_gamma', 'a_gamma'] if syn_type == 'conductance': self.parameter_names.extend(['e_e', 'e_i']) self.set_parameters(locals()) def __set_tau_eta(self, value): self.gif_fun.tau_eta1, self.gif_fun.tau_eta2, self.gif_fun.tau_eta3 = value.value def __get_tau_eta(self): return self.gif_fun.tau_eta1, self.gif_fun.tau_eta2, self.gif_fun.tau_eta3 tau_eta = property(fset=__set_tau_eta, fget=__get_tau_eta) def __set_a_eta(self, value): self.gif_fun.a_eta1, self.gif_fun.a_eta2, self.gif_fun.a_eta3 = value.value def __get_a_eta(self): return self.gif_fun.a_eta1, self.gif_fun.a_eta2, self.gif_fun.a_eta3 a_eta = property(fset=__set_a_eta, fget=__get_a_eta) def __set_tau_gamma(self, value): self.gif_fun.tau_gamma1, self.gif_fun.tau_gamma2, self.gif_fun.tau_gamma3 = value.value def __get_tau_gamma(self): return self.gif_fun.tau_gamma1, self.gif_fun.tau_gamma2, self.gif_fun.tau_gamma3 tau_gamma = property(fset=__set_tau_gamma, fget=__get_tau_gamma) def __set_a_gamma(self, value): self.gif_fun.a_gamma1, self.gif_fun.a_gamma2, self.gif_fun.a_gamma3 = value.value def __get_a_gamma(self): return self.gif_fun.a_gamma1, self.gif_fun.a_gamma2, self.gif_fun.a_gamma3 a_gamma = property(fset=__set_a_gamma, fget=__get_a_gamma) v_reset = _new_property('gif_fun', 'Vr') t_refrac = _new_property('gif_fun', 'Tref') vt_star = _new_property('gif_fun', 'Vt_star') dV = _new_property('gif_fun', 'DV') lambda0 = _new_property('gif_fun', 'lambda0') def memb_init(self): for state_var in ('v', 'v_t', 'i_eta'): initial_value = getattr(self, '{0}_init'.format(state_var)) assert initial_value is not None if state_var == 'v': for seg in self: seg.v = initial_value else: setattr(self.gif_fun, state_var, initial_value) class RandomSpikeSource(hclass(h.NetStimFD)): parameter_names = ('start', '_interval', 'duration') def __init__(self, start=0, _interval=1e12, duration=0): self.start = start self.interval = _interval self.duration = duration self.noise = 1 self.spike_times = h.Vector(0) self.source = self self.rec = h.NetCon(self, None) self.switch = h.NetCon(None, self) self.source_section = None # should allow user to set specific seeds somewhere, e.g. in setup() self.seed(state.mpi_rank + state.native_rng_baseseed) def __new__(cls, *arg, **kwargs): return super().__new__(cls, *arg, **kwargs) def _set_interval(self, value): self.switch.weight[0] = -1 self.switch.event(h.t + 1e-12, 0) self.interval = value self.switch.weight[0] = 1 self.switch.event(h.t + 2e-12, 1) def _get_interval(self): return self.interval _interval = property(fget=_get_interval, fset=_set_interval) class RandomPoissonRefractorySpikeSource(hclass(h.PoissonStimRefractory)): parameter_names = ('rate', 'tau_refrac', 'start', 'duration') def __init__(self, rate=1, tau_refrac=0.0, start=0, duration=0): self.rate = rate self.tau_refrac = tau_refrac self.start = start self.duration = duration self.spike_times = h.Vector(0) self.source = self self.rec = h.NetCon(self, None) self.source_section = None self.seed(state.mpi_rank + state.native_rng_baseseed) def __new__(cls, *arg, **kwargs): return super().__new__(cls, *arg, **kwargs) class RandomGammaSpikeSource(hclass(h.GammaStim)): parameter_names = ('alpha', 'beta', 'start', 'duration') def __init__(self, alpha=1, beta=0.1, start=0, duration=0): self.alpha = alpha self.beta = beta self.start = start self.duration = duration self.spike_times = h.Vector(0) self.source = self self.rec = h.NetCon(self, None) self.switch = h.NetCon(None, self) self.source_section = None self.seed(state.mpi_rank + state.native_rng_baseseed) def __new__(cls, *arg, **kwargs): return super().__new__(cls, *arg, **kwargs) class VectorSpikeSource(hclass(h.VecStim)): parameter_names = ('spike_times',) def __init__(self, spike_times=[]): self.recording = False self.spike_times = spike_times self.source = self self.source_section = None self.rec = None self._recorded_spikes = np.array([]) def __new__(cls, *arg, **kwargs): return super().__new__(cls, *arg, **kwargs) def _set_spike_times(self, spike_times): # spike_times should be a Sequence object try: self._spike_times = h.Vector(spike_times.value) except (RuntimeError, AttributeError): raise errors.InvalidParameterValueError("spike_times must be an array of floats") if np.any(spike_times.value[:-1] > spike_times.value[1:]): raise errors.InvalidParameterValueError( "Spike times given to SpikeSourceArray must be in increasing order") self.play(self._spike_times) if self.recording: self._recorded_spikes = np.hstack((self._recorded_spikes, spike_times.value)) def _get_spike_times(self): return self._spike_times spike_times = property(fget=_get_spike_times, fset=_set_spike_times) @property def recording(self): return self._recording @recording.setter def recording(self, value): self._recording = value if value: # when we turn recording on, the cell may already have had its spike times assigned self._recorded_spikes = np.hstack((self._recorded_spikes, self.spike_times)) def get_recorded_spike_times(self): return self._recorded_spikes def clear_past_spikes(self): """If previous recordings are cleared, need to remove spikes from before the current time.""" self._recorded_spikes = self._recorded_spikes[self._recorded_spikes > h.t] class ArtificialCell(object): """Wraps NEURON 'ARTIFICIAL_CELL' models for PyNN""" def __init__(self, mechanism_name, **parameters): self.source = getattr(h, mechanism_name)() for name, value in parameters.items(): setattr(self.source, name, value) dummy = nrn.Section() # needed for PyNN self.source_section = dummy # todo: only need a single dummy for entire network, not one per cell self.parameter_names = ('tau', 'refrac') self.traces = {} self.spike_times = h.Vector(0) self.rec = h.NetCon(self.source, None) self.recording_time = False self.default = self.source def _set_tau(self, value): self.source.tau = value def _get_tau(self): return self.source.tau tau = property(fget=_get_tau, fset=_set_tau) def _set_refrac(self, value): self.source.refrac = value def _get_refrac(self): return self.source.refrac refrac = property(fget=_get_refrac, fset=_set_refrac) # ... gkbar and g_leak properties defined similarly def memb_init(self): self.source.m = self.m_init class IntFire1(NativeCellType): default_parameters = {'tau': 10.0, 'refrac': 5.0} default_initial_values = {'m': 0.0} recordable = ['m'] units = {'m': 'dimensionless'} receptor_types = ['default'] model = ArtificialCell extra_parameters = {"mechanism_name": "IntFire1"} class IntFire2(NativeCellType): default_parameters = {'taum': 10.0, 'taus': 20.0, 'ib': 0.0} default_initial_values = {'m': 0.0, 'i': 0.0} recordable = ['m', 'i'] units = {'m': 'dimensionless', 'i': 'dimensionless'} receptor_types = ['default'] model = ArtificialCell extra_parameters = {"mechanism_name": "IntFire2"} class IntFire4(NativeCellType): default_parameters = { 'taum': 50.0, 'taue': 5.0, 'taui1': 10.0, 'taui2': 20.0, } default_initial_values = {'m': 0.0, 'e': 0.0, 'i1': 0.0, 'i2': 0.0} recordable = ['e', 'i1', 'i2', 'm'] units = {'e': 'dimensionless', 'i1': 'dimensionless', 'i2': 'dimensionless', 'm': 'dimensionless'} receptor_types = ['default'] model = ArtificialCell extra_parameters = {"mechanism_name": "IntFire4"} PyNN-0.10.0/pyNN/neuron/connectors.py000066400000000000000000000017601415343567000173510ustar00rootroot00000000000000""" Connection method classes for the neuron module :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.neuron import simulator from pyNN.connectors import AllToAllConnector, \ OneToOneConnector, \ FixedProbabilityConnector, \ DistanceDependentProbabilityConnector, \ DisplacementDependentProbabilityConnector, \ IndexBasedProbabilityConnector, \ FromListConnector, \ FromFileConnector, \ FixedNumberPreConnector, \ FixedNumberPostConnector, \ SmallWorldConnector, \ CSAConnector, \ CloneConnector, \ ArrayConnector, \ FixedTotalNumberConnector PyNN-0.10.0/pyNN/neuron/nineml.py000066400000000000000000000067611415343567000164640ustar00rootroot00000000000000""" Support cell types defined in 9ML with NEURON. Requires the 9ml2nmodl script to be on the path. Classes: NineMLCell - a single neuron instance NineMLCellType - base class for cell types, not used directly Functions: nineml_cell_type - return a new NineMLCellType subclass Constants: NMODL_DIR - subdirectory to which NMODL mechanisms will be written :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import os import neuron from pyNN.models import BaseCellType from pyNN.nineml.cells import build_nineml_celltype from nineml2nmodl import write_nmodl, write_nmodldirect, call_nrnivmodl h = neuron.h logger = logging.getLogger("PyNN") NMODL_DIR = "nineml_mechanisms" class NineMLCell(object): def __init__(self, **parameters): self.type = parameters.pop("type") self.source_section = h.Section() self.source = getattr(h, self.type.model_name)(0.5, sec=self.source_section) for param, value in parameters.items(): setattr(self.source, param, value) # for recording self.rec = h.NetCon(self.source, None) self.spike_times = h.Vector(0) self.traces = {} self.recording_time = False def __getattr__(self, name): try: return self.__getattribute__(name) except AttributeError: if name in self.type.receptor_types: return self.source # source is also target else: raise AttributeError( "'NineMLCell' object has no attribute or receptor type '%s'" % name) def memb_init(self): # this is a bit of a hack for var in self.type.recordable: if hasattr(self, "%s_init" % var): initial_value = getattr(self, "%s_init" % var) logger.debug("Initialising %s to %g" % (var, initial_value)) setattr(self.source, var, initial_value) class NineMLCellType(BaseCellType): model = NineMLCell def __init__(self, **parameters): BaseCellType.__init__(self, **parameters) self.extra_parameters = {"type": self} # self.__class__? # weight variables should really be within component def _compile_nmodl(nineml_component, weight_variables, hierarchical_mode=None): """ Generate NMODL code for the 9ML component, run "nrnivmodl" and then load the mechanisms into NEURON. """ wdir = os.path.join(NMODL_DIR, nineml_component.name) if not os.path.exists(wdir): os.makedirs(wdir) cwd = os.getcwd() os.chdir(wdir) xml_file = "%s.xml" % nineml_component.name logger.debug("Writing NineML component to %s" % xml_file) nineml_component.write(xml_file) mod_filename = nineml_component.name + ".mod" write_nmodldirect(component=nineml_component, mod_filename=mod_filename, weight_variables=weight_variables) # write_nmodl(xml_file, weight_variables) # weight variables should really come from xml file print("Running 'nrnivmodl' from %s" % wdir) call_nrnivmodl() os.chdir(cwd) neuron.load_mechanisms(wdir) def nineml_cell_type(name, combined_model, weight_vars): """ Return a new NineMLCellType subclass. """ return build_nineml_celltype(name, (NineMLCellType,), {'combined_model': combined_model, 'weight_variables': weight_vars, 'builder': _compile_nmodl}) PyNN-0.10.0/pyNN/neuron/nmodl/000077500000000000000000000000001415343567000157275ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuron/nmodl/adexp.mod000066400000000000000000000067761415343567000175510ustar00rootroot00000000000000: Insert in a passive compartment to get an adaptive-exponential (Brette-Gerstner) : integrate-and-fire neuron with a refractory period. : This calculates the adaptive current, sets the membrane potential to the : correct value at the start and end of the refractory period, and prevents spikes : during the refractory period by clamping the membrane potential to the reset : voltage with a huge conductance. : : Reference: : : Brette R and Gerstner W. Adaptive exponential integrate-and-fire : model as an effective description of neuronal activity. : J. Neurophysiol. 94: 3637-3642, 2005. : : Implemented by Andrew Davison. UNIC, CNRS, March 2009. NEURON { POINT_PROCESS AdExpIF RANGE vreset, trefrac, vspike, vthresh, vpeak, spikewidth RANGE w, winit RANGE a, b, tauw, EL, GL, delta NONSPECIFIC_CURRENT i } UNITS { (mV) = (millivolt) (nA) = (nanoamp) (uS) = (microsiemens) } PARAMETER { vthresh = -50 (mV) : spike threshold for exponential calculation purposes vreset = -60 (mV) : reset potential after a spike vspike = -40 (mV) : spike detection threshold vpeak = 0 (mV) : peak of spike trefrac = 1 (ms) : refractory period gon = 1e6 (uS) : refractory clamp conductance spikewidth = 1e-12 (ms) : must be less than trefrac a = 0.004 (uS) : level of adaptation b = 0.0805 (nA) : increment of adaptation tauw = 144 (ms) : time constant of adaptation EL = -70.6 (mV) : leak reversal (must be equal to e_pas) GL = 0.03 (uS) : leak conductance (must be equal to g_pas(S/cm2)*membrane area(um2)*1e-2) delta = 2 (mV) : steepness of exponential approach to threshold winit = 0 (nA) } ASSIGNED { v (mV) i (nA) irefrac (nA) iexp (nA) grefrac (uS) refractory spike_threshold (mV) } STATE { w (nA) } INITIAL { grefrac = 0 net_send(0,4) w = winit if (delta == 0) { spike_threshold = vthresh } else { spike_threshold = vspike } } BREAKPOINT { SOLVE states METHOD derivimplicit : cnexp irefrac = grefrac*(v-vreset) iexp = exp_current(v) i = iexp + w + irefrac :printf("BP: t = %f dt = %f v = %f w = %f irefrac = %f iexp = %f i = %f\n", t, dt, v, w, irefrac, iexp, i) } DERIVATIVE states { : solve eq for adaptation variable w' = (a*(v-EL) - w)/tauw } FUNCTION exp_current(v) { : handle the case where delta is 0 or very small if (delta == 0) { exp_current = 0 } else if ((v - vthresh)/delta > 100) { exp_current = -exp(99) } else { exp_current = -GL*delta*exp((v-vthresh)/delta) } } NET_RECEIVE (weight) { if (flag == 1) { : beginning of spike v = vpeak w = w + b net_send(spikewidth, 2) net_event(t) :printf("spike: t = %f v = %f w = %f i = %f\n", t, v, w, i) } else if (flag == 2) { : end of spike, beginning of refractory period v = vreset grefrac = gon if (trefrac > spikewidth) { net_send(trefrac-spikewidth, 3) } else { : also the end of the refractory period grefrac = 0 } :printf("refrac: t = %f v = %f w = %f i = %f\n", t, v, w, i) } else if (flag == 3) { : end of refractory period v = vreset grefrac = 0 :printf("end_refrac: t = %f v = %f w = %f i = %f\n", t, v, w, i) } else if (flag == 4) { : watch membrane potential WATCH (v > spike_threshold) 1 } }PyNN-0.10.0/pyNN/neuron/nmodl/alphaisyn.mod000066400000000000000000000027721415343567000204300ustar00rootroot00000000000000TITLE Alpha-function synaptic current, with NET_RECEIVE COMMENT This model works with variable time-step methods (although it may not be very accurate) but at the expense of having to maintain the queues of spike times and weights. Andrew P. Davison, UNIC, CNRS, May 2006 ENDCOMMENT DEFINE MAX_SPIKES 1000 DEFINE CUTOFF 20 NEURON { POINT_PROCESS AlphaISyn RANGE tau, i, q NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) } PARAMETER { tau = 5 (ms) <1e-9,1e9> } ASSIGNED { i (nA) q quiet onset_times[MAX_SPIKES] (ms) weight_list[MAX_SPIKES] (nA) } INITIAL { i = 0 q = 0 : queue index quiet = 0 } BREAKPOINT { LOCAL k, expired_spikes, x i = 0 expired_spikes = 0 FROM k=0 TO q-1 { x = (t - onset_times[k])/tau if (x > CUTOFF) { expired_spikes = expired_spikes + 1 } else { i = i - weight_list[k] * alpha(x) } } update_queue(expired_spikes) } FUNCTION update_queue(n) { LOCAL k :if (n > 0) { printf("Queue changed. t = %4.2f onset_times=[",t) } FROM k=0 TO q-n-1 { onset_times[k] = onset_times[k+n] weight_list[k] = weight_list[k+n] :if (n > 0) { printf("%4.2f ",onset_times[k]) } } :if (n > 0) { printf("]\n") } q = q-n } FUNCTION alpha(x) { if (x < 0) { alpha = 0 } else { alpha = x * exp(1 - x) } } NET_RECEIVE(weight (nA)) { onset_times[q] = t weight_list[q] = weight :printf("t = %f, weight = %f\n", t, weight) if (q >= MAX_SPIKES-1) { if (!quiet) { printf("Error in AlphaSynI. Spike queue is full\n") quiet = 1 } } else { q = q + 1 } } PyNN-0.10.0/pyNN/neuron/nmodl/alphasyn.mod000066400000000000000000000030461415343567000202520ustar00rootroot00000000000000TITLE Alpha-function synaptic conductance, with NET_RECEIVE COMMENT This model works with variable time-step methods (although it may not be very accurate) but at the expense of having to maintain the queues of spike times and weights. Andrew P. Davison, UNIC, CNRS, May 2006 ENDCOMMENT DEFINE MAX_SPIKES 1000 DEFINE CUTOFF 20 NEURON { POINT_PROCESS AlphaSyn RANGE tau, i, g, e, q NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) (uS) = (microsiemens) (mV) = (millivolts) } PARAMETER { tau = 5 (ms) <1e-9,1e9> e = 0 (mV) } ASSIGNED { v (mV) i (nA) g (uS) q quiet onset_times[MAX_SPIKES] (ms) weight_list[MAX_SPIKES] (uS) } INITIAL { i = 0 q = 0 : queue index quiet = 0 } BREAKPOINT { LOCAL k, expired_spikes, x g = 0 expired_spikes = 0 FROM k=0 TO q-1 { x = (t - onset_times[k])/tau if (x > CUTOFF) { expired_spikes = expired_spikes + 1 } else { g = g + weight_list[k] * alpha(x) } } i = g*(v - e) update_queue(expired_spikes) } FUNCTION update_queue(n) { LOCAL k :if (n > 0) { printf("Queue changed. t = %4.2f onset_times=[",t) } FROM k=0 TO q-n-1 { onset_times[k] = onset_times[k+n] weight_list[k] = weight_list[k+n] :if (n > 0) { printf("%4.2f ",onset_times[k]) } } :if (n > 0) { printf("]\n") } q = q-n } FUNCTION alpha(x) { if (x < 0) { alpha = 0 } else { alpha = x * exp(1 - x) } } NET_RECEIVE(weight (uS)) { onset_times[q] = t weight_list[q] = weight if (q >= MAX_SPIKES-1) { if (!quiet) { printf("Error in AlphaSyn. Spike queue is full\n") quiet = 1 } } else { q = q + 1 } } PyNN-0.10.0/pyNN/neuron/nmodl/expisyn.mod000077500000000000000000000005711415343567000201350ustar00rootroot00000000000000NEURON { POINT_PROCESS ExpISyn RANGE tau, i NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) (mV) = (millivolt) } PARAMETER { tau = 0.1 (ms) <1e-9,1e9> } STATE { i (nA) } INITIAL { i = 0 } BREAKPOINT { SOLVE state METHOD cnexp } DERIVATIVE state { i' = -i/tau } NET_RECEIVE(weight (nA)) { :printf("t = %f, weight = %f\n", t, weight) i = i - weight } PyNN-0.10.0/pyNN/neuron/nmodl/gammastim.mod000066400000000000000000000045461415343567000204200ustar00rootroot00000000000000COMMENT Spike generator following a gamma process. Gamma distributed random variables are generated using the Marsaglia-Tang method: G. Marsaglia and W. Tsang (2000) A simple method for generating gamma variables. ACM Transactions on Mathematical Software, 26(3):363-372. doi:10.1145/358407.358414 Parameters: alpha: shape parameter of the gamma distribution. 1 = Poisson process. beta: rate parameter of gamma distribution (/ms). Note that the mean interval is alpha/beta start: start of gamma process (ms) duration: length in ms of the spike train. Author: Andrew P. Davison, UNIC, CNRS ENDCOMMENT NEURON { ARTIFICIAL_CELL GammaStim RANGE alpha, beta, start, duration } PARAMETER { alpha = 1 : shape parameter of gamma distribution. 1 = Poisson process. beta = 0.1 (/ms) <1e-9,1e9> : rate parameter of gamma distribution : mean interval is alpha/beta start = 1 (ms) : start of first spike duration = 1000 (ms) : input duration } ASSIGNED { event (ms) on end (ms) } PROCEDURE seed(x) { set_seed(x) } INITIAL { on = 0 if (start >= 0) { net_send(event, 2) } } PROCEDURE event_time() { event = event + rand_gamma(alpha, beta) if (event > end) { on = 0 } } NET_RECEIVE (w) { if (flag == 2) { : from INITIAL if (on == 0) { on = 1 event = t end = t + 1e-6 + duration net_send(0, 1) } } if (flag == 1 && on == 1) { net_event(t) event = event + rand_gamma(alpha, beta) if (event > end) { on = 0 } if (on == 1) { net_send(event - t, 1) } } } FUNCTION rand_gamma(a(1), b(/ms)) (1) { LOCAL c, d, Z, U, V if (a > 1) { d = alpha - 1/3 c = 1/sqrt(9*d) Z = normrand(0, 1) U = scop_random() V = (1 + c*Z)^3 while ((Z < -1/c) || (log(U) > 0.5*Z*Z + d - d*V + d*log(V))) { Z = normrand(0, 1) U = scop_random() V = (1 + c*Z)^3 } rand_gamma = d * V / b } else { U = scop_random() rand_gamma = rand_gamma(a + 1, b) * U^(1/alpha) } } PyNN-0.10.0/pyNN/neuron/nmodl/gap.mod000066400000000000000000000005021415343567000171740ustar00rootroot00000000000000NEURON { POINT_PROCESS Gap RANGE g, i, vgap NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) (mV) = (millivolt) (nS) = (nanosiemens) } PARAMETER { g = 0 (uS) } ASSIGNED { v (mV) vgap (mV) i (nA) } BREAKPOINT { if (g>0) {i = g * (v-vgap) } } PyNN-0.10.0/pyNN/neuron/nmodl/gif.mod000066400000000000000000000120741415343567000172010ustar00rootroot00000000000000: Generalized Integrate and Fire model defined in Pozzorini et al. PLOS Comp. Biol. 2015 : Filter for eta and gamma defined as linear combination of three exponential functions each. : : Implemented by Christian Roessert, EPFL/Blue Brain Project, 2016 NEURON { POINT_PROCESS GifCurrent RANGE Vr, Tref, Vt_star RANGE DV, lambda0 RANGE tau_eta1, tau_eta2, tau_eta3, a_eta1, a_eta2, a_eta3 RANGE tau_gamma1, tau_gamma2, tau_gamma3, a_gamma1, a_gamma2, a_gamma3 RANGE i_eta, gamma_sum, v_t, verboseLevel, p_dontspike, rand RANGE e_spike, isrefrac POINTER rng NONSPECIFIC_CURRENT i } UNITS { (mV) = (millivolt) (nA) = (nanoamp) (uS) = (microsiemens) } PARAMETER { Vr = -50 (mV) Tref = 4.0 (ms) Vt_star = -48 (mV) DV = 0.5 (mV) lambda0 = 1.0 (Hz) tau_eta1 = 1 (ms) tau_eta2 = 10. (ms) tau_eta3 = 100. (ms) a_eta1 = 1. (nA) a_eta2 = 1. (nA) a_eta3 = 1. (nA) tau_gamma1 = 1 (ms) tau_gamma2 = 10. (ms) tau_gamma3 = 100. (ms) a_gamma1 = 1. (mV) a_gamma2 = 1. (mV) a_gamma3 = 1. (mV) gon = 1e6 (uS) : refractory clamp conductance e_spike = 0 (mV) : spike height } COMMENT The Verbatim block is needed to allow RNG. ENDCOMMENT VERBATIM #include #include #include double nrn_random_pick(void* r); void* nrn_random_arg(int argpos); ENDVERBATIM ASSIGNED { v (mV) i (nA) i_eta (nA) p_dontspike (1) lambda (Hz) irefrac (nA) rand (1) grefrac (uS) gamma_sum (mV) v_t (mV) verboseLevel (1) dt (ms) rng isrefrac (1) : is in refractory period } STATE { eta1 (nA) eta2 (nA) eta3 (nA) gamma1 (mV) gamma2 (mV) gamma3 (mV) } INITIAL { grefrac = 0 eta1 = 0 eta2 = 0 eta3 = 0 gamma1 = 0 gamma2 = 0 gamma3 = 0 rand = urand() p_dontspike = 2 isrefrac = 0 net_send(0,4) } BREAKPOINT { SOLVE states METHOD cnexp :derivimplicit :euler i_eta = eta1 + eta2 + eta3 gamma_sum = gamma1 + gamma2 + gamma3 v_t = Vt_star + gamma_sum lambda = lambda0*exp( (v-v_t)/DV ) if (isrefrac > 0) { p_dontspike = 2 : is in refractory period, make it impossible to trigger a spike } else { p_dontspike = exp(-lambda*(dt * (1e-3))) } irefrac = grefrac*(v-Vr) i = irefrac + i_eta } AFTER SOLVE { rand = urand() } DERIVATIVE states { : solve spike frequency adaptation and spike triggered current kernels eta1' = -eta1/tau_eta1 eta2' = -eta2/tau_eta2 eta3' = -eta3/tau_eta3 gamma1' = -gamma1/tau_gamma1 gamma2' = -gamma2/tau_gamma2 gamma3' = -gamma3/tau_gamma3 } NET_RECEIVE (weight) { if (flag == 1) { : start spike next dt isrefrac = 1 net_send(dt, 2) if( verboseLevel > 0 ) { printf("Next dt: spike, at time %g: rand=%g, p_dontspike=%g\n", t, rand, p_dontspike) } } else if (flag == 2) { : beginning of spike v = Vr grefrac = gon net_send(Tref-dt, 3) :net_event(t) : increase filters after spike eta1 = eta1 + a_eta1 eta2 = eta2 + a_eta2 eta3 = eta3 + a_eta3 gamma1 = gamma1 + a_gamma1 gamma2 = gamma2 + a_gamma2 gamma3 = gamma3 + a_gamma3 if( verboseLevel > 0 ) { printf("Start spike, at time %g: rand=%g, p_dontspike=%g\n", t, rand, p_dontspike) } } else if (flag == 3) { : end of refractory period v = Vr isrefrac = 0 grefrac = 0 if( verboseLevel > 0 ) { printf("End refrac, at time %g: rand=%g, p_dontspike=%g\n", t, rand, p_dontspike) } } else if (flag == 4) { : watch for spikes WATCH (rand>p_dontspike) 1 } } PROCEDURE setRNG() { VERBATIM { /** * This function takes a NEURON Random object declared in hoc and makes it usable by this mod file. * Note that this method is taken from Brett paper as used by netstim.hoc and netstim.mod * which points out that the Random must be in uniform(1) mode */ void** pv = (void**)(&_p_rng); if( ifarg(1)) { *pv = nrn_random_arg(1); } else { *pv = (void*)0; } } ENDVERBATIM } FUNCTION urand() { VERBATIM double value; if (_p_rng) { /* :Supports separate independent but reproducible streams for : each instance. However, the corresponding hoc Random : distribution MUST be set to Random.negexp(1) */ value = nrn_random_pick(_p_rng); //printf("random stream for this simulation = %lf\n",value); return value; }else{ ENDVERBATIM : the old standby. Cannot use if reproducible parallel sim : independent of nhost or which host this instance is on : is desired, since each instance on this cpu draws from : the same stream value = scop_random(1) VERBATIM } ENDVERBATIM urand = value } FUNCTION toggleVerbose() { verboseLevel = 1-verboseLevel } PyNN-0.10.0/pyNN/neuron/nmodl/gsfa_grr.mod000066400000000000000000000032061415343567000202230ustar00rootroot00000000000000: conductance based spike-frequency adaptation, and a conductance-based relative refractory : mechanism ... to be inserted in a integrate-and-fire neuron : : See: Muller et al (2007) Spike-frequency adapting neural ensembles: Beyond : mean-adaptation and renewal theories. Neural Computation 19: 2958-3010. : : : Implemented from adexp.mod by Eilif Muller. EPFL-BMI, Jan 2011. NEURON { POINT_PROCESS GsfaGrr RANGE vthresh RANGE q_r, q_s RANGE E_s, E_r, tau_s, tau_r NONSPECIFIC_CURRENT i } UNITS { (mV) = (millivolt) (nA) = (nanoamp) (uS) = (microsiemens) (nS) = (nanosiemens) } PARAMETER { vthresh = -57 (mV) : spike threshold q_r = 3214.0 (nS) : relative refractory quantal conductance q_s = 14.48 (nS) : SFA quantal conductance tau_s = 110.0 (ms) : time constant of SFA tau_r = 1.97 (ms) : time constant of relative refractory mechanism E_s = -70 (mV) : SFA reversal potential E_r = -70 (mV) : relative refractory period reversal potential } ASSIGNED { v (mV) i (nA) } STATE { g_s (nS) g_r (nS) } INITIAL { g_s = 0 g_r = 0 net_send(0,2) } BREAKPOINT { SOLVE states METHOD cnexp :derivimplicit i = (0.001)*(g_r*(v-E_r) + g_s*(v-E_s)) } DERIVATIVE states { : solve eq for adaptation, relref variable g_s' = -g_s/tau_s g_r' = -g_r/tau_r } NET_RECEIVE (weight) { if (flag == 1) { : beginning of spike state_discontinuity(g_s, g_s + q_s) state_discontinuity(g_r, g_r + q_r) } else if (flag == 2) { : watch membrane potential WATCH (v > vthresh) 1 } }PyNN-0.10.0/pyNN/neuron/nmodl/hh_traub.mod000077500000000000000000000044611415343567000202340ustar00rootroot00000000000000COMMENT Modified Hodgkin-Huxley model ENDCOMMENT UNITS { (mA) = (milliamp) (mV) = (millivolt) (uS) = (microsiemens) (S) = (siemens) } NEURON { SUFFIX hh_traub USEION na READ ena WRITE ina USEION k READ ek WRITE ik NONSPECIFIC_CURRENT il RANGE gnabar, gkbar, gl, el, gna, gk, vT GLOBAL minf, hinf, ninf, mtau, htau, ntau THREADSAFE : assigned GLOBALs will be per thread } PARAMETER { gnabar = 0.02 (S/cm2) <0,1e9> gkbar = 0.006 (S/cm2) <0,1e9> gl = 0.00001 (S/cm2) <0,1e9> el = -60.0 (mV) vT = -63.0 (mV) } STATE { m h n } ASSIGNED { v (mV) ena (mV) ek (mV) gna (S/cm2) gk (S/cm2) ina (mA/cm2) ik (mA/cm2) il (mA/cm2) minf hinf ninf mtau (ms) htau (ms) ntau (ms) } BREAKPOINT { SOLVE states METHOD cnexp gna = gnabar*m*m*m*h ina = gna*(v - ena) gk = gkbar*n*n*n*n ik = gk*(v - ek) il = gl*(v - el) } INITIAL { : the following (commented out) is the preferred initialization :rates(v) :m = minf :h = hinf :n = ninf : but for compatibility with NEST, we use the following m = 0 h = 0 n = 0 } DERIVATIVE states { rates(v) m' = (minf-m)/mtau h' = (hinf-h)/htau n' = (ninf-n)/ntau } PROCEDURE rates(v(mV)) { LOCAL alpha, beta, sum, u TABLE minf, mtau, hinf, htau, ninf, ntau FROM -100 TO 100 WITH 200 UNITSOFF u = v - vT :"m" sodium activation system alpha = 0.32 * vtrap(13-u, 4) beta = 0.28 * vtrap(u-40, 5) sum = alpha + beta mtau = 1/sum minf = alpha/sum :"h" sodium inactivation system alpha = 0.128 * exp((17-u)/18) beta = 4 / (exp((40-u)/5) + 1) sum = alpha + beta htau = 1/sum hinf = alpha/sum :"n" potassium activation system alpha = 0.032*vtrap(15-u, 5) beta = 0.5*exp((10-u)/40) sum = alpha + beta ntau = 1/sum ninf = alpha/sum } FUNCTION vtrap(x,y) { :Traps for 0 in denominator of rate eqns. if (fabs(x/y) < 1e-6) { vtrap = y*(1 - x/y/2) }else{ vtrap = x/(exp(x/y) - 1) } } UNITSON PyNN-0.10.0/pyNN/neuron/nmodl/izhikevich.mod000066400000000000000000000025561415343567000205750ustar00rootroot00000000000000: Izhikevich artificial neuron model from : EM Izhikevich "Simple Model of Spiking Neurons" : IEEE Transactions On Neural Networks, Vol. 14, No. 6, November 2003 pp 1569-1572 : : This NMODL file may not work properly if used outside of PyNN. : Ted Carnevale has written a more complete, general-purpose implementation - see http://www.neuron.yale.edu/ftp/ted/devel/izhdistrib.zip NEURON { POINT_PROCESS Izhikevich RANGE a, b, c, d, u, uinit, vthresh NONSPECIFIC_CURRENT i } UNITS { (mV) = (millivolt) (nA) = (nanoamp) (nF) = (nanofarad) } INITIAL { u = uinit net_send(0, 1) } PARAMETER { a = 0.02 (/ms) b = 0.2 (/ms) c = -65 (mV) : reset potential after a spike d = 2 (mV/ms) vthresh = 30 (mV) : spike threshold Cm = 0.001 (nF) uinit = -14 (mV/ms) } ASSIGNED { v (mV) i (nA) } STATE { u (mV/ms) } BREAKPOINT { SOLVE states METHOD derivimplicit i = -Cm * (0.04*v*v + 5*v + 140 - u) :printf("t=%f, v=%f u=%f, i=%f, dv=%f, du=%f\n", t, v, u, i, 0.04*v*v + 5*v + 140 - u, a*(b*v-u)) } DERIVATIVE states { u' = a*(b*v - u) } NET_RECEIVE (weight (mV)) { if (flag == 1) { WATCH (v > vthresh) 2 } else if (flag == 2) { net_event(t) v = c u = u + d } else { : synaptic activation v = v + weight } } PyNN-0.10.0/pyNN/neuron/nmodl/netstim2.mod000077500000000000000000000121531415343567000202020ustar00rootroot00000000000000: Based on netstim : but with fixed duration rather than fixed number of spikes, and an interval : that can safely be varied during the simulation : Modified by Andrew Davison, UNIC, CNRS NEURON { ARTIFICIAL_CELL NetStimFD RANGE interval, start, duration RANGE noise THREADSAFE : only true if every instance has its own distinct Random POINTER donotuse } PARAMETER { interval = 10 (ms) <1e-9,1e9> : time between spikes (msec) duration = 100 (ms) <0,1e9> : duration of firing (msec) start = 50 (ms) : start of first spike noise = 0 <0,1> : amount of randomness (0.0 - 1.0) } ASSIGNED { event (ms) on donotuse valid } PROCEDURE seed(x) { set_seed(x) } INITIAL { valid = 4 on = 0 : off if (noise < 0) { noise = 0 } if (noise > 1) { noise = 1 } if (start >= 0 && duration > 0) { : randomize the first spike so on average it occurs at : start + noise*interval invl(interval) : for some reason, the first invl() call seems to give implausibly large values, so we discard it event = start + invl(interval) - interval*(1. - noise) : but not earlier than 0 if (event < 0) { event = 0 } if (event < start+duration) { on = 1 net_send(event, 3) } } } PROCEDURE init_sequence(t(ms)) { if (duration > 0) { on = 1 event = 0 } } FUNCTION invl(mean (ms)) (ms) { if (mean <= 0.0) { mean = 0.01 (ms) : I would worry if it were 0.0 } if (noise == 0) { invl = mean }else{ invl = (1.0 - noise)*mean + noise*mean*erand() } } VERBATIM double nrn_random_pick(void* r); void* nrn_random_arg(int argpos); ENDVERBATIM FUNCTION erand() { VERBATIM if (_p_donotuse) { /* :Supports separate independent but reproducible streams for : each instance. However, the corresponding hoc Random : distribution MUST be set to Random.negexp(1) */ _lerand = nrn_random_pick(_p_donotuse); }else{ /* only can be used in main thread */ if (_nt != nrn_threads) { hoc_execerror("multithread random in NetStim"," only via hoc Random"); } ENDVERBATIM : the old standby. Cannot use if reproducible parallel sim : independent of nhost or which host this instance is on : is desired, since each instance on this cpu draws from : the same stream erand = exprand(1) VERBATIM } ENDVERBATIM } PROCEDURE noiseFromRandom() { VERBATIM { void** pv = (void**)(&_p_donotuse); if (ifarg(1)) { *pv = nrn_random_arg(1); }else{ *pv = (void*)0; } } ENDVERBATIM } PROCEDURE next_invl() { if (duration > 0) { event = invl(interval) } if (t+event >= start+duration) { on = 0 } :printf("t=%g, event=%g, t+event=%g, on=%g\n", t, event, t+event, on) } NET_RECEIVE (w) { if (flag == 0) { : external event :printf("external event. w = %g\n", w) if (w > 0) { : turn on spike sequence : but not if a netsend is on the queue init_sequence(t) : randomize the first spike so on average it occurs at : noise*interval (most likely interval is always 0) next_invl() event = event - interval*(1.0 - noise) valid = valid + 1 : events with previous values of valid will be ignored. net_send(event, valid) }else if (w < 0) { : turn off spiking definitively on = 0 } } if (flag == 3) { : from INITIAL if (on == 1) { : but ignore if turned off by external event init_sequence(t) net_send(0, valid) :printf("init_sequence(%g)\n", t) } } if (flag == valid && on == 1) { net_event(t) next_invl() :printf("%g %g %g flag=%g valid=%g\n", t, interval, event, flag, valid) if (on == 1) { net_send(event, valid) } } } COMMENT Presynaptic spike generator --------------------------- This mechanism has been written to be able to use synapses in a single neuron receiving various types of presynaptic trains. This is a "fake" presynaptic compartment containing a spike generator. The trains of spikes can be either periodic or noisy (Poisson-distributed) Parameters; noise: between 0 (no noise-periodic) and 1 (fully noisy) interval: mean time between spikes (ms) [number: number of spikes (independent of noise)] - deleted duration: duration of spiking (ms) - added Written by Z. Mainen, modified by A. Destexhe, The Salk Institute Modified by Michael Hines for use with CVode The intrinsic bursting parameters have been removed since generators can stimulate other generators to create complicated bursting patterns with independent statistics (see below) Modified by Michael Hines to use logical event style with NET_RECEIVE This stimulator can also be triggered by an input event. If the stimulator is in the on==0 state (no net_send events on queue) and receives a positive weight event, then the stimulator changes to the on=1 state and goes through its entire spike sequence before changing to the on=0 state. During that time it ignores any positive weight events. If, in an on!=0 state, the stimulator receives a negative weight event, the stimulator will change to the on==0 state. In the on==0 state, it will ignore any ariving net_send events. A change to the on==1 state immediately fires the first spike of its sequence. ENDCOMMENT PyNN-0.10.0/pyNN/neuron/nmodl/poisson_stim_refractory.mod000066400000000000000000000024241415343567000234200ustar00rootroot00000000000000COMMENT Spike generator following a Poisson process with a refractory period. Parameters: rate: Mean spike frequency (Hz) tau_refrac: Minimum time between spikes (ms) start: Start time (ms) duration: Duration of spike sequence (ms) Author: Andrew P. Davison, UNIC, CNRS ENDCOMMENT NEURON { ARTIFICIAL_CELL PoissonStimRefractory RANGE rate, tau_refrac, start, duration } PARAMETER { rate = 1.0 (Hz) tau_refrac = 0.0 (ms) start = 1 (ms) duration = 1000 (ms) } ASSIGNED { event (ms) on end (ms) } PROCEDURE seed(x) { set_seed(x) } INITIAL { on = 0 if (start >= 0) { net_send(event, 2) } } NET_RECEIVE (w) { LOCAL mean_poisson_interval if (flag == 2) { : from INITIAL if (on == 0) { on = 1 event = t end = t + 1e-6 + duration net_send(0, 1) } } if (flag == 1 && on == 1) { net_event(t) mean_poisson_interval = 1000.0/rate - tau_refrac event = event + tau_refrac + mean_poisson_interval * exprand(1) if (event > end) { on = 0 } if (on == 1) { net_send(event - t, 1) } } } PyNN-0.10.0/pyNN/neuron/nmodl/quantal_stp.mod000066400000000000000000000054261415343567000207720ustar00rootroot00000000000000COMMENT Implementation of the NEST quantal_stp_connection model for NEURON Original NEST version by Marc-Oliver Gewaltig Adapted to NMODL by Andrew Davison, UNIC, CNRS, 2016. ENDCOMMENT NEURON { POINT_PROCESS QuantalSTPWA RANGE tau_rec, tau_fac, U, u0, n POINTER wsyn, rng } PARAMETER { tau_rec = 800 (ms) : time constant for depression tau_fac = 0 (ms) : time constant for facilitation U = 0.5 (1) <0, 1> : maximal fraction of available resource u0 = 0.5 (1) <0, 1> : initial available fraction of resources n = 1 : total number of release sites } ASSIGNED { u (1) : available fraction of resources wsyn : transmitted synaptic weight rng } INITIAL { u = u0 } NET_RECEIVE(w, available, t_last (ms)) { : available - number of available release sites : t_last - time of the last spike LOCAL depleted, rv, p_decay, u_decay, n_release, i INITIAL{ available = n t_last = -1e99 } : Compute the decay factors, based on the time since the last spike. p_decay = exp(-(t - t_last)/tau_rec) if (tau_fac < 1e-10) { u_decay = 0.0 } else { u_decay = exp( -(t - t_last)/tau_fac) } : Compute release probability u = U + u*(1 - U)*u_decay : Compute number of sites that recovered during the interval. depleted = n - available while (depleted > 0) { rv = urand() if (rv < (1 - p_decay)) { available = available + 1 } depleted = depleted - 1 } : Compute number of released sites n_release = 0 i = available while (i > 0) { rv = urand() if (rv < u) { n_release = n_release + 1 } i = i - 1 } if (n_release > 0) { wsyn = n_release/n * w available = available - n_release } else { wsyn = 0 } t_last = t } PROCEDURE setRNG() { : This function takes a NEURON Random object declared in hoc and makes it usable by this mod file : The Random must be in uniform(1) mode VERBATIM { void** pv = (void**)(&_p_rng); if( ifarg(1)) { *pv = nrn_random_arg(1); } else { *pv = (void*)0; } } ENDVERBATIM } FUNCTION urand() { VERBATIM double value; if (_p_rng) { /* :Supports separate independent but reproducible streams for : each instance. However, the corresponding hoc Random : distribution MUST be set to Random.negexp(1) */ value = nrn_random_pick(_p_rng); //printf("random stream for this simulation = %lf\n",value); return value; } else { ENDVERBATIM value = scop_random(1) VERBATIM } ENDVERBATIM urand = value } PyNN-0.10.0/pyNN/neuron/nmodl/refrac.mod000066400000000000000000000032371415343567000176770ustar00rootroot00000000000000: Insert in a passive compartment to get an integrate-and-fire neuron : with a refractory period. : Note that this only sets the membrane potential to the correct value : at the start and end of the refractory period, and prevents spikes : during the period by clamping the membrane potential to the reset : voltage with a huge conductance. : : Andrew P. Davison. UNIC, CNRS, May 2006. NEURON { POINT_PROCESS ResetRefrac RANGE vreset, trefrac, vspike, vthresh NONSPECIFIC_CURRENT i } UNITS { (mV) = (millivolt) (nA) = (nanoamp) (uS) = (microsiemens) } PARAMETER { vthresh = -50 (mV) : spike threshold vreset = -60 (mV) : reset potential after a spike vspike = 40 (mV) : spike height (mainly for graphical purposes) trefrac = 1 (ms) g_on = 1e12 (uS) spikewidth = 1e-12 (ms) : must be less than trefrac. Check for this? } ASSIGNED { v (mV) i (nA) g (uS) refractory } INITIAL { g = 0 net_send(0,4) } BREAKPOINT { i = g*(v-vreset) } NET_RECEIVE (weight) { if (flag == 1) { : beginning of spike g = g_on state_discontinuity(v,vspike) net_send(spikewidth,2) net_event(t) } else if (flag == 2) { : end of spike, beginning of refractory period state_discontinuity(v,vreset) if (trefrac > spikewidth) { net_send(trefrac-spikewidth,3) } else { : also the end of the refractory period g = 0 } } else if (flag == 3) { : end of refractory period state_discontinuity(v,vreset) g = 0 } else if (flag == 4) { : watch membrane potential WATCH (v > vthresh) 1 } }PyNN-0.10.0/pyNN/neuron/nmodl/reset.mod000066400000000000000000000011411415343567000175470ustar00rootroot00000000000000: Insert in a passive compartment to get an integrate-and-fire neuron : (no refractory period). : Andrew P. Davison. UNIC, CNRS, May 2006. NEURON { POINT_PROCESS Reset RANGE vreset, vspike } UNITS { (mV) = (millivolt) } PARAMETER { vreset = -60 (mV) : reset potential after a spike vspike = 40 (mV) : spike height (mainly for graphical purposes) } ASSIGNED { v (millivolt) } NET_RECEIVE (weight) { if (flag == 1) { v = vreset } else { v = vspike net_send(1e-12,1) : using variable time step, this should allow the spike to be detected using threshold crossing net_event(t) } } PyNN-0.10.0/pyNN/neuron/nmodl/stdwa_guetig.mod000066400000000000000000000037421415343567000211240ustar00rootroot00000000000000COMMENT Spike Timing Dependent Weight Adjuster based on Song and Abbott, 2001, but with weight limits according to Guetig et al, 2003 Andrew Davison, UNIC, CNRS, 2003-2005, 2009 ENDCOMMENT NEURON { POINT_PROCESS StdwaGuetig RANGE interval, tlast_pre, tlast_post, M, P RANGE deltaw, wmax, wmin, aLTP, aLTD, tauLTP, tauLTD, on RANGE muLTP, muLTD RANGE allow_update_on_post POINTER wsyn } ASSIGNED { interval (ms) : since last spike of the other kind tlast_pre (ms) : time of last presynaptic spike tlast_post (ms) : time of last postsynaptic spike M : LTD function P : LTP function deltaw : change in weight wsyn : weight of the synapse } INITIAL { interval = 0 tlast_pre = 0 tlast_post = 0 M = 0 P = 0 deltaw = 0 } PARAMETER { tauLTP = 20 (ms) : decay time for LTP part ( values from ) tauLTD = 20 (ms) : decay time for LTD part ( Song and Abbott, 2001 ) wmax = 1 : min and max values of synaptic weight wmin = 0 aLTP = 0.001 : amplitude of LTP steps aLTD = 0.00106 : amplitude of LTD steps muLTP = 0.0 muLTD = 0.0 on = 1 : allows learning to be turned on and off allow_update_on_post = 1 : if this is true, we update the weight on receiving both pre- and post-synaptic spikes : if it is false, weight updates are accumulated and applied only for a pre-synaptic spike } NET_RECEIVE (w) { if (w >= 0) { : this is a pre-synaptic spike P = P*exp((tlast_pre-t)/tauLTP) + aLTP interval = tlast_post - t : interval is negative tlast_pre = t deltaw = deltaw + (wsyn-wmin)^muLTD * M * exp(interval/tauLTD) } else { : this is a post-synaptic spike M = M*exp((tlast_post-t)/tauLTD) - aLTD interval = t - tlast_pre : interval is positive tlast_post = t deltaw = deltaw + (wmax-wsyn)^muLTP * P * exp(-interval/tauLTP) } if (on) { if (w >= 0 || allow_update_on_post) { wsyn = wsyn + deltaw deltaw = 0.0 } } } PyNN-0.10.0/pyNN/neuron/nmodl/stdwa_softlimits.mod000066400000000000000000000037511415343567000220350ustar00rootroot00000000000000COMMENT Spike Timing Dependent Weight Adjuster based on Song and Abbott, 2001, but with soft weight limits Andrew Davison, UNIC, CNRS, 2003-2005, 2009 ENDCOMMENT NEURON { POINT_PROCESS StdwaSoft RANGE interval, tlast_pre, tlast_post, M, P RANGE deltaw, wmax, wmin, aLTP, aLTD, wprune, tauLTP, tauLTD, on RANGE allow_update_on_post POINTER wsyn } ASSIGNED { interval (ms) : since last spike of the other kind tlast_pre (ms) : time of last presynaptic spike tlast_post (ms) : time of last postsynaptic spike M : LTD function P : LTP function deltaw : change in weight wsyn : weight of the synapse } INITIAL { interval = 0 tlast_pre = 0 tlast_post = 0 M = 0 P = 0 deltaw = 0 } PARAMETER { tauLTP = 20 (ms) : decay time for LTP part ( values from ) tauLTD = 20 (ms) : decay time for LTD part ( Song and Abbott, 2001 ) wmax = 1 : min and max values of synaptic weight wmin = 0 aLTP = 0.001 : amplitude of LTP steps aLTD = 0.00106 : amplitude of LTD steps on = 1 : allows learning to be turned on and off globally wprune = 0 : default is no pruning allow_update_on_post = 1 : if this is true, we update the weight on receiving both pre- and post-synaptic spikes : if it is false, weight updates are accumulated and applied only for a pre-synaptic spike } NET_RECEIVE (w) { if (w >= 0) { : this is a pre-synaptic spike P = P*exp((tlast_pre-t)/tauLTP) + aLTP interval = tlast_post - t : interval is negative tlast_pre = t deltaw = deltaw + (wsyn-wmin) * M * exp(interval/tauLTD) } else { : this is a post-synaptic spike M = M*exp((tlast_post-t)/tauLTD) - aLTD interval = t - tlast_pre : interval is positive tlast_post = t deltaw = deltaw + (wmax-wsyn) * P * exp(-interval/tauLTP) } if (on) { if (w >= 0 || allow_update_on_post) { if (wsyn > wprune) { wsyn = wsyn + deltaw } else { wsyn = 0 } deltaw = 0.0 } } } PyNN-0.10.0/pyNN/neuron/nmodl/stdwa_songabbott.mod000066400000000000000000000043531415343567000220010ustar00rootroot00000000000000COMMENT Spike Timing Dependent Weight Adjuster based on Song and Abbott, 2001. Andrew Davison, UNIC, CNRS, 2003-2004, 2009 ENDCOMMENT NEURON { POINT_PROCESS StdwaSA RANGE interval, tlast_pre, tlast_post, M, P RANGE deltaw, wmax, wmin, aLTP, aLTD, tauLTP, tauLTD, on RANGE allow_update_on_post POINTER wsyn } ASSIGNED { interval (ms) : since last spike of the other kind tlast_pre (ms) : time of last presynaptic spike tlast_post (ms) : time of last postsynaptic spike M : LTD function P : LTP function deltaw : change in weight wsyn : weight of the synapse } INITIAL { interval = 0 tlast_pre = 0 tlast_post = 0 M = 0 P = 0 deltaw = 0 } PARAMETER { tauLTP = 20 (ms) : decay time for LTP part ( values from ) tauLTD = 20 (ms) : decay time for LTD part ( Song and Abbott, 2001 ) wmax = 1 : min and max values of synaptic weight wmin = 0 : aLTP = 0.001 : amplitude of LTP steps aLTD = 0.00106 : amplitude of LTD steps on = 1 : allows learning to be turned on and off allow_update_on_post = 1 : if this is true, we update the weight on receiving both pre- and post-synaptic spikes : if it is false, weight updates are accumulated and applied only for a pre-synaptic spike } NET_RECEIVE (w) { if (w >= 0) { : this is a pre-synaptic spike P = P*exp((tlast_pre-t)/tauLTP) + aLTP interval = tlast_post - t : interval is negative tlast_pre = t deltaw = deltaw + wmax * M * exp(interval/tauLTD) :printf("pre: t=%f P=%f M=%f interval=%f deltaw=%f w_syn(b4)=%f\n", t, P, M, interval, deltaw, wsyn) } else { : this is a post-synaptic spike M = M*exp((tlast_post-t)/tauLTD) - aLTD interval = t - tlast_pre : interval is positive tlast_post = t deltaw = deltaw + wmax * P * exp(-interval/tauLTP) :printf("post: t=%f P=%f M=%f interval=%f deltaw=%f, w_syn(b4)=%f\n", t, P, M, interval, deltaw, wsyn) } if (on) { if (w >= 0 || allow_update_on_post) { wsyn = wsyn + deltaw if (wsyn > wmax) { wsyn = wmax } if (wsyn < wmin) { wsyn = wmin } deltaw = 0.0 } } :printf("update: w=%f\n", wsyn) } PyNN-0.10.0/pyNN/neuron/nmodl/stdwa_symm.mod000066400000000000000000000036611415343567000206250ustar00rootroot00000000000000COMMENT Spike Timing Dependent Weight Adjuster with symmetric functions (i.e. only depends on the absolute value of the time difference, not on its sign. Andrew Davison, UNIC, CNRS, 2004, 2009 ENDCOMMENT NEURON { POINT_PROCESS StdwaSymm RANGE interval, tlast_pre, tlast_post RANGE deltaw, wmax, f, tau_a, tau_b, a, on RANGE allow_update_on_post POINTER wsyn } ASSIGNED { interval (ms) : since last spike of the other kind tlast_pre (ms) : time of last presynaptic spike tlast_post (ms) : time of last postsynaptic spike f : weight change function deltaw : change in weight wsyn : weight of the synapse tas (ms2) : tau_a squared } INITIAL { interval = 0 tlast_pre = 0 tlast_post = 0 f = 0 deltaw = 0 } PARAMETER { tau_a = 20 (ms) : crossing point from LTP to LTD tau_b = 15 (ms) : decay time constant for exponential part of f wmax = 1 : min and max values of synaptic weight a = 0.001 : step amplitude on = 1 : allows learning to be turned on and off allow_update_on_post = 1 : if this is true, we update the weight on receiving both pre- and post-synaptic spikes : if it is false, weight updates are accumulated and applied only for a pre-synaptic spike } NET_RECEIVE (w) { tas = tau_a * tau_a : do it here in case tau_a has been changed since the last spike if (w >= 0) { : this is a pre-synaptic spike interval = tlast_post - t tlast_pre = t f = (1 - interval*interval/tas) * exp(interval/tau_b) deltaw = deltaw + wmax * a * f } else { : this is a post-synaptic spike interval = t - tlast_pre tlast_post = t f = (1 - interval*interval/tas) * exp(-interval/tau_b) deltaw = deltaw + wmax * a* f } if (on) { if (w >= 0 || allow_update_on_post) { wsyn = wsyn + deltaw if (wsyn > wmax) { wsyn = wmax } if (wsyn < 0) { wsyn = 0 } deltaw = 0.0 } } } PyNN-0.10.0/pyNN/neuron/nmodl/stdwa_vogels2011.mod000066400000000000000000000045071415343567000214430ustar00rootroot00000000000000COMMENT Spike Timing Dependent Weight Adjuster implementing the rule from Vogels TP, Sprekeler H, Zenke F, Clopath C, Gerstner W (2011) Inhibitory plasticity balances excitation and inhibition in sensory pathways and memory networks. Science 334:1569-73 http://dx.doi.org/10.1126/science.1211095 also see http://senselab.med.yale.edu/modeldb/ShowModel.asp?model=143751 Andrew Davison, UNIC, CNRS, 2013 ENDCOMMENT NEURON { POINT_PROCESS StdwaVogels2011 RANGE interval, tlast_pre, tlast_post RANGE deltaw, wmax, wmin, tau, eta, rho, on RANGE allow_update_on_post POINTER wsyn } ASSIGNED { interval (ms) : since last spike of the other kind tlast_pre (ms) : time of last presynaptic spike tlast_post (ms) : time of last postsynaptic spike deltaw : change in weight wsyn : weight of the synapse alpha } INITIAL { interval = 0 tlast_pre = -1e12 tlast_post = -1e12 deltaw = 0 } PARAMETER { tau = 20 (ms) : decay time constant for exponential part of f wmax = 1 : maximum value of synaptic weight wmin = 0 : minimum value synaptic weight eta = 1e-10 : learning rate rho = 3e-3 : strength of non-Hebbian synaptic depression relative to Hebbian potentiation. on = 1 : allows learning to be turned on and off allow_update_on_post = 1 : if this is true, we update the weight on receiving both pre- and post-synaptic spikes : if it is false, weight updates are accumulated and applied only for a pre-synaptic spike } NET_RECEIVE (w) { if (w >= 0) { : this is a pre-synaptic spike interval = tlast_post - t tlast_pre = t alpha = 2*rho*tau deltaw = deltaw + eta*(exp(interval/tau) - alpha) } else { : this is a post-synaptic spike interval = t - tlast_pre tlast_post = t deltaw = deltaw + eta*exp(-interval/tau) } if (on) { if (w >= 0 || allow_update_on_post) { wsyn = wsyn + deltaw if (wsyn > wmax) { wsyn = wmax } if (wsyn < wmin) { wsyn = wmin } deltaw = 0.0 } } } PyNN-0.10.0/pyNN/neuron/nmodl/stochastic_synapse.mod000066400000000000000000000030461415343567000223410ustar00rootroot00000000000000COMMENT Implementation of a simple stochastic synapse (constant release probability) as a "weight adjuster" (i.e. it sets the weight of the synapse to zero if transmission fails). Andrew Davison, UNIC, CNRS, 2016 ENDCOMMENT NEURON { POINT_PROCESS SimpleStochasticWA RANGE p POINTER rng, wsyn } PARAMETER { p = 0.5 : probability that transmission succeeds } VERBATIM #include #include #include double nrn_random_pick(void* r); void* nrn_random_arg(int argpos); ENDVERBATIM ASSIGNED { wsyn rng } NET_RECEIVE(w) { if (urand() < p) { wsyn = w } else { wsyn = 0.0 } } PROCEDURE setRNG() { : This function takes a NEURON Random object declared in hoc and makes it usable by this mod file : The Random must be in uniform(1) mode VERBATIM { void** pv = (void**)(&_p_rng); if( ifarg(1)) { *pv = nrn_random_arg(1); } else { *pv = (void*)0; } } ENDVERBATIM } FUNCTION urand() { VERBATIM double value; if (_p_rng) { /* :Supports separate independent but reproducible streams for : each instance. However, the corresponding hoc Random : distribution MUST be set to Random.negexp(1) */ value = nrn_random_pick(_p_rng); //printf("random stream for this simulation = %lf\n",value); return value; } else { ENDVERBATIM value = scop_random(1) VERBATIM } ENDVERBATIM urand = value } PyNN-0.10.0/pyNN/neuron/nmodl/stochastic_tsodyksmarkram.mod000066400000000000000000000063321415343567000237330ustar00rootroot00000000000000COMMENT Implementation of the stochastic Tsodyks-Markram mechanism for synaptic depression and facilitation as a "weight adjuster" cf Fuhrmann et al. 2002 The algorithm is as in ProbGABAAB_EMS.mod from the Blue Brain Project. Andrew Davison, UNIC, CNRS, 2016. ENDCOMMENT NEURON { POINT_PROCESS StochasticTsodyksMarkramWA RANGE tau_rec, tau_facil, U, u0 POINTER wsyn, rng } PARAMETER { tau_rec = 100 (ms) <1e-9, 1e9> tau_facil = 1000 (ms) <0, 1e9> U = 0.04 (1) <0, 1> u0 = 0 (1) <0, 1> } ASSIGNED { u (1) : release probability t_last (ms) : time of the last spike wsyn : transmitted synaptic weight R (1) : recovered state {0=unrecovered, 1=recovered} rng } INITIAL { u = u0 t_last = -1e99 R = 1 } NET_RECEIVE(w, p_surv, t_surv) { : p_surv - survival probability of unrecovered state : t_surv - time since last evaluation of survival LOCAL result INITIAL{ t_last = t } if (w > 0) { :printf("START tau_facil=%-4g tau_rec=%-4g U=%-4.2g time=%g p_surv=%-5.3g t_surv=%4.1f t_last=%4.1f u=%-5.3g R=%g wsyn=%g\n", tau_facil, tau_rec, U, t, p_surv, t_surv, t_last, u, R, wsyn) : calculation of u if (tau_facil > 0) { u = u*exp(-(t - t_last)/tau_facil) u = u + U*(1-u) } else { u = U } t_last = t : check for recovery if (R == 0) { wsyn = 0 : probability of survival of unrecovered state based on Poisson recovery with rate 1/tau_rec p_surv = exp(-(t - t_surv)/tau_rec) result = urand() if (result > p_surv) { R = 1 : recovered :printf("recovered\n") } else { t_surv = t : failed to recover } } : check for release if (R == 1) { result = urand() if (result < u) { : release wsyn = w R = 0 t_surv = t :printf("release\n") } else { wsyn = 0 } } :printf("END tau_facil=%-4g tau_rec=%-4g U=%-4.2g time=%g p_surv=%-5.3g t_surv=%4.1f t_last=%4.1f u=%-5.3g R=%g wsyn=%g\n\n", tau_facil, tau_rec, U, t, p_surv, t_surv, t_last, u, R, wsyn) } } PROCEDURE setRNG() { : This function takes a NEURON Random object declared in hoc and makes it usable by this mod file : The Random must be in uniform(1) mode VERBATIM { void** pv = (void**)(&_p_rng); if( ifarg(1)) { *pv = nrn_random_arg(1); } else { *pv = (void*)0; } } ENDVERBATIM } FUNCTION urand() { VERBATIM double value; if (_p_rng) { /* :Supports separate independent but reproducible streams for : each instance. However, the corresponding hoc Random : distribution MUST be set to Random.negexp(1) */ value = nrn_random_pick(_p_rng); //printf("random stream for this simulation = %lf\n",value); return value; } else { ENDVERBATIM value = scop_random(1) VERBATIM } ENDVERBATIM urand = value } PyNN-0.10.0/pyNN/neuron/nmodl/tmgsyn.mod000066400000000000000000000113501415343567000177510ustar00rootroot00000000000000: This model implemented by Ted Carnevale, and obtained from : http://senselab.med.yale.edu/modeldb/showmodel.asp?model=3815 COMMENT Revised 12/15/2000 in light of a personal communication from Misha Tsodyks that u is incremented _before_ x is converted to y--a point that was not clear in the paper. If u is incremented _after_ x is converted to y, then the first synaptic activation after a long interval of silence will produce smaller and smaller postsynaptic effect as the length of the silent interval increases, eventually becoming vanishingly small. Implementation of a model of short-term facilitation and depression based on the kinetics described in Tsodyks et al. Synchrony generation in recurrent networks with frequency-dependent synapses Journal of Neuroscience 20:RC50:1-5, 2000. Their mechanism represented synapses as current sources. The mechanism implemented here uses a conductance change instead. The basic scheme is x -------> y Instantaneous, spike triggered. Increment is u*x (see discussion of u below). x == fraction of "synaptic resources" that have "recovered" (fraction of xmtr pool that is ready for release, or fraction of postsynaptic channels that are ready to be opened, or some joint function of these two factors) y == fraction of "synaptic resources" that are in the "active state." This is proportional to the number of channels that are open, or the fraction of max synaptic current that is being delivered. tau y -------> z z == fraction of "synaptic resources" that are in the "inactive state" tau_rec z -------> x where x + y + z = 1 The active state y is multiplied by a synaptic weight to compute the actual synaptic conductance (or current, in the original form of the model). In addition, there is a "facilition" term u that governs the fraction of x that is converted to y on each synaptic activation. -------> u Instantaneous, spike triggered, happens _BEFORE_ x is converted to y. Increment is U*(1-u) where U and u both lie in the range 0 - 1. tau_facil u -------> decay of facilitation This implementation for NEURON offers the user a parameter u0 that has a default value of 0 but can be used to specify a nonzero initial value for u. When tau_facil = 0, u is supposed to equal U. Note that the synaptic conductance in this mechanism has the same kinetics as y, i.e. decays with time constant tau. This mechanism can receive multiple streams of synaptic input via NetCon objects. Each stream keeps track of its own weight and activation history. The printf() statements are for testing purposes only. ENDCOMMENT NEURON { POINT_PROCESS tmgsyn RANGE e, i RANGE tau, tau_rec, tau_facil, U, u0 NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) (mV) = (millivolt) (umho) = (micromho) } PARAMETER { : e = -90 mV for inhibitory synapses, : 0 mV for excitatory e = -90 (mV) : tau was the same for inhibitory and excitatory synapses : in the models used by T et al. tau = 3 (ms) < 1e-9, 1e9 > : tau_rec = 100 ms for inhibitory synapses, : 800 ms for excitatory tau_rec = 100 (ms) < 1e-9, 1e9 > : tau_facil = 1000 ms for inhibitory synapses, : 0 ms for excitatory tau_facil = 1000 (ms) < 0, 1e9 > : U = 0.04 for inhibitory synapses, : 0.5 for excitatory : the (1) is needed for the < 0, 1 > to be effective : in limiting the values of U and u0 U = 0.04 (1) < 0, 1 > : initial value for the "facilitation variable" u0 = 0 (1) < 0, 1 > } ASSIGNED { v (mV) i (nA) x } STATE { g (umho) } INITIAL { g=0 } BREAKPOINT { SOLVE state METHOD cnexp i = g*(v - e) } DERIVATIVE state { g' = -g/tau } NET_RECEIVE(weight (umho), y, z, u, tsyn (ms)) { INITIAL { : these are in NET_RECEIVE to be per-stream y = 0 z = 0 : u = 0 u = u0 tsyn = t : this header will appear once per stream :printf("t\t t-tsyn\t y\t z\t u\t newu\t g\t dg\t newg\t newy\n") } : first calculate z at event- : based on prior y and z z = z*exp(-(t - tsyn)/tau_rec) z = z + ( y*(exp(-(t - tsyn)/tau) - exp(-(t - tsyn)/tau_rec)) / ((tau/tau_rec)-1) ) : now calc y at event- y = y*exp(-(t - tsyn)/tau) x = 1-y-z : calc u at event-- if (tau_facil > 0) { u = u*exp(-(t - tsyn)/tau_facil) } else { u = U } :printf("%g\t%g\t%g\t%g\t%g", t, t-tsyn, y, z, u) if (tau_facil > 0) { state_discontinuity(u, u + U*(1-u)) } :printf("\t%g\t%g\t%g", u, g, weight*x*u) state_discontinuity(g, g + weight*x*u) state_discontinuity(y, y + x*u) tsyn = t :printf("\t%g\t%g\n", g, y) } PyNN-0.10.0/pyNN/neuron/nmodl/tmisyn.mod000066400000000000000000000113311415343567000177520ustar00rootroot00000000000000: This model is a minor modification of tmgsyn.mod, which was implemented by : Ted Carnevale, and obtained from : http://senselab.med.yale.edu/modeldb/showmodel.asp?model=3815 : The modified version is a current-based, not conductance-based synapse. COMMENT Revised 12/15/2000 in light of a personal communication from Misha Tsodyks that u is incremented _before_ x is converted to y--a point that was not clear in the paper. If u is incremented _after_ x is converted to y, then the first synaptic activation after a long interval of silence will produce smaller and smaller postsynaptic effect as the length of the silent interval increases, eventually becoming vanishingly small. Implementation of a model of short-term facilitation and depression based on the kinetics described in Tsodyks et al. Synchrony generation in recurrent networks with frequency-dependent synapses Journal of Neuroscience 20:RC50:1-5, 2000. Their mechanism represented synapses as current sources. The mechanism implemented here uses a conductance change instead. The basic scheme is x -------> y Instantaneous, spike triggered. Increment is u*x (see discussion of u below). x == fraction of "synaptic resources" that have "recovered" (fraction of xmtr pool that is ready for release, or fraction of postsynaptic channels that are ready to be opened, or some joint function of these two factors) y == fraction of "synaptic resources" that are in the "active state." This is proportional to the number of channels that are open, or the fraction of max synaptic current that is being delivered. tau y -------> z z == fraction of "synaptic resources" that are in the "inactive state" tau_rec z -------> x where x + y + z = 1 The active state y is multiplied by a synaptic weight to compute the actual synaptic conductance (or current, in the original form of the model). In addition, there is a "facilition" term u that governs the fraction of x that is converted to y on each synaptic activation. -------> u Instantaneous, spike triggered, happens _BEFORE_ x is converted to y. Increment is U*(1-u) where U and u both lie in the range 0 - 1. tau_facil u -------> decay of facilitation This implementation for NEURON offers the user a parameter u0 that has a default value of 0 but can be used to specify a nonzero initial value for u. When tau_facil = 0, u is supposed to equal U. Note that the synaptic conductance in this mechanism has the same kinetics as y, i.e. decays with time constant tau. This mechanism can receive multiple streams of synaptic input via NetCon objects. Each stream keeps track of its own weight and activation history. The printf() statements are for testing purposes only. ENDCOMMENT NEURON { POINT_PROCESS tmisyn RANGE i RANGE tau, tau_rec, tau_facil, U, u0 NONSPECIFIC_CURRENT i } UNITS { (nA) = (nanoamp) (mV) = (millivolt) } PARAMETER { : tau was the same for inhibitory and excitatory synapses : in the models used by T et al. tau = 3 (ms) < 1e-9, 1e9 > : tau_rec = 100 ms for inhibitory synapses, : 800 ms for excitatory tau_rec = 100 (ms) < 1e-9, 1e9 > : tau_facil = 1000 ms for inhibitory synapses, : 0 ms for excitatory tau_facil = 1000 (ms) < 0, 1e9 > : U = 0.04 for inhibitory synapses, : 0.5 for excitatory : the (1) is needed for the < 0, 1 > to be effective : in limiting the values of U and u0 U = 0.04 (1) < 0, 1 > : initial value for the "facilitation variable" u0 = 0 (1) < 0, 1 > } ASSIGNED { x } STATE { i (nA) } INITIAL { i=0 } BREAKPOINT { SOLVE state METHOD cnexp } DERIVATIVE state { i' = -i/tau } NET_RECEIVE(weight (nA), y, z, u, tsyn (ms)) { INITIAL { : these are in NET_RECEIVE to be per-stream y = 0 z = 0 : u = 0 u = u0 tsyn = t : this header will appear once per stream :printf("t\t t-tsyn\t y\t z\t u\t newu\t i\t dg\t newi\t newy\n") } : first calculate z at event- : based on prior y and z z = z*exp(-(t - tsyn)/tau_rec) z = z + ( y*(exp(-(t - tsyn)/tau) - exp(-(t - tsyn)/tau_rec)) / ((tau/tau_rec)-1) ) : now calc y at event- y = y*exp(-(t - tsyn)/tau) x = 1-y-z : calc u at event-- if (tau_facil > 0) { u = u*exp(-(t - tsyn)/tau_facil) } else { u = U } :printf("%g\t%g\t%g\t%g\t%g", t, t-tsyn, y, z, u) if (tau_facil > 0) { state_discontinuity(u, u + U*(1-u)) } :printf("\t%g\t%g\t%g", u, i, weight*x*u) state_discontinuity(i, i + weight*x*u) state_discontinuity(y, y + x*u) tsyn = t :printf("\t%g\t%g\n", i, y) } PyNN-0.10.0/pyNN/neuron/nmodl/tsodyksmarkram.mod000066400000000000000000000023401415343567000215020ustar00rootroot00000000000000COMMENT Implementation of the Tsodyks-Markram mechanism for synaptic depression and facilitation as a "weight adjuster" Andrew Davison, UNIC, CNRS, 2013 ENDCOMMENT NEURON { POINT_PROCESS TsodyksMarkramWA RANGE tau_rec, tau_facil, U, u0, tau_syn POINTER wsyn } PARAMETER { tau_rec = 100 (ms) <1e-9, 1e9> tau_facil = 1000 (ms) <0, 1e9> U = 0.04 (1) <0, 1> u0 = 0 (1) <0, 1> tau_syn = 2 (ms) <1e-9, 1e9> : should be set to be the same as the receiving synapse } ASSIGNED { x y z u t_last (ms) wsyn } INITIAL { y = 0 z = 0 u = u0 t_last = -1e99 } NET_RECEIVE(w) { INITIAL { t_last = t } z = z*exp(-(t - t_last)/tau_rec) z = z + y*(exp(-(t - t_last)/tau_syn) - exp(-(t - t_last)/tau_rec)) / ((tau_syn/tau_rec) - 1) y = y*exp(-(t - t_last)/tau_syn) x = 1 - y - z if (tau_facil > 0) { u = u*exp(-(t - t_last)/tau_facil) u = u + U*(1-u) } else { u = U } wsyn = w*x*u y = y + x*u :printf("U=%g tau_rec=%g tau_facil=%g tau_syn=%g w=%g\n", U, tau_rec, tau_facil, tau_syn, w) :printf("%g\t%g\t%g\t%g\t%g\t%g\t%g\n", t, t - t_last, y, z, u, x, wsyn) t_last = t } PyNN-0.10.0/pyNN/neuron/nmodl/vecstim.mod000066400000000000000000000116101415343567000201010ustar00rootroot00000000000000COMMENT This mechanism emits spike events at the times given in a supplied Vector. Example usage: objref vs vs = new VecStim(.5) vs.play(spikevec) This is a modified version of the original vecstim.mod (author unknown?) which allows multiple vectors to be used sequentially. This saves memory in a long simulation, as the same storage can be reused. The mechanism checks at intervals `ping` whether a new vector has been provided using the play() procedure and if so resets its pointer to the first element in the new vector. Note that any spikes remaining in the first vector will be lost. Any spiketimes in the new vector that are earlier than the current time are ignored. The mechanism actually checks slightly after the ping interval, to avoid play() and the ping check occurring at the same time step but in the wrong order. Extracts from the comments on the original vecstim: The idiom for getting a Vector argument in a model description is encapsulated in the "play" procedure. There are potentially many VecStim instances and so the Vector pointer must be stored in the space allocated for the particular instance when "play" is called. The assigned variable "space" gives us space for a double precision number, 64 bits, which is sufficient to store an opaque pointer. The "element" procedure uses this opaque pointer to make sure that the requested "index" element is within the size of the vector and assigns the "etime" double precision variable to the value of that element. Since index is defined at the model description level it is a double precision variable as well and must be treated as such in the VERBATIM block. An index value of -1 means that no further events should be sent from this instance. Fortunately, space for model data is cleared when it is first allocated. So if play is not called, the pointer will be 0 and the test in the element procedure would turn off the VecStim by setting index to -1. Also, because the existence of the first argument is checked in the "play" procedure, one can turn off the VecStim with vs.play() No checking is done if the stimvec is destroyed (when the reference count for the underlying Vector becomes 0). Continued use of the VecStim instance in this case would cause a memory error. So it is up to the user to call vs.play() or to destroy the VecStim instance before running another simulation. The strategy of the INITIAL and NET_RECEIVE blocks is to send a self event (with flag 1) to be delivered at the time specified by the index of the Vector starting at index 0. When the self event is delivered to the NET_RECEIVE block, it causes an immediate input event on every NetCon which has this VecStim as its source. These events, would then be delivered to their targets after the appropriate delay specified for each NetCon. ENDCOMMENT : Vector stream of events NEURON { ARTIFICIAL_CELL VecStim RANGE ping } PARAMETER { ping = 1 (ms) } ASSIGNED { index etime (ms) space } INITIAL { index = 0 element() if (index > 0) { net_send(etime - t, 1) } if (ping > 0) { net_send(ping, 2) } } NET_RECEIVE (w) { if (flag == 1) { net_event(t) element() if (index > 0) { if (etime < t) { printf("Warning in VecStim: spike time (%g ms) before current time (%g ms)\n",etime,t) } else { net_send(etime - t, 1) } } } else if (flag == 2) { : ping - reset index to 0 :printf("flag=2, etime=%g, t=%g, ping=%g, index=%g\n",etime,t,ping,index) if (index == -2) { : play() has been called :printf("Detected new vector\n") index = 0 : the following loop ensures that if the vector : contains spiketimes earlier than the current : time, they are ignored. while (etime < t && index >= 0) { element() :printf("element(): index=%g, etime=%g, t=%g\n",index,etime,t) } if (index > 0) { net_send(etime - t, 1) } } net_send(ping, 2) } } VERBATIM extern double* vector_vec(); extern int vector_capacity(); extern void* vector_arg(); ENDVERBATIM PROCEDURE element() { VERBATIM { void* vv; int i, size; double* px; i = (int)index; if (i >= 0) { vv = *((void**)(&space)); if (vv) { size = vector_capacity(vv); px = vector_vec(vv); if (i < size) { etime = px[i]; index += 1.; } else { index = -1.; } } else { index = -1.; } } } ENDVERBATIM } PROCEDURE play() { VERBATIM void** vv; vv = (void**)(&space); *vv = (void*)0; if (ifarg(1)) { *vv = vector_arg(1); } index = -2; ENDVERBATIM } PyNN-0.10.0/pyNN/neuron/populations.py000066400000000000000000000130611415343567000175460ustar00rootroot00000000000000# encoding: utf-8 """ nrnpython implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import logging from pyNN import common from pyNN.parameters import ArrayParameter, Sequence, ParameterSpace, simplify, LazyArray from pyNN.standardmodels import StandardCellType from pyNN.random import RandomDistribution from . import simulator from .recording import Recorder logger = logging.getLogger("PyNN") class PopulationMixin(object): def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" parameter_space.evaluate(mask=np.where(self._mask_local)[0]) for cell, parameters in zip(self, parameter_space): for name, val in parameters.items(): setattr(cell._cell, name, val) def _get_parameters(self, *names): """ return a ParameterSpace containing native parameters """ parameter_dict = {} for name in names: if name == 'spike_times': # hack parameter_dict[name] = [Sequence(getattr(id._cell, name)) for id in self] else: val = np.array([getattr(id._cell, name) for id in self]) if isinstance(val[0], tuple) or len(val.shape) == 2: val = np.array([ArrayParameter(v) for v in val]) val = LazyArray(simplify(val), shape=(self.local_size,), dtype=ArrayParameter) parameter_dict[name] = val else: parameter_dict[name] = simplify(val) parameter_dict[name] = simplify(val) return ParameterSpace(parameter_dict, shape=(self.local_size,)) def _set_initial_value_array(self, variable, initial_values): if initial_values.is_homogeneous: value = initial_values.evaluate(simplify=True) for cell in self: # only on local node setattr(cell._cell, "%s_init" % variable, value) else: if isinstance(initial_values.base_value, RandomDistribution) and initial_values.base_value.rng.parallel_safe: local_values = initial_values.evaluate()[self._mask_local] else: local_values = initial_values[self._mask_local] for cell, value in zip(self, local_values): setattr(cell._cell, "%s_init" % variable, value) class Assembly(common.Assembly): __doc__ = common.Assembly.__doc__ _simulator = simulator class PopulationView(common.PopulationView, PopulationMixin): __doc__ = common.PopulationView.__doc__ _simulator = simulator _assembly_class = Assembly def _get_view(self, selector, label=None): return PopulationView(self, selector, label) class Population(common.Population, PopulationMixin): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def __init__(self, size, cellclass, cellparams=None, structure=None, initial_values={}, label=None): __doc__ = common.Population.__doc__ common.Population.__init__(self, size, cellclass, cellparams, structure, initial_values, label) simulator.initializer.register(self) def _get_view(self, selector, label=None): return PopulationView(self, selector, label) def _create_cells(self): """ Create cells in NEURON using the celltype of the current Population. """ # this method should never be called more than once # perhaps should check for that self.first_id = simulator.state.gid_counter self.last_id = simulator.state.gid_counter + self.size - 1 self.all_cells = np.array([id for id in range(self.first_id, self.last_id + 1)], simulator.ID) # mask_local is used to extract those elements from arrays that apply to the cells on the current node # round-robin distribution of cells between nodes self._mask_local = self.all_cells % simulator.state.num_processes == simulator.state.mpi_rank if isinstance(self.celltype, StandardCellType): parameter_space = self.celltype.native_parameters else: parameter_space = self.celltype.parameter_space parameter_space.shape = (self.size,) parameter_space.evaluate(mask=None) for i, (id, is_local, params) in enumerate(zip(self.all_cells, self._mask_local, parameter_space)): self.all_cells[i] = simulator.ID(id) self.all_cells[i].parent = self if is_local: if hasattr(self.celltype, "extra_parameters"): params.update(self.celltype.extra_parameters) self.all_cells[i]._build_cell(self.celltype.model, params) simulator.initializer.register(*self.all_cells[self._mask_local]) simulator.state.gid_counter += self.size def _native_rset(self, parametername, rand_distr): """ 'Random' set. Set the value of parametername to a value taken from rand_distr, which should be a RandomDistribution object. """ assert isinstance(rand_distr.rng, NativeRNG) rng = simulator.h.Random(rand_distr.rng.seed or 0) native_rand_distr = getattr(rng, rand_distr.name) rarr = [native_rand_distr(*rand_distr.parameters)] + [rng.repick() for i in range(self.all_cells.size - 1)] self.tset(parametername, rarr) PyNN-0.10.0/pyNN/neuron/projections.py000066400000000000000000000157341415343567000175410ustar00rootroot00000000000000# encoding: utf-8 """ nrnpython implementation of the PyNN API. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from copy import deepcopy import numpy as np import logging from itertools import repeat, chain from collections import defaultdict from pyNN import common, errors, core from pyNN.random import RandomDistribution, NativeRNG from pyNN.space import Space from . import simulator from .standardmodels.synapses import StaticSynapse, TsodyksMarkramSynapse logger = logging.getLogger("PyNN") _projections = [] # if a Projection is created but not assigned to a variable, # the connections will not exist, so we store a reference here class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator _static_synapse_class = StaticSynapse def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type=None, source=None, receptor_type=None, space=Space(), label=None): __doc__ = common.Projection.__init__.__doc__ common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) self._connections = dict((index, defaultdict(list)) for index in self.post._mask_local.nonzero()[0]) connector.connect(self) self._presynaptic_components = dict((index, {}) for index in self.pre._mask_local.nonzero()[0]) if self.synapse_type.presynaptic_type: self._configure_presynaptic_components() _projections.append(self) logger.info("--- Projection[%s].__init__() ---" % self.label) @property def connections(self): for x in self._connections.values(): for y in x.values(): for z in y: yield z def __getitem__(self, i): __doc__ = common.Projection.__getitem__.__doc__ if isinstance(i, int): if i < len(self): return self.connections[i] else: raise IndexError("%d > %d" % (i, len(self) - 1)) elif isinstance(i, slice): if i.stop < len(self): return [self.connections[j] for j in range(*i.indices(i.stop))] else: raise IndexError("%d > %d" % (i.stop, len(self) - 1)) def __len__(self): """Return the number of connections on the local MPI node.""" return len(list(self.connections)) def _convergent_connect(self, presynaptic_indices, postsynaptic_index, **connection_parameters): """ Connect a neuron to one or more other neurons with a static connection. `presynaptic_cells` -- a 1D array of pre-synaptic cell IDs `postsynaptic_cell` -- the ID of the post-synaptic cell. `connection_parameters` -- each parameter should be either a 1D array of the same length as `sources`, or a single value. """ #logger.debug("Convergent connect. Weights=%s" % connection_parameters['weight']) postsynaptic_cell = self.post[postsynaptic_index] if not isinstance(postsynaptic_cell, int) or not (0 <= postsynaptic_cell <= simulator.state.gid_counter): errmsg = "Invalid post-synaptic cell: %s (gid_counter=%d)" % ( postsynaptic_cell, simulator.state.gid_counter) raise errors.ConnectionError(errmsg) for name, value in connection_parameters.items(): if isinstance(value, (float, int)): connection_parameters[name] = repeat(value) assert postsynaptic_cell.local for pre_idx, values in core.ezip(presynaptic_indices, *connection_parameters.values()): parameters = dict(zip(connection_parameters.keys(), values)) # logger.debug("Connecting neuron #%s to neuron #%s with synapse type %s, receptor type %s, parameters %s", pre_idx, postsynaptic_index, self.synapse_type, self.receptor_type, parameters) self._connections[postsynaptic_index][pre_idx].append( self.synapse_type.connection_type(self, pre_idx, postsynaptic_index, **parameters)) def _configure_presynaptic_components(self): """ For gap junctions potentially other complex synapse types the presynaptic side of the connection also needs to be initiated. This is a little tricky with sources distributed on different nodes as the parameters need to be gathered to the node where the source is hosted before it can be set """ # Get the list of all connections on all nodes conn_list = np.array(self.get(self.synapse_type.get_parameter_names(), 'list', gather='all', with_address=True)) # Loop through each of the connections where the presynaptic index (first column) is on # the local node mask_local = np.array(np.in1d(np.squeeze(conn_list[:, 0]), np.nonzero(self.pre._mask_local)[0]), dtype=bool) for conn in conn_list[mask_local, :]: pre_idx = int(conn[0]) post_idx = int(conn[1]) params = dict(zip(self.synapse_type.get_parameter_names(), conn[2:])) self._presynaptic_components[pre_idx][post_idx] = \ self.synapse_type.presynaptic_type(self, pre_idx, post_idx, **params) def _set_attributes(self, parameter_space): # If synapse has pre-synaptic components evaluate the parameters for them if self.synapse_type.presynaptic_type: presyn_param_space = deepcopy(parameter_space) presyn_param_space.evaluate(mask=(slice(None), self.pre._mask_local)) for component, connection_parameters in zip(self._presynaptic_components.values(), presyn_param_space.columns()): for name, value in connection_parameters.items(): for index in component: setattr(component[index], name, value[index]) # Evaluate the parameters for the post-synaptic components (typically the "Connection" object) # only columns for connections that exist on this machine parameter_space.evaluate(mask=(slice(None), self.post._mask_local)) for connection_group, connection_parameters in zip(self._connections.values(), parameter_space.columns()): for name, value in connection_parameters.items(): for index in connection_group: for connection in connection_group[index]: setattr(connection, name, value[index]) def _set_initial_value_array(self, variable, value): raise NotImplemented PyNN-0.10.0/pyNN/neuron/random.py000066400000000000000000000066351415343567000164620ustar00rootroot00000000000000""" docstring missing """ import numpy as np from neuron import h from pyNN.random import NativeRNG, WrappedRNG class NativeRNG(NativeRNG, WrappedRNG): """ Signals that the random numbers will be drawn by NEURON's Random() class and takes care of transforming pyNN parameters for the random distributions to NEURON parameters. """ translations = { 'binomial': ('binomial', ('n', 'p')), 'gamma': ('gamma', ('k', 'theta')), 'exponential': ('negexp', ('beta',)), 'lognormal': ('lognormal', ('mu', 'sigma')), 'normal': ('normal', ('mu', 'sigma')), 'normal_clipped': ('normal_clipped', ('mu', 'sigma', 'low', 'high')), # 'normal_clipped_to_boundary': # ('normal_clipped_to_boundary', {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}), 'poisson': ('poisson', ('lambda_',)), 'uniform': ('uniform', ('low', 'high')), 'uniform_int': ('discunif', ('low', 'high')), # 'vonmises': ('vonmises', {'mu': 'mu', 'kappa': 'kappa'}), } def __init__(self, seed=None, parallel_safe=True): WrappedRNG.__init__(self, seed, parallel_safe) if self.seed is None: self.rng = h.Random() else: self.rng = h.Random(self.seed) self.last_distribution = None def _next(self, distribution, n, parameters): distribution_nrn, parameter_ordering = self.translations[distribution] if set(parameters.keys()) != set(parameter_ordering): # all parameters must be provided. We do not provide default values (this can be discussed). errmsg = "Incorrect parameterization of random distribution. Expected %s, got %s." raise KeyError(errmsg % (parameter_ordering, parameters.keys())) parameters_nrn = [parameters[k] for k in parameter_ordering] if hasattr(self, distribution_nrn): return getattr(self, distribution_nrn)(n, *parameters_nrn) else: return self._next_n(distribution_nrn, n, parameters_nrn) def _next_n(self, distribution_nrn, n, parameters_nrn): if self.last_distribution == distribution_nrn: values = np.fromiter((self.rng.repick() for i in range(n)), dtype=float, count=n) else: self.last_distribution = distribution_nrn f_distr = getattr(self.rng, distribution_nrn) values = np.empty((n,)) values[0] = f_distr(*parameters_nrn) for i in range(1, n): values[i] = self.rng.repick() return values def gamma(self, n, k, theta): if k % 1 == 0: # k is equal to an integer # Remap k, theta to mean, variance: # gamma(k, 1/lambda) = erlang(k, lambda) # mean(erlang) = k/lambda # var(erlang) = k/lambda^2 mean = k * theta variance = mean * theta return self._next_n("erlang", n, (mean, variance)) else: raise Exception("The general case of the gamma distribution is not supported.") def normal(self, n, mu, sigma): return self._next_n("normal", n, (mu, sigma * sigma)) def normal_clipped(self, n, mu, sigma, low, high): """ """ gen = lambda n: self.normal(n, mu, sigma) return self._clipped(gen, low=low, high=high, size=n) PyNN-0.10.0/pyNN/neuron/recording.py000066400000000000000000000120631415343567000171460ustar00rootroot00000000000000""" :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np from pyNN import recording from pyNN.neuron import simulator import re from neuron import h recordable_pattern = re.compile( r'((?P

\w+)(\((?P[-+]?[0-9]*\.?[0-9]+)\))?\.)?(?P\w+)') class Recorder(recording.Recorder): """Encapsulates data and functions related to recording model variables.""" _simulator = simulator def _record(self, variable, new_ids, sampling_interval=None): """Add the cells in `new_ids` to the set of recorded cells.""" if variable == 'spikes': for id in new_ids: if id._cell.rec is not None: id._cell.rec.record(id._cell.spike_times) else: # SpikeSourceArray id._cell.recording = True else: self.sampling_interval = sampling_interval or self._simulator.state.dt for id in new_ids: self._record_state_variable(id._cell, variable) def _record_state_variable(self, cell, variable): if hasattr(cell, 'recordable') and variable in cell.recordable: hoc_var = cell.recordable[variable] elif variable == 'v': hoc_var = cell.source_section(0.5)._ref_v # or use "seg.v"? elif variable == 'gsyn_exc': hoc_var = cell.esyn._ref_g elif variable == 'gsyn_inh': hoc_var = cell.isyn._ref_g else: source, var_name = self._resolve_variable(cell, variable) hoc_var = getattr(source, "_ref_%s" % var_name) cell.traces[variable] = vec = h.Vector() if self.sampling_interval == self._simulator.state.dt: vec.record(hoc_var) else: vec.record(hoc_var, self.sampling_interval) if not cell.recording_time: cell.record_times = h.Vector() if self.sampling_interval == self._simulator.state.dt: cell.record_times.record(h._ref_t) else: cell.record_times.record(h._ref_t, self.sampling_interval) cell.recording_time += 1 # could be staticmethod def _resolve_variable(self, cell, variable_path): match = recordable_pattern.match(variable_path) if match: parts = match.groupdict() if parts['section']: section = getattr(cell, parts['section']) if parts['location']: source = section(float(parts['location'])) else: source = section else: source = cell.source return source, parts['var'] else: raise AttributeError("Recording of %s not implemented." % variable_path) def _reset(self): """Reset the list of things to be recorded.""" for id in set.union(*self.recorded.values()): id._cell.traces = {} id._cell.spike_times = h.Vector(0) id._cell.recording_time == 0 id._cell.record_times = None def _clear_simulator(self): """ Should remove all recorded data held by the simulator and, ideally, free up the memory. """ for id in set.union(*self.recorded.values()): if hasattr(id._cell, "traces"): for variable in id._cell.traces: id._cell.traces[variable].resize(0) if id._cell.rec is not None: id._cell.spike_times.resize(0) else: id._cell.clear_past_spikes() def _get_spiketimes(self, id, clear=False): if hasattr(id, "__len__"): all_spiketimes = {} for cell_id in id: if cell_id._cell.rec is None: # SpikeSourceArray spikes = cell_id._cell.get_recorded_spike_times() else: spikes = np.array(cell_id._cell.spike_times) all_spiketimes[cell_id] = spikes[spikes <= simulator.state.t + 1e-9] return all_spiketimes else: spikes = np.array(id._cell.spike_times) return spikes[spikes <= simulator.state.t + 1e-9] def _get_all_signals(self, variable, ids, clear=False): # assuming not using cvode, otherwise need to get times as well and use IrregularlySampledAnalogSignal if len(ids) > 0: signals = np.vstack([id._cell.traces[variable] for id in ids]).T expected_length = np.rint(simulator.state.tstop / self.sampling_interval) + 1 if signals.shape[0] != expected_length: # generally due to floating point/rounding issues signals = np.vstack((signals, signals[-1, :])) else: signals = np.array([]) return signals def _local_count(self, variable, filter_ids=None): N = {} if variable == 'spikes': for id in self.filter_recorded(variable, filter_ids): N[int(id)] = id._cell.spike_times.size() else: raise Exception("Only implemented for spikes") return N PyNN-0.10.0/pyNN/neuron/simulator.py000066400000000000000000000560361415343567000172210ustar00rootroot00000000000000# encoding: utf8 """ Implementation of the "low-level" functionality used by the common implementation of the API, for the NEURON simulator. Classes and attributes useable by the common implementation: Classes: ID Connection Attributes: state -- a singleton instance of the _State class. All other functions and classes are private, and should not be used by other modules. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import __path__ as pyNN_path from pyNN import common import logging import numpy as np import os.path from neuron import h, nrn_dll_loaded from operator import itemgetter logger = logging.getLogger("PyNN") name = "NEURON" # for use in annotating output data # Instead of starting the projection var-GID range from 0, the first _MIN_PROJECTION_VARGID are # reserved for other potential uses _MIN_PROJECTION_VARGID = 1000000 # --- Internal NEURON functionality -------------------------------------------- def load_mechanisms(path): """ Search for and load NMODL mechanisms from the path given. This a stricter version of NEURON's own load_mechanisms function, which will raise an IOError if no mechanisms are found at the given path. This function will not load a mechanism path twice. The path should specify the directory in which nrnivmodl was run, and in which the directory 'i686' (or 'x86_64' or 'powerpc' depending on your platform) was created. """ import platform global nrn_dll_loaded if path in nrn_dll_loaded: logger.warning("Mechanisms already loaded from path: %s" % path) return # in case NEURON is assuming a different architecture to Python, # we try multiple possibilities arch_list = [platform.machine(), 'i686', 'x86_64', 'powerpc', 'umac'] for arch in arch_list: lib_path = os.path.join(path, arch, '.libs', 'libnrnmech.so') if os.path.exists(lib_path): h.nrn_load_dll(lib_path) nrn_dll_loaded.append(path) return raise IOError("NEURON mechanisms not found in %s. You may need to run 'nrnivmodl' in this directory." % path) def is_point_process(obj): """Determine whether a particular object is a NEURON point process.""" return hasattr(obj, 'loc') def nativeRNG_pick(n, rng, distribution='uniform', parameters=[0, 1]): """ Pick random numbers from a Hoc Random object. Return a Numpy array. """ native_rng = h.Random(0 or rng.seed) rarr = [getattr(native_rng, distribution)(*parameters)] rarr.extend([native_rng.repick() for j in range(n - 1)]) return np.array(rarr) def h_property(name): """Return a property that accesses a global variable in Hoc.""" def _get(self): return getattr(h, name) def _set(self, val): setattr(h, name, val) return property(fget=_get, fset=_set) class _Initializer(object): """ Manage initialization of NEURON cells. Rather than create an `FInializeHandler` instance for each cell that needs to initialize itself, we create a single instance, and use an instance of this class to maintain a list of cells that need to be initialized. Public methods: register() """ def __init__(self): """ Create an `FinitializeHandler` object in Hoc, which will call the `_initialize()` method when NEURON is initialized. """ h('objref initializer') h.initializer = self self.fih = h.FInitializeHandler(1, "initializer._initialize()") self.clear() def register(self, *items): """ Add items to the list of cells/populations to be initialized. Cell objects must have a `memb_init()` method. """ for item in items: if isinstance(item, (common.BasePopulation, common.Assembly)): if item.celltype.injectable: # don't do memb_init() on spike sources self.population_list.append(item) else: if hasattr(item._cell, "memb_init"): self.cell_list.append(item) def _initialize(self): """Call `memb_init()` for all registered cell objects.""" logger.info("Initializing membrane potential of %d cells and %d Populations." % (len(self.cell_list), len(self.population_list))) for cell in self.cell_list: cell._cell.memb_init() for population in self.population_list: for cell in population: cell._cell.memb_init() def clear(self): self.cell_list = [] self.population_list = [] # --- For implementation of get_time_step() and similar functions -------------- class _State(common.control.BaseState): """Represent the simulator state.""" def __init__(self): """Initialize the simulator.""" super(_State, self).__init__() h('min_delay = -1') h('tstop = 0') h('steps_per_ms = 1/dt') self.parallel_context = h.ParallelContext() self.parallel_context.spike_compress(1, 0) self.num_processes = int(self.parallel_context.nhost()) self.mpi_rank = int(self.parallel_context.id()) self.cvode = h.CVode() h('objref plastic_connections') self.clear() self.default_maxstep = 10.0 self.native_rng_baseseed = 0 t = h_property('t') def __get_dt(self): return h.dt def __set_dt(self, dt): h.steps_per_ms = 1.0 / dt h.dt = dt dt = property(fget=__get_dt, fset=__set_dt) tstop = h_property('tstop') # these are stored in hoc so that we def __set_min_delay(self, val): # can interact with the GUI if val != 'auto': h.min_delay = val def __get_min_delay(self): if h.min_delay < 0: return 'auto' else: return h.min_delay min_delay = property(fset=__set_min_delay, fget=__get_min_delay) def register_gid(self, gid, source, section=None): """Register a global ID with the global `ParallelContext` instance.""" ###print("registering gid %s to %s (section=%s)" % (gid, source, section)) self.parallel_context.set_gid2node(gid, self.mpi_rank) # assign the gid to this node if is_point_process(source): nc = h.NetCon(source, None) # } associate the cell spike source else: nc = h.NetCon(source, None, sec=section) self.parallel_context.cell(gid, nc) # } with the gid (using a temporary NetCon) self.gid_sources.append(source) # gid_clear (in _State.reset()) will cause a # segmentation fault if any of the sources # registered using pc.cell() no longer exist, so # we keep a reference to all sources in the # global gid_sources list. It would be nicer to # be able to unregister a gid and have a __del__ # method in ID, but this will do for now. def clear(self): self.parallel_context.gid_clear() self.gid_sources = [] self.recorders = set([]) self.current_sources = [] self.gid_counter = 0 self.vargid_offsets = dict() # Contains the start of the available "variable"-GID range for each projection (as opposed to "cell"-GIDs) h.plastic_connections = [] self.segment_counter = -1 self.reset() def reset(self): """Reset the state of the current network to time t = 0.""" self.running = False self.t = 0 self.tstop = 0 self.t_start = 0 self.segment_counter += 1 h.finitialize() def _pre_run(self): if not self.running: self.running = True local_minimum_delay = self.parallel_context.set_maxstep(self.default_maxstep) if state.vargid_offsets: logger.info("Setting up transfer on MPI process {}".format(state.mpi_rank)) state.parallel_context.setup_transfer() h.finitialize() self.tstop = 0 logger.debug("default_maxstep on host #%d = %g" % (self.mpi_rank, self.default_maxstep)) logger.debug("local_minimum_delay on host #%d = %g" % (self.mpi_rank, local_minimum_delay)) if self.min_delay == 'auto': self.min_delay = local_minimum_delay else: if self.num_processes > 1: assert local_minimum_delay >= self.min_delay, \ "There are connections with delays (%g) shorter than the minimum delay (%g)" % (local_minimum_delay, self.min_delay) def _update_current_sources(self, tstop): for source in self.current_sources: for iclamp in source._devices: source._update_iclamp(iclamp, tstop) def run(self, simtime): """Advance the simulation for a certain time.""" self.run_until(self.tstop + simtime) def run_until(self, tstop): self._update_current_sources(tstop) self._pre_run() self.tstop = tstop #logger.info("Running the simulation until %g ms" % tstop) if self.tstop > self.t: self.parallel_context.psolve(self.tstop) def finalize(self, quit=False): """Finish using NEURON.""" self.parallel_context.runworker() self.parallel_context.done() if quit: logger.info("Finishing up with NEURON.") h.quit() def get_vargids(self, projection, pre_idx, post_idx): """ Get new "variable"-GIDs (as opposed to the "cell"-GIDs) for a given pre->post connection pair for a given projection. `projection` -- projection `pre_idx` -- index of the presynaptic cell `post_idx` -- index of the postsynaptic cell """ try: offset = self.vargid_offsets[projection] except KeyError: # Get the projection with the current maximum vargid offset if len(self.vargid_offsets): newest_proj, offset = max(self.vargid_offsets.items(), key=itemgetter(1)) # Allocate it a large enough range for a mutual all-to-all connection (assumes that # there are no duplicate pre_idx->post_idx connections for the same projection. If # that is really desirable a new projection will need to be used) offset += 2 * len(newest_proj.pre) * len(newest_proj.post) else: offset = _MIN_PROJECTION_VARGID self.vargid_offsets[projection] = offset pre_post_vargid = offset + 2 * (pre_idx + post_idx * len(projection.pre)) post_pre_vargid = pre_post_vargid + 1 return (pre_post_vargid, post_pre_vargid) # --- For implementation of access to individual neurons' parameters ----------- class ID(int, common.IDMixin): __doc__ = common.IDMixin.__doc__ def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) def _build_cell(self, cell_model, cell_parameters): """ Create a cell in NEURON, and register its global ID. `cell_model` -- one of the cell classes defined in the `neuron.cells` module (more generally, any class that implements a certain interface, but I haven't explicitly described that yet). `cell_parameters` -- a ParameterSpace containing the parameters used to initialise the cell model. """ gid = int(self) self._cell = cell_model(**cell_parameters) # create the cell object state.register_gid(gid, self._cell.source, section=self._cell.source_section) if hasattr(self._cell, "get_threshold"): # this is not adequate, since the threshold may be changed after cell creation state.parallel_context.threshold(int(self), self._cell.get_threshold()) # the problem is that self._cell does not know its own gid def get_initial_value(self, variable): """Get the initial value of a state variable of the cell.""" return getattr(self._cell, "%s_init" % variable) def set_initial_value(self, variable, value): """Set the initial value of a state variable of the cell.""" index = self.parent.id_to_local_index(self) self.parent.initial_values[variable][index] = value setattr(self._cell, "%s_init" % variable, value) class Connection(common.Connection): """ Store an individual plastic connection and information about it. Provide an interface that allows access to the connection's weight, delay and other attributes. """ def __init__(self, projection, pre, post, **parameters): """ Create a new connection. """ #logger.debug("Creating connection from %d to %d, weight %g" % (pre, post, parameters['weight'])) self.presynaptic_index = pre self.postsynaptic_index = post self.presynaptic_cell = projection.pre[pre] self.postsynaptic_cell = projection.post[post] if "." in projection.receptor_type: section, target = projection.receptor_type.split(".") target_object = getattr(getattr(self.postsynaptic_cell._cell, section), target) else: target_object = getattr(self.postsynaptic_cell._cell, projection.receptor_type) self.nc = state.parallel_context.gid_connect(int(self.presynaptic_cell), target_object) self.nc.weight[0] = parameters.pop('weight') # if we have a mechanism (e.g. from 9ML) that includes multiple # synaptic channels, need to set nc.weight[1] here if self.nc.wcnt() > 1 and hasattr(self.postsynaptic_cell._cell, "type"): self.nc.weight[1] = self.postsynaptic_cell._cell.type.receptor_types.index(projection.receptor_type) self.nc.delay = parameters.pop('delay') if projection.synapse_type.model is not None: self._setup_plasticity(projection.synapse_type, parameters) # nc.threshold is supposed to be set by ParallelContext.threshold, called in _build_cell(), above, but this hasn't been tested def _setup_plasticity(self, synapse_type, parameters): """ Set this connection to use spike-timing-dependent plasticity. `mechanism` -- the name of an NMODL mechanism that modifies synaptic weights based on the times of pre- and post-synaptic spikes. `parameters` -- a dictionary containing the parameters of the weight- adjuster mechanism. """ mechanism = synapse_type.model self.weight_adjuster = getattr(h, mechanism)(0.5) if synapse_type.postsynaptic_variable == 'spikes': parameters['allow_update_on_post'] = int(False) # for compatibility with NEST self.ddf = parameters.pop('dendritic_delay_fraction') # If ddf=1, the synaptic delay # `d` is considered to occur entirely in the post-synaptic # dendrite, i.e., the weight adjuster receives the pre- # synaptic spike at the time of emission, and the post- # synaptic spike a time `d` after emission. If ddf=0, the # synaptic delay is considered to occur entirely in the # pre-synaptic axon. elif synapse_type.postsynaptic_variable is None: self.ddf = 0 else: raise NotImplementedError("Only post-synaptic-spike-dependent mechanisms available for now.") self.pre2wa = state.parallel_context.gid_connect(int(self.presynaptic_cell), self.weight_adjuster) self.pre2wa.threshold = self.nc.threshold self.pre2wa.delay = self.nc.delay * (1 - self.ddf) if self.pre2wa.delay > 1e-9: self.pre2wa.delay -= 1e-9 # we subtract a small value so that the synaptic weight gets updated before it is used. if synapse_type.postsynaptic_variable == 'spikes': # directly create NetCon as wa is on the same machine as the post-synaptic cell self.post2wa = h.NetCon(self.postsynaptic_cell._cell.source, self.weight_adjuster, sec=self.postsynaptic_cell._cell.source_section) self.post2wa.threshold = 1 self.post2wa.delay = self.nc.delay * self.ddf self.post2wa.weight[0] = -1 self.pre2wa.weight[0] = 1 else: self.pre2wa.weight[0] = self.nc.weight[0] parameters.pop('x', None) # for the Tsodyks-Markram model parameters.pop('y', None) # would be better to actually use these initial values for name, value in parameters.items(): setattr(self.weight_adjuster, name, value) if mechanism == 'TsodyksMarkramWA': # or could assume that any weight_adjuster parameter called "tau_syn" should be set like this self.weight_adjuster.tau_syn = self.nc.syn().tau elif 'Stochastic' in mechanism: pass # todo: (optionally?) set per-stream RNG, i.e. #self.rng = h.Random(seed) #self.rng.uniform() #self.weight_adjuster.setRNG(self.rng) # setpointer i = len(h.plastic_connections) h.plastic_connections.append(self) h('setpointer plastic_connections._[%d].weight_adjuster.wsyn, plastic_connections._[%d].nc.weight' % (i, i)) def _set_weight(self, w): self.nc.weight[0] = w def _get_weight(self): """Synaptic weight in nA or µS.""" return self.nc.weight[0] def _set_delay(self, d): self.nc.delay = d if hasattr(self, 'pre2wa'): self.pre2wa.delay = float(d) * (1 - self.ddf) if hasattr(self, 'post2wa'): self.post2wa.delay = float(d) * self.ddf def _get_delay(self): """Connection delay in ms.""" return self.nc.delay weight = property(_get_weight, _set_weight) delay = property(_get_delay, _set_delay) def as_tuple(self, *attribute_names): # need to do translation of names, or perhaps that should be handled in common? return tuple(getattr(self, name) for name in attribute_names) class GapJunction(object): """ Store an individual gap junction connection and information about it. Provide an interface that allows access to the connection's conductance attributes """ def __init__(self, projection, pre, post, **parameters): self.presynaptic_index = pre self.postsynaptic_index = post segment_name = projection.receptor_type # Strip 'gap' string from receptor_type (not sure about this, it is currently appended to # the available synapse types in the NCML model segments but is not really necessary and # it feels a bit hacky but it makes the list of receptor types more comprehensible) if segment_name.endswith('.gap'): segment_name = segment_name[:-4] self.segment = getattr(projection.post[post]._cell, segment_name) pre_post_vargid, post_pre_vargid = state.get_vargids(projection, pre, post) self._make_connection(self.segment, parameters.pop('weight'), pre_post_vargid, post_pre_vargid, projection.pre[pre], projection.post[post]) def _make_connection(self, segment, weight, local_to_remote_vargid, remote_to_local_vargid, local_gid, remote_gid): logger.debug("Setting source_var on local cell {} to connect to target_var on remote " "cell {} with vargid {} on process {}" .format(local_gid, remote_gid, local_to_remote_vargid, state.mpi_rank)) # Set up the source reference for the local->remote connection state.parallel_context.source_var(segment(0.5)._ref_v, local_to_remote_vargid) # Create the gap_junction and set its weight self.gap = h.Gap(0.5, sec=segment) self.gap.g = weight # Connect the gap junction with the source_var logger.debug("Setting target_var on local cell {} to connect to source_var on remote " "cell {} with vargid {} on process {}" .format(local_gid, remote_gid, remote_to_local_vargid, state.mpi_rank)) # set up the target reference for the remote->local connection state.parallel_context.target_var(self.gap._ref_vgap, remote_to_local_vargid) def _set_weight(self, w): self.gap.g = w def _get_weight(self): """Gap junction conductance in µS.""" return self.gap.g weight = property(_get_weight, _set_weight) def as_tuple(self, *attribute_names): return tuple(getattr(self, name) for name in attribute_names) class GapJunctionPresynaptic(GapJunction): """ The presynaptic component of a gap junction. Gap junctions in NEURON are actually symmetrical so it shares its functionality with the GapJunction connection object, with the exception that the pre and post synaptic cells are switched """ def __init__(self, projection, pre, post, **parameters): self.presynaptic_index = pre self.postsynaptic_index = post if projection.source.endswith('.gap'): segment_name = projection.source[:-4] else: segment_name = projection.source self.segment = getattr(projection.pre[pre]._cell, segment_name) pre_post_vargid, post_pre_vargid = state.get_vargids(projection, pre, post) self._make_connection(self.segment, parameters.pop('weight'), post_pre_vargid, pre_post_vargid, projection.post[post], projection.pre[pre]) def generate_synapse_property(name): def _get(self): return getattr(self.weight_adjuster, name) def _set(self, val): setattr(self.weight_adjuster, name, val) return property(_get, _set) setattr(Connection, 'wmax', generate_synapse_property('wmax')) setattr(Connection, 'wmin', generate_synapse_property('wmin')) setattr(Connection, 'aLTP', generate_synapse_property('aLTP')) setattr(Connection, 'aLTD', generate_synapse_property('aLTD')) setattr(Connection, 'tauLTP', generate_synapse_property('tauLTP')) setattr(Connection, 'tauLTD', generate_synapse_property('tauLTD')) setattr(Connection, 'U', generate_synapse_property('U')) setattr(Connection, 'tau_rec', generate_synapse_property('tau_rec')) setattr(Connection, 'tau_facil', generate_synapse_property('tau_facil')) setattr(Connection, 'u0', generate_synapse_property('u0')) setattr(Connection, 'tau', generate_synapse_property('tau')) setattr(Connection, 'eta', generate_synapse_property('eta')) setattr(Connection, 'rho', generate_synapse_property('rho')) # --- Initialization, and module attributes ------------------------------------ mech_path = os.path.join(pyNN_path[0], 'neuron', 'nmodl') load_mechanisms(mech_path) # maintains a list of mechanisms that have already been imported state = _State() # a Singleton, so only a single instance ever exists del _State initializer = _Initializer() del _Initializer PyNN-0.10.0/pyNN/neuron/standardmodels/000077500000000000000000000000001415343567000176225ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuron/standardmodels/__init__.py000066400000000000000000000000001415343567000217210ustar00rootroot00000000000000PyNN-0.10.0/pyNN/neuron/standardmodels/cells.py000066400000000000000000000211361415343567000213010ustar00rootroot00000000000000# encoding: utf-8 """ Standard base_cells for the neuron module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import cells as base_cells, build_translations from pyNN.neuron.cells import (StandardIF, SingleCompartmentTraub, RandomSpikeSource, VectorSpikeSource, RandomGammaSpikeSource, RandomPoissonRefractorySpikeSource, BretteGerstnerIF, GsfaGrrIF, Izhikevich_, GIFNeuron) import logging logger = logging.getLogger("PyNN") class IF_curr_alpha(base_cells.IF_curr_alpha): __doc__ = base_cells.IF_curr_alpha.__doc__ translations = build_translations( ('tau_m', 'tau_m'), ('cm', 'c_m'), ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('tau_refrac', 't_refrac'), ('i_offset', 'i_offset'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ) model = StandardIF extra_parameters = {'syn_type': 'current', 'syn_shape': 'alpha'} class IF_curr_exp(base_cells.IF_curr_exp): __doc__ = base_cells.IF_curr_exp.__doc__ translations = build_translations( ('tau_m', 'tau_m'), ('cm', 'c_m'), ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('tau_refrac', 't_refrac'), ('i_offset', 'i_offset'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ) model = StandardIF extra_parameters = {'syn_type': 'current', 'syn_shape': 'exp'} class IF_cond_alpha(base_cells.IF_cond_alpha): __doc__ = base_cells.IF_cond_alpha.__doc__ translations = build_translations( ('tau_m', 'tau_m'), ('cm', 'c_m'), ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('tau_refrac', 't_refrac'), ('i_offset', 'i_offset'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('e_rev_E', 'e_e'), ('e_rev_I', 'e_i') ) model = StandardIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'alpha'} class IF_cond_exp(base_cells.IF_cond_exp): __doc__ = base_cells.IF_cond_exp.__doc__ translations = build_translations( ('tau_m', 'tau_m'), ('cm', 'c_m'), ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('tau_refrac', 't_refrac'), ('i_offset', 'i_offset'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('e_rev_E', 'e_e'), ('e_rev_I', 'e_i') ) model = StandardIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp'} class IF_facets_hardware1(base_cells.IF_facets_hardware1): __doc__ = base_cells.IF_facets_hardware1.__doc__ translations = build_translations( ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('g_leak', 'tau_m', "0.2*1000.0/g_leak", "0.2*1000.0/tau_m"), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('e_rev_I', 'e_i') ) model = StandardIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp', 'i_offset': 0.0, 'c_m': 0.2, 't_refrac': 1.0, 'e_e': 0.0} class HH_cond_exp(base_cells.HH_cond_exp): __doc__ = base_cells.HH_cond_exp.__doc__ translations = build_translations( ('gbar_Na', 'gbar_Na', 1e-3), # uS -> mS ('gbar_K', 'gbar_K', 1e-3), ('g_leak', 'g_leak', 1e-3), ('cm', 'c_m'), ('v_offset', 'v_offset'), ('e_rev_Na', 'ena'), ('e_rev_K', 'ek'), ('e_rev_leak', 'e_leak'), ('e_rev_E', 'e_e'), ('e_rev_I', 'e_i'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('i_offset', 'i_offset'), ) model = SingleCompartmentTraub extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp'} class IF_cond_exp_gsfa_grr(base_cells.IF_cond_exp_gsfa_grr): __doc__ = base_cells.IF_cond_exp_gsfa_grr.__doc__ translations = build_translations( ('v_rest', 'v_rest'), ('v_reset', 'v_reset'), ('cm', 'c_m'), ('tau_m', 'tau_m'), ('tau_refrac', 't_refrac'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('v_thresh', 'v_thresh'), ('i_offset', 'i_offset'), ('e_rev_E', 'e_e'), ('e_rev_I', 'e_i'), ('tau_sfa', 'tau_sfa'), ('e_rev_sfa', 'e_sfa'), ('q_sfa', 'q_sfa'), ('tau_rr', 'tau_rr'), ('e_rev_rr', 'e_rr'), ('q_rr', 'q_rr') ) model = GsfaGrrIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp'} class SpikeSourcePoisson(base_cells.SpikeSourcePoisson): __doc__ = base_cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('start', 'start'), ('rate', '_interval', "1000.0/rate", "1000.0/_interval"), ('duration', 'duration'), ) model = RandomSpikeSource class SpikeSourcePoissonRefractory(base_cells.SpikeSourcePoissonRefractory): __doc__ = base_cells.SpikeSourcePoissonRefractory.__doc__ translations = build_translations( ('start', 'start'), ('rate', 'rate'), ('tau_refrac', 'tau_refrac'), ('duration', 'duration'), ) model = RandomPoissonRefractorySpikeSource class SpikeSourceGamma(base_cells.SpikeSourceGamma): __doc__ = base_cells.SpikeSourceGamma.__doc__ translations = build_translations( ('alpha', 'alpha'), ('beta', 'beta', 0.001), ('start', 'start'), ('duration', 'duration'), ) model = RandomGammaSpikeSource class SpikeSourceArray(base_cells.SpikeSourceArray): __doc__ = base_cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'spike_times'), ) model = VectorSpikeSource class EIF_cond_alpha_isfa_ista(base_cells.EIF_cond_alpha_isfa_ista): __doc__ = base_cells.EIF_cond_alpha_isfa_ista.__doc__ translations = build_translations( ('cm', 'c_m'), ('tau_refrac', 't_refrac'), ('v_spike', 'v_spike'), ('v_reset', 'v_reset'), ('v_rest', 'v_rest'), ('tau_m', 'tau_m'), ('i_offset', 'i_offset'), ('a', 'A', 0.001), # nS --> uS ('b', 'B'), ('delta_T', 'delta'), ('tau_w', 'tau_w'), ('v_thresh', 'v_thresh'), ('e_rev_E', 'e_e'), ('tau_syn_E', 'tau_e'), ('e_rev_I', 'e_i'), ('tau_syn_I', 'tau_i'), ) model = BretteGerstnerIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'alpha'} class EIF_cond_exp_isfa_ista(base_cells.EIF_cond_exp_isfa_ista): __doc__ = base_cells.EIF_cond_exp_isfa_ista.__doc__ translations = EIF_cond_alpha_isfa_ista.translations model = BretteGerstnerIF extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp'} class Izhikevich(base_cells.Izhikevich): __doc__ = base_cells.Izhikevich.__doc__ translations = build_translations( ('a', 'a_'), ('b', 'b'), ('c', 'c'), ('d', 'd'), ('i_offset', 'i_offset') ) model = Izhikevich_ class GIF_cond_exp(base_cells.GIF_cond_exp): translations = build_translations( ('v_rest', 'v_rest'), ('cm', 'c_m'), ('tau_m', 'tau_m'), ('tau_refrac', 't_refrac'), ('tau_syn_E', 'tau_e'), ('tau_syn_I', 'tau_i'), ('e_rev_E', 'e_e'), ('e_rev_I', 'e_i'), ('v_reset', 'v_reset'), ('i_offset', 'i_offset'), ('delta_v', 'dV'), ('v_t_star', 'vt_star'), ('lambda0', 'lambda0'), ('tau_eta', 'tau_eta'), ('tau_gamma', 'tau_gamma'), ('a_eta', 'a_eta'), ('a_gamma', 'a_gamma'), ) model = GIFNeuron extra_parameters = {'syn_type': 'conductance', 'syn_shape': 'exp'} PyNN-0.10.0/pyNN/neuron/standardmodels/electrodes.py000066400000000000000000000223401415343567000223260ustar00rootroot00000000000000""" Current source classes for the neuron module. Classes: DCSource -- a single pulse of current of constant amplitude. StepCurrentSource -- a step-wise time-varying current. NoisyCurrentSource -- a Gaussian whitish noise current. ACSource -- a sine modulated current. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from neuron import h import numpy as np from pyNN.standardmodels import electrodes, build_translations, StandardCurrentSource from pyNN.parameters import ParameterSpace, Sequence from pyNN.neuron import simulator class NeuronCurrentSource(StandardCurrentSource): """Base class for a source of current to be injected into a neuron.""" def __init__(self, **parameters): self._devices = [] self.cell_list = [] self._amplitudes = None self._times = None self._h_iclamps = {} parameter_space = ParameterSpace(self.default_parameters, self.get_schema(), shape=(1,)) parameter_space.update(**parameters) parameter_space = self.translate(parameter_space) self.set_native_parameters(parameter_space) simulator.state.current_sources.append(self) @property def _h_amplitudes(self): if self._amplitudes is None: if isinstance(self.amplitudes, Sequence): self._amplitudes = h.Vector(self.amplitudes.value) else: self._amplitudes = h.Vector(self.amplitudes) return self._amplitudes @property def _h_times(self): if self._times is None: if isinstance(self.times, Sequence): self._times = h.Vector(self.times.value) else: self._times = h.Vector(self.times) return self._times def _reset(self): if self._is_computed: self._amplitudes = None self._times = None self._generate() for iclamp in self._h_iclamps.values(): self._update_iclamp(iclamp, 0.0) # send tstop = 0.0 on _reset() def _update_iclamp(self, iclamp, tstop): if not self._is_playable: iclamp.delay = self.start iclamp.dur = self.stop - self.start iclamp.amp = self.amplitude if self._is_playable: iclamp.delay = 0.0 iclamp.dur = 1e12 iclamp.amp = 0.0 # check exists only for StepCurrentSource (_is_playable = True, _is_computed = False) # t_stop should be part of the time sequence to handle repeated runs if not self._is_computed and tstop not in self._h_times.to_python(): ind = self._h_times.indwhere(">=", tstop) if ind == -1: # tstop beyond last specified time instant ind = self._h_times.size() if ind == 0.0: # tstop before first specified time instant amp_val = 0.0 else: amp_val = self._h_amplitudes.x[int(ind)-1] self._h_times.insrt(ind, tstop) self._h_amplitudes.insrt(ind, amp_val) self._h_amplitudes.play(iclamp._ref_amp, self._h_times) def _check_step_times(self, times, amplitudes, resolution): # ensure that all time stamps are non-negative if not (times >= 0.0).all(): raise ValueError("Step current cannot accept negative timestamps.") # ensure that times provided are of strictly increasing magnitudes dt_times = np.diff(times) if not all(dt_times > 0.0): raise ValueError("Step current timestamps should be monotonically increasing.") # map timestamps to actual simulation time instants based on specified dt for ind in range(len(times)): times[ind] = self._round_timestamp(times[ind], resolution) # remove duplicate timestamps, and corresponding amplitudes, after mapping step_times = [] step_amplitudes = [] for ts0, amp0, ts1 in zip(times, amplitudes, times[1:]): if ts0 != ts1: step_times.append(ts0) step_amplitudes.append(amp0) step_times.append(times[-1]) step_amplitudes.append(amplitudes[-1]) return step_times, step_amplitudes def set_native_parameters(self, parameters): parameters.evaluate(simplify=True) for name, value in parameters.items(): if name == "amplitudes": # key used only by StepCurrentSource step_times = parameters["times"].value step_amplitudes = parameters["amplitudes"].value step_times, step_amplitudes = self._check_step_times( step_times, step_amplitudes, simulator.state.dt) parameters["times"].value = step_times parameters["amplitudes"].value = step_amplitudes if isinstance(value, Sequence): # this shouldn't be necessary, but seems to prevent a segfault value = value.value object.__setattr__(self, name, value) self._reset() def get_native_parameters(self): return ParameterSpace(dict((k, self.__getattribute__(k)) for k in self.get_native_names())) def inject_into(self, cells): __doc__ = StandardCurrentSource.inject_into.__doc__ for id in cells: if id.local: if not id.celltype.injectable: raise TypeError("Can't inject current into a spike source.") if not (id in self._h_iclamps): self.cell_list += [id] self._h_iclamps[id] = h.IClamp(0.5, sec=id._cell.source_section) self._devices.append(self._h_iclamps[id]) def record(self): self.itrace = h.Vector() self.itrace.record(self._devices[0]._ref_i) self.record_times = h.Vector() self.record_times.record(h._ref_t) def _get_data(self): # NEURON and pyNN have different concepts of current initiation times # To keep this consistent across simulators, pyNN will have current # initiating at the electrode at t_start and effect on cell at next dt. # This requires removing the first element from the current Vector # as NEURON computes the currents one time step later. The vector length # is compensated by repeating the last recorded value of current. t_arr = np.array(self.record_times) i_arr = np.array(self.itrace)[1:] i_arr = np.append(i_arr, i_arr[-1]) return (t_arr, i_arr) class DCSource(NeuronCurrentSource, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop') ) _is_playable = False _is_computed = False class StepCurrentSource(NeuronCurrentSource, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitudes'), ('times', 'times') ) _is_playable = True _is_computed = False def _generate(self): pass class ACSource(NeuronCurrentSource, electrodes.ACSource): __doc__ = electrodes.ACSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop'), ('frequency', 'frequency'), ('offset', 'offset'), ('phase', 'phase') ) _is_playable = True _is_computed = True def __init__(self, **parameters): NeuronCurrentSource.__init__(self, **parameters) self._generate() def _generate(self): # Not efficient at all... Is there a way to have those vectors computed on the fly ? # Otherwise should have a buffer mechanism temp_num_t = int(round(((self.stop + simulator.state.dt) - self.start) / simulator.state.dt)) tmp = simulator.state.dt * np.arange(temp_num_t) self.times = tmp + self.start self.amplitudes = self.offset + self.amplitude * np.sin(tmp * 2 * np.pi * self.frequency / 1000. + 2 * np.pi * self.phase / 360) self.amplitudes[-1] = 0.0 class NoisyCurrentSource(NeuronCurrentSource, electrodes.NoisyCurrentSource): __doc__ = electrodes.NoisyCurrentSource.__doc__ translations = build_translations( ('mean', 'mean'), ('start', 'start'), ('stop', 'stop'), ('stdev', 'stdev'), ('dt', 'dt') ) _is_playable = True _is_computed = True def __init__(self, **parameters): NeuronCurrentSource.__init__(self, **parameters) self._generate() def _generate(self): # Not efficient at all... Is there a way to have those vectors computed on the fly ? # Otherwise should have a buffer mechanism temp_num_t = int(round((self.stop - self.start) / max(self.dt, simulator.state.dt))) self.times = self.start + max(self.dt, simulator.state.dt) * np.arange(temp_num_t) self.times = np.append(self.times, self.stop) self.amplitudes = self.mean + self.stdev * np.random.randn(len(self.times)) self.amplitudes[-1] = 0.0 PyNN-0.10.0/pyNN/neuron/standardmodels/synapses.py000066400000000000000000000142621415343567000220460ustar00rootroot00000000000000""" Synapse Dynamics classes for the neuron module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import synapses, build_translations from pyNN.neuron.simulator import state, Connection, GapJunction, GapJunctionPresynaptic class BaseSynapse(object): """ Base synapse type for all NEURON standard synapses (sets a default 'connection_type') """ connection_type = Connection presynaptic_type = None class StaticSynapse(BaseSynapse, synapses.StaticSynapse): __doc__ = synapses.StaticSynapse.__doc__ translations = build_translations( ('weight', 'weight'), ('delay', 'delay') ) model = None def _get_minimum_delay(self): return state.min_delay class ElectricalSynapse(BaseSynapse, synapses.ElectricalSynapse): __doc__ = synapses.ElectricalSynapse.__doc__ connection_type = GapJunction presynaptic_type = GapJunctionPresynaptic translations = build_translations( ('weight', 'weight'), ) model = 'Gap' def _get_minimum_delay(self): return state.min_delay class STDPMechanism(BaseSynapse, synapses.STDPMechanism): __doc__ = synapses.STDPMechanism.__doc__ postsynaptic_variable = 'spikes' base_translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ('dendritic_delay_fraction', 'dendritic_delay_fraction') ) # will be extended by translations from timing_dependence, etc. def __init__(self, timing_dependence=None, weight_dependence=None, voltage_dependence=None, dendritic_delay_fraction=1.0, weight=0.0, delay=None): super(STDPMechanism, self).__init__(timing_dependence, weight_dependence, voltage_dependence, dendritic_delay_fraction, weight, delay) if dendritic_delay_fraction > 0.5 and state.num_processes > 1: # depending on delays, can run into problems with the delay from the # pre-synaptic neuron to the weight-adjuster mechanism being zero. # The best (only?) solution would be to create connections on the # node with the pre-synaptic neurons for ddf>0.5 and on the node # with the post-synaptic neuron (as is done now) for ddf<0.5 raise NotImplementedError( "STDP with dendritic_delay_fraction > 0.5 is not yet supported for parallel computation.") def _get_minimum_delay(self): return state.min_delay class TsodyksMarkramSynapse(BaseSynapse, synapses.TsodyksMarkramSynapse): __doc__ = synapses.TsodyksMarkramSynapse.__doc__ translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ('U', 'U'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_facil'), ) model = 'TsodyksMarkramWA' postsynaptic_variable = None def _get_minimum_delay(self): return state.min_delay class SimpleStochasticSynapse(BaseSynapse, synapses.SimpleStochasticSynapse): translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ('p', 'p'), ) model = 'SimpleStochasticWA' postsynaptic_variable = None def _get_minimum_delay(self): return state.min_delay class StochasticTsodyksMarkramSynapse(BaseSynapse, synapses.StochasticTsodyksMarkramSynapse): translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ('U', 'U'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_facil'), ) model = 'StochasticTsodyksMarkramWA' postsynaptic_variable = None def _get_minimum_delay(self): return state.min_delay class MultiQuantalSynapse(BaseSynapse, synapses.MultiQuantalSynapse): translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ('U', 'U'), ('n', 'n'), ('tau_rec', 'tau_rec'), ('tau_facil', 'tau_fac') ) model = 'QuantalSTPWA' postsynaptic_variable = None def _get_minimum_delay(self): return state.min_delay class AdditiveWeightDependence(BaseSynapse, synapses.AdditiveWeightDependence): __doc__ = synapses.AdditiveWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ) possible_models = set(['StdwaSA', 'StdwaVogels2011']) class MultiplicativeWeightDependence(BaseSynapse, synapses.MultiplicativeWeightDependence): __doc__ = synapses.MultiplicativeWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ) possible_models = set(['StdwaSoft']) class AdditivePotentiationMultiplicativeDepression(BaseSynapse, synapses.AdditivePotentiationMultiplicativeDepression): __doc__ = synapses.AdditivePotentiationMultiplicativeDepression.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ) possible_models = set(['StdwaGuetig']) extra_parameters = { 'muLTP': 0.0, 'muLTD': 1.0 } class GutigWeightDependence(BaseSynapse, synapses.GutigWeightDependence): __doc__ = synapses.GutigWeightDependence.__doc__ translations = build_translations( ('w_max', 'wmax'), ('w_min', 'wmin'), ('mu_plus', 'muLTP'), ('mu_minus', 'muLTD'), ) possible_models = set(['StdwaGuetig']) class SpikePairRule(BaseSynapse, synapses.SpikePairRule): __doc__ = synapses.SpikePairRule.__doc__ translations = build_translations( ('tau_plus', 'tauLTP'), ('tau_minus', 'tauLTD'), ('A_plus', 'aLTP'), ('A_minus', 'aLTD'), ) possible_models = set(['StdwaSA', 'StdwaSoft', 'StdwaGuetig']) class Vogels2011Rule(synapses.Vogels2011Rule): __doc__ = synapses.Vogels2011Rule.__doc__ translations = build_translations( ('tau', 'tau'), ('eta', 'eta'), ('rho', 'rho'), ) possible_models = set(['StdwaVogels2011']) PyNN-0.10.0/pyNN/nineml/000077500000000000000000000000001415343567000145725ustar00rootroot00000000000000PyNN-0.10.0/pyNN/nineml/__init__.py000066400000000000000000000073301415343567000167060ustar00rootroot00000000000000""" Export of PyNN scripts as NineML. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import nineml.user as ul from neo.io import get_io from .. import common, random, standardmodels as std from . import simulator from .standardmodels import * from .populations import Population, PopulationView, Assembly from .projections import Projection from .connectors import * logger = logging.getLogger("PyNN") random.get_mpi_config = lambda: (0, 1) def list_standard_models(): """Return a list of all the StandardCellType classes available for this simulator.""" return [obj.__name__ for obj in globals().values() if isinstance(obj, type) and issubclass(obj, std.StandardCellType)] def setup(timestep=0.1, min_delay=0.1, **extra_params): common.setup(timestep, min_delay, **extra_params) simulator.state.clear() simulator.state.dt = timestep # move to common.setup? simulator.state.min_delay = min_delay simulator.state.max_delay = extra_params.get('max_delay', 10.0) simulator.state.mpi_rank = extra_params.get('rank', 0) simulator.state.num_processes = extra_params.get('num_processes', 1) simulator.state.output_filename = extra_params.get("filename", "PyNN29ML.xml") simulator.state.net = Network(label=extra_params.get("label", "PyNN29ML")) return rank() def end(compatible_output=True): """Do any necessary cleaning up before exiting.""" for (population, variables, filename) in simulator.state.write_on_end: io = get_io(filename) population.write_data(io, variables) simulator.state.write_on_end = [] # should have common implementation of end() simulator.state.net.to_nineml().write(simulator.state.output_filename) run = common.build_run(simulator) reset = common.build_reset(simulator) initialize = common.initialize get_current_time, get_time_step, get_min_delay, get_max_delay, \ num_processes, rank = common.build_state_queries(simulator) create = common.build_create(Population) connect = common.build_connect(Projection, FixedProbabilityConnector, StaticSynapse) set = common.set record = common.build_record(simulator) def record_v(source, filename): return record(['v'], source, filename) def record_gsyn(source, filename): return record(['gsyn_exc', 'gsyn_inh'], source, filename) class Network(object): # move to .simulator ? def __init__(self, label): self.label = label self.populations = [] self.projections = [] self.current_sources = [] self.assemblies = [] def to_xml(self): return self.to_nineml().to_xml() def to_nineml(self): model = ul.Model(name=self.label) for cs in self.current_sources: model.add_component(cs.to_nineml()) # needToDefineWhichCellsTheCurrentIsInjectedInto # doWeJustReuseThePopulationProjectionIdiom="?" main_group = ul.Network(name="Network") _populations = self.populations[:] _projections = self.projections[:] for a in self.assemblies: group = a.to_nineml() for p in a.populations: _populations.remove(p) group.add(p.to_nineml()) for prj in self.projections: if (prj.pre is a or prj.pre in a.populations) and \ (prj.post is a or prj.post in a.populations): _projections.remove(prj) group.add(prj.to_nineml()) model.add_group(group) for p in _populations: main_group.add(p.to_nineml()) for prj in _projections: main_group.add(prj.to_nineml()) model.add_group(main_group) model.check() return model PyNN-0.10.0/pyNN/nineml/cells.py000066400000000000000000000107401415343567000162500ustar00rootroot00000000000000""" Cell models generated from 9ML :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging from itertools import chain import nineml.abstraction as nineml from pyNN.models import BaseCellType logger = logging.getLogger("PyNN") # Neuron Models derived from a 9ML AL definition # class NineMLCellType(BaseCellType): # #model = NineMLCell # # def __init__(self, parameters): # BaseCellType.__init__(self, parameters) # self.parameters["type"] = self # def unimplemented_builder(*args, **kwargs): # raise NotImplementedError("TODO: 9ML neuron builder") # def nineml_cell_type(name, neuron_model, port_map={}, weight_variables={}, **synapse_models): # """ # Return a new NineMLCellType subclass. # """ # return _build_nineml_celltype(name, (NineMLCellType,), # {'neuron_model': neuron_model, # 'synapse_models': synapse_models, # 'port_map': port_map, # 'weight_variables': weight_variables, # 'builder': unimplemented_builder}) # Helpers for Neuron Models derived from a 9ML AL definition def _add_prefix(synapse_model, prefix, port_map): assert False, "Deprecated" """ Add a prefix to all variables in `synapse_model`, except for variables with receive ports and specified in `port_map`. """ synapse_model.__cache__ = {} exclude = [] new_port_map = [] for name1, name2 in port_map: if synapse_model.ports_map[name2].mode == 'recv': exclude.append(name2) new_port_map.append((name1, name2)) else: new_port_map.append((name1, prefix + '_' + name2)) synapse_model.add_prefix(prefix + '_', exclude=exclude) return new_port_map _default_units = { "time": "ms", "voltage": "mV", "current": "nA" } class build_nineml_celltype(type): """ Metaclass for building NineMLCellType subclasses Called by nineml_celltype_from_model """ def __new__(cls, name, bases, dct): import nineml.abstraction as al from nineml.abstraction.dynamics.utils import ( flattener, xml, modifiers) # Extract Parameters Back out from Dict: combined_model = dct['combined_model'] weight_vars = dct['weight_variables'] # Flatten the model: assert isinstance(combined_model, al.ComponentClass) if combined_model.is_flat(): flat_component = combined_model else: flat_component = flattener.flatten(combined_model, name) # Make the substitutions: flat_component.backsub_all() # flat_component.backsub_aliases() # flat_component.backsub_equations() # Close any open reduce ports: modifiers.DynamicPortModifier.close_all_reduce_ports(componentclass=flat_component) # New: dct["combined_model"] = flat_component dct["default_parameters"] = dict((param.name, 1.0) for param in flat_component.parameters) dct["default_initial_values"] = dict((statevar.name, 0.0) for statevar in chain(flat_component.state_variables)) dct["receptor_types"] = list(weight_vars.keys()) dct["standard_receptor_type"] = (dct["receptor_types"] == ('excitatory', 'inhibitory')) # how to determine this? neuron component has a receive analog port with dimension current, that is not connected to a synapse port? dct["injectable"] = False # how to determine this? synapse component has a receive analog port with dimension voltage? dct["conductance_based"] = True dct["model_name"] = name dct["units"] = dict((statevar.name, _default_units[statevar.dimension.name]) for statevar in chain(flat_component.state_variables)) # Recording from bindings: dct["recordable"] = ([port.name for port in flat_component.analog_ports] + ['spikes', 'regime'] + [alias.lhs for alias in flat_component.aliases] + [statevar.name for statevar in flat_component.state_variables]) logger.debug("Creating class '%s' with bases %s and dictionary %s" % (name, bases, dct)) dct["builder"](flat_component, dct["weight_variables"], hierarchical_mode=True) return type.__new__(cls, name, bases, dct) PyNN-0.10.0/pyNN/nineml/connectors.py000066400000000000000000000026341415343567000173260ustar00rootroot00000000000000""" Export of PyNN scripts as NineML. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nineml.user as nineml from pyNN import connectors from .utility import build_parameter_set, catalog_url class ConnectorMixin(object): def to_nineml(self, label): connector_parameters = {} for name in self.__class__.parameter_names: connector_parameters[name] = getattr(self, name) connection_rule = nineml.ConnectionRuleComponent( name="connection rule for projection %s" % label, definition=nineml.Definition(self.definition_url, "connection_generator"), parameters=build_parameter_set(connector_parameters)) return connection_rule class FixedProbabilityConnector(ConnectorMixin, connectors.FixedProbabilityConnector): definition_url = "%s/connectionrules/random_fixed_probability.xml" % catalog_url parameter_names = ('p_connect', 'allow_self_connections') class DistanceDependentProbabilityConnector(ConnectorMixin, connectors.DistanceDependentProbabilityConnector): definition_url = "%s/connectionrules/distance_dependent_probability.xml" % catalog_url parameter_names = ('d_expression', 'allow_self_connections') # space def list_connectors(): return [FixedProbabilityConnector, DistanceDependentProbabilityConnector] PyNN-0.10.0/pyNN/nineml/populations.py000066400000000000000000000111051415343567000175170ustar00rootroot00000000000000""" Export of PyNN scripts as NineML. :copyright: Copyright 2006-2013 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import nineml.user as nineml from pyNN import common from pyNN.standardmodels import StandardCellType from pyNN.parameters import ParameterSpace, simplify from . import simulator from .recording import Recorder from .utility import build_parameter_set, catalog_url class BasePopulation(object): def get_synaptic_response_components(self, synaptic_mechanism_name): return [self.celltype.synaptic_receptor_component_to_nineml(synaptic_mechanism_name, self.label, (self.size,))] def _get_view(self, selector, label=None): return PopulationView(self, selector, label) class Assembly(common.Assembly): _simulator = simulator def __init__(self, label=None, *populations): common.Assembly.__init__(self, label, *populations) self._simulator.state.net.assemblies.append(self) def get_synaptic_response_components(self, synaptic_mechanism_name): components = set([]) for p in self.populations: components.add(p.celltype.synaptic_receptor_component_to_nineml( synaptic_mechanism_name, self.label)) return components def to_nineml(self): group = nineml.Group(self.label) for p in self.populations: group.add(p.to_nineml()) return group class PopulationView(BasePopulation, common.PopulationView): _assembly_class = Assembly _simulator = simulator def __init__(self, parent, selector, label=None): common.PopulationView.__init__(self, parent, selector, label) self._simulator.state.net.populations.append(self) def to_nineml(self): if isinstance(self.mask, slice): ids = "%s:%s:%s" % (self.mask.start or "", self.mask.stop or "", self.mask.step or "") else: ids = str(self.mask.tolist()) selection = nineml.Selection(self.label, nineml.All( nineml.Eq("population[@name]", self.parent.label), nineml.In("population[@id]", ids) ) ) return selection class Population(BasePopulation, common.Population): __doc__ = common.Population.__doc__ _simulator = simulator _recorder_class = Recorder _assembly_class = Assembly def __init__(self, size, cellclass, cellparams=None, structure=None, initial_values={}, label=None): common.Population.__init__(self, size, cellclass, cellparams, structure, initial_values, label) self._simulator.state.net.populations.append(self) def _create_cells(self): id_range = np.arange(simulator.state.id_counter, simulator.state.id_counter + self.size) self.all_cells = np.array([simulator.ID(id) for id in id_range], dtype=simulator.ID) def is_local(id): return (id % simulator.state.num_processes) == simulator.state.mpi_rank self._mask_local = is_local(self.all_cells) if isinstance(self.celltype, StandardCellType): parameter_space = self.celltype.native_parameters else: parameter_space = self.celltype.parameter_space parameter_space.shape = (self.size,) self._parameters = parameter_space for id in self.all_cells: id.parent = self self._simulator.state.id_counter += self.size def _set_initial_value_array(self, variable, initial_values): pass def _set_parameters(self, parameter_space): """parameter_space should contain native parameters""" self._parameters.update(parameter_space) def to_nineml(self): if self.structure: structure = nineml.Structure( name="structure for %s" % self.label, definition=nineml.Definition("%s/networkstructures/%s.xml" % (catalog_url, self.structure.__class__.__name__), "structure"), parameters=build_parameter_set(self.structure.get_parameters()) ) else: structure = None population = nineml.Population( name=self.label, number=len(self), prototype=self.celltype.to_nineml(self.label, (self.size,))[0], positions=nineml.PositionList(structure=structure)) return population PyNN-0.10.0/pyNN/nineml/projections.py000066400000000000000000000037041415343567000175070ustar00rootroot00000000000000# encoding: utf-8 """ Export of PyNN scripts as NineML. :copyright: Copyright 2006-2013 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nineml.user as nineml from pyNN import common from pyNN.parameters import ParameterSpace from pyNN.space import Space from . import simulator from .utility import catalog_url, build_parameter_set class Projection(common.Projection): __doc__ = common.Projection.__doc__ _simulator = simulator def __init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source=None, receptor_type=None, space=Space(), label=None): common.Projection.__init__(self, presynaptic_population, postsynaptic_population, connector, synapse_type, source, receptor_type, space, label) self._simulator.state.net.projections.append(self) def __len__(self): return 0 def to_nineml(self): safe_label = self.label.replace(u"→", "---") connection_rule = self._connector.to_nineml(safe_label) connection_type = nineml.ConnectionType( name="connection type for projection %s" % safe_label, definition=nineml.Definition("%s/connectiontypes/static_synapse.xml" % catalog_url, "dynamics"), parameters=build_parameter_set(self.synapse_type.native_parameters, self.shape)) synaptic_responses = self.post.get_synaptic_response_components(self.receptor_type) synaptic_response, = synaptic_responses projection = nineml.Projection( name=safe_label, source=self.pre.to_nineml(), # or just pass ref, and then resolve later? target=self.post.to_nineml(), rule=connection_rule, synaptic_response=synaptic_response, connection_type=connection_type) return projection PyNN-0.10.0/pyNN/nineml/read.py000066400000000000000000000355561415343567000160750ustar00rootroot00000000000000""" Enables creating neuronal network models in PyNN from a 9ML description. Classes: Network -- container for a network model. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import nineml.user as nineml import nineml.abstraction as al import pyNN.nineml import pyNN.random import pyNN.space from .utility import build_random_distribution def scale(quantity): """Primitive unit handling. Should probably use Piquant, quantities, or similar.""" factors = { 'ms': 1, 'mV': 1, 's': 1000, 'V': 1000, 'Hz': 1, 'nF': 1, 'nA': 1, 'Mohm': 1, # ?? 'unknown': 1, 'unitless': 1, } if quantity.units: return quantity.value * factors[quantity.units.name] else: # dimensionless return quantity.value def resolve_parameters(nineml_component, random_distributions, resolve="properties", qualified_names=True): """ Turn a 9ML ParameterSet or InitialValueSet into a Python dict, including turning 9ML RandomDistribution objects into PyNN RandomDistribution objects. """ P = {} for p in getattr(nineml_component, resolve): if qualified_names: qname = "%s_%s" % (nineml_component.name, p.name) else: qname = p.name if p.is_random(): rd = p.random_distribution if rd.name in random_distributions: P[qname] = random_distributions[rd.name] else: rand_distr = build_random_distribution(rd) P[qname] = rand_distr random_distributions[rd.name] = rand_distr elif p.value in ('True', 'False'): P[qname] = eval(p.value) elif isinstance(p.value, str): P[qname] = p.value else: P[qname] = scale(p) return P def _build_structure(nineml_structure): """ Return a PyNN Structure object that corresponds to the provided 9ML Structure object. For now, we do this by mapping names rather than parsing the 9ML abstraction layer file. """ if nineml_structure: # ideally should parse abstraction layer file # for now we'll just match file names P = nineml_structure.parameters if "Grid2D" in nineml_structure.definition.url: pyNN_structure = pyNN.space.Grid2D( aspect_ratio=P["aspect_ratio"].value, dx=P["dx"].value, dy=P["dy"].value, x0=P["x0"].value, y0=P["y0"].value, fill_order=P["fill_order"].value) elif "Grid3D" in nineml_structure.definition.url: pyNN_structure = pyNN.space.Grid3D( aspect_ratioXY=P["aspect_ratioXY"].value, aspect_ratioXZ=P["aspect_ratioXZ"].value, dx=P["dx"].value, dy=P["dy"].value, dz=P["dz"].value, x0=P["x0"].value, y0=P["y0"].value, z0=P["z0"].value, fill_order=P["fill_order"].value) elif "Line" in nineml_structure.definition.url: pyNN_structure = pyNN.space.Line( dx=P["dx"].value, x0=P["x0"].value, y0=P["y0"].value, z0=P["z0"].value) else: raise Exception("nineml_structure %s not supported by PyNN" % nineml_structure) else: pyNN_structure = None return pyNN_structure def _generate_variable_name(name): return name.replace(" ", "_").replace("-", "") class Network(object): """ Container for a neuronal network model, created from a 9ML user-layer file. There is not a one-to-one mapping between 9ML and PyNN concepts. The two main differences are: (1) a 9ML Group contains both neurons (populations) and connections (projections), whereas a PyNN Assembly contains only neurons: the connections are contained in global Projections. (2) in 9ML, the post-synaptic response is defined in the projection, whereas in PyNN it is a property of the target population. Attributes: assemblies -- a dict containing PyNN Assembly objects projections -- a dict containing PyNN Projection objects """ def __init__(self, sim, nineml_model): """ Instantiate a network from a 9ML file, in the specified simulator. """ global random_distributions self.sim = sim if isinstance(nineml_model, str): self.nineml_model = nineml.Network.read(nineml_model) elif isinstance(nineml_model, nineml.Network): self.nineml_model = nineml_model else: raise TypeError( "nineml_model must be a nineml.Network instance or the path to a NineML XML file.") self.random_distributions = {} self.populations = {} self.assemblies = {} self.projections = {} _tmp = __import__(sim.__name__, globals(), locals(), ["nineml"]) self._nineml_module = _tmp.nineml self._build() def _build(self): # extract post-synaptic response definitions from projections self.psr_map = {} for projection in self.nineml_model.projections.values(): if isinstance(projection.destination, nineml.Selection): target_populations = projection.destination.evaluate() # target_populations = [x[0] for x in projection.destination.evaluate()] # just take the population, not the slice else: assert isinstance(projection.destination, nineml.Population) target_populations = [projection.destination] for target_population in target_populations: if target_population.name in self.psr_map: self.psr_map[target_population.name]['port_connections'].update( projection.port_connections) # hack? what about clashes? self.psr_map[target_population.name]['response_component'] = projection.response else: self.psr_map[target_population.name] = {'port_connections': set(projection.port_connections), 'response_component': projection.response} # create populations for population in self.nineml_model.populations.values(): self._build_population(population) for selection in self.nineml_model.selections.values(): self._evaluate_selection(selection) # create projections for projection in self.nineml_model.projections.values(): self._build_projection(projection) def _generate_cell_type_and_parameters(self, nineml_population): """ """ neuron_model = nineml_population.cell.component_class neuron_namespace = _generate_variable_name(nineml_population.cell.name) synapse_models = {} response_components = {} connections = [] weight_vars = {} if nineml_population.name in self.psr_map: for pc in self.psr_map[nineml_population.name]['port_connections']: if pc._receive_role == 'destination' and pc._send_role == 'response': synapse_name = _generate_variable_name(pc.sender.name) synapse_models[synapse_name] = pc.send_class assert pc.send_class.query.analog_ports_map[pc.send_port].mode == 'send' assert neuron_model.query.analog_ports_map[pc.receive_port].mode in ( 'recv', 'reduce') connections.append(("%s.%s" % (synapse_name, pc.send_port), "%s.%s" % (neuron_namespace, pc.receive_port))) # else: # assert neuron_model.query.analog_ports_map[nrn_port].mode == 'send' # connections.append(("%s.%s" % (neuron_namespace, nrn_port), "%s.%s" % (synapse_name, psr_port))) elif pc._receive_role == 'response' and pc._send_role == 'destination': raise NotImplementedError elif pc._receive_role == 'response' and pc._send_role == 'plasticity': synapse_name = _generate_variable_name(pc.receiver.name) weight_vars[synapse_name] = "%s_%s" % (synapse_name, pc.receive_port) else: raise Exception("Unexpected") response_components[synapse_name] = self.psr_map[nineml_population.name]['response_component'] subnodes = {neuron_namespace: neuron_model} subnodes.update(synapse_models) combined_model = al.Dynamics(name=_generate_variable_name(nineml_population.name), subnodes=subnodes) # now connect ports for connection in connections: combined_model.connect_ports(*connection) celltype_cls = self._nineml_module.nineml_cell_type( combined_model.name, combined_model, weight_vars) cell_params = resolve_parameters(nineml_population.cell, self.random_distributions) for response_component in response_components.values(): cell_params.update(resolve_parameters(response_component, self.random_distributions)) return celltype_cls, cell_params def _build_population(self, nineml_population): # assert isinstance(nineml_population.cell, nineml.SpikingNodeType) # to implement in NineML library n = nineml_population.size if nineml_population.positions is not None: pyNN_structure = _build_structure(nineml_population.positions.structure) else: pyNN_structure = None # TODO: handle explicit list of positions cell_class, cell_params = self._generate_cell_type_and_parameters(nineml_population) p_obj = self.sim.Population(n, cell_class, cell_params, structure=pyNN_structure, initial_values=resolve_parameters(nineml_population.cell, self.random_distributions, resolve="initial_values"), label=nineml_population.name) self.populations[p_obj.label] = p_obj def _evaluate_selection(self, nineml_selection): new_assembly = self.sim.Assembly(label=nineml_selection.name) for population in nineml_selection.evaluate(): new_assembly += self.populations[population.name] # for population, selector in nineml_selection.populations: # parent = self.populations['population.name'] # if selector is not None: # view = eval("parent[%s]" % selector) # view.label = nineml_selection.name # new_assembly += view # else: # new_assembly += parent self.assemblies[nineml_selection.name] = new_assembly def _build_connector(self, nineml_projection): connector_params = resolve_parameters( nineml_projection.connectivity, self.random_distributions, qualified_names=False) translations = {'number': ('n', int)} # todo: complete for all standard connectors translated_params = {} for name, value in connector_params.items(): tname, f = translations[name] translated_params[tname] = f(value) builtin_connectors = { 'RandomFanIn': self.sim.FixedNumberPreConnector, 'RandomFanOut': self.sim.FixedNumberPostConnector, 'AllToAll': self.sim.AllToAllConnector, 'OneToOne': self.sim.OneToOneConnector } connector_type = builtin_connectors[nineml_projection.connectivity.component_class.name] connector = connector_type(**translated_params) return connector #inline_csa = nineml_projection.rule.definition.component._connection_rule[0] # cset = inline_csa(*connector_params.values()).cset # TODO: csa should handle named parameters; handle random params # return self.sim.CSAConnector(cset) def _build_synapse_dynamics(self, nineml_projection): # for now, just use static synapse # HACK - only works if there is a single parameter called "weight" ### to be sorted out when we try some real plastic synapses ### parameters = resolve_parameters( nineml_projection.plasticity, self.random_distributions, "properties", qualified_names=False) parameters.update(resolve_parameters(nineml_projection.plasticity, self.random_distributions, "initial_values", qualified_names=False)) parameters["delay"] = nineml_projection.delay.value return self.sim.StaticSynapse(**parameters) def _build_projection(self, nineml_projection): populations = {} for p in self.populations.values(): populations[p.label] = p for a in self.assemblies.values(): populations[a.label] = a connector = self._build_connector(nineml_projection) receptor_type = nineml_projection.response.name try: assert receptor_type in populations[nineml_projection.destination.name].receptor_types except: raise synapse_dynamics = self._build_synapse_dynamics(nineml_projection) prj_obj = self.sim.Projection(populations[nineml_projection.source.name], populations[nineml_projection.destination.name], connector, receptor_type=receptor_type, synapse_type=synapse_dynamics, label=nineml_projection.name) # need to add assembly label to make the name unique self.projections[prj_obj.label] = prj_obj def describe(self): description = "Network model generated from a 9ML description, consisting of:\n " description += "\n ".join(a.describe() for a in self.assemblies.values()) + "\n" description += "\n ".join(prj.describe() for prj in self.projections.values()) return description if __name__ == "__main__": # For testing purposes: read in the network and print its description # if using the nineml or neuroml backend, re-export the network as XML (this doesn't work, but it should). import sys import os from pyNN.utility import get_script_args nineml_file, simulator_name = get_script_args( 2, "Please specify the 9ML file and the simulator backend.") exec("import pyNN.%s as sim" % simulator_name) sim.setup(filename="%s_export.xml" % os.path.splitext(nineml_file)[0]) network = Network(sim, nineml_file) print(network.describe()) sim.end() PyNN-0.10.0/pyNN/nineml/recording.py000066400000000000000000000012321415343567000171160ustar00rootroot00000000000000""" Export of PyNN scripts as NineML. :copyright: Copyright 2006-2013 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np from pyNN import recording from . import simulator class Recorder(recording.Recorder): _simulator = simulator def _record(self, variable, new_ids): pass def get(self, variables, gather=False, filter_ids=None, clear=False, annotations=None): pass def write(self, variables, file=None, gather=False, filter_ids=None, clear=False, annotations=None): pass def _local_count(self, variable, filter_ids=None): return {} PyNN-0.10.0/pyNN/nineml/simulator.py000066400000000000000000000020001415343567000171530ustar00rootroot00000000000000""" Export of PyNN scripts as NineML. :copyright: Copyright 2006-2013 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import common name = "NineML" class ID(int, common.IDMixin): def __init__(self, n): """Create an ID object with numerical value `n`.""" int.__init__(n) common.IDMixin.__init__(self) class State(common.control.BaseState): def __init__(self): common.control.BaseState.__init__(self) self.mpi_rank = 0 self.num_processes = 1 self.clear() self.dt = 0.1 def run(self, simtime): self.t += simtime self.running = True def clear(self): self.recorders = set([]) self.id_counter = 0 self.segment_counter = -1 self.reset() def reset(self): """Reset the state of the current network to time t = 0.""" self.running = False self.t = 0 self.t_start = 0 self.segment_counter += 1 state = State() PyNN-0.10.0/pyNN/nineml/standardmodels.py000066400000000000000000000355331415343567000201610ustar00rootroot00000000000000# encoding: utf-8 """ Standard cells for the nineml module. :copyright: Copyright 2006-2013 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import nineml.user as nineml import nineml.abstraction as al from pyNN.standardmodels import cells, synapses, electrodes, build_translations, StandardCurrentSource from .simulator import state from .utility import (build_parameter_set, catalog_url, map_random_distribution_parameters) logger = logging.getLogger("PyNN") class CellTypeMixin(object): @property def spiking_component_parameters(self): smp = self.native_parameters for name in smp.keys(): if name not in self.__class__.spiking_component_parameter_names: smp.pop(name) return smp @property def synaptic_receptor_parameters(self): smp = {} for receptor_type in self.__class__.receptor_types: smp[receptor_type] = {} for name in self.__class__.synaptic_receptor_parameter_names[receptor_type]: smp[receptor_type][name.split(".")[1]] = self.native_parameters[name] return smp def to_nineml(self, label, shape): inline_definition = False # todo: get inline definitions to work components = [self.spiking_component_to_nineml(label, shape, inline_definition)] + \ [self.synaptic_receptor_component_to_nineml( st, label, shape, inline_definition) for st in self.receptor_types] return components def spiking_component_to_nineml(self, label, shape, inline_definition=False): """ Return a 9ML user layer Component describing the neuron type. (mathematical model + parameterization). """ if inline_definition: definition = self.spiking_component_type_to_nineml() else: definition = self.spiking_component_definition_url return nineml.SpikingNodeType( name="neuron type for population %s" % label, definition=nineml.Definition(definition, "dynamics"), parameters=build_parameter_set(self.spiking_component_parameters, shape)) def synaptic_receptor_component_to_nineml(self, receptor_type, label, shape, inline_definition=False): """ Return a 9ML user layer Component describing the post-synaptic mechanism (mathematical model + parameterization). Note that we use the name "receptor" as a shorthand for "receptor, ion channel and any associated signalling mechanisms". """ if inline_definition: definition = self.synaptic_receptor_component_type_to_nineml(receptor_type) else: definition = self.synaptic_receptor_component_definition_urls[receptor_type] return nineml.SynapseType( name="%s post-synaptic response for %s" % (receptor_type, label), definition=nineml.Definition(definition, "dynamics"), parameters=build_parameter_set(self.synaptic_receptor_parameters[receptor_type], shape)) # class IF_curr_exp(cells.IF_curr_exp, CellTypeMixin): # # __doc__ = cells.IF_curr_exp.__doc__ # # translations = build_translations( # ('tau_m', 'membraneTimeConstant'), # ('cm', 'membraneCapacitance'), # ('v_rest', 'restingPotential'), # ('v_thresh', 'threshold'), # ('v_reset', 'resetPotential'), # ('tau_refrac', 'refractoryTime'), # ('i_offset', 'offsetCurrent'), # ('tau_syn_E', 'excitatory_decayTimeConstant'), # ('tau_syn_I', 'inhibitory_decayTimeConstant'), # ) # spiking_component_definition_url = "%s/neurons/IaF_tau.xml" % catalog_url # synaptic_component_definition_urls = { # 'excitatory': "%s/postsynapticresponses/exp_i.xml" % catalog_url, # 'inhibitory': "%s/postsynapticresponses/exp_i.xml" % catalog_url # } # spiking_component_parameter_names = ('membraneTimeConstant','membraneCapacitance', # 'restingPotential', 'threshold', # 'resetPotential', 'refractoryTime') # synaptic_component_parameter_names = { # 'excitatory': ['excitatory_decayTimeConstant',], # 'inhibitory': ['inhibitory_decayTimeConstant',] # } class IF_cond_exp(cells.IF_cond_exp, CellTypeMixin): __doc__ = cells.IF_cond_exp.__doc__ translations = build_translations( ('tau_m', 'tau_m'), ('cm', 'cm'), ('v_rest', 'v_rest'), ('v_thresh', 'v_thresh'), ('v_reset', 'v_reset'), ('tau_refrac', 'tau_refrac'), ('i_offset', 'i_offset'), ('tau_syn_E', 'excitatory.tau_syn'), ('tau_syn_I', 'inhibitory.tau_syn'), ('e_rev_E', 'excitatory.e_rev'), ('e_rev_I', 'inhibitory.e_rev') ) spiking_component_definition_url = "%s/neurons/iaf_tau.xml" % catalog_url synaptic_receptor_component_definition_urls = { 'excitatory': "%s/postsynapticresponses/cond_exp_syn.xml" % catalog_url, 'inhibitory': "%s/postsynapticresponses/cond_exp_syn.xml" % catalog_url } spiking_component_parameter_names = ('tau_m', 'cm', 'v_rest', 'v_thresh', 'v_reset', 'i_offset', 'tau_refrac') synaptic_receptor_parameter_names = { 'excitatory': ['excitatory.tau_syn', 'excitatory.e_rev'], 'inhibitory': ['inhibitory.tau_syn', 'inhibitory.e_rev'] } @classmethod def spiking_component_type_to_nineml(cls): """Return a 9ML ComponentClass describing the neuron model.""" iaf = al.ComponentClass( name="iaf_tau", regimes=[ al.Regime( name="subthreshold_regime", time_derivatives=["dv/dt = (v_rest - v)/tau_m + (i_offset + i_syn)/cm"], transitions=al.On("v > v_thresh", do=["t_spike = t", "v = v_reset", al.OutputEvent('spike_output')], to="refractory_regime"), ), al.Regime( name="refractory_regime", time_derivatives=["dv/dt = 0"], transitions=[al.On("t >= t_spike + tau_refrac", to="subthreshold_regime")], ) ], state_variables=[ # , dimension='[V]' # '[V]' should be an alias for [M][L]^2[T]^-3[I]^-1 al.StateVariable('v'), al.StateVariable('t_spike'), # , dimension='[T]' ], analog_ports=[al.AnalogSendPort("v"), al.AnalogReducePort("i_syn", reduce_op="+"), ], event_ports=[al.EventSendPort('spike_output'), ], parameters=['cm', 'tau_refrac', 'tau_m', 'v_reset', 'v_rest', 'v_thresh', 'i_offset'] # add dimensions, or infer them from dimensions of variables # in fact, we should be able to infer what are the parameters, without listing them ) return iaf @classmethod def synaptic_receptor_component_type_to_nineml(cls, synapse_type): """Return a 9ML ComponentClass describing the synaptic receptor model.""" coba = al.ComponentClass( name="cond_exp_syn", aliases=["i_syn:=g_syn*(e_rev-v)", ], regimes=[ al.Regime( name="coba_default_regime", time_derivatives=["dg_syn/dt = -g_syn/tau_syn", ], transitions=al.On('spike_input', do=["g_syn=g_syn+q"]), ) ], # , dimension='[G]' # alias [M]^-1[L]^-2[T]^3[I]^2 state_variables=[al.StateVariable('g_syn')], analog_ports=[al.AnalogReceivePort("v"), al.AnalogSendPort( "i_syn"), al.AnalogReceivePort('q')], parameters=['tau_syn', 'e_rev'] ) return coba # iaf_2coba_model = al.ComponentClass( # name="IF_cond_exp", # subnodes={"cell": iaf, # "excitatory": coba, # "inhibitory": coba}) #iaf_2coba_model.connect_ports("cell.v", "excitatory.v") #iaf_2coba_model.connect_ports("cell.v", "inhibitory.v") #iaf_2coba_model.connect_ports("excitatory.i_syn", "cell.i_syn") #iaf_2coba_model.connect_ports("excitatory.i_syn", "cell.i_syn") # # return iaf_2coba_model # class IF_cond_alpha(cells.IF_cond_exp, CellTypeMixin): # # __doc__ = cells.IF_cond_alpha.__doc__ # # translations = build_translations( # ('tau_m', 'membraneTimeConstant'), # ('cm', 'membraneCapacitance'), # ('v_rest', 'restingPotential'), # ('v_thresh', 'threshold'), # ('v_reset', 'resetPotential'), # ('tau_refrac', 'refractoryTime'), # ('i_offset', 'offsetCurrent'), # ('tau_syn_E', 'excitatory_timeConstant'), # ('tau_syn_I', 'inhibitory_timeConstant'), # ('e_rev_E', 'excitatory_reversalPotential'), # ('e_rev_I', 'inhibitory_reversalPotential') # ) # spiking_component_definition_url = "%s/neurons/IaF_tau.xml" % catalog_url # synaptic_component_definition_urls = { # 'excitatory': "%s/postsynapticresponses/alpha_g.xml" % catalog_url, # 'inhibitory': "%s/postsynapticresponses/alpha_g.xml" % catalog_url # } # spiking_component_parameter_names = ('membraneTimeConstant','membraneCapacitance', # 'restingPotential', 'threshold', # 'resetPotential', 'refractoryTime') # synaptic_component_parameter_names = { # 'excitatory': ['excitatory_timeConstant', 'excitatory_reversalPotential'], # 'inhibitory': ['inhibitory_timeConstant', 'inhibitory_reversalPotential'] # } class SpikeSourcePoisson(cells.SpikeSourcePoisson, CellTypeMixin): __doc__ = cells.SpikeSourcePoisson.__doc__ translations = build_translations( ('start', 'start'), ('rate', 'rate'), ('duration', 'duration'), ) spiking_component_definition_url = "%s/neurons/poisson_spike_source.xml" % catalog_url spiking_component_parameter_names = ("onset", "frequency", "duration") @classmethod def spiking_component_type_to_nineml(cls): """Return a 9ML ComponentClass describing the spike source model.""" source = al.ComponentClass( name="poisson_spike_source", regimes=[ al.Regime( name="before", transitions=[al.On("t > start", do=["t_spike = -1"], to="on")]), al.Regime( name="on", transitions=[al.On("t >= t_spike", do=["t_spike = t_spike + random.exponential(rate)", al.OutputEvent('spike_output')]), al.On("t >= start + duration", to="after")], ), al.Regime(name="after") ], state_variables=[ al.StateVariable('t_spike'), # , dimension='[T]' ], event_ports=[al.EventSendPort('spike_output'), ], # add dimensions, or infer them from dimensions of variables parameters=['start', 'rate', 'duration'], ) return source class SpikeSourceArray(cells.SpikeSourceArray, CellTypeMixin): __doc__ = cells.SpikeSourceArray.__doc__ translations = build_translations( ('spike_times', 'spike_times'), ) spiking_component_definition_url = "%s/neurons/spike_source_array.xml" % catalog_url spiking_component_parameter_names = ("spike_times",) @classmethod def spiking_component_type_to_nineml(cls): """Return a 9ML ComponentClass describing the spike source model.""" source = al.ComponentClass( name="spike_source_array", regimes=[ al.Regime( name="on", transitions=[al.On("t >= spike_times[i]", # this is currently illegal do=["i = i + 1", al.OutputEvent('spike_output')])], ), ], state_variables=[ al.StateVariable('t_spike'), # , dimension='[T]' al.StateVariable('i'), # , dimension='[T]' ], event_ports=[al.EventSendPort('spike_output'), ], # add dimensions, or infer them from dimensions of variables parameters=['start', 'rate', 'duration'], ) return source class SynapseTypeMixin(object): counter = 0 def to_nineml(self): return nineml.ConnectionType( name="synapse type %d" % self.__class__.counter, definition=nineml.Definition("%s/connectiontypes/%s" % (catalog_url, self.definition_file), "dynamics"), parameters=build_parameter_set(self.parameters)) class StaticSynapse(SynapseTypeMixin, synapses.StaticSynapse): __doc__ = synapses.StaticSynapse.__doc__ definition_url = "%s/connectiontypes/static_connection.xml" % catalog_url translations = build_translations( ('weight', 'weight'), ('delay', 'delay'), ) def _get_minimum_delay(self): return state.min_delay class CurrentSourceMixin(object): """Base class for a source of current to be injected into a neuron.""" counter = 0 def __init__(self): state.net.current_sources.append(self) self.__class__.counter += 1 self.cell_list = [] def inject_into(self, cell_list): """Inject this current source into some cells.""" self.cell_list.extend(cell_list) def to_nineml(self): return nineml.CurrentSourceType( name="current source %d" % self.__class__.counter, definition=nineml.Definition("%s/currentsources/%s" % (catalog_url, self.definition_file), "dynamics"), parameters=build_parameter_set(self.parameters)) class DCSource(CurrentSourceMixin, electrodes.DCSource): __doc__ = electrodes.DCSource.__doc__ translations = build_translations( ('amplitude', 'amplitude'), ('start', 'start'), ('stop', 'stop') ) class StepCurrentSource(CurrentSourceMixin, electrodes.StepCurrentSource): __doc__ = electrodes.StepCurrentSource.__doc__ translations = build_translations( ('amplitudes', 'amplitudes'), ('times', 'times') ) PyNN-0.10.0/pyNN/nineml/synapses.py000066400000000000000000000004131415343567000170070ustar00rootroot00000000000000""" :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from utility import catalog_url class StaticSynapticConnection(object): definition_url = "%s/connectiontypes/static_connection.xml" % catalog_url PyNN-0.10.0/pyNN/nineml/utility.py000066400000000000000000000105311415343567000166470ustar00rootroot00000000000000# encoding: utf-8 """ :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from lazyarray import larray from pyNN import random from pyNN.parameters import Sequence import nineml.user as nineml #catalog_url = "http://svn.incf.org/svn/nineml/catalog" #catalog_url = join(dirname(__file__), "catalog") catalog_url = "catalog" units_map = { # arguably we should do the units mapping with the PyNN names, i.e. before translation. "time": "ms", "potential": "mV", "threshold": "mV", "capacitance": "nF", "frequency": "Hz", "duration": "ms", "onset": "ms", "amplitude": "nA", # dodgy. Probably better to include units with class definitions "weight": "dimensionless", "delay": "ms", "dx": u"µm", "dy": u"µm", "dz": u"µm", "x0": u"µm", "y0": u"µm", "z0": u"µm", "aspectratio": "dimensionless", } def reverse_map(D): """ Return a dict having D.values() as its keys and D.keys() as its values. """ E = {} for k, v in D.items(): if v in E: raise KeyError( "Cannot reverse this mapping, as it is not one-to-one ('%s' would map to both '%s' and '%s')" % (v, E[v], k)) E[v] = k return E def infer_units(parameter_name): unit = "unknown" for fragment, u in units_map.items(): if fragment in parameter_name.lower(): unit = u break return unit random_distribution_url_map = { 'uniform': 'http://www.uncertml.org/distributions/uniform', 'normal': 'http://www.uncertml.org/distributions/normal', 'exponential': 'http://www.uncertml.org/distributions/exponential', } random_distribution_parameter_map = { 'normal': ('mean', 'variance'), 'uniform': ('minimum', 'maximum'), 'exponential': ('rate',), # need to translate - see hack below } def build_random_distribution(random_distribution_component): """ Given a NineML random distribution component, return a PyNN RandomDistribution object. """ rd = random_distribution_component rd_name = reverse_map(random_distribution_url_map)[rd.standard_library] rd_param_names = random_distribution_parameter_map[rd_name] rd_params = [rd.property(rdp_name).value for rdp_name in rd_param_names] if rd_name == 'exponential': # temporary hack - need to implement a proper translation mechanism # UncertML uses rate in Hz, PyNN uses beta (1/rate) in ms rd_params[0] = 1000.0 / rd_params[0] rand_distr = random.RandomDistribution(rd_name, rd_params) return rand_distr def build_parameter_set(parameters, shape=None, dimensionless=False): parameter_list = [] for name, value in parameters.items(): if isinstance(value, larray): value.shape = shape if value.is_homogeneous: value = value.evaluate(simplify=True) if isinstance(value, Sequence): value = value.value.tolist() elif isinstance(value, bool): value = int(value) elif isinstance(value, random.RandomDistribution): rand_distr = value value = nineml.RandomDistributionComponent( name="%s(%s)" % (rand_distr.name, ",".join(str(p) for p in rand_distr.parameters)), definition=nineml.Definition(random_distribution_url_map[rand_distr.name], "random"), parameters=build_parameter_set(map_random_distribution_parameters(rand_distr.name, rand_distr.parameters), dimensionless=True)) else: raise Exception("not supported") else: if isinstance(value, bool): value = int(value) if dimensionless: unit = "dimensionless" elif isinstance(value, str): unit = None else: unit = infer_units(name) parameter_list.append(nineml.Property(name, value, unit)) return nineml.PropertySet(*parameter_list) def map_random_distribution_parameters(name, parameters): parameter_map = random_distribution_parameter_map P = {} for name, val in zip(parameter_map[name], parameters): P[name] = val return P PyNN-0.10.0/pyNN/parameters.py000066400000000000000000000454111415343567000160320ustar00rootroot00000000000000""" Parameter set handling :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import collections from pyNN.core import is_listlike from pyNN import errors from pyNN.random import RandomDistribution, NativeRNG from lazyarray import larray, partial_shape import numpy as np class LazyArray(larray): """ Optimises storage of arrays in various ways: - stores only a single value if all the values in the array are the same - if the array is created from a :class:`~pyNN.random.RandomDistribution` or a function `f(i,j)`, then elements are only evaluated when they are accessed. Any operations performed on the array are also queued up to be executed on access. The main intention of the latter is to save memory for very large arrays by accessing them one row or column at a time: the entire array need never be in memory. Arguments: `value`: may be an int, float, bool, NumPy array, iterator, generator or a function, `f(i)` or `f(i,j)`, depending on the dimensions of the array. `f(i,j)` should return a single number when `i` and `j` are integers, and a 1D array when either `i` or `j` or both is a NumPy array (in the latter case the two arrays must have equal lengths). `shape`: a tuple giving the shape of the array, or `None` `dtype`: the NumPy `dtype`. """ # most of the implementation moved to external lazyarray package # the plan is ultimately to move everything to lazyarray def __init__(self, value, shape=None, dtype=None): if isinstance(value, str): errmsg = "Value should be a string expressing a function of d. " try: value = eval("lambda d: %s" % value) except SyntaxError: raise errors.InvalidParameterValueError(errmsg + "Incorrect syntax.") try: value(0.0) except NameError as err: raise errors.InvalidParameterValueError(errmsg + str(err)) super(LazyArray, self).__init__(value, shape, dtype) def __setitem__(self, addr, new_value): self.check_bounds(addr) if (self.is_homogeneous and isinstance(new_value, (int, float, bool)) and self.evaluate(simplify=True) == new_value): pass else: self.base_value = self.evaluate() self.base_value[addr] = new_value self.operations = [] def by_column(self, mask=None): """ Iterate over the columns of the array. Columns will be yielded either as a 1D array or as a single value (for a flat array). `mask`: either `None` or a boolean array indicating which columns should be included. """ column_indices = np.arange(self.ncols) if mask is not None: if not isinstance(mask, slice): assert len(mask) == self.ncols column_indices = column_indices[mask] if isinstance(self.base_value, RandomDistribution) and self.base_value.rng.parallel_safe: if mask is None: for j in column_indices: yield self._partially_evaluate((slice(None), j), simplify=True) else: column_indices = np.arange(self.ncols) for j, local in zip(column_indices, mask): col = self._partially_evaluate((slice(None), j), simplify=True) if local: yield col else: for j in column_indices: yield self._partially_evaluate((slice(None), j), simplify=True) class ArrayParameter(object): """ Represents a parameter whose value consists of multiple values, e.g. a tuple or array. The reason for defining this class rather than just using a NumPy array is to avoid the ambiguity of "is a given array a single parameter value (e.g. a spike train for one cell) or an array of parameter values (e.g. one number per cell)?". Arguments: `value`: anything which can be converted to a NumPy array, or another :class:`ArrayParameter` object. """ def __init__(self, value): if isinstance(value, ArrayParameter): self.value = value.value elif isinstance(value, np.ndarray): # dont overwrite dtype of int arrays self.value = value else: self.value = np.array(value, float) # def __len__(self): # This must not be defined, otherwise ArrayParameter is insufficiently different from NumPy array def max(self): """Return the maximum value.""" return self.value.max() def __add__(self, val): """ Return a new :class:`ArrayParameter` in which all values in the original :class:`ArrayParameter` have `val` added to them. If `val` is itself an array, return an array of :class:`ArrayParameter` objects, where ArrayParameter `i` is the original ArrayParameter added to element `i` of val. """ if hasattr(val, '__len__'): # reshape if necessary? return np.array([self.__class__(self.value + x) for x in val], dtype=self.__class__) else: return self.__class__(self.value + val) def __sub__(self, val): """ Return a new :class:`ArrayParameter` in which all values in the original :class:`ArrayParameter` have `val` subtracted from them. If `val` is itself an array, return an array of :class:`ArrayParameter` objects, where ArrayParameter `i` is the original ArrayParameter with element `i` of val subtracted from it. """ if hasattr(val, '__len__'): # reshape if necessary? return np.array([self.__class__(self.value - x) for x in val], dtype=self.__class__) else: return self.__class__(self.value - val) def __mul__(self, val): """ Return a new :class:`ArrayParameter` in which all values in the original :class:`ArrayParameter` have been multiplied by `val`. If `val` is itself an array, return an array of :class:`ArrayParameter` objects, where ArrayParameter `i` is the original ArrayParameter multiplied by element `i` of `val`. """ if hasattr(val, '__len__'): # reshape if necessary? return np.array([self.__class__(self.value * x) for x in val], dtype=self.__class__) else: return self.__class__(self.value * val) __rmul__ = __mul__ def __div__(self, val): """ Return a new :class:`ArrayParameter` in which all values in the original :class:`ArrayParameter` have been divided by `val`. If `val` is itself an array, return an array of :class:`ArrayParameter` objects, where ArrayParameter `i` is the original ArrayParameter divided by element `i` of `val`. """ if hasattr(val, '__len__'): # reshape if necessary? return np.array([self.__class__(self.value / x) for x in val], dtype=self.__class__) else: return self.__class__(self.value / val) __truediv__ = __div__ def __eq__(self, other): if isinstance(other, ArrayParameter): return self.value.size == other.value.size and (self.value == other.value).all() elif isinstance(other, np.ndarray) and other.size > 0 and isinstance(other[0], ArrayParameter): return np.array([(self == seq).all() for seq in other]) else: return False def __repr__(self): return "%s(%s)" % (self.__class__.__name__, self.value) class Sequence(ArrayParameter): """ Represents a sequence of numerical values. Arguments: `value`: anything which can be converted to a NumPy array, or another :class:`Sequence` object. """ # should perhaps use neo.SpikeTrain instead of this class, or at least allow a neo SpikeTrain pass class ParameterSpace(object): """ Representation of one or more points in a parameter space. i.e. represents one or more parameter sets, where each parameter set has the same parameter names and types but the parameters may have different values. Arguments: `parameters`: a dict containing values of any type that may be used to construct a `lazy array`_, i.e. `int`, `float`, NumPy array, :class:`~pyNN.random.RandomDistribution`, function that accepts a single argument. `schema`: a dict whose keys are the expected parameter names and whose values are the expected parameter types `component`: optional - class for which the parameters are destined. Used in error messages. `shape`: the shape of the lazy arrays that will be constructed. .. _`lazy array`: https://lazyarray.readthedocs.org/ """ def __init__(self, parameters, schema=None, shape=None, component=None): """ """ self._parameters = {} self.schema = schema self._shape = shape self.component = component self.update(**parameters) self._evaluated = False def _set_shape(self, shape): for value in self._parameters.values(): value.shape = shape self._shape = shape shape = property(fget=lambda self: self._shape, fset=_set_shape, doc="Size of the lazy arrays contained within the parameter space") def keys(self): """ PS.keys() -> list of PS's keys. """ return self._parameters.keys() def items(self): """ PS.items() -> an iterator over the (key, value) items of PS. Note that the values will all be :class:`LazyArray` objects. """ return self._parameters.items() def __repr__(self): return "" % (", ".join(self.keys()), self.shape) def update(self, **parameters): """ Update the contents of the parameter space according to the `(key, value)` pairs in ``**parameters``. All values will be turned into lazy arrays. If the :class:`ParameterSpace` has a schema, the keys and the data types of the values will be checked against the schema. """ if self.schema: for name, value in parameters.items(): try: expected_dtype = self.schema[name] except KeyError: if self.component: model_name = self.component.__name__ else: model_name = 'unknown' raise errors.NonExistentParameterError(name, model_name, valid_parameter_names=self.schema.keys()) if issubclass(expected_dtype, ArrayParameter) and isinstance(value, collections.Sized): if len(value) == 0: value = ArrayParameter([]) elif not isinstance(value[0], ArrayParameter): # may be a more generic way to do it, but for now this special-casing seems like the most robust approach if isinstance(value[0], collections.Sized): # e.g. list of tuples value = type(value)([ArrayParameter(x) for x in value]) else: value = ArrayParameter(value) try: self._parameters[name] = LazyArray(value, shape=self._shape, dtype=expected_dtype) except (TypeError, errors.InvalidParameterValueError): raise errors.InvalidParameterValueError( "For parameter %s expected %s, got %s" % (name, expected_dtype, type(value))) except ValueError as err: # maybe put the more specific error classes into lazyarray raise errors.InvalidDimensionsError(err) else: for name, value in parameters.items(): self._parameters[name] = LazyArray(value, shape=self._shape) def __getitem__(self, name): """x.__getitem__(y) <==> x[y]""" return self._parameters[name] def __setitem__(self, name, value): # need to add check against schema self._parameters[name] = value def pop(self, name, d=None): """ Remove the given parameter from the parameter set and from its schema, and return its value. """ value = self._parameters.pop(name, d) if self.schema: self.schema.pop(name, d) return value @property def is_homogeneous(self): """ True if all of the lazy arrays within are homogeneous. """ return all(value.is_homogeneous for value in self._parameters.values()) def evaluate(self, mask=None, simplify=False): """ Evaluate all lazy arrays contained in the parameter space, using the given mask. """ if self._shape is None: raise Exception("Must set shape of parameter space before evaluating") if mask is None: for name, value in self._parameters.items(): self._parameters[name] = value.evaluate(simplify=simplify) self._evaluated_shape = self._shape else: for name, value in self._parameters.items(): try: if isinstance(value.base_value, RandomDistribution) and value.base_value.rng.parallel_safe: value = value.evaluate() # can't partially evaluate if using parallel safe self._parameters[name] = value[mask] except ValueError: raise errors.InvalidParameterValueError(f"{name} should not be of type {type(value)}") self._evaluated_shape = partial_shape(mask, self._shape) self._evaluated = True # should possibly update self.shape according to mask? def as_dict(self): """ Return a plain dict containing the same keys and values as the parameter space. The values must first have been evaluated. """ if not self._evaluated: raise Exception("Must call evaluate() method before calling ParameterSpace.as_dict()") D = {} for name, value in self._parameters.items(): D[name] = value assert not isinstance(D[name], LazyArray) # should all have been evaluated by now return D def __iter__(self): r""" Return an array-element-wise iterator over the parameter space. Each item in the iterator is a dict, containing the same keys as the :class:`ParameterSpace`. For the `i`\th dict returned by the iterator, each value is the `i`\th element of the corresponding lazy array in the parameter space. Example: >>> ps = ParameterSpace({'a': [2, 3, 5, 8], 'b': 7, 'c': lambda i: 3*i+2}, shape=(4,)) >>> ps.evaluate() >>> for D in ps: ... print(D) ... {'a': 2, 'c': 2, 'b': 7} {'a': 3, 'c': 5, 'b': 7} {'a': 5, 'c': 8, 'b': 7} {'a': 8, 'c': 11, 'b': 7} """ if not self._evaluated: raise Exception("Must call evaluate() method before iterating over a ParameterSpace") for i in range(self._evaluated_shape[0]): D = {} for name, value in self._parameters.items(): if is_listlike(value): D[name] = value[i] else: D[name] = value assert not isinstance(D[name], LazyArray) # should all have been evaluated by now yield D def columns(self): """ For a 2D space, return a column-wise iterator over the parameter space. """ if not self._evaluated: raise Exception("Must call evaluate() method before iterating over a ParameterSpace") assert len(self.shape) == 2 if len(self._evaluated_shape) == 1: # values will be one-dimensional yield self._parameters else: for j in range(self._evaluated_shape[1]): D = {} for name, value in self._parameters.items(): if is_listlike(value): D[name] = value[:, j] else: D[name] = value # should all have been evaluated by now assert not isinstance(D[name], LazyArray) yield D def __eq__(self, other): return (all(a == b for a, b in zip(self._parameters.items(), other._parameters.items())) and self.schema == other.schema and self._shape == other._shape) @property def parallel_safe(self): return any(isinstance(value.base_value, RandomDistribution) and value.base_value.rng.parallel_safe for value in self._parameters.values()) @property def has_native_rngs(self): """ Return True if the parameter set contains any NativeRNGs """ return any(isinstance(rd.base_value.rng, NativeRNG) for rd in self._random_distributions()) def _random_distributions(self): """ An iterator over those values contained in the PS that are derived from random distributions. """ return (value for value in self._parameters.values() if isinstance(value.base_value, RandomDistribution)) def expand(self, new_shape, mask): """ Increase the size of the ParameterSpace. Existing array values are mapped to the indices given in mask. New array values are set to NaN. """ for name, value in self._parameters.items(): if isinstance(value.base_value, np.ndarray): new_base_value = np.ones(new_shape) * np.nan new_base_value[mask] = value.base_value self._parameters[name].base_value = new_base_value self.shape = new_shape def simplify(value): """ If `value` is a homogeneous array, return the single value that all elements share. Otherwise, pass the value through. """ if isinstance(value, np.ndarray) and len(value.shape) > 0: # latter condition is for Brian scalar quantities if (value == value[0]).all(): return value[0] else: return value else: return value # alternative - need to benchmark # if np.any(arr != arr[0]): # return arr # else: # return arr[0] PyNN-0.10.0/pyNN/random.py000066400000000000000000000444461415343567000151560ustar00rootroot00000000000000""" Provides wrappers for several random number generators (RNGs), giving them all a common interface so that they can be used interchangeably in PyNN. Classes: NumpyRNG - uses the np.random.RandomState RNG GSLRNG - uses the RNGs from the Gnu Scientific Library NativeRNG - indicates to the simulator that it should use it's own, built-in RNG RandomDistribution - produces random numbers from a specific distribution :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from copy import deepcopy import logging from functools import reduce import operator import time import numpy as np try: import pygsl.rng have_gsl = True except (ImportError, Warning): have_gsl = False from lazyarray import partial_shape logger = logging.getLogger("PyNN") available_distributions = { 'binomial': ('n', 'p'), 'gamma': ('k', 'theta'), 'exponential': ('beta',), 'lognormal': ('mu', 'sigma'), 'normal': ('mu', 'sigma'), 'normal_clipped': ('mu', 'sigma', 'low', 'high'), 'normal_clipped_to_boundary': ('mu', 'sigma', 'low', 'high'), 'poisson': ('lambda_',), 'uniform': ('low', 'high'), 'uniform_int': ('low', 'high'), 'vonmises': ('mu', 'kappa'), } MAX_REDRAWS = 1000 # for clipped distributions def get_mpi_config(): try: from mpi4py import MPI mpi_rank = MPI.COMM_WORLD.rank num_processes = MPI.COMM_WORLD.size except ImportError: mpi_rank = 0 num_processes = 1 return mpi_rank, num_processes class AbstractRNG(object): """Abstract class for wrapping random number generators. The idea is to be able to use either simulator-native rngs, which may be more efficient, or a standard Python rng, e.g. a np.random.RandomState object, which would allow the same random numbers to be used across different simulators, or simply to read externally-generated numbers from files.""" def __init__(self, seed=None): if seed is not None: assert isinstance(seed, int), "`seed` must be an int, not a %s" % type(seed).__name__ self.seed = seed # define some aliases self.random = self.next self.sample = self.next def __repr__(self): return "%s(seed=%r)" % (self.__class__.__name__, self.seed) def next(self, n=None, distribution=None, parameters=None, mask=None): """Return `n` random numbers from the specified distribution. If: * `n` is None, return a float, * `n` >= 1, return a Numpy array, * `n` < 0, raise an Exception, * `n` is 0, return an empty array. If called with distribution=None, returns uniformly distributed floats in the range [0, 1) If `mask` is provided, it should be a boolean or integer NumPy array, indicating that only a subset of the random numbers should be returned. Example:: rng.next(5, mask=np.array([True, False, True, False, True])) or:: rng.next(5, mask=np.array([0, 2, 4])) will each return only three values. If the rng is "parallel safe", an array of n values will be drawn from the rng, and the mask applied. If the rng is not parallel safe, the contents of the mask are disregarded, only its size (for an integer mask) or the number of True values (for a boolean mask) is used in determining how many values to draw. """ raise NotImplementedError class WrappedRNG(AbstractRNG): def __init__(self, seed=None, parallel_safe=True): AbstractRNG.__init__(self, seed) self.parallel_safe = parallel_safe self.mpi_rank, self.num_processes = get_mpi_config() if self.seed is not None and not parallel_safe: self.seed += self.mpi_rank # ensure different nodes get different sequences if self.mpi_rank != 0: logger.warning("Changing the seed to %s on node %d" % (self.seed, self.mpi_rank)) def next(self, n=None, distribution=None, parameters=None, mask=None): if distribution is None: distribution = 'uniform' if parameters is None: parameters = {"low": 0.0, "high": 1.0} if n == 0: rarr = np.random.rand(0) # We return an empty array elif n is None: rarr = self._next(distribution, 1, parameters) elif n > 0: if mask is not None: assert isinstance(mask, np.ndarray) if mask.dtype == np.bool: if mask.size != n: raise ValueError("boolean mask size must equal n") if not self.parallel_safe: if mask.dtype == np.bool: n = mask.sum() elif mask.dtype == np.integer: n = mask.size rarr = self._next(distribution, n, parameters) else: raise ValueError("The sample number must be positive") if not isinstance(rarr, np.ndarray): rarr = np.array(rarr) if self.parallel_safe and mask is not None: # strip out the random numbers that should be used on other processors. rarr = rarr[mask] if n is None: return rarr[0] else: return rarr next.__doc__ = AbstractRNG.next.__doc__ def _clipped(self, gen, low=-np.inf, high=np.inf, size=None): """ """ res = gen(size) iterations = 0 errmsg = "Maximum number of redraws exceeded. Check the parameterization of your distribution." if size is None: while res < low or res > high: # limit the number of iterations. Possibility of infinite loop, depending on parameters if iterations > MAX_REDRAWS: raise Exception(errmsg) res = gen(size) iterations += 1 else: idx = np.where((res > high) | (res < low))[0] while idx.size > 0: if iterations > MAX_REDRAWS: raise Exception(errmsg) redrawn = gen(idx.size) res[idx] = redrawn idx = idx[np.where((redrawn > high) | (redrawn < low))[0]] iterations += 1 return res def describe(self): return "%s() with seed %s for MPI rank %d (MPI processes %d). %s parallel safe." % ( self.__class__.__name__, self.seed, self.mpi_rank, self.num_processes, self.parallel_safe and "Is" or "Not") class NumpyRNG(WrappedRNG): """Wrapper for the :class:`np.random.RandomState` class (Mersenne Twister PRNG).""" translations = { 'binomial': ('binomial', {'n': 'n', 'p': 'p'}), 'gamma': ('gamma', {'k': 'shape', 'theta': 'scale'}), 'exponential': ('exponential', {'beta': 'scale'}), 'lognormal': ('lognormal', {'mu': 'mean', 'sigma': 'sigma'}), 'normal': ('normal', {'mu': 'loc', 'sigma': 'scale'}), 'normal_clipped': ('normal_clipped', {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}), 'normal_clipped_to_boundary': ('normal_clipped_to_boundary', {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}), 'poisson': ('poisson', {'lambda_': 'lam'}), 'uniform': ('uniform', {'low': 'low', 'high': 'high'}), 'uniform_int': ('randint', {'low': 'low', 'high': 'high'}), 'vonmises': ('vonmises', {'mu': 'mu', 'kappa': 'kappa'}), } def __init__(self, seed=None, parallel_safe=True): WrappedRNG.__init__(self, seed, parallel_safe) self.rng = np.random.RandomState() if self.seed is not None: self.rng.seed(self.seed) else: self.rng.seed() def __getattr__(self, name): """ This is to give the PyNN RNGs the same methods as the wrapped RNGs (:class:`np.random.RandomState` or the GSL RNGs.) """ return getattr(self.rng, name) def _next(self, distribution, n, parameters): # TODO: allow non-standardized distributions to pass through without translation distribution_np, parameter_map = self.translations[distribution] if set(parameters.keys()) != set(parameter_map.keys()): # all parameters must be provided. We do not provide default values (this can be discussed). errmsg = "Incorrect parameterization of random distribution. Expected %s, got %s." raise KeyError(errmsg % (parameter_map.keys(), parameters.keys())) parameters_np = dict((parameter_map[k], v) for k, v in parameters.items()) if hasattr(self, distribution_np): f_distr = getattr(self, distribution_np) else: f_distr = getattr(self.rng, distribution_np) return f_distr(size=n, **parameters_np) def __deepcopy__(self, memo): obj = NumpyRNG.__new__(NumpyRNG) WrappedRNG.__init__(obj, seed=deepcopy(self.seed, memo), parallel_safe=deepcopy(self.parallel_safe, memo)) obj.rng = deepcopy(self.rng) return obj def normal_clipped(self, mu=0.0, sigma=1.0, low=-np.inf, high=np.inf, size=None): """ """ # not sure how well this works with parallel_safe, mask_local gen = lambda n: self.rng.normal(loc=mu, scale=sigma, size=n) return self._clipped(gen, low=low, high=high, size=size) def normal_clipped_to_boundary(self, mu=0.0, sigma=1.0, low=-np.inf, high=np.inf, size=None): # Not recommended, used `normal_clipped` instead. # Provided because some models in the literature use this. res = self.rng.normal(loc=mu, scale=sigma, size=size) return np.maximum(np.minimum(res, high), low) class GSLRNG(WrappedRNG): """Wrapper for the GSL random number generators.""" translations = { 'binomial': ('binomial', {'n': 'n', 'p': 'p'}), 'gamma': ('gamma', {'k': 'k', 'theta': 'theta'}), 'exponential': ('exponential', {'beta': 'mu'}), 'lognormal': ('lognormal', {'mu': 'zeta', 'sigma': 'sigma'}), 'normal': ('normal', {'mu': 'mu', 'sigma': 'sigma'}), 'normal_clipped': ('normal_clipped', {'mu': 'mu', 'sigma': 'sigma', 'low': 'low', 'high': 'high'}), 'poisson': ('poisson', {'lambda_': 'mu'}), 'uniform': ('flat', {'low': 'a', 'high': 'b'}), 'uniform_int': ('uniform_int', {'low': 'low', 'high': 'high'}), } def __init__(self, seed=None, type='mt19937', parallel_safe=True): if not have_gsl: raise ImportError("GSLRNG: Cannot import pygsl") WrappedRNG.__init__(self, seed, parallel_safe) self.rng = getattr(pygsl.rng, type)() if self.seed is not None: self.rng.set(self.seed) else: self.seed = int(time.time()) self.rng.set(self.seed) def __getattr__(self, name): """This is to give GSLRNG the same methods as the GSL RNGs.""" return getattr(self.rng, name) def _next(self, distribution, n, parameters): distribution_gsl, parameter_map = self.translations[distribution] if set(parameters.keys()) != set(parameter_map.keys()): # all parameters must be provided. We do not provide default values (this can be discussed). errmsg = "Incorrect parameterization of random distribution. Expected %s, got %s." raise KeyError(errmsg % (parameter_map.keys(), parameters.keys())) parameters_gsl = dict((parameter_map[k], v) for k, v in parameters.items()) if hasattr(self, distribution_gsl): f_distr = getattr(self, distribution_gsl) else: f_distr = getattr(self.rng, distribution_gsl) # Has this been tested? If so, move most of _next to Wrapped RNG since there is almost complete overlap with NumpyRNG._next values = f_distr(size=n, **parameters_gsl) if n == 1: values = [values] # to be consistent with NumpyRNG return values def uniform_int(self, low, high, size=None): return low + self.rng.uniform_int(high - low, size) def gamma(self, k, theta, size=None): """ """ return self.rng.gamma(k, 1 / theta, size) def normal(self, mu=0.0, sigma=1.0, size=None): """ """ return mu + self.rng.gaussian(sigma, size) def normal_clipped(self, mu=0.0, sigma=1.0, low=-np.inf, high=np.inf, size=None): """ """ gen = lambda n: self.normal(mu, sigma, n) return self._clipped(gen, low=low, high=high, size=size) # should add a wrapper for the built-in Python random module. class NativeRNG(AbstractRNG): """ Signals that the simulator's own native RNG should be used. Each simulator module should implement a class of the same name which inherits from this and which sets the seed appropriately. """ def __str__(self): return "NativeRNG(seed=%s)" % self.seed class RandomDistribution(object): """ Class which defines a next(n) method which returns an array of `n` random numbers from a given distribution. Arguments: `distribution`: the name of a random number distribution. `parameters_pos`: parameters of the distribution, provided as a tuple. For the correct ordering, see `random.available_distributions`. `rng`: if present, should be a :class:`NumpyRNG`, :class:`GSLRNG` or :class:`NativeRNG` object. `parameters_named`: parameters of the distribution, provided as keyword arguments. Parameters may be provided either through `parameters_pos` or through `parameters_named`, but not both. All parameters must be provided, there are no default values. Parameter names are, in general, as used in Wikipedia. Examples: >>> rd = RandomDistribution('uniform', (-70, -50)) >>> rd = RandomDistribution('normal', mu=0.5, sigma=0.1) >>> rng = NumpyRNG(seed=8658764) >>> rd = RandomDistribution('gamma', k=2.0, theta=5.0, rng=rng) Available distributions: ========================== ==================== ==================================================== Name Parameters Comments -------------------------- -------------------- ---------------------------------------------------- binomial n, p gamma k, theta exponential beta lognormal mu, sigma normal mu, sigma normal_clipped mu, sigma, low, high Values outside (low, high) are redrawn normal_clipped_to_boundary mu, sigma, low, high Values below/above low/high are set to low/high poisson lambda_ Trailing underscore since lambda is a Python keyword uniform low, high uniform_int low, high vonmises mu, kappa ========================== ==================== ==================================================== """ def __init__(self, distribution, parameters_pos=None, rng=None, **parameters_named): """ Create a new RandomDistribution. """ self.name = distribution self.parameters = self._resolve_parameters(parameters_pos, parameters_named) if rng: assert isinstance(rng, AbstractRNG), "rng must be a pyNN.random RNG object" self.rng = rng else: # use np.random.RandomState() by default self.rng = NumpyRNG() # should we provide a seed? def next(self, n=None, mask=None): """Return `n` random numbers from the distribution.""" res = self.rng.next(n=n, distribution=self.name, parameters=self.parameters, mask=mask) return res def __str__(self): return "RandomDistribution('%(name)s', %(parameters)s, %(rng)s)" % self.__dict__ def _resolve_parameters(self, positional, named): if positional is None: if set(named.keys()) != set(available_distributions[self.name]): errmsg = "Incorrect parameterization of random distribution. Expected %s, got %s." raise KeyError(errmsg % (available_distributions[self.name], tuple(named.keys()))) return named elif len(named) == 0: if isinstance(positional, dict): raise TypeError("Positional parameters should be a tuple, not a dict") expected_parameter_names = available_distributions[self.name] if len(positional) != len(expected_parameter_names): errmsg = "Incorrect number of parameters for random distribution. For %s received %s" raise ValueError(errmsg % (expected_parameter_names, positional)) else: return dict((name, value) for name, value in zip(expected_parameter_names, positional)) else: raise ValueError("Mixed positional and named parameters") def lazily_evaluate(self, mask=None, shape=None): """ Generate an array of random numbers of the requested shape. If a mask is given, produce only enough numbers to fill the region defined by the mask (hence 'lazily'). This method is called by the lazyarray `evaluate()` and `_partially_evaluate()` methods. """ if mask is None: # produce an array of random numbers with the requested shape n = reduce(operator.mul, shape) res = self.next(n) if res.shape != shape: res = res.reshape(shape) if n == 1: res = res[0] else: # produce an array of random numbers whose shape is # that of a sub-array produced by applying the mask # to an array of the requested global shape p_shape = partial_shape(mask, shape) if p_shape: n = reduce(operator.mul, p_shape) else: n = 1 res = self.next(n).reshape(p_shape) return res PyNN-0.10.0/pyNN/recording/000077500000000000000000000000001415343567000152645ustar00rootroot00000000000000PyNN-0.10.0/pyNN/recording/__init__.py000066400000000000000000000375071415343567000174110ustar00rootroot00000000000000""" Defines classes and functions for managing recordings (spikes, membrane potential etc). These classes and functions are not part of the PyNN API, and are only for internal use. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import logging import numpy as np import os from copy import copy from collections import defaultdict from warnings import warn from pyNN import errors import neo from datetime import datetime import quantities as pq logger = logging.getLogger("PyNN") MPI_ROOT = 0 def get_mpi_comm(): try: from mpi4py import MPI except ImportError: raise Exception( "Trying to gather data without MPI installed. If you are not running a distributed simulation, this is a bug in PyNN.") return MPI.COMM_WORLD, {'DOUBLE': MPI.DOUBLE, 'SUM': MPI.SUM} def rename_existing(filename): if os.path.exists(filename): os.system('mv %s %s_old' % (filename, filename)) logger.warning("File %s already exists. Renaming the original file to %s_old" % (filename, filename)) def gather_array(data): # gather 1D or 2D numpy arrays mpi_comm, mpi_flags = get_mpi_comm() assert isinstance(data, np.ndarray) assert len(data.shape) < 3 # first we pass the data size size = data.size sizes = mpi_comm.gather(size, root=MPI_ROOT) or [] # now we pass the data displacements = [sum(sizes[:i]) for i in range(len(sizes))] gdata = np.empty(sum(sizes)) mpi_comm.Gatherv([data.flatten(), size, mpi_flags['DOUBLE']], [gdata, (sizes, displacements), mpi_flags['DOUBLE']], root=MPI_ROOT) if len(data.shape) == 1: return gdata else: num_columns = data.shape[1] return gdata.reshape((gdata.size / num_columns, num_columns)) def gather_dict(D, all=False): # Note that if the same key exists on multiple nodes, the value from the # node with the highest rank will appear in the final dict. mpi_comm, mpi_flags = get_mpi_comm() if all: Ds = mpi_comm.allgather(D) else: Ds = mpi_comm.gather(D, root=MPI_ROOT) if Ds: for otherD in Ds: D.update(otherD) return D def gather_blocks(data, ordered=True): """Gather Neo Blocks""" mpi_comm, mpi_flags = get_mpi_comm() assert isinstance(data, neo.Block) # for now, use gather_dict, which will probably be slow. Can optimize later D = {mpi_comm.rank: data} D = gather_dict(D) blocks = list(D.values()) merged = data if mpi_comm.rank == MPI_ROOT: merged = blocks[0] # the following business with setting sig.segment is a workaround for a bug in Neo for seg in merged.segments: for sig in seg.analogsignals: sig.segment = seg for block in blocks[1:]: for seg, mseg in zip(block.segments, merged.segments): for sig in seg.analogsignals: sig.segment = mseg merged.merge(block) if ordered: for segment in merged.segments: ordered_spiketrains = sorted( segment.spiketrains, key=lambda s: s.annotations['source_id']) segment.spiketrains = ordered_spiketrains return merged def mpi_sum(x): mpi_comm, mpi_flags = get_mpi_comm() if mpi_comm.size > 1: return mpi_comm.allreduce(x, op=mpi_flags['SUM']) else: return x def normalize_variables_arg(variables): """If variables is a single string, encapsulate it in a list.""" if isinstance(variables, str) and variables != 'all': return [variables] else: return variables def safe_makedirs(dir): """ Version of makedirs not subject to race condition when using MPI. """ if dir and not os.path.exists(dir): try: os.makedirs(dir) except OSError as e: if e.errno != 17: raise def get_io(filename): """ Return a Neo IO instance, guessing the type based on the filename suffix. """ logger.debug("Creating Neo IO for filename %s" % filename) dir = os.path.dirname(filename) safe_makedirs(dir) extension = os.path.splitext(filename)[1] if extension in ('.txt', '.ras', '.v', '.gsyn'): raise IOError( "ASCII-based formats are not currently supported for output data. Try using the file extension '.pkl' or '.h5'") elif extension in ('.h5',): return neo.io.NeoHdf5IO(filename=filename) elif extension in ('.pkl', '.pickle'): return neo.io.PickleIO(filename=filename) elif extension == '.mat': return neo.io.NeoMatlabIO(filename=filename) else: # function to be improved later raise Exception("file extension %s not supported" % extension) def filter_by_variables(segment, variables): """ Return a new `Segment` containing only recordings of the variables given in the list `variables` """ if variables == 'all': return segment else: new_segment = copy(segment) # shallow copy if 'spikes' not in variables: new_segment.spiketrains = [] new_segment.analogsignals = [sig for sig in segment.analogsignals if sig.name in variables] # also need to handle Units, RecordingChannels return new_segment def remove_duplicate_spiketrains(data): for segment in data.segments: spiketrains = {} for spiketrain in segment.spiketrains: index = spiketrain.annotations["source_index"] spiketrains[index] = spiketrain min_index = min(spiketrains.keys()) max_index = max(spiketrains.keys()) segment.spiketrains = [spiketrains[i] for i in range(min_index, max_index + 1)] return data class DataCache(object): # primitive implementation for now, storing in memory - later can consider caching to disk def __init__(self): self._data = [] def __iter__(self): return iter(self._data) def store(self, obj): if obj not in self._data: logger.debug("Adding %s to cache" % obj) self._data.append(obj) def clear(self): self._data = [] class Recorder(object): """Encapsulates data and functions related to recording model variables.""" def __init__(self, population, file=None): """ Create a recorder. `population` -- the Population instance which is being recorded by the recorder `file` -- one of: - a file-name, - `None` (write to a temporary file) - `False` (write to memory). """ self.file = file self.population = population # needed for writing header information self.recorded = defaultdict(set) self.cache = DataCache() self._simulator.state.recorders.add(self) self.clear_flag = False self._recording_start_time = self._simulator.state.t * pq.ms self.sampling_interval = self._simulator.state.dt def record(self, variables, ids, sampling_interval=None): """ Add the cells in `ids` to the sets of recorded cells for the given variables. """ logger.debug('Recorder.record(<%d cells>)' % len(ids)) self._check_sampling_interval(sampling_interval) ids = set([id for id in ids if id.local]) for variable in normalize_variables_arg(variables): if not self.population.can_record(variable): raise errors.RecordingError(variable, self.population.celltype) new_ids = ids.difference(self.recorded[variable]) self.recorded[variable] = self.recorded[variable].union(ids) self._record(variable, new_ids, sampling_interval) def _check_sampling_interval(self, sampling_interval): """ Check whether record() has been called previously with a different sampling interval (we exclude recording of spikes, as the sampling interval does not apply in that case) """ if sampling_interval is not None and sampling_interval != self.sampling_interval: recorded_variables = list(self.recorded.keys()) if "spikes" in recorded_variables: recorded_variables.remove("spikes") if len(recorded_variables) > 0: raise ValueError( "All neurons in a population must be recorded with the same sampling interval.") def reset(self): """Reset the list of things to be recorded.""" self._reset() self.recorded = defaultdict(set) def filter_recorded(self, variable, filter_ids): if filter_ids is not None: return set(filter_ids).intersection(self.recorded[variable]) else: return self.recorded[variable] def _get_current_segment(self, filter_ids=None, variables='all', clear=False): segment = neo.Segment(name="segment%03d" % self._simulator.state.segment_counter, description=self.population.describe(), rec_datetime=datetime.now()) # would be nice to get the time at the start of the recording, not the end variables_to_include = set(self.recorded.keys()) if variables != 'all': variables_to_include = variables_to_include.intersection(set(variables)) for variable in variables_to_include: if variable == 'spikes': t_stop = self._simulator.state.t * pq.ms # must run on all MPI nodes sids = sorted(self.filter_recorded('spikes', filter_ids)) data = self._get_spiketimes(sids, clear=clear) segment.spiketrains = [] for id in sids: times = pq.Quantity(data.get(int(id), []), pq.ms) if times.size > 0 and times.max() > t_stop: warn("Recorded at least one spike after t_stop") times = times[times <= t_stop] segment.spiketrains.append( neo.SpikeTrain(times, t_start=self._recording_start_time, t_stop=t_stop, units='ms', source_population=self.population.label, source_id=int(id), source_index=self.population.id_to_index(int(id))) ) else: ids = sorted(self.filter_recorded(variable, filter_ids)) signal_array = self._get_all_signals(variable, ids, clear=clear) t_start = self._recording_start_time t_stop = self._simulator.state.t * pq.ms sampling_period = self.sampling_interval * pq.ms current_time = self._simulator.state.t * pq.ms mpi_node = self._simulator.state.mpi_rank # for debugging if signal_array.size > 0: # may be empty if none of the recorded cells are on this MPI node units = self.population.find_units(variable) source_ids = np.fromiter(ids, dtype=int) signal = neo.AnalogSignal( signal_array, units=units, t_start=t_start, sampling_period=sampling_period, name=variable, source_population=self.population.label, source_ids=source_ids, array_annotations={"channel_index": np.array([self.population.id_to_index(id) for id in ids])}) segment.analogsignals.append(signal) logger.debug("%d **** ids=%s, channels=%s", mpi_node, source_ids, signal.array_annotations["channel_index"]) assert segment.analogsignals[0].t_stop - \ current_time - 2 * sampling_period < 1e-10 return segment def get(self, variables, gather=False, filter_ids=None, clear=False, annotations=None): """Return the recorded data as a Neo `Block`.""" variables = normalize_variables_arg(variables) data = neo.Block() data.segments = [filter_by_variables(segment, variables) for segment in self.cache] if self._simulator.state.running: # reset() has not been called, so current segment is not in cache data.segments.append(self._get_current_segment( filter_ids=filter_ids, variables=variables, clear=clear)) data.name = self.population.label data.description = self.population.describe() data.rec_datetime = data.segments[0].rec_datetime data.annotate(**self.metadata) if annotations: data.annotate(**annotations) if gather and self._simulator.state.num_processes > 1: data = gather_blocks(data) if hasattr(self.population.celltype, "always_local") and self.population.celltype.always_local: data = remove_duplicate_spiketrains(data) if clear: self.clear() return data def clear(self): """ Clear all recorded data, both from the cache and the simulator. """ self.cache.clear() self.clear_flag = True self._recording_start_time = self._simulator.state.t * pq.ms self._clear_simulator() def write(self, variables, file=None, gather=False, filter_ids=None, clear=False, annotations=None): """Write recorded data to a Neo IO""" if isinstance(file, str): file = get_io(file) io = file or self.file if gather is False and self._simulator.state.num_processes > 1: io.filename += '.%d' % self._simulator.state.mpi_rank logger.debug("Recorder is writing '%s' to file '%s' with gather=%s" % ( variables, io.filename, gather)) data = self.get(variables, gather, filter_ids, clear, annotations=annotations) if self._simulator.state.mpi_rank == 0 or gather is False: # Open the output file, if necessary and write the data logger.debug("Writing data to file %s" % io) io.write_block(data) @property def metadata(self): metadata = { 'size': self.population.size, 'first_index': 0, 'last_index': len(self.population), 'first_id': int(self.population.first_id), 'last_id': int(self.population.last_id), 'label': self.population.label, 'simulator': self._simulator.name, } metadata.update(self.population.annotations) # note that this has to run on all nodes (at least for NEST) metadata['dt'] = self._simulator.state.dt metadata['mpi_processes'] = self._simulator.state.num_processes return metadata def count(self, variable, gather=True, filter_ids=None): """ Return the number of data points for each cell, as a dict. This is mainly useful for spike counts or for variable-time-step integration methods. """ if variable == 'spikes': N = self._local_count(variable, filter_ids) else: raise Exception("Only implemented for spikes.") if gather and self._simulator.state.num_processes > 1: N = gather_dict(N) return N def store_to_cache(self, annotations=None): # make sure we haven't called get with clear=True since last reset # and that we did not do two resets in a row if (self._simulator.state.t != 0) and (not self.clear_flag): if annotations is None: annotations = {} segment = self._get_current_segment() segment.annotate(**annotations) self.cache.store(segment) self.clear_flag = False self._recording_start_time = 0.0 * pq.ms PyNN-0.10.0/pyNN/recording/files.py000066400000000000000000000211251415343567000167410ustar00rootroot00000000000000""" Provides standard interfaces to various text and binary file formats for saving position and connectivity data. Note that saving spikes, membrane potential and synaptic conductances is now done via Neo. Classes: StandardTextFile PickleFile NumpyBinaryFile HDF5ArrayFile - requires PyTables :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import numpy as np import os import shutil import pickle try: import tables have_hdf5 = True except ImportError: have_hdf5 = False DEFAULT_BUFFER_SIZE = 10000 def _savetxt(filename, data, format, delimiter): """ Due to the lack of savetxt in older versions of numpy we provide a cut-down version of that function. """ f = open(filename, 'w') for row in data: f.write(delimiter.join([format % val for val in row]) + '\n') f.close() def savez(file, *args, **kwds): __doc__ = np.savez.__doc__ import zipfile from numpy.lib import format if isinstance(file, str): if not file.endswith('.npz'): file = file + '.npz' namedict = kwds for i, val in enumerate(args): key = 'arr_%d' % i if key in namedict.keys(): raise ValueError("Cannot use un-named variables and keyword %s" % key) namedict[key] = val zip = zipfile.ZipFile(file, mode="w") # Place to write temporary .npy files # before storing them in the zip. We need to path this to have a working # function in parallel ! import tempfile direc = tempfile.mkdtemp() for key, val in namedict.items(): fname = key + '.npy' filename = os.path.join(direc, fname) fid = open(filename, 'wb') format.write_array(fid, np.asanyarray(val)) fid.close() zip.write(filename, arcname=fname) zip.close() shutil.rmtree(direc) class BaseFile(object): """ Base class for PyNN File classes. """ def __init__(self, filename, mode='rb'): """ Open a file with the given filename and mode. """ self.name = filename self.mode = mode dir = os.path.dirname(filename) if dir and not os.path.exists(dir): try: # wrapping in try...except block for MPI os.makedirs(dir) except IOError: pass # we assume that the directory was already created by another MPI node try: # Need this because in parallel, file names are changed self.fileobj = open(self.name, mode, DEFAULT_BUFFER_SIZE) except Exception as err: self.open_error = err def __del__(self): self.close() def _check_open(self): if not hasattr(self, 'fileobj'): raise self.open_error def rename(self, filename): self.close() try: # Need this because in parallel, only one node will delete the file with NFS os.remove(self.name) except Exception: pass self.name = filename self.fileobj = open(self.name, self.mode, DEFAULT_BUFFER_SIZE) def write(self, data, metadata): """ Write data and metadata to file. `data` should be a NumPy array, `metadata` should be a dictionary. """ raise NotImplementedError def read(self): """ Read data from the file and return a NumPy array. """ raise NotImplementedError def get_metadata(self): """ Read metadata from the file and return a dict. """ raise NotImplementedError def close(self): """Close the file.""" if hasattr(self, 'fileobj'): self.fileobj.close() class StandardTextFile(BaseFile): """ Data and metadata is written as text. Metadata is written at the top of the file, with each line preceded by "#". Data is written with one data point per line. """ def write(self, data, metadata): __doc__ = BaseFile.write.__doc__ self._check_open() # can we write to the file more than once? In this case, should use seek,tell # to always put the header information at the top? # write header header_lines = ["# %s = %s" % item for item in metadata.items()] header = "\n".join(header_lines) + '\n' self.fileobj.write(header.encode('utf-8')) # write data savetxt = getattr(np, 'savetxt', _savetxt) savetxt(self.fileobj, data, fmt='%r', delimiter='\t') self.fileobj.close() def read(self): self._check_open() return np.loadtxt(self.fileobj) def get_metadata(self): self._check_open() D = {} for line in self.fileobj: if line: if line[0] != "#": break name, value = line[1:].split("=") name = name.strip() value = eval(value) if type(value) in [list, tuple]: D[name] = value else: raise TypeError("Column headers must be specified using a list or tuple.") else: break self.fileobj.seek(0) return D class PickleFile(BaseFile): """ Data and metadata are pickled and saved to file. """ def write(self, data, metadata): __doc__ = BaseFile.write.__doc__ self._check_open() pickle.dump((data, metadata), self.fileobj) def read(self): __doc__ = BaseFile.read.__doc__ self._check_open() data = pickle.load(self.fileobj)[0] self.fileobj.seek(0) return data def get_metadata(self): __doc__ = BaseFile.get_metadata.__doc__ self._check_open() metadata = pickle.load(self.fileobj)[1] self.fileobj.seek(0) return metadata class NumpyBinaryFile(BaseFile): """ Data and metadata are saved in .npz format, which is a zipped archive of arrays. """ def write(self, data, metadata): __doc__ = BaseFile.write.__doc__ self._check_open() metadata_array = np.array(list(metadata.items()), dtype=object) savez(self.fileobj, data=data, metadata=metadata_array) def read(self): __doc__ = BaseFile.read.__doc__ self._check_open() data = np.load(self.fileobj)['data'] self.fileobj.seek(0) return data def get_metadata(self): __doc__ = BaseFile.get_metadata.__doc__ self._check_open() D = {} for name, value in np.load(self.fileobj, allow_pickle=True)['metadata']: try: D[name] = eval(value) except Exception: D[name] = value self.fileobj.seek(0) return D if have_hdf5: class HDF5ArrayFile(BaseFile): """ Data are saved as an array within a node named "data". Metadata are saved as attributes of this node. """ def __init__(self, filename, mode='r', title="PyNN data file"): """ Open an HDF5 file with the given filename, mode and title. """ self.name = filename self.mode = mode try: self.fileobj = tables.open_file(filename, mode=mode, title=title) self._new_pytables = True except AttributeError: self.fileobj = tables.openFile(filename, mode=mode, title=title) self._new_pytables = False # may not work with old versions of PyTables < 1.3, since they only support numarray, not numpy def write(self, data, metadata): __doc__ = BaseFile.write.__doc__ if len(data) > 0: try: if self._new_pytables: node = self.fileobj.create_array(self.fileobj.root, "data", data) else: node = self.fileobj.createArray(self.fileobj.root, "data", data) except tables.HDF5ExtError as e: raise tables.HDF5ExtError("%s. data.shape=%s, metadata=%s" % (e, data.shape, metadata)) for name, value in metadata.items(): setattr(node.attrs, name, value) self.fileobj.close() def read(self): __doc__ = BaseFile.read.__doc__ return self.fileobj.root.data.read() def get_metadata(self): __doc__ = BaseFile.get_metadata.__doc__ D = {} node = self.fileobj.root.data for name in node._v_attrs._f_list(): D[name] = node.attrs.__getattr__(name) return D PyNN-0.10.0/pyNN/serialization/000077500000000000000000000000001415343567000161655ustar00rootroot00000000000000PyNN-0.10.0/pyNN/serialization/__init__.py000066400000000000000000000002451415343567000202770ustar00rootroot00000000000000""" Functions for exporting and importing networks to/from files """ from .sonata import import_from_sonata, export_to_sonata, asciify, load_sonata_simulation_plan PyNN-0.10.0/pyNN/serialization/sonata.py000066400000000000000000001457111415343567000200350ustar00rootroot00000000000000# encoding: utf-8 """ Support for the SONATA format (https://github.com/AllenInstitute/sonata/) Public functions ---------------- - export_to_sonata() - import_from_sonata() - load_sonata_simulation_plan() Usage ----- This module would typically be used as follows:: from pyNN.serialization import import_from_sonata, load_sonata_simulation_plan import pyNN.neuron as sim simulation_plan = load_sonata_simulation_plan("simulation_config.json") simulation_plan.setup(sim) net = import_from_sonata("circuit_config.json", sim) simulation_plan.execute(net) :copyright: Copyright 2018 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import os from os.path import join, isdir, exists from collections import defaultdict import shutil from string import Template import csv from warnings import warn import json import logging try: import h5py HAVE_H5PY = True except ImportError: HAVE_H5PY = False import numpy as np from pyNN.network import Network from pyNN.parameters import Sequence # Note: The SonataIO class will be moved to Neo once fully implemented #from neo.io import SonataIO import neo from neo.io.baseio import BaseIO class SonataIO(BaseIO): """ Neo IO module for simulation input and output in the SONATA format. See https://github.com/AllenInstitute/sonata/blob/master/docs/SONATA_DEVELOPER_GUIDE.md Currently only supports spike files. Support for current inputs and for reports will be implemented soon. """ def __init__(self, base_dir, spikes_file="spikes.h5", spikes_sort_order=None, report_config=None, node_sets=None): if not HAVE_H5PY: raise Exception("You need to install h5py to use SonataIO") self.base_dir = base_dir self.spike_file = spikes_file self.spikes_sort_order = spikes_sort_order self.report_config = report_config self.node_sets = node_sets def read(self): """ Read all data* from a SONATA dataset directory. Returns a list of Blocks. (*Currently only spike data supported) """ file_path = join(self.base_dir, self.spike_file) block = neo.Block(file_origin=file_path) segment = neo.Segment(file_origin=file_path) spikes_file = h5py.File(file_path, 'r') for gid in np.unique(spikes_file['spikes']['gids']): index = spikes_file['spikes']['gids'][()] == gid spike_times = spikes_file['spikes']['timestamps'][index] segment.spiketrains.append( neo.SpikeTrain(spike_times, t_stop=spike_times.max() + 1.0, t_start=0.0, units='ms', source_id=gid) ) block.segments.append(segment) return [block] def write(self, blocks): """ Write a list of Blocks to SONATA HDF5 files. """ if not os.path.isdir(self.base_dir): os.makedirs(self.base_dir) # Write spikes spike_file_path = join(self.base_dir, self.spike_file) spikes_file = h5py.File(spike_file_path, 'w') spike_trains = [] for block in blocks: for segment in block.segments: spike_trains.extend(segment.spiketrains) n_spikes = sum(st.size for st in spike_trains) spikes_group = spikes_file.create_group("spikes") all_spike_times = np.hstack(st.rescale('ms').magnitude for st in spike_trains).astype(np.float64) gids = np.hstack(st.annotations["source_index"] * np.ones(st.shape, dtype=np.uint64) for st in spike_trains) # todo: handle sorting spikes_group.create_dataset("timestamps", data=all_spike_times, dtype=np.float64) spikes_group.create_dataset("gids", data=gids, dtype=np.uint64) spikes_file.close() logger.info("Wrote spike output to {}".format(spike_file_path)) # Write signals for report_name, report_metadata in self.report_config.items(): file_name = report_metadata.get("file_name", report_name + ".h5") file_path = join(self.base_dir, file_name) signal_file = h5py.File(file_path, 'w') targets = self.node_sets[report_metadata["cells"]] for block in blocks: for (assembly, mask) in targets: if block.name == assembly.label: if len(block.segments) > 1: raise NotImplementedError() signal = block.segments[0].filter(name=report_metadata["variable_name"]) if len(signal) != 1: raise NotImplementedError() node_ids = np.arange(assembly.size)[mask] report_group = signal_file.create_group("report") population_group = report_group.create_group(assembly.label) dataset = population_group.create_dataset("data", data=signal[0].magnitude) dataset.attrs["units"] = signal[0].units.dimensionality.string dataset.attrs["variable_name"] = report_metadata["variable_name"] n = dataset.shape[1] mapping_group = population_group.create_group("mapping") mapping_group.create_dataset("node_ids", data=node_ids) # "gids" not in the spec, but expected by some bmtk utils mapping_group.create_dataset("gids", data=node_ids) #mapping_group.create_dataset("index_pointers", data=np.zeros((n,))) mapping_group.create_dataset( "index_pointer", data=np.arange(0, n+1)) # ??spec unclear mapping_group.create_dataset("element_ids", data=np.zeros((n,))) mapping_group.create_dataset("element_pos", data=np.zeros((n,))) time_ds = mapping_group.create_dataset("time", data=(float(signal[0].t_start.rescale('ms')), float( signal[0].t_stop.rescale('ms')), float(signal[0].sampling_period.rescale('ms')))) time_ds.attrs["units"] = "ms" logger.info("Wrote block {} to {}".format(block.name, file_path)) signal_file.close() MAGIC = 0x0a7a logger = logging.getLogger("pyNN.serialization.sonata") # ----- utility functions, not intended for use outside this module ---------- def asciify(label): """To be safe, we will use only ascii for strings inside HDF5""" return label.replace(u"→", "-").encode('ascii') def cast(value): """Try to cast strings to numeric values""" try: value = int(value) except ValueError: try: value = float(value) except ValueError: pass return value def to_string(s): if isinstance(s, bytes): s = s.decode('utf-8') return s def read_types_file(file_path, node_or_edge): """Read node type or edge type parameters from a SONATA CSV file. Returns a dict representing the parameters row-wise, indexed by the type id. """ with open(file_path) as csv_file: csv_reader = csv.DictReader(csv_file, delimiter=' ', quotechar='"') types_table = list(csv_reader) types_map = {} id_label = "{}_type_id".format(node_or_edge) for row in types_table: types_map[int(row[id_label])] = row # NodeType(**row) logger.info(types_map) return types_map def sonata_id_to_index(population, id): # this relies on SONATA ids being sequential if "first_sonata_id" in population.annotations: offset = population.annotations["first_sonata_id"] else: raise Exception("Population not annotated with SONATA ids") return id - offset def condense(value, types_array): """Transform parameters taken from SONATA CSV and/or HDF5 files into a suitable form for PyNN. Arguments --------- value - NumPy array or dict NumPy arrays are returned directly. Dicts should have node type ids as keys and the parameter values for the different types as values. Where all node/edge types have the same value, this single value is returned. Where different node/edge types have different values for a given parameter, a NumPy array of size equal to the number of nodes/edges in the SONATA group (cells in the PyNN Population or connections in the PyNN Projection) is constructed to contain the different parameter values. types_array - NumPy array Subset of the data from "/nodes//node_type_id" or from "/edges//edge_type_id" that applies to this group. Needed to construct parameter arrays. """ # todo: use lazyarray if isinstance(value, np.ndarray): return value elif isinstance(value, dict): assert len(value) > 0 value_array = np.array(list(value.values())) if np.all(value_array == value_array[0]): return value_array[0] else: if np.issubdtype(value_array.dtype, np.number): new_value = np.ones_like(types_array) * np.nan elif np.issubdtype(value_array.dtype, np.str_): new_value = np.array(["UNDEFINED"] * types_array.size) else: raise TypeError("Cannot handle annotations that are neither numbers or strings") for node_type_id, val in value.items(): new_value[types_array == node_type_id] = val return new_value else: raise TypeError( "Unexpected type. Expected Numpy array or dict, got {}".format(type(value))) def load_config(config_file): """Load a SONATA circuit or simulation config file This performs substitutions for the variables in the "manifest" section, then returns the config as a dict. """ with open(config_file) as fp: config = json.load(fp) # Build substitutions from manifest substitutions = {} for key, value in config["manifest"].items(): if key.startswith('$'): key = key[1:] substitutions[key] = value for key, value in substitutions.items(): if '$' in value: substitutions[key] = Template(value).substitute(**substitutions) # Perform substitutions def traverse(obj): if isinstance(obj, dict): return {k: traverse(v) for k, v in obj.items()} elif isinstance(obj, list): return [traverse(elem) for elem in obj] else: if isinstance(obj, str) and obj.startswith('$'): return Template(obj).substitute(**substitutions) else: return obj return traverse(config) # ----- public functions ----------------------------------------------------- def export_to_sonata(network, output_path, target="PyNN", overwrite=False): """Export a PyNN network in SONATA format If `target` is "neuron" or "NEURON", then PyNN cell types are exported as single compartment neurons with NEURON .mod files, and only StaticSynapses are supported. If `target` is "PyNN", then the names of PyNN standard cells and synapse models are used in the exported files. This allows simulations with any PyNN-supported simulator. If `target` is "nest" or "NEST", then the names of NEST cell and synapse models are used in the exported files. A "node group" in SONATA corresponds approximately to a PyNN Population or PopulationView. A "node population" in SONATA corresponds approximately to a PyNN Assembly, except that node populations must be disjoint, whereas a given neuron may be in more than one PyNN Assembly (it is PyNN Populations that are disjoint). In this first version of the export, we simplify this by exporting each Population as an implicit Assembly with a single member, at the cost of losing some information about the PyNN network structure. This approach could be improved in future. """ if not HAVE_H5PY: raise Exception("You need to install h5py to use SONATA") # --- define directory layout --- config = { "target_simulator": target, "manifest": { "$BASE_DIR": "{}".format(output_path), "$NETWORK_DIR": "$BASE_DIR/networks", "$COMPONENT_DIR": "$BASE_DIR/components" }, "components": { "morphologies_dir": "$COMPONENT_DIR/morphologies", "synaptic_models_dir": "$COMPONENT_DIR/synapse_dynamics", "point_neuron_models_dir": "$COMPONENT_DIR/point_neuron_dynamics", "mechanisms_dir": "$COMPONENT_DIR/mechanisms", "biophysical_neuron_models_dir": "$COMPONENT_DIR/biophysical_neuron_dynamics", "templates": "$COMPONENT_DIR/hoc_templates", }, "networks": { "nodes": [], "edges": [] } } base_dir = config["manifest"]["$BASE_DIR"] network_dir = Template(config["manifest"]["$NETWORK_DIR"]).substitute(BASE_DIR=base_dir) component_dir = Template(config["manifest"]["$COMPONENT_DIR"]).substitute(BASE_DIR=base_dir) for directory in (base_dir, network_dir, component_dir): if exists(directory) and isdir(directory) and overwrite: shutil.rmtree(directory) os.makedirs(directory) # --- export neuronal morphologies --- # - not necessary for the current version of PyNN # --- export NMODL files for neuron and synapse models --- # - note that this assumes that plasticity mechanisms are # implemented as part of the synapse model rather than as # separate weight-adjuster mechanisms # - also note that future versions of this format may # allow exporting mechanisms in LEMS format if target.lower() == "neuron": raise NotImplementedError # --- export biophysical neuron channel distribution --- # - not necessary for the current version of PyNN # --- export nodes --- # - we define a separate SONATA node population for each PyNN Population # - we may in future use node groups and/or node types to support PopulationViews # - Assemblies are not explicitly represented in the SONATA structure, # rather their constituent Populations are exported individually. for i, population in enumerate(network.populations): config["networks"]["nodes"].append({ "nodes_file": "$NETWORK_DIR/nodes_{}.h5".format(population.label), "node_types_file": "$NETWORK_DIR/node_types_{}.csv".format(population.label) }) node_type_path = Template(config["networks"]["nodes"][i] ["node_types_file"]).substitute(NETWORK_DIR=network_dir) nodes_path = Template(config["networks"]["nodes"][i] ["nodes_file"]).substitute(NETWORK_DIR=network_dir) n = population.size population_label = asciify(population.label) csv_rows = [] csv_columns = set() node_type_info = { "node_type_id": i, } if "SpikeSource" in population.celltype.__class__.__name__: node_type_info["model_type"] = "virtual" else: if target.lower() == "neuron": node_type_info["model_type"] = "single_compartment" elif target.lower() in ("pynn", "nest"): node_type_info["model_type"] = "point_neuron" node_type_info["model_template"] = "{}:{}".format(target.lower(), population.celltype.__class__.__name__) else: raise NotImplementedError group_label = 0 # "default" # todo: add "population" column # write HDF5 file nodes_file = h5py.File(nodes_path, 'w') # ??? unclear what is the required format or the current version! nodes_file.attrs["version"] = (0, 1) nodes_file.attrs["magic"] = MAGIC # needs to be uint32 root = nodes_file.create_group("nodes") # todo: add attribute with network name # we use a single node group for the full Population default = root.create_group(population_label) # todo: check and fix the dtypes in the following default.create_dataset("node_id", data=population.all_cells.astype('i4'), dtype='i4') default.create_dataset("node_type_id", data=i * np.ones((n,)), dtype='i2') default.create_dataset("node_group_id", data=np.array( [group_label] * n), dtype='i2') # todo: calculate the max label size # required data type not specified. Optional? default.create_dataset("node_group_index", data=np.arange(n, dtype=int), dtype='i2') # parameters node_group = default.create_group(str(group_label)) node_params_group = node_group.create_group("dynamics_params") for parameter_name in population.celltype.default_parameters: if parameter_name == "spike_times": warn("spike times should be added manually to simulation_config") else: # we could make a single get call to get all params at once, at the expense # of higher memory usage. To profile... values = population.get(parameter_name, gather=True, simplify=True) if isinstance(values, np.ndarray): # array, put into the HDF5 file and put 'NONE' in the CSV file node_params_group.create_dataset(parameter_name, data=values) node_type_info[parameter_name] = "NONE" else: # scalar, put into the CSV file node_type_info[parameter_name] = values # positions in space x, y, z = population.positions node_group.create_dataset('x', data=x) node_group.create_dataset('y', data=y) node_group.create_dataset('z', data=z) csv_rows.append(node_type_info) csv_columns.update(node_type_info.keys()) nodes_file.close() # now write csv file field_names = sorted(set.union(*(set(row) for row in csv_rows)) ) # todo: `node_type_id` must be first with open(node_type_path, 'w') as csv_file: csv_writer = csv.DictWriter(csv_file, fieldnames=field_names, delimiter=' ', quotechar='"') csv_writer.writeheader() for row in csv_rows: logger.info(row) for column_name in csv_columns: if column_name not in row: row[column_name] = "NONE" csv_writer.writerow(row) # --- export edges --- # - we define a separate group and edge-type for each PyNN Projection for i, projection in enumerate(network.projections): projection_label = asciify(projection.label) config["networks"]["edges"].append({ "edges_file": "$NETWORK_DIR/edges_{}.h5".format(projection_label), "edge_types_file": "$NETWORK_DIR/edge_types_{}.csv".format(projection_label) }) edge_type_path = Template(config["networks"]["edges"][i] ["edge_types_file"]).substitute(NETWORK_DIR=network_dir) edges_path = Template(config["networks"]["edges"][i] ["edges_file"]).substitute(NETWORK_DIR=network_dir) n = projection.size() csv_rows = [] edge_type_info = { "edge_type_id": i, "model_template": "{}:{}".format(target.lower(), projection.synapse_type.__class__.__name__), "receptor_type": projection.receptor_type } parameter_names = list(projection.synapse_type.default_parameters) values = np.array( projection.get(parameter_names, format="list", gather=True, with_address=True) ) source_index = values[:, 0].astype(int) target_index = values[:, 1].astype(int) source_gids = projection.pre.all_cells[source_index].astype('i4') target_gids = projection.post.all_cells[target_index].astype('i4') group_label = 0 # "default" # Write HDF5 file edges_file = h5py.File(edges_path, 'w') root = edges_file.create_group("edges") # todo: add attribute with network name default_edge_pop = root.create_group(projection_label) default_edge_pop.create_dataset("source_node_id", data=source_gids, dtype='i4') default_edge_pop.create_dataset("target_node_id", data=target_gids, dtype='i4') default_edge_pop["source_node_id"].attrs["node_population"] = asciify( projection.pre.label) # todo: handle PopualtionViews default_edge_pop["target_node_id"].attrs["node_population"] = asciify( projection.post.label) default_edge_pop.create_dataset("edge_type_id", data=i * np.ones((n,)), dtype='i2') # S32') # todo: calculate the max label size default_edge_pop.create_dataset( "edge_group_id", data=np.array([group_label] * n), dtype='i2') default_edge_pop.create_dataset( "edge_group_index", data=np.arange(n, dtype=int), dtype='i2') edge_group = default_edge_pop.create_group(str(group_label)) edge_params = edge_group.create_group("dynamics_params") for j, parameter_name in zip(range(2, values.shape[1]), parameter_names): if isinstance(values[:, j], np.ndarray): # array, put into the HDF5 file edge_params.create_dataset(parameter_name, data=values[:, j]) else: # scalar, put into the CSV file # for now, this will never be the case - need to detect homogeneous case edge_type_info[parameter_name] = values[:, j] csv_rows.append(edge_type_info) # todo: add receptor_type to csv_rows edges_file.close() # now write csv file field_names = sorted(set.union(*(set(row) for row in csv_rows))) with open(edge_type_path, 'w') as csv_file: csv_writer = csv.DictWriter(csv_file, fieldnames=field_names, delimiter=' ', quotechar='"') csv_writer.writeheader() for row in csv_rows: csv_writer.writerow(row) # --- write the config file --- with open(join(output_path, "circuit_config.json"), "w") as fp: json.dump(config, fp, indent=2) # --- export recording configuration --- # todo def import_from_sonata(config_file, sim): """ We map a SONATA population to a PyNN Assembly, since both allow heterogeneous cell types. We map a SONATA node group to a PyNN Population, since both have homogeneous parameter namespaces. SONATA node types are used to give different parameters to different subsets of nodes in a group. This can be handled in PyNN by indexing and, equivalently, by defining PopulationViews. We map a SONATA edge group to a PyNN Projection, i.e. a SONATA edge population may result in multiple PyNN Projections. """ if not HAVE_H5PY: raise Exception("You need to install h5py to use SONATA") config = load_config(config_file) if config.get("target_simulator", None) not in ("PyNN", "NEST"): warn("`target_simulator` is not set to 'PyNN' or 'NEST'. Proceeding with caution...") sonata_node_populations = [] for nodes_config in config["networks"]["nodes"]: # Load node types into node_types_map node_types_map = read_types_file(nodes_config["node_types_file"], 'node') # Open nodes file, check it is valid nodes_file = h5py.File(nodes_config["nodes_file"], 'r') version = nodes_file.attrs.get("version", None) magic = nodes_file.attrs.get("magic", None) if magic is not None and magic != MAGIC: # for now we assume that not all SONATA files will have the magic attribute set raise Exception("Invalid SONATA file") # Read data about node populations and groups sonata_node_populations.extend([ NodePopulation.from_data(np_label, np_data, node_types_map, config) for np_label, np_data in nodes_file["nodes"].items() ]) sonata_edge_populations = [] if "edges" in config["networks"]: for edges_config in config["networks"]["edges"]: # Load edge types into edge_types_map edge_types_map = read_types_file(edges_config["edge_types_file"], 'edge') # Open edges file, check it is valid edges_file = h5py.File(edges_config["edges_file"], 'r') version = edges_file.attrs.get("version", None) magic = edges_file.attrs.get("magic", None) if magic is not None and magic != MAGIC: # for now we assume that not all SONATA files will have the magic attribute set raise Exception("Invalid SONATA file") # Read data about edge populations and groups sonata_edge_populations.extend([ EdgePopulation.from_data(ep_label, ep_data, edge_types_map, config) for ep_label, ep_data in edges_file["edges"].items() ]) # Now map the SONATA data structures to PyNN ones net = Network() for node_population in sonata_node_populations: assembly = node_population.to_assembly(sim) net.add(assembly) for edge_population in sonata_edge_populations: projections = edge_population.to_projections(net, sim) net.add(*projections) return net def load_sonata_simulation_plan(config_file): """Create a simulation plan (what to record, etc.) from a simulation config file.""" config = load_config(config_file) plan = SimulationPlan(**config) return plan # ----- internal API --------------------------------------------------------- # The following classes are not intended to be instantiated directly, but # are used internally by the public functions. class NodePopulation(object): """Representation of a SONATA node population""" @classmethod def from_data(cls, name, h5_data, node_types_map, config): """Create a NodePopulation instance, containing one or more NodeGroups, from data. Arguments --------- name : string Taken from the SONATA nodes HDF5 file. h5_data : HDF5 Group The "/nodes/" group. node_types_map : dict Data loaded from node types CSV file. Top-level keys are node type ids. config : dict Circuit config loaded from JSON. """ obj = cls() obj.name = name obj.node_groups = [] obj.node_ids = h5_data['node_id'] node_group_ids = h5_data['node_group_id'][()] for ng_label in np.unique(node_group_ids): mask = node_group_ids == ng_label logger.info("NODE GROUP {}, size {}".format(ng_label, mask.sum())) node_type_array = h5_data['node_type_id'][mask] node_group_index = h5_data['node_group_index'][mask].tolist() obj.node_groups.append( NodeGroup.from_data(ng_label, node_type_array, node_group_index, h5_data[str(ng_label)], node_types_map, config) ) # todo: handle spatial structure - h5_data['x'], etc. return obj def __repr__(self): return "NodePopulation(name='{}', node_groups={})".format(self.name, self.node_groups) def to_assembly(self, sim): """Create a PyNN Assembly from this NodePopulation. The Assembly will contain one Population for each NodeGroup. """ assembly = sim.Assembly(label=self.name) assembly.annotations["first_sonata_id"] = self.node_ids[()].min() for node_group in self.node_groups: pop = node_group.to_population(sim) assembly += pop return assembly class NodeGroup(object): """Representation of a SONATA node population""" def __len__(self): return self.node_types_array.size @property def size(self): return len(self) @classmethod def from_data(cls, id, node_types_array, index, h5_data, node_types_map, config): """Create a NodeGroup instance from data. Arguments --------- id : integer Taken from the SONATA nodes HDF5 file. node_types_array : NumPy array Subset of the data from "/nodes//node_type_id" that applies to this group. index : list Subset of the data from "/nodes//node_group_index" that applies to this group. h5_data : HDF5 Group The "/nodes//" group. node_types_map : dict Data loaded from node types CSV file. Top-level keys are node type ids. config : dict Circuit config loaded from JSON. """ obj = cls() obj.id = id obj.node_types_array = node_types_array parameters = defaultdict(dict) node_type_ids = np.unique(node_types_array) # parameters defined directly in node_types csv file for node_type_id in node_type_ids: for name, value in node_types_map[node_type_id].items(): parameters[name][node_type_id] = cast(value) # parameters defined in json files referenced from node_types.csv if "dynamics_params" in parameters: for node_type_id in node_type_ids: parameter_file_name = parameters["dynamics_params"][node_type_id] parameter_file_path = join(config["components"]["point_neuron_models_dir"], parameter_file_name) with open(parameter_file_path) as fp: dynamics_params = json.load(fp) for name, value in dynamics_params.items(): parameters[name][node_type_id] = value # parameters defined in .h5 files if 'dynamics_params' in h5_data: dynamics_params_group = h5_data['dynamics_params'] # not sure the next bit is using `index` correctly for key in dynamics_params_group.keys(): parameters[key] = dynamics_params_group[key][index] obj.parameters = parameters obj.config = config logger.info(parameters) return obj def __repr__(self): return "NodeGroup(id='{}', parameters={})".format(self.id, self.parameters) def get_cell_type(self, sim): """Determine which PyNN cell type to use, and return its class.""" cell_types = set() model_types = self.parameters["model_type"] for node_type_id, model_type in model_types.items(): if model_type not in ("point_neuron", "point_process", "virtual"): raise NotImplementedError("Only point neurons currently supported.") if model_type == "virtual": if self.config.get("target_simulator") == "NEST": cell_types.add("nest:spike_generator") else: cell_types.add("pyNN:SpikeSourceArray") else: cell_types.add(self.parameters["model_template"][node_type_id]) if len(cell_types) != 1: raise Exception("Heterogeneous group, not currently supported.") cell_type = cell_types.pop() prefix, cell_type_name = cell_type.split(":") if prefix.lower() not in ("pynn", "nrn", "nest"): raise NotImplementedError("Only PyNN, NEST and NEURON-native networks currently supported, not: %s (from %s)." % (prefix, self.parameters["model_template"][node_type_id])) if prefix.lower() == "nest": cell_type_cls = sim.native_cell_type(cell_type_name) if cell_type_name == "spike_generator": cell_type_cls.uses_parrot = False else: cell_type_cls = getattr(sim, cell_type_name) logger.info(" cell_type: {}".format(cell_type_cls)) return cell_type_cls def to_population(self, sim): """Create a PyNN Population from this NodeGroup.""" cell_type_cls = self.get_cell_type(sim) parameters = {} annotations = {} for name, value in self.parameters.items(): if name in cell_type_cls.default_parameters: parameters[name] = condense(value, self.node_types_array) else: annotations[name] = condense(value, self.node_types_array) # todo: handle spatial structure - nodes_file["nodes"][np_label][ng_label]['x'], etc. # temporary hack to work around problem with 300 Intfire cell example if cell_type_cls.__name__ == 'IntFire1': parameters['tau'] *= 1000.0 parameters['refrac'] *= 1000.0 # end hack cell_type = cell_type_cls(**parameters) pop = sim.Population(self.size, cell_type, label=str(self.id)) pop.annotate(**annotations) logger.info("--------> {}".format(pop)) # todo: create PopulationViews if multiple node_types return pop class EdgePopulation(object): """Representation of a SONATA edge population""" @classmethod def from_data(cls, name, h5_data, edge_types_map, config): """Create an EdgePopulation instance, containing one or more EdgeGroups, from data. Arguments --------- name : string Taken from the SONATA edges HDF5 file. h5_data : HDF5 Group The "/edges/" group. edge_types_map : dict Data loaded from edge types CSV file. Top-level keys are edge type ids. config : dict Circuit config loaded from JSON. """ obj = cls() obj.name = name obj.source_node_ids = h5_data["source_node_id"][()] obj.source_node_population = to_string(h5_data["source_node_id"].attrs["node_population"]) obj.target_node_ids = h5_data["target_node_id"][()] obj.target_node_population = to_string(h5_data["target_node_id"].attrs["node_population"]) obj.edge_groups = [] for eg_label in np.unique(h5_data['edge_group_id'][()]): mask = h5_data['edge_group_id'][()] == eg_label logger.info("EDGE GROUP {}, size {}".format(eg_label, mask.sum())) edge_type_array = h5_data['edge_type_id'][mask] edge_group_index = h5_data['edge_group_index'][mask].tolist() source_ids = obj.source_node_ids[mask] target_ids = obj.target_node_ids[mask] # note: it may be more efficient in an MPI context # to defer the extraction of source_ids, etc. obj.edge_groups.append( EdgeGroup.from_data(eg_label, edge_type_array, edge_group_index, source_ids, target_ids, h5_data[str(eg_label)], edge_types_map, config) ) return obj def __repr__(self): return "EdgePopulation(name='{}', edge_groups={})".format(self.name, self.edge_groups) def to_projections(self, net, sim): """Create a list of PyNN Projections from this EdgePopulation.""" pre = net.get_component(self.source_node_population) post = net.get_component(self.target_node_population) projections = [] for edge_group in self.edge_groups: projection = edge_group.to_projection(pre, post, self.name, sim) projections.append(projection) return projections class EdgeGroup(object): """Representation of a SONATA edge group.""" @classmethod def from_data(cls, id, edge_types_array, index, source_ids, target_ids, h5_data, edge_types_map, config): """Create an EdgeGroup instance from data. Arguments --------- id : integer Taken from the SONATA edges HDF5 file. node_types_array : NumPy array Subset of the data from "/edges//edge_type_id" that applies to this group. index : list Subset of the data from "/edges//edge_group_index" that applies to this group. h5_data : HDF5 Group The "/edges//" group. edge_types_map : dict Data loaded from edge types CSV file. Top-level keys are edge type ids. config : dict Circuit config loaded from JSON. """ obj = cls() obj.id = id obj.edge_types_array = edge_types_array obj.source_ids = source_ids obj.target_ids = target_ids parameters = defaultdict(dict) edge_type_ids = np.unique(edge_types_array) # parameters defined directly in edge_types csv file for edge_type_id in edge_type_ids: for name, value in edge_types_map[edge_type_id].items(): parameters[name][edge_type_id] = cast(value) # parameters defined in json files referenced from edge_types.csv if "dynamics_params" in parameters: for edge_type_id in edge_type_ids: parameter_file_name = parameters["dynamics_params"][edge_type_id] parameter_file_path = join(config["components"]["synaptic_models_dir"], parameter_file_name) with open(parameter_file_path) as fp: dynamics_params = json.load(fp) for name, value in dynamics_params.items(): parameters[name][edge_type_id] = value # parameters defined in .h5 files if 'dynamics_params' in h5_data: dynamics_params_group = h5_data['dynamics_params'] # not sure the next bit is using `index` correctly for key in dynamics_params_group.keys(): parameters[key] = dynamics_params_group[key][index] if 'nsyns' in h5_data: parameters['nsyns'] = h5_data['nsyns'][index] if 'syn_weight' in h5_data: parameters['syn_weight'] = h5_data['syn_weight'][index] obj.parameters = parameters obj.config = config logger.info(parameters) return obj def __repr__(self): return "EdgeGroup(id='{}', parameters={})".format(self.id, self.parameters) def get_synapse_and_receptor_type(self, sim): """Determine which PyNN synapse and receptor type to use. Returns the synapse type class, and the receptor type label.""" synapse_types = set() model_templates = self.parameters.get("model_template", None) if model_templates: for edge_type_id, model_template in model_templates.items(): synapse_types.add(model_template) if len(synapse_types) != 1: raise Exception("Heterogeneous group, not currently supported.") synapse_type = synapse_types.pop() prefix, synapse_type_name = model_template.split(":") if prefix.lower() not in ("pynn", "nrn", "nest"): raise NotImplementedError( "Only PyNN, NEST and NEURON-native networks currently supported.") else: prefix = "pyNN" synapse_type_name = "StaticSynapse" if prefix == "nest": synapse_type_cls = sim.native_synapse_type(synapse_type_name) else: synapse_type_cls = getattr(sim, synapse_type_name) receptor_types = self.parameters.get("receptor_type", None) if receptor_types: receptor_types = set(receptor_types.values()) if len(receptor_types) != 1: raise Exception("Heterogeneous receptor types, not currently supported.") # but should be, since SONATA egde groups can contain mixed excitatory # and inhibitory connections. # Would need to split into separate PyNN Projections, in this case. receptor_type = receptor_types.pop() else: receptor_type = "default" # temporary hack to make 300-cell example work, due to PyNN bug #597 # value should really be None. logger.info(" synapse_type: {}".format(synapse_type_cls)) logger.info(" receptor_type: {}".format(receptor_type)) return synapse_type_cls, receptor_type def to_projection(self, pre, post, edge_population_name, sim): """Create a PyNN Projection from this EdgeGroup. Arguments --------- pre - PyNN Assembly The assembly created from the pre-synaptic node population. post - PyNN Assembly The assembly created from the post-synaptic node population. edge_population_name - string Name of the edge population containing this edge group. """ synapse_type_cls, receptor_type = self.get_synapse_and_receptor_type(sim) parameters = {} annotations = {} for name, value in self.parameters.items(): if name in synapse_type_cls.default_parameters: parameters[name] = condense(value, self.edge_types_array) elif name == "syn_weight": parameters["weight"] = condense(value, self.edge_types_array) else: annotations[name] = value if "sign" in annotations: # special case from the 300 IF example, not mentioned in the SONATA spec # nor in the .mod file for IntFire1 sign = condense(annotations["sign"], self.edge_types_array) parameters["weight"] *= sign if "nsyns" in annotations: # special case (?) from the 300 IF example, not mentioned in the SONATA spec # nor in the .mod file for IntFire1 nsyns = condense(annotations["nsyns"], self.edge_types_array) parameters["weight"] *= nsyns conn_list_args = [ sonata_id_to_index(pre, self.source_ids), sonata_id_to_index(post, self.target_ids) ] column_names = [] synapse_type_parameters = {} for name, value in parameters.items(): if isinstance(value, (np.ndarray)): column_names.append(name) conn_list_args.append(value) else: synapse_type_parameters[name] = value conn_list = np.array(conn_list_args).transpose() connector = sim.FromListConnector(conn_list, column_names=column_names) syn = synapse_type_cls(**synapse_type_parameters) prj = sim.Projection(pre, post, connector, syn, receptor_type=receptor_type, label="{}-{}".format(edge_population_name, self.id)) prj.annotate(**annotations) logger.info("--------> {}".format(prj)) return prj class SimulationPlan(object): """ """ def __init__(self, run, inputs=None, output=None, reports=None, target_simulator=None, node_sets_file=None, conditions=None, manifest=None, **additional_sections): self.run_config = run self.inputs = inputs self.output = output self.reports = reports self.target_simulator = target_simulator self.node_sets_file = node_sets_file self.conditions = conditions self.manifest = manifest self.additional_sections = additional_sections if self.inputs is None: self.inputs = {} if self.reports is None: self.reports = {} if self.node_sets_file is not None: with open(self.node_sets_file) as fp: self.node_sets = json.load(fp) # make all node set names lower case, needed by 300 IF neuron example self.node_sets = {k.lower(): v for k, v in self.node_sets.items()} # todo: handle compound node sets def setup(self, sim): self.sim = sim sim.setup(timestep=self.run_config["dt"]) def _get_target(self, config, net): if "node_set" in config: # input config targets = self.node_set_map[config["node_set"]] elif "cells" in config: # recording config # inconsistency in SONATA spec? Why not call this "node_set" also? targets = self.node_set_map[config["cells"]] return targets def _set_input_spikes(self, input_config, net): # determine which assembly the spikes are for targets = self._get_target(input_config, net) if len(targets) != 1: raise NotImplementedError() base_assembly, mask = targets[0] assembly = base_assembly[mask] assert isinstance(assembly, self.sim.Assembly) # load spike data from file if input_config["module"] != "h5": raise NotImplementedError() io = SonataIO(base_dir="", spikes_file=input_config["input_file"]) data = io.read() assert len(data) == 1 if "trial" in input_config: raise NotImplementedError() # assuming we can map trials to segments assert len(data[0].segments) == 1 spiketrains = data[0].segments[0].spiketrains if len(spiketrains) != assembly.size: raise NotImplementedError() # todo: map cell ids in spikes file to ids/index in the population assembly.set(spike_times=[Sequence(st.times.rescale('ms').magnitude) for st in spiketrains]) def _set_input_currents(self, input_config, net): # determine which assembly the currents are for if "input_file" in input_config: raise NotImplementedError("Current clamp from source file not yet supported.") targets = self._get_target(input_config, net) if len(targets) != 1: raise NotImplementedError() base_assembly, mask = targets[0] assembly = base_assembly[mask] assert isinstance(assembly, self.sim.Assembly) amplitude = input_config["amp"] # nA if self.target_simulator == "NEST": amplitude = input_config["amp"]/1000.0 # pA current_source = self.sim.DCSource(amplitude=amplitude, start=input_config["delay"], stop=input_config["delay"] + input_config["duration"]) assembly.inject(current_source) def _calculate_node_set_map(self, net): # for each "node set" in the config, determine which populations # and node_ids it corresponds to self.node_set_map = {} # first handle implicit node sets - i.e. each node population is an implicit node set for assembly in net.assemblies: self.node_set_map[assembly.label] = [(assembly, slice(None))] # now handle explictly-declared node sets # todo: handle compound node sets for node_set_name, node_set_definition in self.node_sets.items(): if isinstance(node_set_definition, dict): # basic node set filters = node_set_definition if "population" in filters: assemblies = [net.get_component(filters["population"])] else: assemblies = list(net.assemblies) self.node_set_map[node_set_name] = [] for assembly in assemblies: mask = True for attr_name, attr_value in filters.items(): print(attr_name, attr_value, "____") if attr_name == "population": continue elif attr_name == "node_id": # convert integer mask to boolean mask node_mask = np.zeros(assembly.size, dtype=bool) node_mask[attr_value] = True mask = np.logical_and(mask, node_mask) else: values = assembly.get_annotations(attr_name)[attr_name] mask = np.logical_and(mask, values == attr_value) if isinstance(mask, (bool, np.bool_)) and mask == True: mask = slice(None) self.node_set_map[node_set_name].append((assembly, mask)) elif isinstance(node_set_definition, list): # compound node set raise NotImplementedError("Compound node sets not yet supported") else: raise TypeError("Expecting node set definition to be a list or dict") def execute(self, net): self._calculate_node_set_map(net) # create/configure inputs for input_name, input_config in self.inputs.items(): if input_config["input_type"] == "spikes": self._set_input_spikes(input_config, net) elif input_config["input_type"] == "current_clamp": self._set_input_currents(input_config, net) else: raise NotImplementedError("Only 'spikes' and 'current_clamp' supported") # configure recording # SONATA requires that we record spikes from all non-virtual nodes net.record('spikes', include_spike_source=False) for report_name, report_config in self.reports.items(): targets = self._get_target(report_config, net) for (base_assembly, mask) in targets: assembly = base_assembly[mask] assembly.record(report_config["variable_name"]) # run simulation self.sim.run(self.run_config["tstop"]) # write output if "overwrite_output_dir" in self.run_config: directory = self.output["output_dir"] if exists(directory) and isdir(directory): shutil.rmtree(directory) os.makedirs(directory) io = SonataIO(self.output["output_dir"], spikes_file=self.output.get("spikes_file", "spikes.h5"), spikes_sort_order=self.output["spikes_sort_order"], report_config=self.reports, node_sets=self.node_set_map) # todo: handle reports net.write_data(io) @classmethod def from_config(cls, config): obj = cls(**config) return obj PyNN-0.10.0/pyNN/space.py000066400000000000000000000340021415343567000147540ustar00rootroot00000000000000# encoding: utf-8 """ Tools for performing spatial/topographical calculations. Classes: Space - representation of a Cartesian space for use in calculating distances Line - represents a structure with neurons distributed evenly on a straight line. Grid2D - represents a structure with neurons distributed on a 2D grid. Grid3D - represents a structure with neurons distributed on a 3D grid. RandomStructure - represents a structure with neurons distributed randomly within a given volume. Cuboid - representation of a cuboidal volume, for use with RandomStructure. Sphere - representation of a spherical volume, for use with RandomStructure. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ # There must be some Python package out there that provides most of this stuff. # Distance computations are provided by scipy.spatial, but scipy is a fairly heavy dependency. from functools import reduce import math from operator import and_ import logging import numpy as np from pyNN.random import NumpyRNG from pyNN import descriptions logger = logging.getLogger("PyNN") def distance(src, tgt, mask=None, scale_factor=1.0, offset=0.0, periodic_boundaries=None): # may need to add an offset parameter """ Return the Euclidian distance between two cells. `mask` allows only certain dimensions to be considered, e.g.:: * to ignore the z-dimension, use `mask=array([0,1])` * to ignore y, `mask=array([0,2])` * to just consider z-distance, `mask=array([2])` `scale_factor` allows for different units in the pre- and post- position (the post-synaptic position is multipied by this quantity). """ d = src.position - scale_factor * (tgt.position + offset) if periodic_boundaries is not None: d = np.minimum(abs(d), periodic_boundaries - abs(d)) if mask is not None: d = d[mask] return np.sqrt(np.dot(d, d)) class Space(object): """ Class representing a space within distances can be calculated. The space is Cartesian, may be 1-, 2- or 3-dimensional, and may have periodic boundaries in any of the dimensions. Arguments: axes: if not supplied, then the 3D distance is calculated. If supplied, axes should be a string containing the axes to be used, e.g. 'x', or 'yz'. axes='xyz' is the same as axes=None. scale_factor: it may be that the pre and post populations use different units for position, e.g. degrees and µm. In this case, `scale_factor` can be specified, which is applied to the positions in the post-synaptic population. offset: if the origins of the coordinate systems of the pre- and post- synaptic populations are different, `offset` can be used to adjust for this difference. The offset is applied before any scaling. periodic_boundaries: either `None`, or a tuple giving the boundaries for each dimension, e.g. `((x_min, x_max), None, (z_min, z_max))`. """ AXES = {'x': [0], 'y': [1], 'z': [2], 'xy': [0, 1], 'yz': [1, 2], 'xz': [0, 2], 'xyz': range(3), None: range(3)} def __init__(self, axes=None, scale_factor=1.0, offset=0.0, periodic_boundaries=None): """ """ self.periodic_boundaries = periodic_boundaries self.axes = np.array(Space.AXES[axes]) self.scale_factor = scale_factor self.offset = offset def distances(self, A, B, expand=False): """ Calculate the distance matrix between two sets of coordinates, given the topology of the current space. From http://projects.scipy.org/pipermail/numpy-discussion/2007-April/027203.html """ #logger.debug("Calculating distance between A (shape=%s) and B (shape=%s)" % (A.shape, B.shape)) assert A.ndim <= 2 assert B.ndim <= 2 assert A.shape[-1] == 3 if len(A.shape) == 1: A = A.reshape(1, 3) if len(B.shape) == 1: B = B.reshape(1, 3) B = self.scale_factor * (B + self.offset) d = np.zeros((len(self.axes), A.shape[0], B.shape[0]), dtype=A.dtype) for i, axis in enumerate(self.axes): diff2 = A[:, None, axis] - B[:, axis] if self.periodic_boundaries is not None: boundaries = self.periodic_boundaries[axis] if boundaries is not None: range = boundaries[1] - boundaries[0] ad2 = abs(diff2) diff2 = np.minimum(ad2, range - ad2) diff2 **= 2 d[i] = diff2 if not expand: d = np.sum(d, 0) np.sqrt(d, d) return d.flatten() def distance_generator(self, f, g): def distance_map(i, j): shape = [] if isinstance(i, np.ndarray) and i.ndim == 2: i = i[:, 0] shape.append(i.size) if isinstance(j, np.ndarray) and j.ndim == 2: j = j[0, :] shape.append(j.size) d = self.distances(f(i), g(j)) if shape: return d.reshape(shape) else: return d return distance_map class BaseStructure(object): def __repr__(self): return "%s(%s)" % (self.__class__.__name__, ", ".join("%s=%r" % item for item in self.get_parameters().items())) def __eq__(self, other): return reduce(and_, (getattr(self, attr) == getattr(other, attr) for attr in self.parameter_names)) def get_parameters(self): """Return a dict containing the parameters of the :class:`Structure`.""" P = {} for name in self.parameter_names: P[name] = getattr(self, name) return P def describe(self, template='structure_default.txt', engine='default'): """ Returns a human-readable description of the network structure. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = {'name': self.__class__.__name__, 'parameters': self.get_parameters()} return descriptions.render(engine, template, context) def generate_positions(self, n): """ Calculate and return the positions of `n` neurons positioned according to this structure. """ raise NotImplementedError class Line(BaseStructure): """ Represents a structure with neurons distributed evenly on a straight line. Arguments: `dx`: distance between points in the line. `y`, `z`,: y- and z-coordinates of all points in the line. `x0`: x-coordinate of the first point in the line. """ parameter_names = ("dx", "x0", "y", "z") def __init__(self, dx=1.0, x0=0.0, y=0.0, z=0.0): self.dx = dx self.x0 = x0 self.y = y self.z = z def generate_positions(self, n): x = self.dx * np.arange(n, dtype=float) + self.x0 y = np.zeros(n) + self.y z = np.zeros(n) + self.z return np.array((x, y, z)) generate_positions.__doc__ = BaseStructure.generate_positions.__doc__ class Grid2D(BaseStructure): """ Represents a structure with neurons distributed on a 2D grid. Arguments: `dx`, `dy`: distances between points in the x, y directions. `x0`, `y0`: coordinates of the starting corner of the grid. `z`: the z-coordinate of all points in the grid. `aspect_ratio`: ratio of the number of grid points per side (not the ratio of the side lengths, unless ``dx == dy``) `fill_order`: may be 'sequential' or 'random' """ parameter_names = ("aspect_ratio", "dx", "dy", "x0", "y0", "z", "fill_order") def __init__(self, aspect_ratio=1.0, dx=1.0, dy=1.0, x0=0.0, y0=0.0, z=0, fill_order="sequential", rng=None): self.aspect_ratio = aspect_ratio assert fill_order in ('sequential', 'random') self.fill_order = fill_order self.rng = rng self.dx = dx self.dy = dy self.x0 = x0 self.y0 = y0 self.z = z def calculate_size(self, n): """docstring goes here""" nx = math.sqrt(n * self.aspect_ratio) if not math.isclose(n % nx, 0, rel_tol=1e-12, abs_tol=1e-12): raise Exception(f"Invalid size: n={n}, nx={nx}") nx = int(round(nx)) ny = n // nx return nx, ny def generate_positions(self, n): nx, ny = self.calculate_size(n) x, y, z = np.indices((nx, ny, 1), dtype=float) x = self.x0 + self.dx * x.flatten() y = self.y0 + self.dy * y.flatten() z = self.z + z.flatten() # use column_stack, if we decide to switch from (3,n) to (n,3) positions = np.array((x, y, z)) if self.fill_order == 'sequential': return positions else: # random if self.rng is None: self.rng = NumpyRNG() return self.rng.permutation(positions.T).T generate_positions.__doc__ = BaseStructure.generate_positions.__doc__ class Grid3D(BaseStructure): """ Represents a structure with neurons distributed on a 3D grid. Arguments: `dx`, `dy`, `dz`: distances between points in the x, y, z directions. `x0`, `y0`. `z0`: coordinates of the starting corner of the grid. `aspect_ratioXY`, `aspect_ratioXZ`: ratios of the number of grid points per side (not the ratio of the side lengths, unless ``dx == dy == dz``) `fill_order`: may be 'sequential' or 'random'. If `fill_order` is 'sequential', the z-index will be filled first, then y then x, i.e. the first cell will be at (0,0,0) (given default values for the other arguments), the second at (0,0,1), etc. """ parameter_names = ("aspect_ratios", "dx", "dy", "dz", "x0", "y0", "z0", "fill_order") def __init__(self, aspect_ratioXY=1.0, aspect_ratioXZ=1.0, dx=1.0, dy=1.0, dz=1.0, x0=0.0, y0=0.0, z0=0, fill_order="sequential", rng=None): self.aspect_ratios = (aspect_ratioXY, aspect_ratioXZ) assert fill_order in ('sequential', 'random') self.fill_order = fill_order self.rng = rng self.dx = dx self.dy = dy self.dz = dz self.x0 = x0 self.y0 = y0 self.z0 = z0 def calculate_size(self, n): """docstring goes here""" a, b = self.aspect_ratios nx = int(round(math.pow(n * a * b, 1 / 3.0))) ny = int(round(nx / a)) nz = int(round(nx / b)) assert nx * ny * nz == n, str((nx, ny, nz, nx * ny * nz, n, a, b)) return nx, ny, nz def generate_positions(self, n): nx, ny, nz = self.calculate_size(n) x, y, z = np.indices((nx, ny, nz), dtype=float) x = self.x0 + self.dx * x.flatten() y = self.y0 + self.dy * y.flatten() z = self.z0 + self.dz * z.flatten() positions = np.array((x, y, z)) if self.fill_order == 'sequential': return positions else: if self.rng is None: self.rng = NumpyRNG() return self.rng.permutation(positions.T).T generate_positions.__doc__ = BaseStructure.generate_positions.__doc__ class Shape(object): pass class Cuboid(Shape): """ Represents a cuboidal volume within which neurons may be distributed. Arguments: height: extent in y direction width: extent in x direction depth: extent in z direction """ def __init__(self, width, height, depth): self.height = height self.width = width self.depth = depth def __repr__(self): return "Cuboid(width=%r, height=%r, depth=%r)" % (self.width, self.height, self.depth) def sample(self, n, rng): """Return `n` points distributed randomly with uniform density within the cuboid.""" return 0.5 * rng.uniform(-1, 1, size=(n, 3)) * (self.width, self.height, self.depth) class Sphere(Shape): """ Represents a spherical volume within which neurons may be distributed. """ def __init__(self, radius): Shape.__init__(self) self.radius = radius def __repr__(self): return "Sphere(radius=%r)" % self.radius def sample(self, n, rng): """Return `n` points distributed randomly with uniform density within the sphere.""" # this implementation is wasteful, as it throws away a lot of numbers, # but simple. More efficient implementations welcome. positions = np.empty((n, 3)) i = 0 while i < n: candidate = rng.uniform(-1, 1, size=(1, 3)) if (candidate**2).sum() < 1: positions[i] = candidate i += 1 return self.radius * positions class RandomStructure(BaseStructure): """ Represents a structure with neurons distributed randomly within a given volume. Arguments: `boundary` - a subclass of :class:`Shape`. `origin` - the coordinates (x,y,z) of the centre of the volume. """ parameter_names = ('boundary', 'origin', 'rng') def __init__(self, boundary, origin=(0.0, 0.0, 0.0), rng=None): assert isinstance(boundary, Shape) assert len(origin) == 3 self.boundary = boundary self.origin = origin self.rng = rng or NumpyRNG() def generate_positions(self, n): return (np.array(self.origin) + self.boundary.sample(n, self.rng)).T generate_positions.__doc__ = BaseStructure.generate_positions.__doc__ # what about rotations? PyNN-0.10.0/pyNN/standardmodels/000077500000000000000000000000001415343567000163145ustar00rootroot00000000000000PyNN-0.10.0/pyNN/standardmodels/__init__.py000066400000000000000000000322051415343567000204270ustar00rootroot00000000000000# encoding: utf-8 """ Machinery for implementation of "standard models", i.e. neuron and synapse models that are available in multiple simulators: Functions: build_translations() Classes: StandardModelType StandardCellType ModelNotAvailable STDPWeightDependence STDPTimingDependence :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import errors, models from pyNN.parameters import ParameterSpace import numpy as np from pyNN.core import is_listlike from copy import deepcopy import neo import quantities as pq # ============================================================================== # Standard cells # ============================================================================== def build_translations(*translation_list): """ Build a translation dictionary from a list of translations/transformations. """ translations = {} for item in translation_list: assert 2 <= len(item) <= 4, "Translation tuples must have between 2 and 4 items. Actual content: %s" % str(item) pynn_name = item[0] sim_name = item[1] if len(item) == 2: # no transformation f = pynn_name g = sim_name elif len(item) == 3: # simple multiplicative factor scale_factor = item[2] f = "float(%g)*%s" % (scale_factor, pynn_name) g = "%s/float(%g)" % (sim_name, scale_factor) elif len(item) == 4: # more complex transformation f = item[2] g = item[3] translations[pynn_name] = {'translated_name': sim_name, 'forward_transform': f, 'reverse_transform': g} return translations class StandardModelType(models.BaseModelType): """Base class for standardized cell model and synapse model classes.""" translations = {} extra_parameters = {} @property def native_parameters(self): """ A :class:`ParameterSpace` containing parameter names and values translated from the standard PyNN names and units to simulator-specific ("native") names and units. """ return self.translate(self.parameter_space) def translate(self, parameters, copy=True): """Translate standardized model parameters to simulator-specific parameters.""" if copy: _parameters = deepcopy(parameters) else: _parameters = parameters cls = self.__class__ if parameters.schema != self.get_schema(): raise Exception("Schemas do not match: %s != %s" % (parameters.schema, self.get_schema())) # should replace this with a PyNN-specific exception type native_parameters = {} for name in parameters.keys(): D = self.translations[name] pname = D['translated_name'] if callable(D['forward_transform']): pval = D['forward_transform'](**_parameters) else: try: pval = eval(D['forward_transform'], globals(), _parameters) except NameError as errmsg: raise NameError("Problem translating '%s' in %s. Transform: '%s'. Parameters: %s. %s" % (pname, cls.__name__, D['forward_transform'], parameters, errmsg)) except ZeroDivisionError: raise #pval = 1e30 # this is about the highest value hoc can deal with native_parameters[pname] = pval return ParameterSpace(native_parameters, schema=None, shape=parameters.shape) def reverse_translate(self, native_parameters): """Translate simulator-specific model parameters to standardized parameters.""" cls = self.__class__ standard_parameters = {} for name, D in self.translations.items(): tname = D['translated_name'] if tname in native_parameters.keys(): if callable(D['reverse_transform']): standard_parameters[name] = D['reverse_transform'](**native_parameters) else: try: standard_parameters[name] = eval(D['reverse_transform'], {}, native_parameters) except NameError as errmsg: raise NameError("Problem translating '%s' in %s. Transform: '%s'. Parameters: %s. %s" % (name, cls.__name__, D['reverse_transform'], native_parameters, errmsg)) return ParameterSpace(standard_parameters, schema=self.get_schema(), shape=native_parameters.shape) def simple_parameters(self): """Return a list of parameters for which there is a one-to-one correspondance between standard and native parameter values.""" return [name for name in self.translations if self.translations[name]['forward_transform'] == name] def scaled_parameters(self): """Return a list of parameters for which there is a unit change between standard and native parameter values.""" def scaling(trans): return (not callable(trans)) and ("float" in trans) return [name for name in self.translations if scaling(self.translations[name]['forward_transform'])] def computed_parameters(self): """Return a list of parameters whose values must be computed from more than one other parameter.""" return [name for name in self.translations if name not in self.simple_parameters() + self.scaled_parameters()] def get_native_names(self, *names): """ Return a list of native parameter names for a given model. """ if names: translations = (self.translations[name] for name in names) else: # return all names translations = self.translations.values() return [D['translated_name'] for D in translations] class StandardCellType(StandardModelType, models.BaseCellType): """Base class for standardized cell model classes.""" recordable = ['spikes', 'v', 'gsyn'] receptor_types = ('excitatory', 'inhibitory') always_local = False # override for NEST spike sources class StandardCurrentSource(StandardModelType, models.BaseCurrentSource): """Base class for standardized current source model classes.""" def inject_into(self, cells): """ Inject the current from this source into the supplied group of cells. `cells` may be a :class:`Population`, :class:`PopulationView`, :class:`Assembly` or a list of :class:`ID` objects. """ raise NotImplementedError("Should be redefined in the local simulator electrodes") def __getattr__(self, name): if name == "set": errmsg = "For current sources, set values using the parameter name directly, " \ "e.g. source.amplitude = 0.5, or use 'set_parameters()' " \ "e.g. source.set_parameters(amplitude=0.5)" raise AttributeError(errmsg) try: val = self.__getattribute__(name) except AttributeError: try: val = self.get_parameters()[name] except KeyError: raise errors.NonExistentParameterError(name, self.__class__.__name__, self.get_parameter_names()) return val def __setattr__(self, name, value): if self.has_parameter(name): self.set_parameters(**{name: value}) else: object.__setattr__(self, name, value) def set_parameters(self, copy=True, **parameters): """ Set current source parameters, given as a sequence of parameter=value arguments. """ # if some of the parameters are computed from the values of other # parameters, need to get and translate all parameters computed_parameters = self.computed_parameters() have_computed_parameters = np.any([p_name in computed_parameters for p_name in parameters]) if have_computed_parameters: all_parameters = self.get_parameters() all_parameters.update(parameters) parameters = all_parameters else: parameters = ParameterSpace(parameters, self.get_schema(), (1,)) parameters = self.translate(parameters, copy=copy) self.set_native_parameters(parameters) def get_parameters(self): """Return a dict of all current source parameters.""" parameters = self.get_native_parameters() parameters = self.reverse_translate(parameters) return parameters def set_native_parameters(self, parameters): raise NotImplementedError def get_native_parameters(self): raise NotImplementedError def _round_timestamp(self, value, resolution): # todo: consider using decimals module, since rounding of floating point numbers is so horrible return np.rint(value/resolution) * resolution def get_data(self): """Return the recorded current as a Neo signal object""" t_arr, i_arr = self._get_data() intervals = np.diff(t_arr) if intervals.size > 0 and intervals.max() - intervals.min() < 1e-9: signal = neo.AnalogSignal(i_arr, units="nA", t_start=t_arr[0] * pq.ms, sampling_period=intervals[0] * pq.ms) else: signal = neo.IrregularlySampledSignal(t_arr, i_arr, units="nA", time_units="ms") return signal class ModelNotAvailable(object): """Not available for this simulator.""" def __init__(self, *args, **kwargs): raise NotImplementedError("The %s model is not available for this simulator." % self.__class__.__name__) # ============================================================================== # Synapse Dynamics classes # ============================================================================== def check_weights(weights, projection): # if projection.post is an Assembly, some components might have cond-synapses, others curr, # so need a more sophisticated check here. For now, skipping check and emitting a warning if hasattr(projection.post, "_homogeneous_synapses") and not projection.post._homogeneous_synapses: warnings.warn("Not checking weights due to due mixture of synapse types") if isinstance(weights, np.ndarray): all_negative = (weights <= 0).all() all_positive = (weights >= 0).all() if not (all_negative or all_positive): raise errors.ConnectionError("Weights must be either all positive or all negative") elif np.isreal(weights): all_positive = weights >= 0 all_negative = weights <= 0 else: raise errors.ConnectionError("Weights must be a number or an array of numbers.") if projection.post.conductance_based or projection.receptor_type == 'excitatory': if not all_positive: raise errors.ConnectionError( "Weights must be positive for conductance-based and/or excitatory synapses" ) elif projection.post.conductance_based is False and projection.receptor_type == 'inhibitory': if not all_negative: raise errors.ConnectionError( "Weights must be negative for current-based, inhibitory synapses" ) else: # This should never happen. raise Exception("Can't check weight, conductance status unknown.") def check_delays(delays, projection): min_delay = projection._simulator.state.min_delay max_delay = projection._simulator.state.max_delay if isinstance(delays, np.ndarray): below_max = (delays <= max_delay).all() above_min = (delays >= min_delay).all() in_range = below_max and above_min elif np.isreal(delays): in_range = min_delay <= delays <= max_delay else: raise errors.ConnectionError("Delays must be a number or an array of numbers.") if not in_range: raise errors.ConnectionError("Delay (%s) is out of range [%s, %s]" % (delays, min_delay, max_delay)) class StandardSynapseType(StandardModelType, models.BaseSynapseType): parameter_checks = { 'weight': check_weights, #'delay': check_delays # this needs to be revisited in the context of min_delay = "auto" } def get_schema(self): """ Returns the model schema: i.e. a mapping of parameter names to allowed parameter types. """ base_schema = dict((name, type(value)) for name, value in self.default_parameters.items()) base_schema['delay'] = float # delay has default value None, meaning "use the minimum delay", so we have to correct the auto-generated schema return base_schema class STDPWeightDependence(StandardModelType): """Base class for models of STDP weight dependence.""" def __init__(self, **parameters): StandardModelType.__init__(self, **parameters) class STDPTimingDependence(StandardModelType): """Base class for models of STDP timing dependence (triplets, etc)""" def __init__(self, **parameters): StandardModelType.__init__(self, **parameters) PyNN-0.10.0/pyNN/standardmodels/cells.py000066400000000000000000000556651415343567000200110ustar00rootroot00000000000000""" Definition of default parameters (and hence, standard parameter names) for standard cell models. Plain integrate-and-fire models: IF_curr_exp IF_curr_alpha IF_cond_exp IF_cond_alpha Integrate-and-fire with adaptation: IF_cond_exp_gsfa_grr EIF_cond_alpha_isfa_ista EIF_cond_exp_isfa_ista Integrate-and-fire model for use with the FACETS hardware IF_facets_hardware1 Hodgkin-Huxley model HH_cond_exp Spike sources (input neurons) SpikeSourcePoisson SpikeSourceArray SpikeSourceInhGamma :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import StandardCellType from pyNN.parameters import ArrayParameter, Sequence class IF_curr_alpha(StandardCellType): """ Leaky integrate and fire model with fixed threshold and alpha-function- shaped post-synaptic current. """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 0.1, # Duration of refractory period in ms. 'tau_syn_E': 0.5, # Rise time of the excitatory synaptic alpha function in ms. 'tau_syn_I': 0.5, # Rise time of the inhibitory synaptic alpha function in ms. 'i_offset': 0.0, # Offset current in nA 'v_reset': -65.0, # Reset potential after a spike in mV. 'v_thresh': -50.0, # Spike threshold in mV. } recordable = ['spikes', 'v'] conductance_based = False default_initial_values = { 'v': -65.0, # 'v_rest', 'isyn_exc': 0.0, 'isyn_inh': 0.0, } units = { 'v': 'mV', 'isyn_exc': 'nA', 'isyn_inh': 'nA', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'i_offset': 'nA', 'v_reset': 'mV', 'v_thresh': 'mV', } class IF_curr_exp(StandardCellType): """ Leaky integrate and fire model with fixed threshold and decaying-exponential post-synaptic current. (Separate synaptic currents for excitatory and inhibitory synapses. """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 0.1, # Duration of refractory period in ms. 'tau_syn_E': 5.0, # Decay time of excitatory synaptic current in ms. 'tau_syn_I': 5.0, # Decay time of inhibitory synaptic current in ms. 'i_offset': 0.0, # Offset current in nA 'v_reset': -65.0, # Reset potential after a spike in mV. 'v_thresh': -50.0, # Spike threshold in mV. } recordable = ['spikes', 'v'] conductance_based = False default_initial_values = { 'v': -65.0, # 'v_rest', 'isyn_exc': 0.0, 'isyn_inh': 0.0, } units = { 'v': 'mV', 'isyn_exc': 'nA', 'isyn_inh': 'nA', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'i_offset': 'nA', 'v_reset': 'mV', 'v_thresh': 'mV', } class IF_cond_alpha(StandardCellType): """ Leaky integrate and fire model with fixed threshold and alpha-function- shaped post-synaptic conductance. """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 0.1, # Duration of refractory period in ms. 'tau_syn_E': 0.3, # Rise time of the excitatory synaptic alpha function in ms. 'tau_syn_I': 0.5, # Rise time of the inhibitory synaptic alpha function in ms. 'e_rev_E': 0.0, # Reversal potential for excitatory input in mV 'e_rev_I': -70.0, # Reversal potential for inhibitory input in mV 'v_thresh': -50.0, # Spike threshold in mV. 'v_reset': -65.0, # Reset potential after a spike in mV. 'i_offset': 0.0, # Offset current in nA } recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -65.0, # 'v_rest', 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'e_rev_E': 'mV', 'e_rev_I': 'mV', 'v_thresh': 'mV', 'v_reset': 'mV', 'i_offset': 'nA', } class IF_cond_exp(StandardCellType): """ Leaky integrate and fire model with fixed threshold and exponentially-decaying post-synaptic conductance. """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 0.1, # Duration of refractory period in ms. 'tau_syn_E': 5.0, # Decay time of the excitatory synaptic conductance in ms. 'tau_syn_I': 5.0, # Decay time of the inhibitory synaptic conductance in ms. 'e_rev_E': 0.0, # Reversal potential for excitatory input in mV 'e_rev_I': -70.0, # Reversal potential for inhibitory input in mV 'v_thresh': -50.0, # Spike threshold in mV. 'v_reset': -65.0, # Reset potential after a spike in mV. 'i_offset': 0.0, # Offset current in nA } recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -65.0, # 'v_rest', 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'e_rev_E': 'mV', 'e_rev_I': 'mV', 'v_thresh': 'mV', 'v_reset': 'mV', 'i_offset': 'nA', } class IF_cond_exp_gsfa_grr(StandardCellType): """ Linear leaky integrate and fire model with fixed threshold, decaying-exponential post-synaptic conductance, conductance based spike-frequency adaptation, and a conductance-based relative refractory mechanism. See: Muller et al (2007) Spike-frequency adapting neural ensembles: Beyond mean-adaptation and renewal theories. Neural Computation 19: 2958-3010. See also: EIF_cond_alpha_isfa_ista """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 0.1, # Duration of refractory period in ms. 'tau_syn_E': 5.0, # Decay time of the excitatory synaptic conductance in ms. 'tau_syn_I': 5.0, # Decay time of the inhibitory synaptic conductance in ms. 'e_rev_E': 0.0, # Reversal potential for excitatory input in mV 'e_rev_I': -70.0, # Reversal potential for inhibitory input in mV 'v_thresh': -50.0, # Spike threshold in mV. 'v_reset': -65.0, # Reset potential after a spike in mV. 'i_offset': 0.0, # Offset current in nA 'tau_sfa': 100.0, # Time constant of spike-frequency adaptation in ms 'e_rev_sfa': -75.0, # spike-frequency adaptation conductance reversal potential in mV 'q_sfa': 15.0, # Quantal spike-frequency adaptation conductance increase in nS 'tau_rr': 2.0, # Time constant of the relative refractory mechanism in ms 'e_rev_rr': -75.0, # relative refractory mechanism conductance reversal potential in mV 'q_rr': 3000.0 # Quantal relative refractory conductance increase in nS } recordable = ['spikes', 'v', 'g_r', 'g_s', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -65.0, # 'v_rest', 'g_r': 0.0, 'g_s': 0.0, 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'g_r': 'nS', 'g_s': 'nS', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'e_rev_E': 'mV', 'e_rev_I': 'mV', 'v_thresh': 'mV', 'v_reset': 'mV', 'i_offset': 'nA', 'tau_sfa': 'ms', 'e_rev_sfa': 'mV', 'q_sfa': 'nS', 'tau_rr': 'ms', 'e_rev_rr': 'mV', 'q_rr': 'nS', } class IF_facets_hardware1(StandardCellType): """ Leaky integrate and fire model with conductance-based synapses and fixed threshold as it is resembled by the FACETS Hardware Stage 1. The following parameters can be assumed for a corresponding software simulation: cm = 0.2 nF, tau_refrac = 1.0 ms, e_rev_E = 0.0 mV. For further details regarding the hardware model see the FACETS-internal Wiki: https://facets.kip.uni-heidelberg.de/private/wiki/index.php/WP7_NNM """ default_parameters = { 'g_leak': 40.0, # nS 'tau_syn_E': 30.0, # ms 'tau_syn_I': 30.0, # ms 'v_reset': -80.0, # mV 'e_rev_I': -80.0, # mV, 'v_rest': -65.0, # mV 'v_thresh': -55.0 # mV } recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -65.0, # 'v_rest', 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'g_leak': 'nS', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'v_reset': 'mV', 'e_rev_I': 'mV', 'v_rest': 'mV', 'v_thresh': 'mV', } class HH_cond_exp(StandardCellType): """Single-compartment Hodgkin-Huxley model. Reference: Traub & Miles, Neuronal Networks of the Hippocampus, Cambridge, 1991. """ default_parameters = { 'gbar_Na': 20.0, # uS 'gbar_K': 6.0, # uS 'g_leak': 0.01, # uS 'cm': 0.2, # nF 'v_offset': -63.0, # mV 'e_rev_Na': 50.0, 'e_rev_K': -90.0, 'e_rev_leak': -65.0, 'e_rev_E': 0.0, 'e_rev_I': -80.0, 'tau_syn_E': 0.2, # ms 'tau_syn_I': 2.0, 'i_offset': 0.0, # nA } recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh'] receptor_types = ('excitatory', 'inhibitory', 'source_section.gap') default_initial_values = { 'v': -65.0, # 'v_rest', 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, 'h': 1.0, 'm': 0.0, 'n': 0.0, } units = { 'v': 'mV', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'gbar_Na': 'uS', 'gbar_K': 'uS', 'g_leak': 'uS', 'cm': 'nF', 'v_offset': 'mV', 'e_rev_Na': 'mV', 'e_rev_K': 'mV', 'e_rev_leak': 'mV', 'e_rev_E': 'mV', 'e_rev_I': 'mV', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'i_offset': 'nA', 'h': '', 'm': '', 'n': '', } class EIF_cond_alpha_isfa_ista(StandardCellType): """ Exponential integrate and fire neuron with spike triggered and sub-threshold adaptation currents (isfa, ista reps.) according to: Brette R and Gerstner W (2005) Adaptive Exponential Integrate-and-Fire Model as an Effective Description of Neuronal Activity. J Neurophysiol 94:3637-3642 See also: IF_cond_exp_gsfa_grr, EIF_cond_exp_isfa_ista """ default_parameters = { 'cm': 0.281, # Capacitance of the membrane in nF 'tau_refrac': 0.1, # Duration of refractory period in ms. 'v_spike': -40.0, # Spike detection threshold in mV. 'v_reset': -70.6, # Reset value for V_m after a spike. In mV. 'v_rest': -70.6, # Resting membrane potential (Leak reversal potential) in mV. 'tau_m': 9.3667, # Membrane time constant in ms 'i_offset': 0.0, # Offset current in nA 'a': 4.0, # Subthreshold adaptation conductance in nS. 'b': 0.0805, # Spike-triggered adaptation in nA 'delta_T': 2.0, # Slope factor in mV 'tau_w': 144.0, # Adaptation time constant in ms 'v_thresh': -50.4, # Spike initiation threshold in mV 'e_rev_E': 0.0, # Excitatory reversal potential in mV. 'tau_syn_E': 5.0, # Rise time of excitatory synaptic conductance in ms (alpha function). 'e_rev_I': -80.0, # Inhibitory reversal potential in mV. 'tau_syn_I': 5.0, # Rise time of the inhibitory synaptic conductance in ms (alpha function). } recordable = ['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -70.6, # 'v_rest', 'w': 0.0, 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'w': 'nA', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'cm': 'nF', 'tau_refrac': 'ms', 'v_spike': 'mV', 'v_reset': 'mV', 'v_rest': 'mV', 'tau_m': 'ms', 'i_offset': 'nA', 'a': 'nS', 'b': 'nA', 'delta_T': 'mV', 'tau_w': 'ms', 'v_thresh': 'mV', 'e_rev_E': 'mV', 'tau_syn_E': 'ms', 'e_rev_I': 'mV', 'tau_syn_I': 'ms', } class EIF_cond_exp_isfa_ista(StandardCellType): """ Exponential integrate and fire neuron with spike triggered and sub-threshold adaptation currents (isfa, ista reps.) according to: Brette R and Gerstner W (2005) Adaptive Exponential Integrate-and-Fire Model as an Effective Description of Neuronal Activity. J Neurophysiol 94:3637-3642 See also: IF_cond_exp_gsfa_grr, EIF_cond_alpha_isfa_ista """ default_parameters = { 'cm': 0.281, # Capacitance of the membrane in nF 'tau_refrac': 0.1, # Duration of refractory period in ms. 'v_spike': -40.0, # Spike detection threshold in mV. 'v_reset': -70.6, # Reset value for V_m after a spike. In mV. 'v_rest': -70.6, # Resting membrane potential (Leak reversal potential) in mV. 'tau_m': 9.3667, # Membrane time constant in ms 'i_offset': 0.0, # Offset current in nA 'a': 4.0, # Subthreshold adaptation conductance in nS. 'b': 0.0805, # Spike-triggered adaptation in nA 'delta_T': 2.0, # Slope factor in mV 'tau_w': 144.0, # Adaptation time constant in ms 'v_thresh': -50.4, # Spike initiation threshold in mV 'e_rev_E': 0.0, # Excitatory reversal potential in mV. 'tau_syn_E': 5.0, # Decay time constant of excitatory synaptic conductance in ms. 'e_rev_I': -80.0, # Inhibitory reversal potential in mV. 'tau_syn_I': 5.0, # Decay time constant of the inhibitory synaptic conductance in ms. } recordable = ['spikes', 'v', 'w', 'gsyn_exc', 'gsyn_inh'] default_initial_values = { 'v': -70.6, # 'v_rest', 'w': 0.0, 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'w': 'nA', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'cm': 'nF', 'tau_refrac': 'ms', 'v_spike': 'mV', 'v_reset': 'mV', 'v_rest': 'mV', 'tau_m': 'ms', 'i_offset': 'nA', 'a': 'nS', 'b': 'nA', 'delta_T': 'mV', 'tau_w': 'ms', 'v_thresh': 'mV', 'e_rev_E': 'mV', 'tau_syn_E': 'ms', 'e_rev_I': 'mV', 'tau_syn_I': 'ms', } class Izhikevich(StandardCellType): """ Izhikevich spiking model with a quadratic non-linearity according to: E. Izhikevich (2003), IEEE transactions on neural networks, 14(6) dv/dt = 0.04*v^2 + 5*v + 140 - u + I du/dt = a*(b*v - u) Synapses are modeled as Dirac delta currents (voltage step), as in the original model NOTE: name should probably be changed to match standard nomenclature, e.g. QIF_cond_delta_etc_etc, although keeping "Izhikevich" as an alias would be good """ default_parameters = { 'a': 0.02, # (/ms) 'b': 0.2, # (/ms) 'c': -65.0, # (mV) aka 'v_reset' 'd': 2.0, # (mV/ms) Reset value for u after a spike. 'i_offset': 0.0 # (nA) } recordable = ['spikes', 'v', 'u'] conductance_based = False voltage_based_synapses = True default_initial_values = { 'v': -70.0, # mV 'u': -14.0 # mV/ms } units = { 'v': 'mV', 'u': 'mV/ms', 'a': '/ms', 'b': '/ms', 'c': 'mV', 'd': 'mV/ms', 'i_offset': 'nA', } class GIF_cond_exp(StandardCellType): """ The GIF model is a leaky integrate-and-fire model including a spike-triggered current eta(t), a moving threshold gamma(t) and stochastic spike emission. References: [1] Mensi, S., Naud, R., Pozzorini, C., Avermann, M., Petersen, C. C., & Gerstner, W. (2012). Parameter extraction and classification of three cortical neuron types reveals two distinct adaptation mechanisms. Journal of Neurophysiology, 107(6), 1756-1775. [2] Pozzorini, C., Mensi, S., Hagens, O., Naud, R., Koch, C., & Gerstner, W. (2015). Automated High-Throughput Characterization of Single Neurons by Means of Simplified Spiking Models. PLoS Comput Biol, 11(6), e1004275. """ default_parameters = { 'v_rest': -65.0, # Resting membrane potential in mV. 'cm': 1.0, # Capacity of the membrane in nF 'tau_m': 20.0, # Membrane time constant in ms. 'tau_refrac': 4.0, # Duration of refractory period in ms. 'tau_syn_E': 5.0, # Decay time of the excitatory synaptic conductance in ms. 'tau_syn_I': 5.0, # Decay time of the inhibitory synaptic conductance in ms. 'e_rev_E': 0.0, # Reversal potential for excitatory input in mV 'e_rev_I': -70.0, # Reversal potential for inhibitory input in mV 'v_reset': -65.0, # Reset potential after a spike in mV. 'i_offset': 0.0, # Offset current in nA 'delta_v': 0.5, # Threshold sharpness in mV. 'v_t_star': -48.0, # Threshold baseline in mV. 'lambda0': 1.0, # Firing intensity at threshold in Hz. 'tau_eta': ArrayParameter([1.0, 10.0, 100.0]), # Time constants for spike-triggered current in ms. 'tau_gamma': ArrayParameter([1.0, 10.0, 100.0]), # Time constants for spike-frequency adaptation in ms. 'a_eta': ArrayParameter([1.0, 1.0, 1.0]), # Post-spike increments for spike-triggered current in ms. 'a_gamma': ArrayParameter([1.0, 1.0, 1.0]), # Post-spike increments for moving threshold in mV } recordable = ['spikes', 'v', 'gsyn_exc', 'gsyn_inh', 'i_eta', 'v_t'] default_initial_values = { 'v': -65.0, 'v_t': -48.0, 'i_eta': 0.0, 'gsyn_exc': 0.0, 'gsyn_inh': 0.0, } units = { 'v': 'mV', 'gsyn_exc': 'uS', 'gsyn_inh': 'uS', 'i_eta': 'nA', 'v_t': 'mV', 'v_rest': 'mV', 'cm': 'nF', 'tau_m': 'ms', 'tau_refrac': 'ms', 'tau_syn_E': 'ms', 'tau_syn_I': 'ms', 'e_rev_E': 'mV', 'e_rev_I': 'mV', 'v_reset': 'mV', 'i_offset': 'nA', 'delta_v': 'mV', 'v_t_star': 'mV', 'lambda0': 'Hz', 'tau_eta': 'ms', 'tau_gamma': 'ms', 'a_eta': 'nA', 'a_gamma': 'mV', } class SpikeSourcePoisson(StandardCellType): """Spike source, generating spikes according to a Poisson process.""" default_parameters = { 'rate': 1.0, # Mean spike frequency (Hz) 'start': 0.0, # Start time (ms) 'duration': 1e10 # Duration of spike sequence (ms) } recordable = ['spikes'] injectable = False receptor_types = () units = { 'rate': 'Hz', 'start': 'ms', 'duration': 'ms', } class SpikeSourcePoissonRefractory(StandardCellType): """Spike source, generating spikes according to a Poisson process with dead time""" default_parameters = { 'rate': 1.0, # Mean spike frequency (Hz) 'tau_refrac': 0.0, # Minimum time between spikes (ms) 'start': 0.0, # Start time (ms) 'duration': 1e10 # Duration of spike sequence (ms) } recordable = ['spikes'] injectable = False receptor_types = () units = { 'rate': 'Hz', 'tau_refrac': 'ms', 'start': 'ms', 'duration': 'ms', } class SpikeSourceGamma(StandardCellType): """Spike source, generating spikes according to a gamma process. The mean inter-spike interval is given by alpha/beta """ default_parameters = { 'alpha': 2, # shape (order) parameter of the gamma distribution 'beta': 1.0, # rate parameter of the gamma distribution (Hz) 'start': 0.0, # Start time (ms) 'duration': 1e10, # Duration of spike sequence (ms) } recordable = ['spikes'] injectable = False receptor_types = () units = { 'alpha': 'dimensionless', 'beta': 'Hz', 'start': 'ms', 'duration': 'ms', } class SpikeSourceInhGamma(StandardCellType): """ Spike source, generating realizations of an inhomogeneous gamma process, employing the thinning method. See: Muller et al (2007) Spike-frequency adapting neural ensembles: Beyond mean-adaptation and renewal theories. Neural Computation 19: 2958-3010. """ default_parameters = { 'a': Sequence([1.0]), # time histogram of parameter a of a gamma distribution (dimensionless) 'b': Sequence([1.0]), # time histogram of parameter b of a gamma distribution (seconds) 'tbins': Sequence([0.0]), # time bins of the time histogram of a,b in units of ms 'start': 0.0, # Start time (ms) 'duration': 1e10 # Duration of spike sequence (ms) } recordable = ['spikes'] injectable = False receptor_types = () units = { 'a': 'dimensionless', 'b': 's', 'tbins': 'ms', 'start': 'ms', 'duration': 'ms', } class SpikeSourceArray(StandardCellType): """Spike source generating spikes at the times given in the spike_times array.""" default_parameters = {'spike_times': Sequence([])} # list or numpy array containing spike times in milliseconds. recordable = ['spikes'] injectable = False receptor_types = () units = { 'spike_times': 'ms', } PyNN-0.10.0/pyNN/standardmodels/electrodes.py000066400000000000000000000064231415343567000210240ustar00rootroot00000000000000""" Definition of default parameters (and hence, standard parameter names) for standard current source models. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN.standardmodels import StandardCurrentSource from pyNN.parameters import Sequence class DCSource(StandardCurrentSource): """Source producing a single pulse of current of constant amplitude. Arguments: `start`: onset time of pulse in ms `stop`: end of pulse in ms `amplitude`: pulse amplitude in nA """ default_parameters = { 'amplitude': 1.0, 'start': 0.0, 'stop': 1e12, } class ACSource(StandardCurrentSource): """Source producing a single pulse of current of constant amplitude. Arguments: `start`: onset time of pulse in ms `stop`: end of pulse in ms `amplitude`: sine amplitude in nA `offset`: sine offset in nA `frequency`: frequency in Hz `phase`: phase in degrees """ default_parameters = { 'amplitude': 1.0, 'start': 0.0, 'stop': 1e12, 'frequency': 10.0, 'offset': 0.0, 'phase': 0.0 } class StepCurrentSource(StandardCurrentSource): """A step-wise time-varying current source. Arguments: `times`: list/array of times at which the injected current changes. `amplitudes`: list/array of current amplitudes to be injected at the times specified in `times`. The injected current will be zero up until the first time in `times`. The current will continue at the final value in `amplitudes` until the end of the simulation. """ default_parameters = { 'amplitudes': Sequence([]), 'times': Sequence([]) } class NoisyCurrentSource(StandardCurrentSource): """A Gaussian "white" noise current source. The current amplitude changes at fixed intervals, with the new value drawn from a Gaussian distribution. Required arguments: `mean`: mean current amplitude in nA `stdev`: standard deviation of the current amplitude in nA Optional arguments: `dt`: interval between updates of the current amplitude. Must be a multiple of the simulation time step. If not specified, the simulation time step will be used. `start`: onset of the current injection in ms. If not specified, the current will begin at the start of the simulation. `stop`: end of the current injection in ms. If not specified, the current will continue until the end of the simulation. `rng`: an RNG object from the `pyNN.random` module. For speed, this should be a `NativeRNG` instance (uses the simulator's internal random number generator). For reproducibility across simulators, use one of the other RNG types. If not specified, a NumpyRNG is used. """ default_parameters = { 'mean': 0.0, 'stdev': 1.0, 'start': 0.0, 'stop': 1e12, 'dt': 0.1 } PyNN-0.10.0/pyNN/standardmodels/synapses.py000066400000000000000000000353171415343567000205440ustar00rootroot00000000000000# encoding: utf-8 """ Definition of default parameters (and hence, standard parameter names) for standard dynamic synapse models. Classes for specifying short-term plasticity (facilitation/depression): TsodyksMarkramSynapse Classes for defining STDP rules: AdditiveWeightDependence MultiplicativeWeightDependence AdditivePotentiationMultiplicativeDepression GutigWeightDependence SpikePairRule :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import descriptions from pyNN.standardmodels import StandardSynapseType, STDPWeightDependence, STDPTimingDependence from pyNN.parameters import ParameterSpace class StaticSynapse(StandardSynapseType): """ Synaptic connection with fixed weight and delay. """ default_parameters = { 'weight': 0.0, 'delay': None } class ElectricalSynapse(StandardSynapseType): """ A bidirectional electrical synapse (gap junction) with fixed conductance """ default_parameters = { 'weight': 0.0 # the (bidirectional) conductance of the gap junction (uS) } class TsodyksMarkramSynapse(StandardSynapseType): """ Synapse exhibiting facilitation and depression, implemented using the model of Tsodyks, Markram et al.: `Tsodyks, Uziel and Markram (2000)`_ Synchrony Generation in Recurrent Networks with Frequency-Dependent Synapses. Journal of Neuroscience 20:RC50 Note that the time constant of the post-synaptic current is set in the neuron model, not here. Arguments: `U`: use parameter. `tau_rec`: depression time constant (ms). `tau_facil`: facilitation time constant (ms). .. _`Tsodyks, Uziel and Markram (2000)`: http://www.jneurosci.org/content/20/1/RC50.long """ default_parameters = { 'weight': 0.0, 'delay': None, 'U': 0.5, # use parameter 'tau_rec': 100.0, # depression time constant (ms) 'tau_facil': 0.0, # facilitation time constant (ms) } default_initial_values = { 'u': 0.0 } class SimpleStochasticSynapse(StandardSynapseType): """ Each spike is transmitted with a fixed probability `p`. """ default_parameters = { 'weight': 0.0, 'delay': None, 'p': 0.5, } class StochasticTsodyksMarkramSynapse(StandardSynapseType): """ Synapse exhibiting facilitation and depression, implemented using the model of Tsodyks, Markram et al.: `Tsodyks, Uziel and Markram (2000)`_ Synchrony Generation in Recurrent Networks with Frequency-Dependent Synapses. Journal of Neuroscience 20:RC50 in its stochastic version (cf Fuhrmann et al. 2002) Arguments: `U`: use parameter. `tau_rec`: depression time constant (ms). `tau_facil`: facilitation time constant (ms). .. _`Tsodyks, Uziel and Markram (2000)`: http://www.jneurosci.org/content/20/1/RC50.long """ default_parameters = { 'weight': 0.0, 'delay': None, 'U': 0.5, # use parameter 'tau_rec': 100.0, # depression time constant (ms) 'tau_facil': 0.0, # facilitation time constant (ms) } class MultiQuantalSynapse(StandardSynapseType): """ docstring needed """ default_parameters = { 'weight': 0.0, 'delay': None, 'U': 0.5, # maximal fraction of available resources 'n': 1, # total number of release sites 'tau_rec': 800.0, # depression time constant (ms) 'tau_facil': 0.0, # facilitation time constant (ms) } class STDPMechanism(StandardSynapseType): """ A specification for an STDP mechanism, combining a weight-dependence, a timing-dependence, and, optionally, a voltage-dependence of the synaptic change. For point neurons, the synaptic delay `d` can be interpreted either as occurring purely in the pre-synaptic axon + synaptic cleft, in which case the synaptic plasticity mechanism 'sees' the post-synaptic spike immediately and the pre-synaptic spike after a delay `d` (`dendritic_delay_fraction = 0`) or as occurring purely in the post- synaptic dendrite, in which case the pre-synaptic spike is seen immediately, and the post-synaptic spike after a delay `d` (`dendritic_delay_fraction = 1`), or as having both pre- and post- synaptic components (`dendritic_delay_fraction` between 0 and 1). In a future version of the API, we will allow the different components of the synaptic delay to be specified separately in milliseconds. """ def __init__(self, timing_dependence=None, weight_dependence=None, voltage_dependence=None, dendritic_delay_fraction=1.0, weight=0.0, delay=None): """ Create a new specification for an STDP mechanism, by combining a weight-dependence, a timing-dependence, and, optionally, a voltage- dependence. """ if timing_dependence: assert isinstance(timing_dependence, STDPTimingDependence) if weight_dependence: assert isinstance(weight_dependence, STDPWeightDependence) assert isinstance(dendritic_delay_fraction, (int, float)) assert 0 <= dendritic_delay_fraction <= 1 self.timing_dependence = timing_dependence self.weight_dependence = weight_dependence self.voltage_dependence = voltage_dependence self.dendritic_delay_fraction = dendritic_delay_fraction self.weight = weight self.delay = delay or self._get_minimum_delay() self._build_translations() def _build_translations(self): self.translations = self.__class__.base_translations # weight and delay for component in (self.timing_dependence, self.weight_dependence, self.voltage_dependence): if component: self.translations.update(component.translations) @property def model(self): return list(self.possible_models)[0] @property def possible_models(self): """ A list of available synaptic plasticity models for the current configuration (weight dependence, timing dependence, ...) in the current simulator. """ td = self.timing_dependence wd = self.weight_dependence pm = td.possible_models.intersection(wd.possible_models) if len(pm) == 0: raise errors.NoModelAvailableError("No available plasticity models") else: # we pass the set of models back to the simulator-specific module for it to deal with return pm def get_parameter_names(self): assert self.voltage_dependence is None # once we have some models with v-dep, need to update the following return ['weight', 'delay', 'dendritic_delay_fraction'] + \ self.timing_dependence.get_parameter_names() + \ self.weight_dependence.get_parameter_names() def has_parameter(self, name): """Does this model have a parameter with the given name?""" return name in self.get_parameter_names() def get_schema(self): """ Returns the model schema: i.e. a mapping of parameter names to allowed parameter types. """ base_schema = {'weight': float, 'delay': float, 'dendritic_delay_fraction': float} for component in (self.timing_dependence, self.weight_dependence, self.voltage_dependence): if component: base_schema.update((name, type(value)) for name, value in component.default_parameters.items()) return base_schema @property def parameter_space(self): timing_parameters = self.timing_dependence.parameter_space weight_parameters = self.weight_dependence.parameter_space parameters = ParameterSpace({'weight': self.weight, 'delay': self.delay, 'dendritic_delay_fraction': self.dendritic_delay_fraction}, self.get_schema()) parameters.update(**timing_parameters) parameters.update(**weight_parameters) return parameters @property def native_parameters(self): """ A dictionary containing the combination of parameters from the different components of the STDP model. """ timing_parameters = self.timing_dependence.native_parameters weight_parameters = self.weight_dependence.native_parameters parameters = self.translate( ParameterSpace({'weight': self.weight, 'delay': self.delay, 'dendritic_delay_fraction': self.dendritic_delay_fraction}, self.get_schema())) parameters.update(**timing_parameters) parameters.update(**weight_parameters) parameters.update(**self.timing_dependence.extra_parameters) parameters.update(**self.weight_dependence.extra_parameters) return parameters def describe(self, template='stdpmechanism_default.txt', engine='default'): """ Returns a human-readable description of the STDP mechanism. The output may be customized by specifying a different template togther with an associated template engine (see ``pyNN.descriptions``). If template is None, then a dictionary containing the template context will be returned. """ context = {'weight_dependence': self.weight_dependence.describe(template=None), 'timing_dependence': self.timing_dependence.describe(template=None), 'voltage_dependence': self.voltage_dependence and self.voltage_dependence.describe(template=None) or None, 'dendritic_delay_fraction': self.dendritic_delay_fraction} return descriptions.render(engine, template, context) class AdditiveWeightDependence(STDPWeightDependence): """ The amplitude of the weight change is independent of the current weight. If the new weight would be less than `w_min` it is set to `w_min`. If it would be greater than `w_max` it is set to `w_max`. Arguments: `w_min`: minimum synaptic weight, in the same units as the weight, i.e. µS or nA. `w_max`: maximum synaptic weight. """ default_parameters = { 'w_min': 0.0, 'w_max': 1.0, } def __init__(self, w_min=0.0, w_max=1.0): parameters = dict(locals()) parameters.pop('self') STDPWeightDependence.__init__(self, **parameters) class MultiplicativeWeightDependence(STDPWeightDependence): """ The amplitude of the weight change depends on the current weight. For depression, Δw ∝ w - w_min For potentiation, Δw ∝ w_max - w Arguments: `w_min`: minimum synaptic weight, in the same units as the weight, i.e. µS or nA. `w_max`: maximum synaptic weight. """ default_parameters = { 'w_min': 0.0, 'w_max': 1.0, } def __init__(self, w_min=0.0, w_max=1.0): parameters = dict(locals()) parameters.pop('self') STDPWeightDependence.__init__(self, **parameters) class AdditivePotentiationMultiplicativeDepression(STDPWeightDependence): """ The amplitude of the weight change depends on the current weight for depression (Δw ∝ w) and is fixed for potentiation. Arguments: `w_min`: minimum synaptic weight, in the same units as the weight, i.e. µS or nA. `w_max`: maximum synaptic weight. """ default_parameters = { 'w_min': 0.0, 'w_max': 1.0, } def __init__(self, w_min=0.0, w_max=1.0): parameters = dict(locals()) parameters.pop('self') STDPWeightDependence.__init__(self, **parameters) class GutigWeightDependence(STDPWeightDependence): """ The amplitude of the weight change depends on (w_max-w)^mu_plus for potentiation and (w-w_min)^mu_minus for depression. Arguments: `w_min`: minimum synaptic weight, in the same units as the weight, i.e. µS or nA. `w_max`: maximum synaptic weight. `mu_plus`: see above `mu_minus`: see above """ default_parameters = { 'w_min': 0.0, 'w_max': 1.0, 'mu_plus': 0.5, 'mu_minus': 0.5 } def __init__(self, w_min=0.0, w_max=1.0, mu_plus=0.5, mu_minus=0.5): """ Create a new specification for the weight-dependence of an STDP rule. """ parameters = dict(locals()) parameters.pop('self') STDPWeightDependence.__init__(self, **parameters) # Not yet implemented for any module #class PfisterSpikeTripletRule(STDPTimingDependence): # raise NotImplementedError class SpikePairRule(STDPTimingDependence): """ The amplitude of the weight change depends only on the relative timing of spike pairs, not triplets, etc. All possible spike pairs are taken into account (cf Song and Abbott). Arguments: `tau_plus`: time constant of the positive part of the STDP curve, in milliseconds. `tau_minus` time constant of the negative part of the STDP curve, in milliseconds. `A_plus`: amplitude of the positive part of the STDP curve. `A_minus`: amplitude of the negative part of the STDP curve. """ default_parameters = { 'tau_plus': 20.0, 'tau_minus': 20.0, 'A_plus': 0.01, 'A_minus': 0.01, } def __init__(self, tau_plus=20.0, tau_minus=20.0, A_plus=0.01, A_minus=0.01): """ Create a new specification for the timing-dependence of an STDP rule. """ parameters = dict(locals()) parameters.pop('self') STDPTimingDependence.__init__(self, **parameters) class Vogels2011Rule(STDPTimingDependence): """ Timing-dependence rule from Vogels TP, Sprekeler H, Zenke F, Clopath C, Gerstner W (2011) Inhibitory plasticity balances excitation and inhibition in sensory pathways and memory networks. Science 334:1569-73 http://dx.doi.org/10.1126/science.1211095 Potentiation depends on the coincidence of pre- and post-synaptic spikes but not on their order. Pre-synaptic spikes in the absence of post- synaptic ones produce depression. Also see http://senselab.med.yale.edu/modeldb/ShowModel.asp?model=143751 """ default_parameters = { 'tau': 20.0, 'eta': 1e-10, 'rho': 3.0 } PyNN-0.10.0/pyNN/utility/000077500000000000000000000000001415343567000150135ustar00rootroot00000000000000PyNN-0.10.0/pyNN/utility/__init__.py000066400000000000000000000331241415343567000171270ustar00rootroot00000000000000# encoding: utf-8 """ A collection of utility functions and classes. Functions: notify() - send an e-mail when a simulation has finished. get_script_args() - get the command line arguments to the script, however it was run (python, nrniv, mpirun, etc.). init_logging() - convenience function for setting up logging to file and to the screen. Timer - a convenience wrapper around the time.time() function from the standard library. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ # If there is a settings.py file on the path, defaults will be # taken from there. try: from settings import SMTPHOST, EMAIL except ImportError: SMTPHOST = None EMAIL = None import sys import logging import time import os from datetime import datetime import functools import numpy as np from importlib import import_module from pyNN.core import deprecated def notify(msg="Simulation finished.", subject="Simulation finished.", smtphost=SMTPHOST, address=EMAIL): """Send an e-mail stating that the simulation has finished.""" if not (smtphost and address): print("SMTP host and/or e-mail address not specified.\nUnable to send notification message.") else: import smtplib import datetime msg = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n") % (address, address, subject) + msg msg += "\nTimestamp: %s" % datetime.datetime.now().strftime("%H:%M:%S, %F") server = smtplib.SMTP(smtphost) server.sendmail(address, address, msg) server.quit() def get_script_args(n_args, usage=''): """ Get command line arguments. This works by finding the name of the main script and assuming any arguments after this in sys.argv are arguments to the script. It would be nicer to use optparse, but this doesn't seem to work too well with nrniv or mpirun. """ calling_frame = sys._getframe(1) if '__file__' in calling_frame.f_locals: script = calling_frame.f_locals['__file__'] try: script_index = sys.argv.index(script) except ValueError: try: script_index = sys.argv.index(os.path.abspath(script)) except ValueError: script_index = 0 else: script_index = 0 args = sys.argv[script_index + 1:script_index + 1 + n_args] if len(args) != n_args: usage = usage or "Script requires %d arguments, you supplied %d" % (n_args, len(args)) raise Exception(usage) return args def get_simulator(*arguments): """ Import and return a PyNN simulator backend module based on command-line arguments. The simulator name should be the first positional argument. If your script needs additional arguments, you can specify them as (name, help_text) tuples. If you need more complex argument handling, you should use argparse directly. Returns (simulator, command-line arguments) """ import argparse parser = argparse.ArgumentParser() parser.add_argument("simulator", help="neuron, nest, brian or another backend simulator") for argument in arguments: arg_name, help_text = argument[:2] extra_args = {} if len(argument) > 2: extra_args = argument[2] parser.add_argument(arg_name, help=help_text, **extra_args) args = parser.parse_args() sim = import_module("pyNN.%s" % args.simulator) return sim, args def init_logging(logfile, debug=False, num_processes=1, rank=0, level=None): """ Simple configuration of logging. """ # allow logfile == None # which implies output to stderr # num_processes and rank should be obtained using mpi4py, rather than having them as arguments if logfile: if num_processes > 1: logfile += '.%d' % rank logfile = os.path.abspath(logfile) # prefix log messages with mpi rank mpi_prefix = "" if num_processes > 1: mpi_prefix = 'Rank %d of %d: ' % (rank, num_processes) if debug: log_level = logging.DEBUG else: log_level = logging.INFO # allow user to override exact log_level if level: log_level = level logging.basicConfig(level=log_level, format=mpi_prefix + '%(asctime)s %(levelname)-8s [%(name)s] %(message)s (%(pathname)s[%(lineno)d]:%(funcName)s)', filename=logfile, filemode='w') return logging.getLogger("PyNN") def save_population(population, filename, variables=None): """ Saves the spike_times of a population and the size, structure, labels such that one can load it back into a SpikeSourceArray population using the load_population() function. """ import shelve s = shelve.open(filename) s['spike_times'] = population.getSpikes() s['label'] = population.label s['size'] = population.size s['structure'] = population.structure # should perhaps just save the positions? variables_dict = {} if variables: for variable in variables: variables_dict[variable] = getattr(population, variable) s['variables'] = variables_dict s.close() def load_population(filename, sim): """ Loads a population that was saved with the save_population() function into SpikeSourceArray. """ import shelve s = shelve.open(filename) ssa = getattr(sim, "SpikeSourceArray") population = getattr(sim, "Population")(s['size'], ssa, structure=s['structure'], label=s['label']) # set the spiketimes spikes = s['spike_times'] for neuron in range(s['size']): spike_times = spikes[spikes[:, 0] == neuron][:, 1] neuron_in_new_population = neuron + population.first_id index = population.id_to_index(neuron_in_new_population) population[index].set_parameters(**{'spike_times': spike_times}) # set the variables for variable, value in s['variables'].items(): setattr(population, variable, value) s.close() return population def normalized_filename(root, basename, extension, simulator, num_processes=None, use_iso8601=False): """ Generate a file path containing a timestamp and information about the simulator used and the number of MPI processes. The date is used as a sub-directory name, the date & time are included in the filename. If use_iso8601 is True, follow https://en.wikipedia.org/wiki/ISO_8601 """ timestamp = datetime.now() if use_iso8601: date = timestamp.strftime("%Y-%m-%d") date_time = timestamp.strftime("%Y-%m-%dT%H:%M:%S") else: date = timestamp.strftime("%Y%m%d") date_time = timestamp.strftime("%Y%m%d-%H%M%S") if num_processes: np = "_np%d" % num_processes else: np = "" return os.path.join(root, date, "%s_%s%s_%s.%s" % (basename, simulator, np, date_time, extension)) def connection_plot(projection, positive='O', zero='.', empty=' ', spacer=''): """ """ connection_array = projection.get('weight', format='array') image = np.zeros_like(connection_array, dtype=str) old_settings = np.seterr(invalid='ignore') # ignore the complaint that x > 0 is invalid for NaN image[connection_array > 0] = positive image[connection_array == 0] = zero np.seterr(**old_settings) # restore original floating point error settings image[np.isnan(connection_array)] = empty return '\n'.join([spacer.join(row) for row in image]) class Timer(object): """ For timing script execution. Timing starts on creation of the timer. """ def __init__(self): self.start() self.marks = [] def start(self): """Start/restart timing.""" self._start_time = time.time() self._last_check = self._start_time def elapsed_time(self, format=None): """ Return the elapsed time in seconds but keep the clock running. If called with ``format="long"``, return a text representation of the time. Examples:: >>> timer.elapsed_time() 987 >>> timer.elapsed_time(format='long') 16 minutes, 27 seconds """ current_time = time.time() elapsed_time = current_time - self._start_time if format == 'long': elapsed_time = Timer.time_in_words(elapsed_time) self._last_check = current_time return elapsed_time @deprecated('elapsed_time()') def elapsedTime(self, format=None): return self.elapsed_time(format) def reset(self): """Reset the time to zero, and start the clock.""" self.start() def diff(self, format=None): # I think delta() would be a better name for this method. """ Return the time since the last time :meth:`elapsed_time()` or :meth:`diff()` was called. If called with ``format='long'``, return a text representation of the time. """ current_time = time.time() time_since_last_check = current_time - self._last_check self._last_check = current_time if format == 'long': time_since_last_check = Timer.time_in_words(time_since_last_check) return time_since_last_check @staticmethod def time_in_words(s): """ Formats a time in seconds as a string containing the time in days, hours, minutes, seconds. Examples:: >>> Timer.time_in_words(1) 1 second >>> Timer.time_in_words(123) 2 minutes, 3 seconds >>> Timer.time_in_words(24*3600) 1 day """ # based on http://mail.python.org/pipermail/python-list/2003-January/181442.html T = {} T['year'], s = divmod(s, 31556952) min, T['second'] = divmod(s, 60) h, T['minute'] = divmod(min, 60) T['day'], T['hour'] = divmod(h, 24) def add_units(val, units): return "%d %s" % (int(val), units) + (val > 1 and 's' or '') return ', '.join([add_units(T[part], part) for part in ('year', 'day', 'hour', 'minute', 'second') if T[part] > 0]) def mark(self, label): """ Store the time since the last time since the last time :meth:`elapsed_time()`, :meth:`diff()` or :meth:`mark()` was called, together with the provided label, in the attribute 'marks'. """ self.marks.append((label, self.diff())) class ProgressBar(object): """ Create a progress bar in the shell. """ def __init__(self, width=77, char="#", mode="fixed"): self.char = char self.mode = mode if self.mode not in ['fixed', 'dynamic']: self.mode = 'fixed' self.width = width def set_level(self, level): """ Rebuild the bar string based on `level`, which should be a number between 0 and 1. """ if level < 0: level = 0 if level > 1: level = 1 # figure the proper number of 'character' make up the bar all_full = self.width - 2 num_hashes = int(round(level * all_full)) if self.mode == 'dynamic': # build a progress bar with self.char (to create a dynamic bar # where the percent string moves along with the bar progress. bar = self.char * num_hashes else: # build a progress bar with self.char and spaces (to create a # fixed bar (the percent string doesn't move) bar = self.char * num_hashes + ' ' * (all_full - num_hashes) bar = u'[ %s ] %3.0f%%' % (bar, 100 * level) print(bar, end=u' \r') sys.stdout.flush() def __call__(self, level): self.set_level(level) class SimulationProgressBar(ProgressBar): def __init__(self, interval, t_stop, char="#", mode="fixed"): super(SimulationProgressBar, self).__init__(width=int(t_stop / interval), char=char, mode=mode) self.interval = interval self.t_stop = t_stop def __call__(self, t): self.set_level(t / self.t_stop) return t + self.interval def sort_by_column(a, col): # see stackoverflow.com/questions/2828059/ return a[a[:, col].argsort(), :] # based on http://wiki.python.org/moin/PythonDecoratorLibrary#Memoize class forgetful_memoize(object): """ Decorator that caches the result from the last time a function was called. If the next call uses the same arguments, the cached value is returned, and not re-evaluated. If the next call uses different arguments, the cached value is overwritten. The use case is when the same, heavy-weight function is called repeatedly with the same arguments in different places. """ def __init__(self, func): self.func = func self.cached_args = None self.cached_value = None def __call__(self, *args): if args == self.cached_args: print("using cached value") return self.cached_value else: #print("calculating value") value = self.func(*args) self.cached_args = args self.cached_value = value return value def __get__(self, obj, objtype): """Support instance methods.""" return functools.partial(self.__call__, obj) PyNN-0.10.0/pyNN/utility/plotting.py000066400000000000000000000316131415343567000172310ustar00rootroot00000000000000""" Simple tools for plotting Neo-format data. These tools are intended for quickly producing basic plots with simple formatting. If you need to produce more complex and/or publication-quality figures, it will probably be easier to use matplotlib or another plotting package directly rather than trying to extend this module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import sys from collections import defaultdict from numbers import Number from itertools import repeat from os import path, makedirs import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import numpy as np from quantities import ms from neo import AnalogSignal, SpikeTrain DEFAULT_FIG_SETTINGS = { 'lines.linewidth': 0.5, 'axes.linewidth': 0.5, 'axes.labelsize': 'small', 'legend.fontsize': 'small', 'font.size': 8, 'savefig.dpi': 150, } def handle_options(ax, options): if "xticks" not in options or options.pop("xticks") is False: plt.setp(ax.get_xticklabels(), visible=False) if "xlabel" in options: ax.set_xlabel(options.pop("xlabel")) if "yticks" not in options or options.pop("yticks") is False: plt.setp(ax.get_yticklabels(), visible=False) if "ylabel" in options: ax.set_ylabel(options.pop("ylabel")) if "ylim" in options: ax.set_ylim(options.pop("ylim")) if "xlim" in options: ax.set_xlim(options.pop("xlim")) def plot_signal(ax, signal, index=None, label='', **options): """ Plot a single channel from an AnalogSignal. """ if "ylabel" in options: if options["ylabel"] == "auto": options["ylabel"] = "%s (%s)" % (signal.name, signal.units._dimensionality.string) handle_options(ax, options) if index is None: label = "%s (Neuron %d)" % (label, signal.array_annotations["channel_index"] or 0) else: label = "%s (Neuron %d)" % (label, signal.array_annotations["channel_index"][index]) signal = signal[:, index] ax.plot(signal.times.rescale(ms), signal.magnitude, label=label, **options) ax.legend() def plot_signals(ax, signal_array, label_prefix='', **options): """ Plot all channels in an AnalogSignal in a single panel. """ if "ylabel" in options: if options["ylabel"] == "auto": options["ylabel"] = "%s (%s)" % (signal_array.name, signal_array.units._dimensionality.string) handle_options(ax, options) offset = options.pop("y_offset", None) show_legend = options.pop("legend", True) for i in signal_array.array_annotations["channel_index"].argsort(): channel = signal_array.array_annotations["channel_index"][i] signal = signal_array[:, i] if label_prefix: label = "%s (Neuron %d)" % (label_prefix, channel) else: label = "Neuron %d" % channel if offset: signal += i * offset ax.plot(signal.times.rescale(ms), signal.magnitude, label=label, **options) if show_legend: ax.legend() def plot_spiketrains(ax, spiketrains, label='', **options): """ Plot all spike trains in a Segment in a raster plot. """ ax.set_xlim(0, spiketrains[0].t_stop / ms) handle_options(ax, options) max_index = 0 min_index = sys.maxsize for spiketrain in spiketrains: ax.plot(spiketrain, np.ones_like(spiketrain) * spiketrain.annotations['source_index'], 'k.', **options) max_index = max(max_index, spiketrain.annotations['source_index']) min_index = min(min_index, spiketrain.annotations['source_index']) ax.set_ylabel("Neuron index") ax.set_ylim(-0.5 + min_index, max_index + 0.5) if label: plt.text(0.95, 0.95, label, transform=ax.transAxes, ha='right', va='top', bbox=dict(facecolor='white', alpha=1.0)) def plot_array_as_image(ax, arr, label='', **options): """ Plots a numpy array as an image. """ handle_options(ax, options) show_legend = options.pop("legend", True) plt.pcolormesh(arr, **options) ax.set_aspect('equal') if label: plt.text(0.95, 0.95, label, transform=ax.transAxes, ha='right', va='top', bbox=dict(facecolor='white', alpha=1.0)) if show_legend: plt.colorbar() def scatterplot(ax, data_table, label='', **options): handle_options(ax, options) if options.pop("show_fit", False): plt.plot(data_table.x, data_table.y_fit, 'k-') plt.scatter(data_table.x, data_table.y, **options) if label: plt.text(0.95, 0.95, label, transform=ax.transAxes, ha='right', va='top', bbox=dict(facecolor='white', alpha=1.0)) def plot_hist(ax, histogram, label='', **options): handle_options(ax, options) for t, n in histogram: ax.bar(t, n, width=histogram.bin_width, color=None) if label: plt.text(0.95, 0.95, label, transform=ax.transAxes, ha='right', va='top', bbox=dict(facecolor='white', alpha=1.0)) def variable_names(segment): """ List the names of all the AnalogSignals (used for the variable name by PyNN) in the given segment. """ return set(signal.name for signal in segment.analogsignals) class Figure(object): """ Provide simple, declarative specification of multi-panel figures. Example:: Figure( Panel(segment.filter(name="v")[0], ylabel="Membrane potential (mV)") Panel(segment.spiketrains, xlabel="Time (ms)"), title="Network activity", ).save("figure3.png") Valid options are: `settings`: for figure settings, e.g. {'font.size': 9} `annotations`: a (multi-line) string to be printed at the bottom of the figure. `title`: a string to be printed at the top of the figure. """ def __init__(self, *panels, **options): n_panels = len(panels) if "settings" in options and options["settings"] is not None: settings = options["settings"] else: settings = DEFAULT_FIG_SETTINGS plt.rcParams.update(settings) width, height = options.get("size", (6, 2 * n_panels + 1.2)) self.fig = plt.figure(1, figsize=(width, height)) gs = gridspec.GridSpec(n_panels, 1) if "annotations" in options: gs.update(bottom=1.2 / height) # leave space for annotations gs.update(top=1 - 0.8 / height, hspace=0.25) # print(gs.get_grid_positions(self.fig)) for i, panel in enumerate(panels): panel.plot(plt.subplot(gs[i, 0])) if "title" in options: self.fig.text(0.5, 1 - 0.5 / height, options["title"], ha="center", va="top", fontsize="large") if "annotations" in options: plt.figtext(0.01, 0.01, options["annotations"], fontsize=6, verticalalignment='bottom') def save(self, filename): """ Save the figure to file. The format is taken from the file extension. """ dirname = path.dirname(filename) if dirname and not path.exists(dirname): makedirs(dirname) self.fig.savefig(filename) def show(self): plt.show() class Panel(object): """ Represents a single panel in a multi-panel figure. A panel is a Matplotlib Axes or Subplot instance. A data item may be an AnalogSignal, AnalogSignal, or a list of SpikeTrains. The Panel will automatically choose an appropriate representation. Multiple data items may be plotted in the same panel. Valid options are any valid Matplotlib formatting options that should be applied to the Axes/Subplot, plus in addition: `data_labels`: a list of strings of the same length as the number of data items. `line_properties`: a list of dicts containing Matplotlib formatting options, of the same length as the number of data items. """ def __init__(self, *data, **options): self.data = list(data) self.options = options self.data_labels = options.pop("data_labels", repeat(None)) self.line_properties = options.pop("line_properties", repeat({})) def plot(self, axes): """ Plot the Panel's data in the provided Axes/Subplot instance. """ for datum, label, properties in zip(self.data, self.data_labels, self.line_properties): properties.update(self.options) if isinstance(datum, DataTable): scatterplot(axes, datum, label=label, **properties) elif isinstance(datum, Histogram): plot_hist(axes, datum, label=label, **properties) elif isinstance(datum, AnalogSignal): plot_signals(axes, datum, label_prefix=label, **properties) elif isinstance(datum, list) and len(datum) > 0 and isinstance(datum[0], SpikeTrain): plot_spiketrains(axes, datum, label=label, **properties) elif isinstance(datum, np.ndarray): if datum.ndim == 2: plot_array_as_image(axes, datum, label=label, **properties) else: raise Exception("Can't handle arrays with %s dimensions" % datum.ndim) else: raise Exception("Can't handle type %s" % type(datum)) def comparison_plot(segments, labels, title='', annotations=None, fig_settings=None, with_spikes=True): """ Given a list of segments, plot all the data they contain so as to be able to compare them. Return a Figure instance. """ variables_to_plot = set.union(*(variable_names(s) for s in segments)) print("Plotting the following variables: %s" % ", ".join(variables_to_plot)) # group signal arrays by name n_seg = len(segments) by_var_and_channel = defaultdict(lambda: defaultdict(list)) line_properties = [] units = {} for k, (segment, label) in enumerate(zip(segments, labels)): lw = 2 * (n_seg - k) - 1 col = 'bcgmkr'[k % 6] line_properties.append({"linewidth": lw, "color": col}) for array in segment.analogsignals: # rescale signals to the same units, for a given variable name if array.name not in units: units[array.name] = array.units elif array.units != units[array.name]: array = array.rescale(units[array.name]) for i in array.array_annotations["channel_index"].argsort(): channel = array.array_annotations["channel_index"][i] signal = array[:, i] by_var_and_channel[array.name][channel].append(signal) # each panel plots the signals for a given variable. panels = [] for by_channel in by_var_and_channel.values(): for array_list in by_channel.values(): ylabel = array_list[0].name if ylabel: ylabel += " ({})".format(array_list[0].dimensionality) panels.append( Panel(*array_list, line_properties=line_properties, yticks=True, ylabel=ylabel, data_labels=labels)) if with_spikes and len(segments[0].spiketrains) > 0: panels += [Panel(segment.spiketrains, data_labels=[label]) for segment, label in zip(segments, labels)] panels[-1].options["xticks"] = True panels[-1].options["xlabel"] = "Time (ms)" fig = Figure(*panels, title=title, settings=fig_settings, annotations=annotations) return fig class DataTable(object): """A lightweight encapsulation of x, y data for scatterplots.""" def __init__(self, x, y): self.x = x self.y = y def fit_curve(self, f, p0, **fitting_parameters): from scipy.optimize import curve_fit self._f = f self._p0 = p0 self._popt, self._pcov = curve_fit(f, self.x, self.y, p0, **fitting_parameters) return self._popt, self._pcov @property def y_fit(self): return self._f(self.x, *self._popt) class Histogram(object): """A lightweight encapsulation of histogram data.""" def __init__(self, data): self.data = data self.evaluated = False def evaluate(self): if not self.evaluated: n_bins = int(np.sqrt(len(self.data))) self.values, self.bins = np.histogram(self.data, bins=n_bins) self.bin_width = self.bins[1] - self.bins[0] self.evaluated = True def __iter__(self): """Iterate over the bars of the histogram""" self.evaluate() for x, y in zip(self.bins[:-1], self.values): yield (x, y) def isi_histogram(segment): all_isis = np.concatenate([np.diff(np.array(st)) for st in segment.spiketrains]) return Histogram(all_isis) PyNN-0.10.0/requirements.txt000066400000000000000000000001751415343567000157130ustar00rootroot00000000000000Jinja2>=2.6 docutils>=0.10 mock>1.0 numpy>=1.16.0 quantities>=0.12.1 lazyarray>=0.5.0 neo>=0.10.0 setuptools>=20.5 nose h5py PyNN-0.10.0/setup.py000066400000000000000000000125561415343567000141470ustar00rootroot00000000000000#!/usr/bin/env python try: from setuptools import setup from setuptools.command.build_py import build_py as _build tests_req = ["mpi4py", "scipy", "matplotlib", "Cheetah3", "h5py"] except ImportError: from distutils.core import setup from distutils.command.build_py import build_py as _build import os import subprocess def run_command(path, working_directory): p = subprocess.Popen(path, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True, cwd=working_directory) stdout, stderr = p.communicate() return p.returncode, stdout.split("\n") class build(_build): """At the end of the build process, try to compile NEURON and NEST extensions.""" def run(self): _build.run(self) # try to compile NEURON extensions nrnivmodl = self.find("nrnivmodl") if nrnivmodl: print("nrnivmodl found at", nrnivmodl) result, stdout = run_command(nrnivmodl, os.path.join(os.getcwd(), self.build_lib, 'pyNN/neuron/nmodl')) # test if nrnivmodl was successful if result != 0: print("Unable to compile NEURON extensions. Output was:") print(' '.join([''] + stdout)) # indent error msg for easy comprehension else: print("Successfully compiled NEURON extensions.") else: print("Unable to find nrnivmodl. It will not be possible to use the pyNN.neuron module.") # try to compile NEST extensions nest_config = self.find("nest-config") if nest_config: print("nest-config found at", nest_config) nest_build_dir = os.path.join(os.getcwd(), self.build_lib, 'pyNN/nest/_build') if not os.path.exists(nest_build_dir): os.mkdir(nest_build_dir) result, stdout = run_command("cmake -Dwith-nest={} ../extensions".format(nest_config), nest_build_dir) if result != 0: print("Problem running cmake. Output was:") print(' '.join([''] + stdout)) else: result, stdout = run_command("make", nest_build_dir) if result != 0: print("Unable to compile NEST extensions. Output was:") print(' '.join([''] + stdout)) else: # should really move this to install stage result, stdout = run_command("make install", nest_build_dir) if result != 0: print("Unable to install NEST extensions. Output was:") print(' '.join([''] + stdout)) else: print("Successfully compiled NEST extensions.") def find(self, command): """Try to find an executable file.""" path = os.environ.get("PATH", "").split(os.pathsep) cmd = '' for dir_name in path: abs_name = os.path.abspath(os.path.normpath(os.path.join(dir_name, command))) if os.path.isfile(abs_name): cmd = abs_name break return cmd setup( name="PyNN", version="0.10.0", packages=['pyNN', 'pyNN.nest', 'pyNN.neuron', 'pyNN.brian2', 'pyNN.common', 'pyNN.mock', 'pyNN.neuroml', 'pyNN.recording', 'pyNN.standardmodels', 'pyNN.descriptions', 'pyNN.nest.standardmodels', 'pyNN.neuroml.standardmodels', 'pyNN.neuron.standardmodels', 'pyNN.brian2.standardmodels', 'pyNN.utility', 'pyNN.nineml', 'pyNN.serialization'], package_data={'pyNN': ['neuron/nmodl/*.mod', 'nest/extensions/*.h', 'nest/extensions/*.cpp', 'nest/extensions/CMakeLists.txt', 'nest/extensions/sli/*.sli', "descriptions/templates/*/*"]}, author="The PyNN team", author_email="andrew.davison@unic.cnrs-gif.fr", description="A Python package for simulator-independent specification of neuronal network models", long_description=open("README.rst").read(), license="CeCILL http://www.cecill.info", keywords="computational neuroscience simulation neuron nest brian2 neuromorphic", url="http://neuralensemble.org/PyNN/", classifiers=['Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Science/Research', 'License :: Other/Proprietary License', 'Natural Language :: English', 'Operating System :: OS Independent', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Topic :: Scientific/Engineering'], cmdclass={'build_py': build}, install_requires=['numpy>=1.16.1', 'lazyarray>=0.5.0', 'neo>=0.10.0', 'quantities>=0.12.1'], extras_require={ 'examples': ['matplotlib', 'scipy'], 'plotting': ['matplotlib', 'scipy'], 'MPI': ['mpi4py'], 'sonata': ['h5py'] }, tests_require=tests_req ) PyNN-0.10.0/test/000077500000000000000000000000001415343567000134035ustar00rootroot00000000000000PyNN-0.10.0/test/README000066400000000000000000000005261415343567000142660ustar00rootroot00000000000000PyNN testing is divided into several different types of test: * checking the API is syntactically consistent between different backends * unit tests * system tests * running the example scripts * doctests For full documentation on testing, see:http://neuralensemble.org/docs/PyNN/developers/contributing.html#testing PyNN-0.10.0/test/benchmarks/000077500000000000000000000000001415343567000155205ustar00rootroot00000000000000PyNN-0.10.0/test/benchmarks/Benchmark_PyNN-0.8dev_FixedNumberPost.py000066400000000000000000000216551415343567000250410ustar00rootroot00000000000000# coding: utf-8 """ This script is a modified version of http://neuralensemble.org/trac/PyNN/wiki/Examples/VogelsAbbott An implementation of benchmarks 1 and 2 from Brette et al. (2007) Journal of Computational Neuroscience 23: 349-398 simulator_name The IF network is based on the CUBA and COBA models of Vogels & Abbott (J. Neurosci, 2005). The model consists of a network of excitatory and inhibitory neurons, connected via current-based "exponential" synapses (instantaneous rise, exponential decay). Andrew Davison, UNIC, CNRS August 2006 Author: Bernhard Kaplan, bkaplan@kth.se """ import time t0 = time.time() # to store timing information from pyNN.utility import Timer timer = Timer() timer.start() times = {} times['t_startup'] = time.time() - t0 # check imports import numpy as np import os import socket from math import * import json from pyNN.utility import get_script_args simulator_name = 'nest' from pyNN.nest import * #exec("from pyNN.%s import *" % simulator_name) try: from mpi4py import MPI USE_MPI = True comm = MPI.COMM_WORLD node_id, n_proc = comm.rank, comm.size print("USE_MPI:", USE_MPI, 'pc_id, n_proc:', node_id, n_proc) except: USE_MPI = False node_id, n_proc, comm = 0, 1, None print("MPI not used") from pyNN.random import NumpyRNG, RandomDistribution times['t_import'] = timer.diff() # === DEFINE PARAMETERS benchmark = "COBA" rngseed = 98765 parallel_safe = True np = num_processes() folder_name = 'Results_PyNN_FixedNumberPost_np%d/' % (np) gather = False # gather spikes and membrane potentials on one process times_fn = 'pynn_times_FixedNumberPost_gather%d_np%d.dat' % (gather, np) n_cells = 200 * np r_ei = 4.0 # number of excitatory cells:number of inhibitory cells n_exc = int(round((n_cells * r_ei / (1 + r_ei)))) # number of excitatory cells n_inh = n_cells - n_exc # number of inhibitory cells n_cells_to_record = np n_conn_out = 1000 # number of outgoing connections per neuron weight = 1e-8 # connection weights f_noise_exc = 3000. f_noise_inh = 2000. w_noise_exc = 1e-3 w_noise_inh = 1e-3 dt = 0.1 # (ms) simulation timestep t_sim = 1000 # (ms) simulaton duration delay = 1 * dt # === SETUP node_id = setup(timestep=dt, min_delay=delay, max_delay=delay) times['t_setup'] = timer.diff() host_name = socket.gethostname() print("Host #%d is on %s" % (node_id + 1, host_name)) # === CREATE print("%s Creating cell populations..." % node_id) #celltype = IF_cond_exp exc_cells = Population(n_exc, IF_cond_exp(), label="Excitatory_Cells") inh_cells = Population(n_inh, IF_cond_exp(), label="Inhibitory_Cells") times['t_create'] = timer.diff() print("Creating noise sources ...") exc_noise_in_exc = Population(n_exc, SpikeSourcePoisson, {'rate': f_noise_exc}) inh_noise_in_exc = Population(n_exc, SpikeSourcePoisson, {'rate': f_noise_inh}) exc_noise_in_inh = Population(n_inh, SpikeSourcePoisson, {'rate': f_noise_exc}) inh_noise_in_inh = Population(n_inh, SpikeSourcePoisson, {'rate': f_noise_inh}) times['t_create_noise'] = timer.diff() print("%s Initialising membrane potential to random values..." % node_id) rng = NumpyRNG(seed=rngseed, parallel_safe=parallel_safe) uniformDistr = RandomDistribution('uniform', [-50, -70], rng=rng) exc_cells.initialize(v=uniformDistr) inh_cells.initialize(v=uniformDistr) times['t_vinit'] = timer.diff() print("%s Connecting populations..." % node_id) ee_conn = FixedNumberPostConnector(n_conn_out) # , weights=weight, delays=delay) ei_conn = FixedNumberPostConnector(n_conn_out) # , weights=weight, delays=delay) ie_conn = FixedNumberPostConnector(n_conn_out) # , weights=weight, delays=delay) ii_conn = FixedNumberPostConnector(n_conn_out) # , weights=weight, delays=delay) times['t_connector'] = timer.diff() connections = {} connections['e2e'] = Projection(exc_cells, exc_cells, ee_conn, receptor_type='excitatory') # , rng=rng) connections['e2i'] = Projection(exc_cells, inh_cells, ei_conn, receptor_type='excitatory') # , rng=rng) connections['i2e'] = Projection(inh_cells, exc_cells, ie_conn, receptor_type='inhibitory') # , rng=rng) connections['i2i'] = Projection(inh_cells, inh_cells, ii_conn, receptor_type='inhibitory') # , rng=rng) connections['e2e'].set(weight=weight) connections['e2i'].set(weight=weight) connections['i2e'].set(weight=weight) connections['i2i'].set(weight=weight) times['t_projection'] = timer.diff() exc_noise_connector = OneToOneConnector() inh_noise_connector = OneToOneConnector() noise_ee_prj = Projection(exc_noise_in_exc, exc_cells, exc_noise_connector, receptor_type="excitatory") noise_ei_prj = Projection(exc_noise_in_inh, inh_cells, exc_noise_connector, receptor_type="excitatory") noise_ie_prj = Projection(inh_noise_in_exc, exc_cells, inh_noise_connector, receptor_type="inhibitory") noise_ii_prj = Projection(inh_noise_in_inh, inh_cells, inh_noise_connector, receptor_type="inhibitory") noise_ee_prj.set(weight=w_noise_exc) noise_ei_prj.set(weight=w_noise_exc) noise_ie_prj.set(weight=w_noise_inh) noise_ii_prj.set(weight=w_noise_inh) times['t_connect_noise'] = timer.diff() # === Setup recording ========================================================== print("%s Setting up recording..." % node_id) exc_cells.record('spikes') inh_cells.record('spikes') cells_to_record = range(n_cells_to_record) exc_cells[list(cells_to_record)].record_v() times['t_record'] = timer.diff() print("%d Print(exc spikes to file..." % node_id) if not(os.path.isdir(folder_name)) and (rank() == 0): os.mkdir(folder_name) # === Save connections to file ================================================= #print("%s Saving projections ..." % node_id #for prj in connections.keys(): # connections[prj].saveConnections('%s/VAbenchmark_%s_%s_%s_np%d.conn' % (folder_name, benchmark, prj, simulator_name, np)) #times['t_save_connections'] = timer.diff() # === Run simulation =========================================================== print("%d Running simulation..." % node_id) run(t_sim) times['t_run'] = timer.diff() # === Print(results to file ==================================================== exc_spike_fn = "%s/VAbenchmark_%s_exc_%s_np%d_%d.pkl" % (folder_name, benchmark, simulator_name, np, node_id) exc_cells.printSpikes(exc_spike_fn, gather=gather) print("%d Print(inh spikes to file..." % node_id) inh_spike_fn = "%s/VAbenchmark_%s_inh_%s_np%d_%d.pkl" % (folder_name, benchmark, simulator_name, np, node_id) inh_cells.printSpikes(inh_spike_fn, gather=gather) print("%d Print(voltage to file..." % node_id) exc_cells[list(cells_to_record)].print_v("%s/VAbenchmark_%s_exc_%s_np%d_%d.pkl" % (folder_name, benchmark, simulator_name, np, node_id), gather=gather) times['t_printSpikes'] = timer.diff() # === Load spike file and calculate conductances ==================== #exc_spikes = np.loadtxt(exc_spike_fn) #E_count = exc_cells.meanSpikeCount() #I_count = inh_cells.meanSpikeCount() #f_exc = E_count*1000.0/t_sim #f_inh = I_count*1000.0/t_sim #g_ee = f_exc * w_ee * tau_exc * connections['e2e'].size() / n_exc #g_ei = f_exc * w_ei * tau_exc * connections['e2i'].size() / n_exc #g_ie = f_inh * w_ie * tau_inh * connections['i2e'].size() / n_inh #g_ii = f_inh * w_ii * tau_inh * connections['i2i'].size() / n_inh connections_string = "%d e→e %d e→i %d i→e %d i→i" % (connections['e2e'].size(), connections['e2i'].size(), connections['i2e'].size(), connections['i2i'].size()) n_total = connections['e2e'].size() + connections['e2i'].size() + connections['i2e'].size() + connections['i2i'].size() n_connections = connections['e2e'].size() + connections['e2i'].size() + connections['i2e'].size() + connections['i2i'].size() n_conn_ee, n_conn_ei, n_conn_ie, n_conn_ii = connections['e2e'].size(), connections['e2i'].size(), connections['i2e'].size(), connections['i2i'].size() times['t_analysis'] = timer.diff() if node_id == 0: print("\n--- Vogels-Abbott Network Simulation ---") print("Nodes : %d" % np) print("Simulation type : %s" % benchmark) print("Simulator name : %s" % simulator_name) print("Number of Neurons : %d n_exc %d n_inh %d" % (n_cells, n_exc, n_inh)) print("Number of Synapses : %s" % connections_string) print("Total Num Synapses : %s" % n_total) # print("Excitatory rate : %g Hz" % f_exc) # print("Inhibitory rate : %g Hz" % f_inh) # print(timing_info) print("%d PyNN end" % node_id) end() print("%d PyNN end finish" % node_id) times['t_end'] = timer.diff() if node_id == 0: t_all = 0. for k in times.keys(): t_all += times[k] times['t_sum'] = t_all times['n_exc'] = n_exc times['n_inh'] = n_inh times['n_cells'] = n_cells times['n_proc'] = np times['n_ee'] = n_conn_ee times['n_ei'] = n_conn_ei times['n_ie'] = n_conn_ie times['n_ii'] = n_conn_ii times['n_connections'] = n_connections f = file(times_fn, 'w') json.dump(times, f) PyNN-0.10.0/test/benchmarks/README.txt000066400000000000000000000026541415343567000172250ustar00rootroot00000000000000===================== Benchmarking strategy ===================== The aim of benchmarking is to provide measurements that 1. allow us to determine the effect of code changes on performance; 2. tell us how much overhead PyNN has compared to the underlying simulators; 3. show how well PyNN scales with the number of MPI processes. Point 2 means that where possible, we should provide both a PyNN version and a PyNEST version of benchmarks. PyNEURON, Brian and NEST-SLI versions would also be interesting. We should use the PyNN Timer object to separate timings for different phases, i.e. * module imports * setup * network construction - creating neurons - creating connections * recording specification * simulation * data retrieval We should start with a fresh kernel every time, except where we specifically test the effect of reset. Each simulation should write to a common database, from which we can generate reports. Ideally, we would use Sumatra or Mozaik for this, but it might be simpler to start with, e.g., shelve or csv Ideas for benchmarks: * a simple network with two connected populations, varying: - number of neurons - number of connections - neuron type (in particular, spike sources are treated very differently in NEST to other neuron models) - synapse type - connection method - number of neurons recorded * incremental simulation, with and without clearing recorders. PyNN-0.10.0/test/benchmarks/all_to_all_network.param000066400000000000000000000016431415343567000224210ustar00rootroot00000000000000{ "simulator": "pyNN.nest", "threads": 1, "populations": { "A": { "n": 10000, "celltype": "IF_cond_exp", "params": { "i_offset": 1.0 }}, "B": { "n": 10000, "celltype": "IF_cond_exp", "params": { "i_offset": 0.5 }}}, "sim_time": 100.0, "recording": { "A": { "v": 10, "spikes": 1000}, "B": { "v": 10, "spikes": 1000}}, "projections": { "AB": { "pre": "A", "post": "B", "connector": { "type": "AllToAllConnector", "params": {}}, "synapse_type": { "type": "StaticSynapse", "params": { "weight": 0.000001, "delay": 1.5}}, "receptor_type": "excitatory"}} } PyNN-0.10.0/test/benchmarks/connectors_benchmark.py000066400000000000000000000147631415343567000222740ustar00rootroot00000000000000from pylab import * from pyNN.utility import Timer, init_logging, ProgressBar import os simulator_name = sys.argv[1] exec("from pyNN.%s import *" % simulator_name) test_cases = [int(x) for x in sys.argv[2:]] from pyNN.recording import files from pyNN.space import * timer = Timer() progress_bar = ProgressBar(mode='fixed', width=20) init_logging("connectors_benchmark_%s.log" % simulator_name, debug=True) def draw_rf(cell, positions, connections, color='k'): idx = np.where(connections[:, 1] == cell)[0] sources = connections[idx, 0] for src in sources: plot([positions[cell, 1], positions[src, 1]], [positions[cell, 2], positions[src, 2]], c=color) def distances(pos_1, pos_2, N): dx = abs(pos_1[:, 0] - pos_2[:, 0]) dy = abs(pos_1[:, 1] - pos_2[:, 1]) dx = np.minimum(dx, N - dx) dy = np.minimum(dy, N - dy) return sqrt(dx * dx + dy * dy) timer.start() node_id = setup(timestep=0.1, min_delay=0.1, max_delay=4.) print("Creating cells population...") N = 30 structure = RandomStructure(Cuboid(1, 1, 1), origin=(0.5, 0.5, 0.5), rng=NumpyRNG(2652)) #structure = Grid2D(dx=1/float(N), dy=1/float(N)) x = Population(N**2, IF_curr_exp(), structure=structure) mytime = timer.diff() print("Time to build the cell population:", mytime, 's') def test(cases=[1]): sp = Space(periodic_boundaries=((0, 1), (0, 1), None), axes='xy') safe = False callback = progress_bar.set_level autapse = False parallel_safe = True render = True to_file = True for case in cases: #w = RandomDistribution('uniform', (0,1)) w = "0.2 + d/0.2" #w = 0.1 #w = lambda dist : 0.1 + np.random.rand(len(dist[0]))*sqrt(dist[0]**2 + dist[1]**2) #delay = RandomDistribution('uniform', (0.1,5.)) #delay = "0.1 + d/0.2" delay = 0.1 #delay = lambda distances : 0.1 + np.random.rand(len(distances))*distances d_expression = "exp(-d**2/(2*0.1**2))" #d_expression = "(d[0] < 0.05) & (d[1] < 0.05)" #d_expression = "(d[0]/(0.05**2) + d[1]/(0.1**2)) < 100*np.random.rand()" timer = Timer() np = num_processes() timer.start() synapse = StaticSynapse(weight=w, delay=delay) rng = NumpyRNG(23434, parallel_safe=parallel_safe) if case is 1: conn = DistanceDependentProbabilityConnector(d_expression, safe=safe, callback=callback, allow_self_connections=autapse, rng=rng) fig_name = "DistanceDependent_%s_np_%d.png" % (simulator_name, np) elif case is 2: conn = FixedProbabilityConnector(0.02, safe=safe, callback=callback, allow_self_connections=autapse, rng=rng) fig_name = "FixedProbability_%s_np_%d.png" % (simulator_name, np) elif case is 3: conn = AllToAllConnector(delays=delay, safe=safe, callback=callback, allow_self_connections=autapse) fig_name = "AllToAll_%s_np_%d.png" % (simulator_name, np) elif case is 4: conn = FixedNumberPostConnector(50, safe=safe, callback=callback, allow_self_connections=autapse, rng=rng) fig_name = "FixedNumberPost_%s_np_%d.png" % (simulator_name, np) elif case is 5: conn = FixedNumberPreConnector(50, safe=safe, callback=callback, allow_self_connections=autapse, rng=rng) fig_name = "FixedNumberPre_%s_np_%d.png" % (simulator_name, np) elif case is 6: conn = OneToOneConnector(safe=safe, callback=callback) fig_name = "OneToOne_%s_np_%d.png" % (simulator_name, np) elif case is 7: conn = FromFileConnector(files.NumpyBinaryFile('Results/connections.dat', mode='r'), safe=safe, callback=callback, distributed=True) fig_name = "FromFile_%s_np_%d.png" % (simulator_name, np) elif case is 8: conn = SmallWorldConnector(degree=0.1, rewiring=0., safe=safe, callback=callback, allow_self_connections=autapse) fig_name = "SmallWorld_%s_np_%d.png" % (simulator_name, np) print("Generating data for %s" % fig_name) prj = Projection(x, x, conn, synapse, space=sp) mytime = timer.diff() print("Time to connect the cell population:", mytime, 's') print("Nb synapses built", prj.size()) if to_file: if not(os.path.isdir('Results')): os.mkdir('Results') print("Saving Connections....") prj.save('all', files.NumpyBinaryFile('Results/connections.dat', mode='w'), gather=True) mytime = timer.diff() print("Time to save the projection:", mytime, 's') if render and to_file: print("Saving Positions....") x.save_positions('Results/positions.dat') end() if node_id == 0 and render and to_file: figure() print("Generating and saving %s" % fig_name) positions = np.loadtxt('Results/positions.dat') positions[:, 0] -= positions[:, 0].min() connections = files.NumpyBinaryFile('Results/connections.dat', mode='r').read() print(positions.shape, connections.shape) connections[:, 0] -= connections[:, 0].min() connections[:, 1] -= connections[:, 1].min() idx_pre = connections[:, 0].astype(int) idx_post = connections[:, 1].astype(int) d = distances(positions[idx_pre, 1:3], positions[idx_post, 1:3], 1) subplot(231) title('Cells positions') plot(positions[:, 1], positions[:, 2], '.') subplot(232) title('Weights distribution') hist(connections[:, 2], 50) subplot(233) title('Delay distribution') hist(connections[:, 3], 50) subplot(234) np.random.seed(74562) ids = np.random.permutation(positions[:, 0])[0:6] colors = ['k', 'r', 'b', 'g', 'c', 'y'] for count, cell in enumerate(ids): draw_rf(cell, positions, connections, colors[count]) subplot(235) plot(d, connections[:, 2], '.') subplot(236) plot(d, connections[:, 3], '.') savefig("Results/" + fig_name) #os.remove('Results/connections.dat') #os.remove('Results/positions.dat') show() if __name__ == '__main__': #import hotshot, os #prof = hotshot.Profile("hotshot_edi_stats") #prof.runcall(test) #prof.close() #from hotshot import stats #s = stats.load("hotshot_edi_stats") #s.sort_stats("time").print_stats() test(test_cases) PyNN-0.10.0/test/benchmarks/ddpc.py000066400000000000000000000014441415343567000170070ustar00rootroot00000000000000from NeuroTools.parameters import ParameterSet import sys from math import sqrt from pyNN.space import Space, Grid2D P = ParameterSet(sys.argv[1]) exec("import pyNN.%s as sim" % P.simulator) sim.setup() dx1 = dy1 = 500.0 / sqrt(P.n1) dx2 = dy2 = 500.0 / sqrt(P.n2) struct1 = Grid2D(dx=dx1, dy=dy1) struct2 = Grid2D(dx=dx2, dy=dy2) p1 = sim.Population(P.n1, sim.IF_cond_exp, structure=struct1) p2 = sim.Population(P.n2, sim.IF_cond_exp, structure=struct2) space = Space() DDPC = sim.DistanceDependentProbabilityConnector c = DDPC(P.d_expression, P.allow_self_connections, P.weights, P.delays, space, P.safe) prj = sim.Projection(p1, p2, c) sys.stdout.write(p1.describe().encode('utf-8')) sys.stdout.write(p2.describe().encode('utf-8')) sys.stdout.write(prj.describe().encode('utf-8')) sim.end() PyNN-0.10.0/test/benchmarks/neurons_no_recording.param000066400000000000000000000001361415343567000227630ustar00rootroot00000000000000{ "simulator": "pyNN.nest", "n": 10000, "sim_time": 100.0, "recording": None }PyNN-0.10.0/test/benchmarks/neurons_with_recording.param000066400000000000000000000007721415343567000233300ustar00rootroot00000000000000{ "simulator": "pyNN.nest", "populations": { "A": { "n": 10000, "celltype": "IF_cond_exp", "params": { "i_offset": 1.0 }}, "B": { "n": 10000, "celltype": "IF_cond_exp", "params": { "i_offset": 0.5 }}}, "sim_time": 100.0, "recording": { "A": { "v": 100, "spikes": 1000}, "B": { "v": 100, "spikes": 1000}} } PyNN-0.10.0/test/benchmarks/plot_figure.py000066400000000000000000000067721415343567000204250ustar00rootroot00000000000000""" Plot graphs summarizing benchmark results generated by simple_network.py Usage: python plot_figure.py [-h] [-o FILENAME] parameter_file data_store positional arguments: parameter_file parameter file given to simple_network.py data_store data file produced by simple_network.py, in CSV format optional arguments: -h, --help show this help message and exit -o FILENAME output file name """ import csv import argparse from pprint import pprint import numpy as np import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec from collections import defaultdict from parameters import ParameterSet # Parse command-line arguments and read parameter file parser = argparse.ArgumentParser() parser.add_argument("parameter_file", help="parameter file given to simple_network.py") parser.add_argument("data_store", help="data file produced by simple_network.py, in CSV format") parser.add_argument("-o", metavar="FILENAME", help="output file name", default="Results/benchmark_summary.png") args = parser.parse_args() parameters = ParameterSet(args.parameter_file) # Read data from CSV file with open(args.data_store, "rb") as csvfile: reader = csv.DictReader(csvfile, quoting=csv.QUOTE_NONNUMERIC) records = list(reader) # Filter and re-format data for plotting independent_variable = "num_processes" dependent_variables = ["import", "setup", "build", "connect", "record", "run", "get_data"] conditions = parameters.flatten() results = dict((var, defaultdict(list)) for var in dependent_variables) stats = dict((var, {}) for var in dependent_variables) def matches_conditions(record, conditions): return all((record[condition] == value) for condition, value in conditions.items()) # ... for each record that matches the conditions, add the timing data to the filtered results for record in records: if matches_conditions(record, conditions): x = record[independent_variable] for var in dependent_variables: results[var][x].append(record[var]) # ... then calculate the statistics across repetitions for var in dependent_variables: for x, values in results[var].items(): stats[var][x] = np.mean(values), np.std(values, ddof=1) # Plot the results def sort_by_first(*y): zipped = zip(*y) zipped_sorted = sorted(zipped, key=lambda x: x[0]) return map(list, zip(*zipped_sorted)) settings = { 'lines.linewidth': 0.5, 'axes.linewidth': 0.5, 'axes.labelsize': 'small', 'legend.fontsize': 'small', 'font.size': 8, 'savefig.dpi': 150, } plt.rcParams.update(settings) width, height = 6, 10 fig = plt.figure(1, figsize=(width, height)) gs = gridspec.GridSpec(1, 1) gs.update(bottom=0.6) # leave space for annotations gs.update(top=1 - 0.8 / height, hspace=0.1) ax = plt.subplot(gs[0, 0]) for var in dependent_variables: x = stats[var].keys() y, yerr = map(list, zip(*stats[var].values())) x, y, yerr = sort_by_first(x, y, yerr) ax.errorbar(x, y, yerr=yerr, fmt='o-', label=var) ax.set_xlabel(independent_variable) ax.set_xlim([x[0] / 1.4, x[-1] * 1.4]) ax.set_ylabel("Time (s)") ax.set_xscale("log", basex=2) ax.set_yscale("log", basex=2) ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.05), ncol=3, fancybox=True, shadow=True) title = args.parameter_file fig.text(0.5, 1 - 0.5 / height, title, ha="center", va="top", fontsize="large") annotations = parameters.pretty() plt.figtext(0.01, 0.01, annotations, fontsize=6, verticalalignment='bottom') fig.savefig(args.o) PyNN-0.10.0/test/benchmarks/simple_network.py000066400000000000000000000105121415343567000211330ustar00rootroot00000000000000# coding: utf-8 """ A simple network model for benchmarking purposes. Usage: python simple_network.py [-h] parameter_file data_store positional arguments: parameter_file Parameter file (for format see http://parameters.readthedocs.org/) data_store filename for output data file optional arguments: -h, --help show this help message and exit """ from importlib import import_module from parameters import ParameterSet from pyNN.utility import Timer def main_pyNN(parameters): timer = Timer() sim = import_module(parameters.simulator) timer.mark("import") sim.setup(threads=parameters.threads) timer.mark("setup") populations = {} for name, P in parameters.populations.parameters(): populations[name] = sim.Population(P.n, getattr(sim, P.celltype)(**P.params), label=name) timer.mark("build") if parameters.projections: projections = {} for name, P in parameters.projections.parameters(): connector = getattr(sim, P.connector.type)(**P.connector.params) synapse_type = getattr(sim, P.synapse_type.type)(**P.synapse_type.params) projections[name] = sim.Projection(populations[P.pre], populations[P.post], connector, synapse_type, receptor_type=P.receptor_type, label=name) timer.mark("connect") if parameters.recording: for pop_name, to_record in parameters.recording.parameters(): for var_name, n_record in to_record.items(): populations[pop_name].sample(n_record).record(var_name) timer.mark("record") sim.run(parameters.sim_time) timer.mark("run") spike_counts = {} if parameters.recording: for pop_name in parameters.recording.names(): block = populations[pop_name].get_data() # perhaps include some summary statistics in the data returned? spike_counts["spikes_%s" % pop_name] = populations[pop_name].mean_spike_count() timer.mark("get_data") mpi_rank = sim.rank() num_processes = sim.num_processes() sim.end() data = dict(timer.marks) data.update(num_processes=num_processes) data.update(spike_counts) return mpi_rank, data def main_pynest(parameters): P = parameters assert P.sim_name == "pynest" timer = Timer() import nest timer.mark("import") nest.SetKernelStatus({"resolution": 0.1}) timer.mark("setup") p = nest.Create("iaf_psc_alpha", n=P.n, params={"I_e": 1000.0}) timer.mark("build") # todo: add recording and data retrieval nest.Simulate(P.sim_time) timer.mark("run") mpi_rank = nest.Rank() num_processes = nest.NumProcesses() data = P.as_dict() data.update(num_processes=num_processes, timings=timer.marks) return mpi_rank, data if __name__ == "__main__": from datetime import datetime import argparse parser = argparse.ArgumentParser() parser.add_argument("parameter_file", help="Parameter file (for format see http://parameters.readthedocs.org/)") parser.add_argument("data_store", help="filename for output data file") args = parser.parse_args() parameters = ParameterSet(args.parameter_file) #print(parameters.pretty()) timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") if parameters.simulator == "pynest": main = main_pynest else: main = main_pyNN mpi_rank, data = main(parameters) if mpi_rank == 0: #import shelve #shelf = shelve.open(args.data_store) #shelf[timestamp] = data #shelf.close() import os, csv parameters_flat = parameters.flatten() fieldnames = ["timestamp"] + parameters_flat.keys() + data.keys() data.update(timestamp=timestamp) data.update(parameters_flat) write_header = True if os.path.exists(args.data_store): write_header = False with open(args.data_store, "a+b") as csvfile: writer = csv.DictWriter(csvfile, fieldnames, quoting=csv.QUOTE_NONNUMERIC) if write_header: writer.writeheader() writer.writerow(data) PyNN-0.10.0/test/system/000077500000000000000000000000001415343567000147275ustar00rootroot00000000000000PyNN-0.10.0/test/system/__init__.py000066400000000000000000000000001415343567000170260ustar00rootroot00000000000000PyNN-0.10.0/test/system/scenarios/000077500000000000000000000000001415343567000167155ustar00rootroot00000000000000PyNN-0.10.0/test/system/scenarios/__init__.py000066400000000000000000000013241415343567000210260ustar00rootroot00000000000000# encoding: utf-8 from testconfig import config if 'testFile' in config: file_name = config['testFile'] exec("from . import ( %s )" % file_name) else: from . import (scenario1, scenario2, scenario3, ticket166, test_simulation_control, test_recording, test_cell_types, test_electrodes, scenario4, test_parameter_handling, test_procedural_api, issue274, test_connectors, issue231, test_connection_handling, test_synapse_types) PyNN-0.10.0/test/system/scenarios/issue231.py000066400000000000000000000013151415343567000206450ustar00rootroot00000000000000""" min_delay should be calculated automatically if not set """ from nose.tools import assert_equal from .registry import register # for NEURON, this only works when run with MPI and more than one process @register(exclude="neuron") def issue231(sim): sim.setup(min_delay='auto') p1 = sim.Population(13, sim.IF_cond_exp()) p2 = sim.Population(25, sim.IF_cond_exp()) connector = sim.AllToAllConnector() synapse = sim.StaticSynapse(delay=0.5) prj = sim.Projection(p1, p2, connector, synapse) sim.run(100.0) assert_equal(sim.get_min_delay(), 0.5) sim.end() if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() issue231(sim) PyNN-0.10.0/test/system/scenarios/issue274.py000066400000000000000000000013101415343567000206470ustar00rootroot00000000000000from .registry import register from pyNN.random import RandomDistribution as rnd @register() def issue274(sim): """Issue with offset in GIDs""" sim.setup(min_delay=0.5) p0 = sim.Population(13, sim.IF_cond_exp()) p1 = sim.Population(1000, sim.IF_cond_exp()) p2 = sim.Population(252, sim.IF_cond_exp()) connector = sim.DistanceDependentProbabilityConnector("exp(-d/100)") prj = sim.Projection(p1, p2, connector) w_dist = rnd("uniform", low=1e-6, high=2e-6) delay_dist = rnd("uniform", low=0.5, high=1.0) prj.set(weight=w_dist, delay=delay_dist) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() issue274(sim) PyNN-0.10.0/test/system/scenarios/registry.py000066400000000000000000000005361415343567000211430ustar00rootroot00000000000000from testconfig import config registry = [] def register(exclude=[]): def inner_register(scenario): if scenario not in registry and not ('testName' in config and not scenario.__name__ == config['testName']): scenario.exclude = exclude registry.append(scenario) return scenario return inner_register PyNN-0.10.0/test/system/scenarios/scenario1.py000066400000000000000000000110121415343567000211460ustar00rootroot00000000000000 import glob import os from nose.tools import assert_equal from pyNN.random import NumpyRNG, RandomDistribution from .registry import register @register() def scenario1(sim): """ Balanced network of integrate-and-fire neurons. """ cell_params = { 'tau_m': 20.0, 'tau_syn_E': 5.0, 'tau_syn_I': 10.0, 'v_rest': -60.0, 'v_reset': -60.0, 'v_thresh': -50.0, 'cm': 1.0, 'tau_refrac': 5.0, 'e_rev_E': 0.0, 'e_rev_I': -80.0 } stimulation_params = {'rate': 100.0, 'duration': 50.0} n_exc = 80 n_inh = 20 n_input = 20 rngseed = 98765 parallel_safe = True n_threads = 1 pconn_recurr = 0.02 pconn_input = 0.01 tstop = 900.0 delay = 0.2 dt = 0.1 weights = { 'excitatory': 4.0e-3, 'inhibitory': 51.0e-3, 'input': 0.1, } sim.setup(timestep=dt, min_delay=dt, threads=n_threads) all_cells = sim.Population(n_exc + n_inh, sim.IF_cond_exp(**cell_params), label="All cells") cells = { 'excitatory': all_cells[:n_exc], 'inhibitory': all_cells[n_exc:], 'input': sim.Population(n_input, sim.SpikeSourcePoisson(**stimulation_params), label="Input") } rng = NumpyRNG(seed=rngseed, parallel_safe=parallel_safe) uniform_distr = RandomDistribution('uniform', low=cell_params['v_reset'], high=cell_params['v_thresh'], rng=rng) all_cells.initialize(v=uniform_distr) connections = {} for name, pconn, receptor_type in ( ('excitatory', pconn_recurr, 'excitatory'), ('inhibitory', pconn_recurr, 'inhibitory'), ('input', pconn_input, 'excitatory'), ): connector = sim.FixedProbabilityConnector(pconn, rng=rng) syn = sim.StaticSynapse(weight=weights[name], delay=delay) connections[name] = sim.Projection(cells[name], all_cells, connector, syn, receptor_type=receptor_type, label=name) all_cells.record('spikes') cells['excitatory'][0:2].record('v') assert_equal(cells['excitatory'][0:2].grandparent, all_cells) sim.run(tstop) E_count = cells['excitatory'].mean_spike_count() I_count = cells['inhibitory'].mean_spike_count() print("Excitatory rate : %g Hz" % (E_count * 1000.0 / tstop,)) print("Inhibitory rate : %g Hz" % (I_count * 1000.0 / tstop,)) sim.end() @register() def scenario1a(sim): """ Balanced network of integrate-and-fire neurons, built with the "low-level" API. """ cell_params = { 'tau_m': 10.0, 'tau_syn_E': 2.0, 'tau_syn_I': 5.0, 'v_rest': -60.0, 'v_reset': -65.0, 'v_thresh': -55.0, 'cm': 0.5, 'tau_refrac': 2.5, 'e_rev_E': 0.0, 'e_rev_I': -75.0 } stimulation_params = {'rate': 80.0, 'duration': 50.0} n_exc = 80 n_inh = 20 n_input = 20 rngseed = 87546 parallel_safe = True n_threads = 1 pconn_recurr = 0.03 pconn_input = 0.01 tstop = 1100.0 delay = 1 w_exc = 3.0e-3 w_inh = 45.0e-3 w_input = 0.12 dt = 0.1 sim.setup(timestep=dt, min_delay=dt, threads=n_threads) iaf_neuron = sim.IF_cond_alpha(**cell_params) excitatory_cells = sim.create(iaf_neuron, n=n_exc) inhibitory_cells = sim.create(iaf_neuron, n=n_inh) inputs = sim.create(sim.SpikeSourcePoisson(**stimulation_params), n=n_input) all_cells = excitatory_cells + inhibitory_cells sim.initialize(all_cells, v=cell_params['v_rest']) sim.connect(excitatory_cells, all_cells, weight=w_exc, delay=delay, receptor_type='excitatory', p=pconn_recurr) sim.connect(inhibitory_cells, all_cells, weight=w_exc, delay=delay, receptor_type='inhibitory', p=pconn_recurr) sim.connect(inputs, all_cells, weight=w_input, delay=delay, receptor_type='excitatory', p=pconn_input) sim.record('spikes', all_cells, "scenario1a_%s_spikes.pkl" % sim.__name__) sim.record('v', excitatory_cells[0:2], "scenario1a_%s_v.pkl" % sim.__name__) sim.run(tstop) E_count = excitatory_cells.mean_spike_count() I_count = inhibitory_cells.mean_spike_count() print("Excitatory rate : %g Hz" % (E_count * 1000.0 / tstop,)) print("Inhibitory rate : %g Hz" % (I_count * 1000.0 / tstop,)) sim.end() for filename in glob.glob("scenario1a_*"): os.remove(filename) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() scenario1(sim) scenario1a(sim) PyNN-0.10.0/test/system/scenarios/scenario2.py000066400000000000000000000042361415343567000211610ustar00rootroot00000000000000 import numpy as np from nose.tools import assert_equal from .registry import register @register() def scenario2(sim): """ Array of neurons, each injected with a different current. firing period of a IF neuron injected with a current I: T = tau_m*log(I*tau_m/(I*tau_m - v_thresh*cm)) (if v_rest = v_reset = 0.0) we set the refractory period to be very large, so each neuron fires only once (except neuron[0], which never reaches threshold). """ n = 83 t_start = 25.0 duration = 100.0 t_stop = 150.0 tau_m = 20.0 v_thresh = 10.0 cm = 1.0 cell_params = {"tau_m": tau_m, "v_rest": 0.0, "v_reset": 0.0, "tau_refrac": 100.0, "v_thresh": v_thresh, "cm": cm} I0 = (v_thresh * cm) / tau_m sim.setup(timestep=0.01, min_delay=0.1, spike_precision="off_grid") neurons = sim.Population(n, sim.IF_curr_exp(**cell_params)) neurons.initialize(v=0.0) I = np.arange(I0, I0 + 1.0, 1.0 / n) currents = [sim.DCSource(start=t_start, stop=t_start + duration, amplitude=amp) for amp in I] for j, (neuron, current) in enumerate(zip(neurons, currents)): if j % 2 == 0: # these should neuron.inject(current) # be entirely else: # equivalent current.inject_into([neuron]) neurons.record(['spikes', 'v']) sim.run(t_stop) spiketrains = neurons.get_data().segments[0].spiketrains assert_equal(len(spiketrains), n) assert_equal(len(spiketrains[0]), 0) # first cell does not fire assert_equal(len(spiketrains[1]), 1) # other cells fire once assert_equal(len(spiketrains[-1]), 1) # other cells fire once expected_spike_times = t_start + tau_m * np.log(I * tau_m / (I * tau_m - v_thresh * cm)) a = spike_times = [np.array(st)[0] for st in spiketrains[1:]] b = expected_spike_times[1:] max_error = abs((a - b) / b).max() print("max error =", max_error) assert max_error < 0.005, max_error sim.end() return a, b, spike_times if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() scenario2(sim) PyNN-0.10.0/test/system/scenarios/scenario3.py000066400000000000000000000060471415343567000211640ustar00rootroot00000000000000# encoding: utf-8 from nose.tools import assert_equal from nose.plugins.skip import SkipTest from pyNN.utility import init_logging from pyNN.random import RandomDistribution from .registry import register @register(exclude=["brian2"]) def scenario3(sim): """ Simple feed-forward network network with additive STDP. The second half of the presynaptic neurons fires faster than the second half, so their connections should be potentiated more. """ try: import scipy.stats except ImportError: raise SkipTest init_logging(logfile=None, debug=True) second = 1000.0 duration = 10 tau_m = 20 # ms cm = 1.0 # nF v_reset = -60 cell_parameters = dict( tau_m=tau_m, cm=cm, v_rest=-70, e_rev_E=0, e_rev_I=-70, v_thresh=-54, v_reset=v_reset, tau_syn_E=5, tau_syn_I=5, ) g_leak = cm / tau_m # µS w_min = 0.0 * g_leak w_max = 0.05 * g_leak r1 = 5.0 r2 = 40.0 sim.setup() pre = sim.Population(100, sim.SpikeSourcePoisson()) post = sim.Population(10, sim.IF_cond_exp()) pre.set(duration=duration * second) pre.set(start=0.0) pre[:50].set(rate=r1) pre[50:].set(rate=r2) assert_equal(pre[49].rate, r1) assert_equal(pre[50].rate, r2) post.set(**cell_parameters) post.initialize(v=RandomDistribution('normal', mu=v_reset, sigma=5.0)) stdp = sim.STDPMechanism( sim.SpikePairRule(tau_plus=20.0, tau_minus=20.0, A_plus=0.01, A_minus=0.01), sim.AdditiveWeightDependence(w_min=w_min, w_max=w_max), # dendritic_delay_fraction=0.5)) dendritic_delay_fraction=1) connections = sim.Projection(pre, post, sim.AllToAllConnector(), synapse_type=stdp, receptor_type='excitatory') initial_weight_distr = RandomDistribution('uniform', low=w_min, high=w_max) connections.randomizeWeights(initial_weight_distr) initial_weights = connections.get('weight', format='array', gather=False) assert initial_weights.min() >= w_min assert initial_weights.max() < w_max assert initial_weights[0, 0] != initial_weights[1, 0] pre.record('spikes') post.record('spikes') post[0:1].record('v') sim.run(duration * second) actual_rate = pre.mean_spike_count() / duration expected_rate = (r1 + r2) / 2 errmsg = "actual rate: %g expected rate: %g" % (actual_rate, expected_rate) assert abs(actual_rate - expected_rate) < 1, errmsg final_weights = connections.get('weight', format='array', gather=False) assert initial_weights[0, 0] != final_weights[0, 0] t, p = scipy.stats.ttest_ind(final_weights[:50, :].flat, final_weights[50:, :].flat) assert p < 0.01, p assert final_weights[:50, :].mean() < final_weights[50:, :].mean() sim.end() return initial_weights, final_weights, pre, post, connections if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() scenario3(sim) PyNN-0.10.0/test/system/scenarios/scenario4.py000066400000000000000000000063601415343567000211630ustar00rootroot00000000000000 import logging from pyNN.random import NumpyRNG, RandomDistribution from pyNN.space import Space, Grid3D, RandomStructure, Cuboid from pyNN.utility import init_logging from .registry import register logger = logging.getLogger("TEST") @register(exclude=["brian2"]) # to fix for Brian 2 def scenario4(sim): """ Network with spatial structure """ init_logging(logfile=None, debug=True) sim.setup() rng = NumpyRNG(seed=76454, parallel_safe=False) input_layout = RandomStructure(boundary=Cuboid(width=500.0, height=500.0, depth=100.0), origin=(0, 0, 0), rng=rng) inputs = sim.Population(100, sim.SpikeSourcePoisson(rate=RandomDistribution('uniform', low=3.0, high=7.0, rng=rng)), structure=input_layout, label="inputs") output_layout = Grid3D(aspect_ratioXY=1.0, aspect_ratioXZ=5.0, dx=10.0, dy=10.0, dz=10.0, x0=0.0, y0=0.0, z0=200.0) outputs = sim.Population(200, sim.EIF_cond_exp_isfa_ista(), initial_values={'v': RandomDistribution('normal', mu=-65.0, sigma=5.0, rng=rng), 'w': RandomDistribution('normal', mu=0.0, sigma=1.0, rng=rng)}, structure=output_layout, # 10x10x2 grid label="outputs") logger.debug("Output population positions:\n %s", outputs.positions) DDPC = sim.DistanceDependentProbabilityConnector input_connectivity = DDPC("0.5*exp(-d/100.0)", rng=rng) recurrent_connectivity = DDPC("sin(pi*d/250.0)**2", rng=rng) depressing = sim.TsodyksMarkramSynapse(weight=RandomDistribution('normal', mu=0.1, sigma=0.02, rng=rng), delay="0.5 + d/100.0", U=0.5, tau_rec=800.0, tau_facil=0.0) facilitating = sim.TsodyksMarkramSynapse(weight=0.05, delay="0.2 + d/100.0", U=0.04, tau_rec=100.0, tau_facil=1000.0) input_connections = sim.Projection(inputs, outputs, input_connectivity, receptor_type='excitatory', synapse_type=depressing, space=Space(axes='xy'), label="input connections") recurrent_connections = sim.Projection(outputs, outputs, recurrent_connectivity, receptor_type='inhibitory', synapse_type=facilitating, # should add "calculate_boundaries" method to Structure classes space=Space(periodic_boundaries=( (-100.0, 100.0), (-100.0, 100.0), None)), label="recurrent connections") outputs.record('spikes') outputs.sample(10, rng=rng).record('v') sim.run(1000.0) data = outputs.get_data() sim.end() return data if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() scenario4(sim) PyNN-0.10.0/test/system/scenarios/test_cell_types.py000066400000000000000000000267721415343567000225070ustar00rootroot00000000000000 import numpy as np from nose.plugins.skip import SkipTest try: import scipy have_scipy = True except ImportError: have_scipy = False from numpy.testing import assert_array_equal import quantities as pq from nose.tools import assert_greater, assert_less, assert_raises from pyNN.parameters import Sequence from pyNN.errors import InvalidParameterValueError from .registry import register @register() def test_EIF_cond_alpha_isfa_ista(sim, plot_figure=False): sim.setup(timestep=0.01, min_delay=0.1, max_delay=4.0) ifcell = sim.create(sim.EIF_cond_alpha_isfa_ista( i_offset=1.0, tau_refrac=2.0, v_spike=-40)) ifcell.record(['spikes', 'v', 'w']) ifcell.initialize(v=-65, w=0) sim.run(200.0) data = ifcell.get_data().segments[0] expected_spike_times = np.array( [10.015, 25.515, 43.168, 63.41, 86.649, 113.112, 142.663, 174.76]) if plot_figure: import matplotlib.pyplot as plt vm = data.filter(name="v")[0] plt.plot(vm.times, vm) plt.plot(expected_spike_times, -40 * np.ones_like(expected_spike_times), "ro") plt.savefig("test_EIF_cond_alpha_isfa_ista_%s.png" % sim.__name__) diff = (data.spiketrains[0].rescale(pq.ms).magnitude - expected_spike_times) / expected_spike_times assert abs(diff).max() < 0.01, abs(diff).max() sim.end() return data test_EIF_cond_alpha_isfa_ista.__test__ = False @register() def test_HH_cond_exp(sim, plot_figure=False): sim.setup(timestep=0.001, min_delay=0.1) cellparams = { 'gbar_Na': 20.0, 'gbar_K': 6.0, 'g_leak': 0.01, 'cm': 0.2, 'v_offset': -63.0, 'e_rev_Na': 50.0, 'e_rev_K': -90.0, 'e_rev_leak': -65.0, 'e_rev_E': 0.0, 'e_rev_I': -80.0, 'tau_syn_E': 0.2, 'tau_syn_I': 2.0, 'i_offset': 1.0, } hhcell = sim.create(sim.HH_cond_exp(**cellparams)) sim.initialize(hhcell, v=-64.0) hhcell.record('v') sim.run(20.0) v = hhcell.get_data().segments[0].filter(name='v')[0] if plot_figure: import matplotlib.pyplot as plt plt.plot(v.times, v) plt.savefig("test_HH_cond_exp_%s.png" % sim.__name__) sim.end() first_spike = v.times[np.where(v > 0)[0][0]] assert first_spike / pq.ms - 2.95 < 0.01 test_HH_cond_exp.__test__ = False @register(exclude=['brian2']) # see issue 370 def issue367(sim, plot_figure=False): # AdEx dynamics for delta_T=0 sim.setup(timestep=0.001, min_delay=0.1, max_delay=4.0) v_thresh = -50 ifcell = sim.create(sim.EIF_cond_exp_isfa_ista( delta_T=0.0, i_offset=1.0, v_thresh=v_thresh, v_spike=-45)) ifcell.record(('spikes', 'v')) ifcell.initialize(v=-70.6) sim.run(100.0) data = ifcell.get_data().segments[0] # we take the average membrane potential 0.1 ms before the spike and # compare it to the spike threshold spike_times = data.spiketrains[0] vm = data.analogsignals[0] spike_bins = ((spike_times - 0.1 * pq.ms) / vm.sampling_period).magnitude.astype(int) vm_before_spike = vm.magnitude[spike_bins] if plot_figure: import matplotlib.pyplot as plt plt.clf() plt.plot(vm.times, vm) plt.savefig("issue367_%s.png" % sim.__name__) print(sim.__name__, vm_before_spike) errmsg = "v_thresh = {0}, vm_before_spike.mean() = {1}".format(v_thresh, vm_before_spike.mean()) assert abs((vm_before_spike.mean() - v_thresh) / v_thresh) < 0.01, errmsg sim.end() return data issue367.__test__ = False @register() def test_SpikeSourcePoisson(sim, plot_figure=False): try: from scipy.stats import kstest except ImportError: raise SkipTest("scipy not available") sim.setup() params = { "rate": [100, 200, 1000.0], } t_stop = 100000.0 sources = sim.Population(3, sim.SpikeSourcePoisson(**params)) sources.record('spikes') sim.run(t_stop) data = sources.get_data().segments[0] sim.end() if plot_figure: import matplotlib.pyplot as plt plt.clf() for i, (st, rate) in enumerate(zip(data.spiketrains, params["rate"])): plt.subplot(3, 1, i + 1) isi = st[1:] - st[:-1] k = rate/1000.0 n_bins = int(np.sqrt(k * t_stop)) values, bins, patches = plt.hist(isi, bins=n_bins, label="{} Hz".format(rate), histtype='step') expected = t_stop * k * (np.exp(-k * bins[:-1]) - np.exp(-k * bins[1:])) plt.plot((bins[1:] + bins[:-1])/2.0, expected, 'r-') plt.xlabel("Inter-spike interval (ms)") plt.legend() plt.savefig("test_SpikeSourcePoisson_%s.png" % sim.__name__) # Kolmogorov-Smirnov test for st, expected_rate in zip(data.spiketrains, params['rate']): expected_mean_isi = 1000.0/expected_rate # ms isi = st[1:] - st[:-1] D, p = kstest(isi.magnitude, "expon", args=(0, expected_mean_isi), # args are (loc, scale) alternative='two-sided') print(expected_rate, expected_mean_isi, isi.mean(), p, D) assert_less(D, 0.1) return data test_SpikeSourcePoisson.__test__ = False @register(exclude=['brian2']) def test_SpikeSourceGamma(sim, plot_figure=False): try: from scipy.stats import kstest except ImportError: raise SkipTest("scipy not available") sim.setup() params = { "beta": [100.0, 200.0, 1000.0], "alpha": [6, 4, 2] } t_stop = 10000.0 sources = sim.Population(3, sim.SpikeSourceGamma(**params)) sources.record('spikes') sim.run(t_stop) data = sources.get_data().segments[0] sim.end() if plot_figure and have_scipy: import matplotlib.pyplot as plt plt.clf() for i, (st, alpha, beta) in enumerate(zip(data.spiketrains, params["alpha"], params["beta"])): plt.subplot(3, 1, i + 1) isi = st[1:] - st[:-1] n_bins = int(np.sqrt(beta * t_stop/1000.0)) values, bins, patches = plt.hist(isi, bins=n_bins, label="alpha={}, beta={} Hz".format(alpha, beta), histtype='step', normed=False) print("isi count: ", isi.size, t_stop/1000.0 * beta/alpha) bin_width = bins[1] - bins[0] expected = (t_stop * beta * bin_width) / (1000.0 * alpha) * \ scipy.stats.gamma.pdf(bins, a=alpha, scale=1000.0/beta) plt.plot(bins, expected, 'r-') plt.xlabel("Inter-spike interval (ms)") plt.legend() plt.savefig("test_SpikeSourceGamma_%s.png" % sim.__name__) # Kolmogorov-Smirnov test print("alpha beta expected-isi actual-isi, p, D") for st, alpha, beta in zip(data.spiketrains, params['alpha'], params['beta']): expected_mean_isi = 1000*alpha/beta # ms isi = st[1:] - st[:-1] # Kolmogorov-Smirnov test D, p = kstest(isi.magnitude, "gamma", args=(alpha, 0, 1000.0/beta), # args are (a, loc, scale) alternative='two-sided') print(alpha, beta, expected_mean_isi, isi.mean(), p, D) assert_less(D, 0.1) return data test_SpikeSourceGamma.__test__ = False @register(exclude=['brian2']) def test_SpikeSourcePoissonRefractory(sim, plot_figure=False): try: from scipy.stats import kstest except ImportError: raise SkipTest("scipy not available") sim.setup() params = { "rate": [100, 100, 50.0], "tau_refrac": [0.0, 5.0, 5.0] } t_stop = 100000.0 sources = sim.Population(3, sim.SpikeSourcePoissonRefractory(**params)) sources.record('spikes') sim.run(t_stop) data = sources.get_data().segments[0] sim.end() if plot_figure: import matplotlib.pyplot as plt plt.clf() for i, (st, rate, tau_refrac) in enumerate(zip(data.spiketrains, params["rate"], params["tau_refrac"])): plt.subplot(3, 1, i + 1) isi = st[1:] - st[:-1] expected_mean_isi = 1000.0/rate poisson_mean_isi = expected_mean_isi - tau_refrac k = 1/poisson_mean_isi n_bins = int(np.sqrt(k * t_stop)) values, bins, patches = plt.hist(isi, bins=n_bins, label="{} Hz".format(rate), histtype='step') expected = t_stop/expected_mean_isi * \ (np.exp(-(k * (bins[:-1] - tau_refrac))) - np.exp(-(k * (bins[1:] - tau_refrac)))) plt.plot((bins[1:] + bins[:-1])/2.0, expected, 'r-') plt.legend() plt.xlabel("Inter-spike interval (ms)") plt.savefig("test_SpikeSourcePoissonRefractory_%s.png" % sim.__name__) # Kolmogorov-Smirnov test for st, expected_rate, tau_refrac in zip(data.spiketrains, params['rate'], params['tau_refrac']): poisson_mean_isi = 1000.0/expected_rate - tau_refrac # ms corrected_isi = (st[1:] - st[:-1]).magnitude - tau_refrac D, p = kstest(corrected_isi, "expon", args=(0, poisson_mean_isi), # args are (loc, scale) alternative='two-sided') print(expected_rate, poisson_mean_isi, corrected_isi.mean(), p, D) assert_less(D, 0.1) return data test_SpikeSourcePoissonRefractory.__test__ = False @register() def issue511(sim): """Giving SpikeSourceArray an array of non-ordered spike times should produce an InvalidParameterValueError error""" sim.setup() celltype = sim.SpikeSourceArray(spike_times=[[2.4, 4.8, 6.6, 9.4], [3.5, 6.8, 9.6, 8.3]]) assert_raises(InvalidParameterValueError, sim.Population, 2, celltype) @register() def test_update_SpikeSourceArray(sim, plot_figure=False): sim.setup() sources = sim.Population(2, sim.SpikeSourceArray(spike_times=[])) sources.record('spikes') sim.run(10.0) sources.set(spike_times=[ Sequence([12, 15, 18]), Sequence([17, 19]) ]) sim.run(10.0) sources.set(spike_times=[ Sequence([22, 25]), Sequence([23, 27, 29]) ]) sim.run(10.0) data = sources.get_data().segments[0].spiketrains assert_array_equal(data[0].magnitude, np.array([12, 15, 18, 22, 25])) test_update_SpikeSourceArray.__test__ = False # todo: add test of Izhikevich model if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator(("--plot-figure", {"help": "generate a figure", "action": "store_true"})) test_EIF_cond_alpha_isfa_ista(sim, plot_figure=args.plot_figure) test_HH_cond_exp(sim, plot_figure=args.plot_figure) issue367(sim, plot_figure=args.plot_figure) test_SpikeSourcePoisson(sim, plot_figure=args.plot_figure) test_SpikeSourceGamma(sim, plot_figure=args.plot_figure) test_SpikeSourcePoissonRefractory(sim, plot_figure=args.plot_figure) issue511(sim) test_update_SpikeSourceArray(sim, plot_figure=args.plot_figure) PyNN-0.10.0/test/system/scenarios/test_connection_handling.py000066400000000000000000000122061415343567000243320ustar00rootroot00000000000000""" Tests of the fine-grained connection API """ from nose.tools import assert_equal, assert_almost_equal, assert_is_instance from numpy.testing import assert_array_equal import numpy as np from pyNN import common from .registry import register @register() def connections_attribute(sim): sim.setup() p1 = sim.Population(4, sim.SpikeSourceArray()) p2 = sim.Population(3, sim.IF_cond_exp()) prj = sim.Projection(p1, p2, sim.FixedProbabilityConnector(0.7), sim.StaticSynapse(weight=0.05, delay=0.5)) connections = list(prj.connections) assert_equal(len(connections), len(prj)) assert_is_instance(connections[0], common.Connection) connections_attribute.__test__ = False @register() def connection_access_weight_and_delay(sim): sim.setup() p1 = sim.Population(4, sim.SpikeSourceArray()) p2 = sim.Population(3, sim.IF_cond_exp()) prj = sim.Projection(p1, p2, sim.FixedProbabilityConnector(0.8), sim.StaticSynapse(weight=0.05, delay=0.5)) connections = list(prj.connections) assert_almost_equal(connections[2].weight, 0.05, places=9) assert_almost_equal(connections[2].delay, 0.5, places=9) connections[2].weight = 0.0123 connections[2].delay = 1.0 target = np.empty((prj.size(), 2)) target[:, 0] = 0.05 target[:, 1] = 0.5 target[2, 0] = 0.0123 target[2, 1] = 1.0 assert_array_equal(np.array(prj.get(('weight', 'delay'), format='list', with_address=False)), target) connection_access_weight_and_delay.__test__ = False @register() def issue672(sim): """ Check that creating new Projections does not mess up existing ones. """ sim.setup(verbosity="error") p1 = sim.Population(5, sim.IF_curr_exp()) p2 = sim.Population(4, sim.IF_curr_exp()) p3 = sim.Population(6, sim.IF_curr_exp()) prj1 = sim.Projection(p2, p3, sim.AllToAllConnector(), sim.StaticSynapse(weight=lambda d: d)) # Get weight array of first Projection wA = prj1.get("weight", format="array") # Create a new Projection prj2 = sim.Projection(p2, p3, sim.AllToAllConnector(), sim.StaticSynapse(weight=lambda w: 1)) # Get weight array of first Projection again # - incorrect use of caching could lead to this giving different results wB = prj1.get("weight", format="array") assert_array_equal(wA, wB) # @register() # def update_synaptic_plasticity_parameters(sim): # sim.setup() # p1 = sim.Population(3, sim.IF_cond_exp(), label="presynaptic") # p2 = sim.Population(2, sim.IF_cond_exp(), label="postsynaptic") # stdp_model = sim.STDPMechanism( # timing_dependence=sim.SpikePairRule(tau_plus=20.0, tau_minus=20.0, # A_plus=0.011, A_minus=0.012), # weight_dependence=sim.AdditiveWeightDependence(w_min=0, w_max=0.0000001), # weight=0.00000005, # delay=0.2) # connections = sim.Projection(p1, p2, sim.AllToAllConnector(), stdp_model) # assert (connections.get("A_minus", format="array") == 0.012).all() # connections.set(A_minus=0.013) # assert (connections.get("A_minus", format="array") == 0.013).all() # connections.set(A_minus=np.array([0.01, 0.011, 0.012, 0.013, 0.014, 0.015])) # assert_array_equal(connections.get("A_minus", format="array"), # np.array([[0.01, 0.011], [0.012, 0.013], [0.014, 0.015]])) @register(exclude=["brian2"]) def issue652(sim): """Correctly handle A_plus = 0 in SpikePairRule.""" sim.setup() neural_population1 = sim.Population(10, sim.IF_cond_exp()) neural_population2 = sim.Population(10, sim.IF_cond_exp()) amount_of_neurons_to_connect_to = 5 synaptic_weight = 0.5 synapse_type = sim.STDPMechanism( weight=synaptic_weight, timing_dependence=sim.SpikePairRule( tau_plus=20, tau_minus=20, A_plus=0.0, A_minus=0.0, ), weight_dependence=sim.AdditiveWeightDependence(w_min=0, w_max=1) ) connection_to_input = sim.Projection( neural_population1, neural_population2, sim.FixedNumberPreConnector(amount_of_neurons_to_connect_to), synapse_type ) a_plus, a_minus = connection_to_input.get(["A_plus", "A_minus"], format="array") assert_equal(a_plus[~np.isnan(a_plus)][0], 0.0) assert_equal(a_minus[~np.isnan(a_minus)][0], 0.0) synapse_type = sim.STDPMechanism( weight=synaptic_weight, timing_dependence=sim.SpikePairRule( tau_plus=20, tau_minus=20, A_plus=0.0, A_minus=0.5, ), weight_dependence=sim.AdditiveWeightDependence(w_min=0, w_max=1) ) connection_to_input = sim.Projection( neural_population1, neural_population2, sim.FixedNumberPreConnector(amount_of_neurons_to_connect_to), synapse_type ) a_plus, a_minus = connection_to_input.get(["A_plus", "A_minus"], format="array") assert_equal(a_plus[~np.isnan(a_plus)][0], 0.0) assert_equal(a_minus[~np.isnan(a_minus)][0], 0.5) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() connections_attribute(sim) PyNN-0.10.0/test/system/scenarios/test_connectors.py000066400000000000000000000261601415343567000225100ustar00rootroot00000000000000 import numpy as np from nose.tools import assert_equal, assert_almost_equal from numpy.testing import assert_array_equal, assert_allclose from pyNN.random import NumpyRNG, RandomDistribution from pyNN.utility import connection_plot, init_logging from .registry import register #init_logging(None, debug=True) # TODO: add some tests with projections between Assemblies and PopulationViews @register() def all_to_all_static_no_self(sim): sim.setup() p = sim.Population(5, sim.IF_cond_exp()) synapse_type = sim.StaticSynapse(weight=RandomDistribution('gamma', k=2.0, theta=0.5), delay="0.2+0.3*d") prj = sim.Projection(p, p, sim.AllToAllConnector(allow_self_connections=False), synapse_type) weights = prj.get('weight', format='array', gather=False) print(weights) delays = prj.get('delay', format='list', gather=False) i, j, d = np.array(delays).T assert_allclose(d, 0.2 + 0.3 * abs(i - j), 1e-9) assert_equal(d.size, p.size * (p.size - 1)) sim.end() @register() def all_to_all_tsodyksmarkram(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) synapse_type = sim.TsodyksMarkramSynapse(weight=lambda d: d, delay=0.5, U=lambda d: 0.02 * d + 0.1) prj = sim.Projection(p1, p2, sim.AllToAllConnector(), synapse_type) i, j, w, d, u = np.array(prj.get(['weight', 'delay', 'U'], format='list', gather=False)).T assert_array_equal(w, abs(i - j)) assert_array_equal(d, 0.5 * np.ones(p2.size * p1.size)) assert_array_equal(u, 0.02 * abs(i - j) + 0.1) weights, delays, U = prj.get(['weight', 'delay', 'U'], format='array', gather=False) print(weights) print(delays) # should all be 0.5 print(U) sim.end() @register() def fixed_number_pre_no_replacement(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) synapse_type1 = sim.StaticSynapse(weight=0.5, delay=0.5) connector1 = sim.FixedNumberPreConnector(n=3, with_replacement=False, rng=NumpyRNG()) prj1 = sim.Projection(p1, p2, connector1, synapse_type1) print("Projection 1\n", connection_plot(prj1)) weights1 = prj1.get('weight', format='array', gather=False) for column in weights1.T: assert_equal((~np.isnan(column)).sum(), 3) column[np.isnan(column)] = 0 assert_equal(column.sum(), 1.5) synapse_type2 = sim.StaticSynapse(weight=RandomDistribution('gamma', k=2, theta=0.5), delay="0.2+0.3*d") prj2 = sim.Projection(p1, p2, connector1, synapse_type2) print("\nProjection 2\n", connection_plot(prj2)) weights2 = prj2.get('weight', format='array', gather=False) delays2 = prj2.get('delay', format='list', gather=False) print(weights2) print(delays2) for i, j, d in delays2: assert_almost_equal(d, 0.2 + 0.3 * abs(i - j), 9) for column in weights2.T: assert_equal((~np.isnan(column)).sum(), 3) column[np.isnan(column)] = 0 sim.end() @register() def fixed_number_pre_with_replacement(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) synapse_type1 = sim.StaticSynapse(weight=0.5, delay=0.5) connector1 = sim.FixedNumberPreConnector(n=3, with_replacement=True, rng=NumpyRNG()) prj1 = sim.Projection(p1, p2, connector1, synapse_type1) print("Projection #1\n", connection_plot(prj1)) delays = prj1.get('delay', format='list', gather=False) assert_equal(len(delays), connector1.n * p2.size) weights = prj1.get('weight', format='array', gather=False) for column in weights.T: column[np.isnan(column)] = 0 assert_equal(column.sum(), 1.5) @register() def fixed_number_pre_with_replacement_heterogeneous_parameters(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) connector1 = sim.FixedNumberPreConnector(n=3, with_replacement=True, rng=NumpyRNG()) synapse_type2 = sim.TsodyksMarkramSynapse(weight=lambda d: d, delay=0.5, U=lambda d: 0.02 * d + 0.1) #synapse_type2 = sim.TsodyksMarkramSynapse(weight=0.001, delay=0.5, U=lambda d: 2*d+0.1) prj2 = sim.Projection(p1, p2, connector1, synapse_type2) print("Projection 2") x = prj2.get(['weight', 'delay', 'U'], format='list', gather=False) from pprint import pprint pprint(x) i, j, w, d, u = np.array(x).T assert_array_equal(w, abs(i - j)) assert_array_equal(d, 0.5 * np.ones(p2.size * connector1.n)) assert_array_equal(u, 0.02 * abs(i - j) + 0.1) sim.end() @register() def fixed_number_post_no_replacement(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) synapse_type1 = sim.StaticSynapse(weight=0.5, delay=0.5) connector1 = sim.FixedNumberPostConnector(n=3, with_replacement=False, rng=NumpyRNG()) prj1 = sim.Projection(p1, p2, connector1, synapse_type1) print("Projection 1\n", connection_plot(prj1)) weights1 = prj1.get('weight', format='array', gather=False) for row in weights1: assert_equal((~np.isnan(row)).sum(), 3) row[np.isnan(row)] = 0 assert_equal(row.sum(), 1.5) synapse_type2 = sim.StaticSynapse(weight=RandomDistribution('gamma', k=2, theta=0.5), delay="0.2+0.3*d") prj2 = sim.Projection(p1, p2, connector1, synapse_type2) print("\nProjection 2\n", connection_plot(prj2)) weights2 = prj2.get('weight', format='array', gather=False) delays2 = prj2.get('delay', format='list', gather=False) print(weights2) print(delays2) for i, j, d in delays2: assert_almost_equal(d, 0.2 + 0.3 * abs(i - j), 9) for row in weights2: assert_equal((~np.isnan(row)).sum(), 3) sim.end() @register() def fixed_number_post_with_replacement(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) synapse_type1 = sim.StaticSynapse(weight=0.5, delay=0.5) connector1 = sim.FixedNumberPostConnector(n=9, with_replacement=True, rng=NumpyRNG()) prj1 = sim.Projection(p1, p2, connector1, synapse_type1) print("Projection #1\n", connection_plot(prj1)) delays = prj1.get('delay', format='list', gather=False) assert_equal(len(delays), connector1.n * p1.size) weights = prj1.get('weight', format='array', gather=False) for row in weights: row[np.isnan(row)] = 0 assert_equal(row.sum(), 4.5) weights2 = prj1.get('weight', format='array', gather=False, multiple_synapses='min') for row in weights2: n_nan = np.isnan(row).sum() row[np.isnan(row)] = 0 assert_equal(row.sum(), (row.size - n_nan)*0.5) @register() def fixed_number_post_with_replacement_heterogeneous_parameters(sim): sim.setup() p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) connector1 = sim.FixedNumberPostConnector(n=3, with_replacement=True, rng=NumpyRNG()) synapse_type2 = sim.TsodyksMarkramSynapse(weight=lambda d: d, delay=0.5, U=lambda d: 0.02 * d + 0.1) #synapse_type2 = sim.TsodyksMarkramSynapse(weight=0.001, delay=0.5, U=lambda d: 2*d+0.1) prj2 = sim.Projection(p1, p2, connector1, synapse_type2) print("Projection 2") x = prj2.get(['weight', 'delay', 'U'], format='list', gather=False) from pprint import pprint pprint(x) i, j, w, d, u = np.array(x).T assert_array_equal(w, abs(i - j)) assert_array_equal(d, 0.5 * np.ones(p1.size * connector1.n)) assert_array_equal(u, 0.02 * abs(i - j) + 0.1) sim.end() @register() def issue309(sim): # check that FixedProbability(1) gives the same results as AllToAll sim.setup() p = sim.Population(5, sim.IF_cond_exp()) synapse_type = sim.StaticSynapse(weight=0.1, delay="0.2+0.3*d") prj_a2a = sim.Projection(p, p, sim.AllToAllConnector(allow_self_connections=False), synapse_type) prj_fp1 = sim.Projection(p, p, sim.FixedProbabilityConnector(p_connect=1, allow_self_connections=False), synapse_type) assert_equal(sorted(prj_a2a.get('weight', format='list', gather=False)), sorted(prj_fp1.get('weight', format='list', gather=False))) assert_equal(sorted(prj_a2a.get('delay', format='list', gather=False)), sorted(prj_fp1.get('delay', format='list', gather=False))) assert_equal(prj_fp1.size(), 20) # 20 rather than 25 because self-connections are excluded sim.end() @register(exclude=['brian2']) # need to implement projection.get() for pre/post assemblies in Brian def issue622(sim): sim.setup() pop = sim.Population(10, sim.IF_cond_exp, {}, label="pop") view1 = sim.PopulationView(pop, [2, 3, 4]) view2 = sim.PopulationView(pop, [2, 3, 4]) proj1 = sim.Projection(view1, view2, sim.AllToAllConnector(allow_self_connections=False), sim.StaticSynapse(weight=0.015, delay=1.0), receptor_type='excitatory') proj2 = sim.Projection(view1, view1, sim.AllToAllConnector(allow_self_connections=False), sim.StaticSynapse(weight=0.015, delay=1.0), receptor_type='excitatory') w1 = proj1.get("weight", "list") w2 = proj2.get("weight", "list") assert_equal(set(w1), set(w2)) assert_equal(set(w1), set([(0.0, 1.0, 0.015), (0.0, 2.0, 0.015), (1.0, 0.0, 0.015), (1.0, 2.0, 0.015), (2.0, 0.0, 0.015), (2.0, 1.0, 0.015)])) # partially overlapping views print("Now with partial overlap") view3 = sim.PopulationView(pop, [3, 4, 5, 6]) proj3 = sim.Projection(view1, view3, sim.AllToAllConnector(allow_self_connections=False), sim.StaticSynapse(weight=0.015, delay=1.0), receptor_type='excitatory') w3 = proj3.get("weight", "list") assert_equal(set(w3), set([ (0.0, 0.0, 0.015), (0.0, 1.0, 0.015), (0.0, 2.0, 0.015), (0.0, 3.0, 0.015), (1.0, 1.0, 0.015), (1.0, 2.0, 0.015), (1.0, 3.0, 0.015), (2.0, 0.0, 0.015), (2.0, 2.0, 0.015), (2.0, 3.0, 0.015) ])) view4 = sim.PopulationView(pop, [0, 1]) assmbl = view3 + view4 proj4 = sim.Projection(view1, assmbl, sim.FixedProbabilityConnector(p_connect=0.99999, allow_self_connections=False), sim.StaticSynapse(weight=0.015, delay=1.0), receptor_type='excitatory') w4 = proj4.get("weight", "list") assert_equal(set(w4), set([ (0, 0, 0.015), (0, 1, 0.015), (0, 2, 0.015), (0, 3, 0.015), (0, 4, 0.015), (0, 5, 0.015), (1, 1, 0.015), (1, 2, 0.015), (1, 3, 0.015), (1, 4, 0.015), (1, 5, 0.015), (2, 0, 0.015), (2, 2, 0.015), (2, 3, 0.015), (2, 4, 0.015), (2, 5, 0.015), ])) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() all_to_all_static_no_self(sim) all_to_all_tsodyksmarkram(sim) fixed_number_pre_no_replacement(sim) fixed_number_pre_with_replacement(sim) fixed_number_pre_with_replacement_heterogeneous_parameters(sim) issue309(sim) PyNN-0.10.0/test/system/scenarios/test_electrodes.py000066400000000000000000000632421415343567000224660ustar00rootroot00000000000000 from nose.tools import assert_equal, assert_true, assert_false, assert_raises from numpy.testing import assert_array_equal import quantities as pq import numpy as np from .registry import register try: import scipy have_scipy = True except ImportError: have_scipy = False from nose.plugins.skip import SkipTest @register() def test_changing_electrode(sim): """ Check that changing the values of the electrodes on the fly is taken into account """ repeats = 2 dt = 0.1 simtime = 100 sim.setup(timestep=dt, min_delay=dt) p = sim.Population(1, sim.IF_curr_exp()) c = sim.DCSource(amplitude=0.0) c.inject_into(p) p.record('v') for i in range(repeats): sim.run(simtime) c.amplitude += 0.1 data = p.get_data().segments[0].analogsignals[0] sim.end() # check that the value of v just before increasing the current is less than # the value at the end of the simulation assert data[int(simtime / dt), 0] < data[-1, 0] test_changing_electrode.__test__ = False @register() def ticket226(sim): """ Check that the start time of DCSources is correctly taken into account http://neuralensemble.org/trac/PyNN/ticket/226) """ sim.setup(timestep=0.1, min_delay=0.1) cell = sim.Population(1, sim.IF_curr_alpha(tau_m=20.0, cm=1.0, v_rest=-60.0, v_reset=-60.0)) cell.initialize(v=-60.0) inj = sim.DCSource(amplitude=1.0, start=10.0, stop=20.0) cell.inject(inj) cell.record_v() sim.run(30.0) v = cell.get_data().segments[0].filter(name='v')[0][:, 0] sim.end() v_10p0 = v.magnitude[abs(v.times - 10.0 * pq.ms) < 0.01 * pq.ms, 0][0] assert abs(v_10p0 - -60.0) < 1e-10 v_10p1 = v.magnitude[abs(v.times - 10.1 * pq.ms) < 0.01 * pq.ms, 0][0] assert v_10p1 > -59.99, v_10p1 @register() def issue165(sim): """Ensure that anonymous current sources are not lost.""" sim.setup(timestep=0.1) p = sim.Population(1, sim.IF_cond_exp()) p.inject(sim.DCSource(amplitude=1.0, start=10.0, stop=20.0)) p.record('v') sim.run(20.0) data = p.get_data().segments[0].filter(name='v')[0] sim.end() assert_equal(data[99, 0], -65.0) assert data[150, 0] > -65.0 @register() def issue321(sim): """Check that non-zero currents at t=0 are taken into account.""" sim.setup(timestep=0.1, min_delay=0.1) cells = sim.Population(3, sim.IF_curr_alpha(tau_m=20.0, cm=1.0, v_rest=-60.0, v_reset=-60.0)) cells.initialize(v=-60.0) cells[0].i_offset = 1.0 inj1 = sim.DCSource(amplitude=1.0, start=0.0) inj2 = sim.StepCurrentSource(times=[0.0], amplitudes=[1.0]) cells[1].inject(inj1) cells[2].inject(inj2) cells.record_v() sim.run(20.0) v = cells.get_data().segments[0].filter(name='v')[0] sim.end() # the DCSource and StepCurrentSource should be equivalent assert_array_equal(v[:, 1], v[:, 2]) # Ideally, the three cells should have identical traces, but in # practice there is always a delay with NEST until the current from # a current generator kicks in assert abs((v[-3:, 1] - v[-3:, 0]).max()) < 0.2 @register() def issue437(sim): """ Checks whether NoisyCurrentSource works properly, by verifying that: 1) no change in vm before start time 2) change in vm at dt after start time 3) monotonic decay of vm after stop time 4) noise.dt is properly implemented Note: On rare occasions this test might fail as the signal is stochastic. Test implementation makes use of certain approximations for thresholding. If fails, run the test again to confirm. Passes 9/10 times on first attempt. """ if not have_scipy: raise SkipTest v_rest = -60.0 # for this test keep v_rest < v_reset sim.setup(timestep=0.1, min_delay=0.1) cells = sim.Population(2, sim.IF_curr_alpha(tau_m=20.0, cm=1.0, v_rest=v_rest, v_reset=-55.0, tau_refrac=5.0)) cells.initialize(v=-60.0) # We test two cases: dt = simulator.state.dt and dt != simulator.state.dt t_start = 25.0 t_stop = 150.0 dt_0 = 0.1 dt_1 = 1.0 noise_0 = sim.NoisyCurrentSource(mean=0.5, stdev=0.25, start=t_start, stop=t_stop, dt=dt_0) noise_1 = sim.NoisyCurrentSource(mean=0.5, stdev=0.25, start=t_start, stop=t_stop, dt=dt_1) cells[0].inject(noise_0) cells[1].inject(noise_1) cells.record('v') sim.run(200.0) v = cells.get_data().segments[0].filter(name="v")[0] v0 = v[:, 0] v1 = v[:, 1] t = v.times sim.end() t_start_ind = int(np.argmax(t >= t_start)) t_stop_ind = int(np.argmax(t >= t_stop)) # test for no change in vm before start time # note: exact matches not appropriate owing to floating point rounding errors assert_true(all(abs(val0 - v_rest*pq.mV) < 1e-9 and abs(val1 - v_rest*pq.mV) < 1e-9 for val0, val1 in zip(v0[:t_start_ind+1], v1[:t_start_ind+1]))) # test for change in vm at dt after start time assert_true(abs(v0[t_start_ind+1] - v_rest*pq.mV) >= 1e-9 and abs(v1[t_start_ind+1] - v_rest*pq.mV) >= 1e-9) # test for monotonic decay of vm after stop time assert_true(all(val0 >= val0_next and val1 >= val1_next for val0, val0_next, val1, val1_next in zip( v0[t_stop_ind:], v0[t_stop_ind+1:], v1[t_stop_ind:], v1[t_stop_ind+1:]))) # test for ensuring noise.dt is properly implemented; checking first instance for each # recording current profiles not implemented currently, thus using double derivative of vm # necessary to upsample signal with noise of dt; else fails in certain scenarios # Test implementation makes use of certain approximations for thresholding. # Note: there can be a much simpler check for this once recording current profiles enabled (for all simulators). # Test implementation makes use of certain approximations for thresholding; hence taking mode of initial values t_up = np.arange(float(min(t)), float(max(t))+dt_0/10.0, dt_0/10.0) v0_up = np.interp(t_up, t, v0.magnitude.flat) v1_up = np.interp(t_up, t, v1.magnitude.flat) d2_v0_up = np.diff(v0_up, n=2) d2_v1_up = np.diff(v1_up, n=2) dt_0_list = [j for (i, j) in zip(d2_v0_up, t_up) if abs(i) >= 0.00005] dt_1_list = [j for (i, j) in zip(d2_v1_up, t_up) if abs(i) >= 0.00005] dt_0_list_diff = np.diff(dt_0_list, n=1) dt_1_list_diff = np.diff(dt_1_list, n=1) dt_0_mode = scipy.stats.mode(dt_0_list_diff[0:10])[0][0] dt_1_mode = scipy.stats.mode(dt_1_list_diff[0:10])[0][0] assert_true(abs(dt_0_mode - dt_0) < 1e-9 or abs(dt_1_mode - dt_1) < 1e-9) @register() def issue442(sim): """ Checks whether ACSource works properly, by verifying that: 1) no change in vm before start time 2) change in vm at dt after start time 3) monotonic decay of vm after stop time 4) accurate frequency of output signal 5) offset included in output signal """ v_rest = -60.0 sim.setup(timestep=0.1, min_delay=0.1) cells = sim.Population(1, sim.IF_curr_alpha(tau_m=20.0, cm=1.0, v_rest=v_rest, v_reset=-65.0, tau_refrac=5.0)) cells.initialize(v=v_rest) # set t_start, t_stop and freq such that # "freq*1e-3*(t_stop-t_start)" is an integral value t_start = 22.5 t_stop = 122.5 freq = 100.0 acsource = sim.ACSource(start=t_start, stop=t_stop, amplitude=0.5, offset=0.1, frequency=freq, phase=0.0) cells[0].inject(acsource) cells.record('v') sim.run(150.0) v = cells.get_data().segments[0].filter(name="v")[0] v0 = v[:, 0] t = v.times sim.end() t_start_ind = int(np.argmax(t >= t_start)) t_stop_ind = int(np.argmax(t >= t_stop)) # test for no change in vm before start time # note: exact matches not appropriate owing to floating point rounding errors assert_true(all(abs(val0 - v_rest*pq.mV) < 1e-9 for val0 in v0[:t_start_ind+1])) # test for change in vm at dt after start time assert_true(abs(v0[t_start_ind+1] - v0[t_start_ind]) >= 1e-9) # test for monotonic decay of vm after stop time assert_true(all(val0 >= val0_next for val0, val0_next in zip( v0[t_stop_ind:], v0[t_stop_ind+1:]))) # test for accurate frequency; simply counts peaks peak_ctr = 0 peak_ind = [] for i in range(t_stop_ind-t_start_ind): if v0[t_start_ind+i-1] < v0[t_start_ind+i] and v0[t_start_ind+i] >= v0[t_start_ind+i+1]: peak_ctr += 1 peak_ind.append(t_start_ind+i) assert_equal(peak_ctr, freq*1e-3*(t_stop-t_start)) # also test for offset; peaks initially increase in magnitude assert_true(v0[peak_ind[0]] < v0[peak_ind[1]] and v0[peak_ind[1]] < v0[peak_ind[2]]) @register() def issue445(sim): """ This test basically checks if a new value of current is calculated at every time step, and that the total number of time steps is as expected theoretically Note: NEST excluded as recording of electrode currents still to be implemented """ sim_dt = 0.1 simtime = 200.0 sim.setup(timestep=sim_dt, min_delay=1.0) cells = sim.Population(1, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0)) t_start = 50.0 t_stop = 125.0 acsource = sim.ACSource(start=t_start, stop=t_stop, amplitude=0.5, offset=0.0, frequency=100.0, phase=0.0) cells[0].inject(acsource) acsource.record() sim.run(simtime) sim.end() i_ac = acsource.get_data() i_t_ac = i_ac.times.magnitude t_start_ind = np.argmax(i_t_ac >= t_start) t_stop_ind = np.argmax(i_t_ac >= t_stop) assert_true(all(val != val_next for val, val_next in zip( i_t_ac[t_start_ind:t_stop_ind-1], i_t_ac[t_start_ind+1:t_stop_ind]))) # note: exact matches not appropriate owing to floating point rounding errors assert_true((len(i_t_ac) - ((max(i_t_ac)-min(i_t_ac))/sim_dt + 1)) < 1e-9) @register() def issue451(sim): """ Modification of test: test_changing_electrode Difference: incorporates a start and stop time for stimulus Check that changing the values of the electrodes on the fly is taken into account """ repeats = 2 dt = 0.1 simtime = 100 sim.setup(timestep=dt, min_delay=dt) v_rest = -60.0 p = sim.Population(1, sim.IF_curr_exp(v_rest=v_rest)) p.initialize(v=v_rest) c = sim.DCSource(amplitude=0.0, start=25.0, stop=50.0) c.inject_into(p) p.record('v') for i in range(repeats): sim.run(simtime) c.amplitude += 0.1 v = p.get_data().segments[0].filter(name="v")[0] sim.end() # check that the value of v is equal to v_rest throughout the simulation # note: exact matches not appropriate owing to floating point rounding errors assert_true(all((val.item()-v_rest) < 1e-9 for val in v[:, 0])) @register() def issue483(sim): """ Test to ensure that length of recorded voltage vector is as expected (checks for the specific scenario that failed earlier) """ dt = 0.1 sim.setup(timestep=dt, min_delay=dt) p = sim.Population(1, sim.IF_curr_exp()) c = sim.DCSource(amplitude=0.5) c.inject_into(p) p.record('v') simtime = 200.0 sim.run(100.0) sim.run(100.0) v = p.get_data().segments[0].filter(name="v")[0] # check that the length of vm vector is as expected theoretically assert (len(v) == (int(simtime/dt) + 1)) @register() def issue487(sim): """ Test to ensure that DCSource and StepCurrentSource work properly for repeated runs. Problem existed under pyNN.neuron. Following sub-tests performed: 1) DCSource active across two runs 2) StepCurrentSource active across two runs 3) DCSource active only during second run (earlier resulted in no current input) 4) StepCurrentSource active only during second run (earlier resulted in current initiation at end of first run) """ dt = 0.1 sim.setup(timestep=dt, min_delay=dt) v_rest = -60.0 cells = sim.Population(4, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, v_rest=v_rest)) cells.initialize(v=v_rest) cells.record('v') dcsource = sim.DCSource(amplitude=0.15, start=25.0, stop=115.0) cells[0].inject(dcsource) step = sim.StepCurrentSource(times=[25.0, 75.0, 115.0], amplitudes=[0.05, 0.10, 0.20]) cells[1].inject(step) dcsource_2 = sim.DCSource(amplitude=0.15, start=115.0, stop=145.0) cells[2].inject(dcsource_2) step_2 = sim.StepCurrentSource(times=[125.0, 175.0, 215.0], amplitudes=[0.05, 0.10, 0.20]) cells[3].inject(step_2) simtime = 100.0 sim.run(simtime) sim.run(simtime) v = cells.get_data().segments[0].filter(name="v")[0] sim.end() v_dc = v[:, 0] v_step = v[:, 1] v_dc_2 = v[:, 2] v_step_2 = v[:, 3] # check that membrane potential does not fall after end of first run # Test 1 assert_true(v_dc[int(simtime/dt)] < v_dc[int(simtime/dt)+1]) # Test 2 assert_true(v_step[int(simtime/dt)] < v_step[int(simtime/dt)+1]) # check that membrane potential of cell undergoes a change # Test 3 v_dc_2_arr = np.squeeze(np.array(v_dc_2)) assert_false(np.isclose(v_dc_2_arr, v_rest).all()) # check that membrane potential of cell undergoes no change till start of current injection # Test 4 v_step_2_arr = np.squeeze(np.array(v_step_2)) assert_true(np.isclose(v_step_2_arr[0:int(step_2.times[0]/dt)], v_rest).all()) @register() def issue_465_474_630(sim): """ Checks the current traces recorded for each of the four types of electrodes in pyNN, and verifies that: 1) Length of the current traces are as expected 2) Values at t = t_start and t = t_stop present 3) Changes in current value occur at the expected time instant 4) Change in Vm begins at the immediate next time instant following current injection """ sim_dt = 0.1 sim.setup(min_delay=1.0, timestep=sim_dt) v_rest = -60.0 cells = sim.Population(4, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, v_rest=v_rest)) cells.initialize(v=v_rest) amp = 0.5 offset = 0.1 start = 50.0 stop = 125.0 acsource = sim.ACSource(start=start, stop=stop, amplitude=amp, offset=offset, frequency=100.0, phase=0.0) cells[0].inject(acsource) acsource.record() dcsource = sim.DCSource(amplitude=amp, start=start, stop=stop) cells[1].inject(dcsource) dcsource.record() noise = sim.NoisyCurrentSource(mean=amp, stdev=0.05, start=start, stop=stop, dt=sim_dt) cells[2].inject(noise) noise.record() step = sim.StepCurrentSource(times=[start, (start+stop)/2, stop], amplitudes=[0.4, 0.6, 0.2]) cells[3].inject(step) step.record() cells.record('v') runtime = 100.0 simtime = 0 # testing for repeated runs sim.run(runtime) simtime += runtime sim.run(runtime) simtime += runtime vm = cells.get_data().segments[0].filter(name="v")[0] sim.end() v_ac = vm[:, 0] v_dc = vm[:, 1] v_noise = vm[:, 2] v_step = vm[:, 3] i_ac = acsource.get_data() i_dc = dcsource.get_data() i_noise = noise.get_data() i_step = step.get_data() # test for length of recorded current traces assert_true(len(i_ac) == (int(simtime/sim_dt)+1) == len(v_ac)) assert_true(len(i_dc) == int(simtime/sim_dt)+1 == len(v_dc)) assert_true(len(i_noise) == int(simtime/sim_dt)+1 == len(v_noise)) assert_true(len(i_step) == int(simtime/sim_dt)+1 == len(v_step)) # test to check values exist at start and end of simulation assert_true(i_ac.t_start == 0.0 * pq.ms and np.isclose(float(i_ac.times[-1]), simtime)) assert_true(i_dc.t_start == 0.0 * pq.ms and np.isclose(float(i_dc.times[-1]), simtime)) assert_true(i_noise.t_start == 0.0 * pq.ms and np.isclose(float(i_noise.times[-1]), simtime)) assert_true(i_step.t_start == 0.0 * pq.ms and np.isclose(float(i_step.times[-1]), simtime)) # test to check current changes at start time instant assert_true(i_ac[(int(start / sim_dt)) - 1, 0] == 0 * pq.nA and i_ac[int(start / sim_dt), 0] != 0 * pq.nA) assert_true(i_dc[int(start / sim_dt) - 1, 0] == 0 * pq.nA and i_dc[int(start / sim_dt), 0] != 0 * pq.nA) assert_true(i_noise[int(start / sim_dt) - 1, 0] == 0 * pq.nA and i_noise[int(start / sim_dt), 0] != 0 * pq.nA) assert_true(i_step[int(start / sim_dt) - 1, 0] == 0 * pq.nA and i_step[int(start / sim_dt), 0] != 0 * pq.nA) # test to check current changes appropriately at stop time instant - issue #630 assert_true(i_ac[(int(stop / sim_dt)) - 1, 0] != 0.0 * pq.nA and i_ac[(int(stop / sim_dt)), 0] == 0.0 * pq.nA) assert_true(i_dc[(int(stop / sim_dt)) - 1, 0] != 0.0 * pq.nA and i_ac[(int(stop / sim_dt)), 0] == 0.0 * pq.nA) assert_true(i_noise[(int(stop / sim_dt)) - 1, 0] != 0.0 * pq.nA and i_ac[(int(stop / sim_dt)), 0] == 0.0 * pq.nA) assert_true(i_step[(int(stop / sim_dt)) - 1, 0] != 0.2 * pq.nA and i_ac[(int(stop / sim_dt)), 0] == 0.0 * pq.nA) # test to check vm changes at the time step following current initiation assert_true(np.isclose(float(v_ac[int(start / sim_dt), 0].item()), v_rest) and v_ac[int(start / sim_dt) + 1] != v_rest * pq.mV) assert_true(np.isclose(float(v_dc[int(start / sim_dt), 0].item()), v_rest) and v_dc[int(start / sim_dt) + 1] != v_rest * pq.mV) assert_true(np.isclose(float(v_noise[int(start / sim_dt), 0].item()), v_rest) and v_noise[int(start / sim_dt) + 1] != v_rest * pq.mV) assert_true(np.isclose(float(v_step[int(start / sim_dt), 0].item()), v_rest) and v_step[int(start / sim_dt) + 1] != v_rest * pq.mV) @register() def issue497(sim): """ This is a test to check that the specified phase for the ACSource is valid at the specified start time (and not, for example, at t=0 as NEST currently does) Approach: > Two signals with different initial specified phases > 'start' of one signal updated on the fly > 'frequency' of other signal updated on the fly > Test to ensure that initial specified phases applicable at t = start """ sim_dt = 0.1 sim.setup(min_delay=1.0, timestep=sim_dt) start1 = 5.0 freq1 = 100.0 phase1 = 0.0 start2 = 5.0 freq2 = 100.0 phase2 = 90.0 amplitude = 1.0 cells = sim.Population(2, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0)) acsource1 = sim.ACSource(start=start1, stop=20.0, amplitude=amplitude, offset=0.0, frequency=freq1, phase=phase1) cells[0].inject(acsource1) acsource1.record() acsource2 = sim.ACSource(start=start2, stop=20.0, amplitude=amplitude, offset=0.0, frequency=freq2, phase=phase2) cells[1].inject(acsource2) acsource2.record() # cannot directly assign/read from electrode variables, as each # simulator currently returns parameters in different units (see #452) start1 = 10.0 acsource1.start = start1 freq2 = 20.0 acsource2.frequency = freq2 cells.record('v') sim.run(25.0) vm = cells.get_data().segments[0].filter(name="v")[0] sim.end() i_ac1 = acsource1.get_data() i_ac2 = acsource2.get_data() # verify that acsource1 has value at t = start as 0 and as non-zero at next dt assert_true(abs(i_ac1[int(start1 / sim_dt), 0]) < 1e-9) assert_true(abs(i_ac1[int(start1 / sim_dt) + 1, 0]) > 1e-9) # verify that acsources has value at t = start as 'amplitude' assert_true(abs(i_ac2[int(start2 / sim_dt), 0] - amplitude * pq.nA) < 1e-9) @register() def issue512(sim): """ Test to ensure that StepCurrentSource times are handled similarly across all simulators. Multiple combinations of step times tested for: 1) dt = 0.1 ms, min_delay = 0.1 ms 2) dt = 0.01 ms, min_delay = 0.01 ms Note: exact matches of times not appropriate owing to floating point rounding errors. If absolute difference <1e-9, then considered equal. """ def get_len(data): if "pyNN.nest" in str(sim): # as NEST uses LazyArray return len(data.evaluate()) else: return len(data) # 1) dt = 0.1 ms, min_delay = 0.1 ms dt = 0.1 sim.setup(timestep=dt, min_delay=dt) cells = sim.Population(1, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, v_rest=-60.0)) # 1.1) Negative time value assert_raises(ValueError, sim.StepCurrentSource, times=[0.4, -0.6, 0.8], amplitudes=[0.5, -0.5, 0.5]) # 1.2) Time values not monotonically increasing assert_raises(ValueError, sim.StepCurrentSource, times=[0.4, 0.2, 0.8], amplitudes=[0.5, -0.5, 0.5]) # 1.3) Check mapping of time values and removal of duplicates step = sim.StepCurrentSource(times=[0.41, 0.42, 0.86], amplitudes=[0.5, -0.5, 0.5]) assert_equal(get_len(step.times), 2) assert_equal(get_len(step.amplitudes), 2) if "pyNN.brian" in str(sim): # Brian requires time in seconds (s) assert_true(abs(step.times[0]-0.4*1e-3) < 1e-9) assert_true(abs(step.times[1]-0.9*1e-3) < 1e-9) # Brain requires amplitudes in amperes (A) assert_true(step.amplitudes[0] == -0.5*1e-9) assert_true(step.amplitudes[1] == 0.5*1e-9) else: # NEST requires amplitudes in picoamperes (pA) but stored # as LazyArray and so needn't manually adjust; use nA # NEURON requires amplitudes in nanoamperes (nA) assert_true(step.amplitudes[0] == -0.5) assert_true(step.amplitudes[1] == 0.5) # NEST and NEURON require time in ms # But NEST has time stamps reduced by min_delay if "pyNN.nest" in str(sim): assert_true(abs(step.times[0]-0.3) < 1e-9) assert_true(abs(step.times[1]-0.8) < 1e-9) else: # neuron assert_true(abs(step.times[0]-0.4) < 1e-9) assert_true(abs(step.times[1]-0.9) < 1e-9) # 2) dt = 0.01 ms, min_delay = 0.01 ms dt = 0.01 sim.setup(timestep=dt, min_delay=dt) cells = sim.Population(1, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, v_rest=-60.0)) # 2.1) Negative time value assert_raises(ValueError, sim.StepCurrentSource, times=[0.4, -0.6, 0.8], amplitudes=[0.5, -0.5, 0.5]) # 2.2) Time values not monotonically increasing assert_raises(ValueError, sim.StepCurrentSource, times=[0.5, 0.4999, 0.8], amplitudes=[0.5, -0.5, 0.5]) # 2.3) Check mapping of time values and removal of duplicates step = sim.StepCurrentSource(times=[0.451, 0.452, 0.86], amplitudes=[0.5, -0.5, 0.5]) assert_equal(get_len(step.times), 2) assert_equal(get_len(step.amplitudes), 2) if "pyNN.brian" in str(sim): # Brian requires time in seconds (s) assert_true(abs(step.times[0]-0.45*1e-3) < 1e-9) assert_true(abs(step.times[1]-0.86*1e-3) < 1e-9) # Brain requires amplitudes in amperes (A) assert_true(step.amplitudes[0] == -0.5*1e-9) assert_true(step.amplitudes[1] == 0.5*1e-9) else: # NEST requires amplitudes in picoamperes (pA) but stored # as LazyArray and so needn't manually adjust; use nA # NEURON requires amplitudes in nanoamperes (nA) assert_true(step.amplitudes[0] == -0.5) assert_true(step.amplitudes[1] == 0.5) # NEST and NEURON require time in ms # But NEST has time stamps reduced by min_delay if "pyNN.nest" in str(sim): assert_true(abs(step.times[0]-0.44) < 1e-9) assert_true(abs(step.times[1]-0.85) < 1e-9) else: # neuron assert_true(abs(step.times[0]-0.45) < 1e-9) assert_true(abs(step.times[1]-0.86) < 1e-9) @register() def issue631(sim): """ Test to ensure that recording of multiple electrode currents do not interfere with one another. """ sim_dt = 0.1 sim.setup(timestep=sim_dt, min_delay=sim_dt) cells = sim.Population(1, sim.IF_curr_exp(v_rest=-65.0, v_thresh=- 55.0, tau_refrac=5.0)) # , i_offset=-1.0*amp)) dc_source = sim.DCSource(amplitude=0.5, start=25, stop=50) ac_source = sim.ACSource(start=75, stop=125, amplitude=0.5, offset=0.25, frequency=100.0, phase=0.0) noisy_source = sim.NoisyCurrentSource(mean=0.5, stdev=0.05, start=150, stop=175, dt=1.0) step_source = sim.StepCurrentSource(times=[200, 225, 250], amplitudes=[0.4, 0.6, 0.2]) cells[0].inject(dc_source) cells[0].inject(ac_source) cells[0].inject(noisy_source) cells[0].inject(step_source) dc_source.record() ac_source.record() noisy_source.record() step_source.record() sim.run(275.0) i_dc = dc_source.get_data() i_ac = ac_source.get_data() i_noisy = noisy_source.get_data() i_step = step_source.get_data() assert_true(np.all(i_dc.magnitude[:int(25.0 / sim_dt) - 1:] == 0) and np.all(i_dc.magnitude[int(50.0 / sim_dt):] == 0)) assert_true(np.all(i_ac.magnitude[:int(75.0 / sim_dt) - 1:] == 0) and np.all(i_ac.magnitude[int(125.0 / sim_dt):] == 0)) assert_true(np.all(i_noisy.magnitude[:int(150.0 / sim_dt) - 1:] == 0) and np.all(i_noisy.magnitude[int(175.0 / sim_dt):] == 0)) assert_true(np.all(i_step.magnitude[:int(200.0 / sim_dt) - 1:] == 0)) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() test_changing_electrode(sim) ticket226(sim) issue165(sim) issue321(sim) issue437(sim) issue442(sim) issue445(sim) issue451(sim) issue483(sim) issue487(sim) issue_465_474_630(sim) issue497(sim) issue512(sim) issue631(sim) PyNN-0.10.0/test/system/scenarios/test_parameter_handling.py000066400000000000000000000223321415343567000241540ustar00rootroot00000000000000 import numpy as np from numpy import nan from numpy.testing import assert_array_equal, assert_allclose from nose.tools import assert_equal from .registry import register @register() def issue241(sim): # "Nest SpikeSourcePoisson populations require all parameters to be passed to constructor" sim.setup() spike_train1 = sim.Population(1, sim.SpikeSourcePoisson, {'rate': [5], 'start': [1000], 'duration': [1234]}) spike_train2 = sim.Population(2, sim.SpikeSourcePoisson, {'rate': [5, 6], 'start': [1000, 1001], 'duration': [1234, 2345]}) spike_train3 = sim.Population(1, sim.SpikeSourcePoisson, {'rate': [5], 'start': [1000], 'duration': 1234}) spike_train4 = sim.Population(1, sim.SpikeSourcePoisson, {'rate': [5], 'start': [1000]}) spike_train5 = sim.Population(2, sim.SpikeSourcePoisson, {'rate': [5, 6], 'start': [1000, 1001]}) assert_array_equal(spike_train2.get('duration'), np.array([1234, 2345])) assert_equal(spike_train3.get(['rate', 'start', 'duration']), [5, 1000, 1234]) sim.end() @register() def issue302(sim): # "Setting attributes fails for Projections where either the pre- or post-synaptic Population has size 1" sim.setup() p1 = sim.Population(1, sim.IF_cond_exp()) p5 = sim.Population(5, sim.IF_cond_exp()) prj15 = sim.Projection(p1, p5, sim.AllToAllConnector()) prj51 = sim.Projection(p5, p1, sim.AllToAllConnector()) prj55 = sim.Projection(p5, p5, sim.AllToAllConnector()) prj15.set(weight=0.123) prj51.set(weight=0.123) prj55.set(weight=0.123) sim.end() @register() def test_set_synaptic_parameters_fully_connected(sim): sim.setup() mpi_rank = sim.rank() p1 = sim.Population(4, sim.IF_cond_exp()) p2 = sim.Population(2, sim.IF_cond_exp()) syn = sim.TsodyksMarkramSynapse(U=0.5, weight=0.123, delay=0.1) prj = sim.Projection(p1, p2, sim.AllToAllConnector(), syn) expected = np.array([ (0.0, 0.0, 0.123, 0.1, 0.5), (0.0, 1.0, 0.123, 0.1, 0.5), (1.0, 0.0, 0.123, 0.1, 0.5), (1.0, 1.0, 0.123, 0.1, 0.5), (2.0, 0.0, 0.123, 0.1, 0.5), (2.0, 1.0, 0.123, 0.1, 0.5), (3.0, 0.0, 0.123, 0.1, 0.5), (3.0, 1.0, 0.123, 0.1, 0.5), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_allclose(actual[ind], expected, 1e-15) positional_weights = np.array([[0, 1], [2, 3], [4, 5], [6, 7]], dtype=float) prj.set(weight=positional_weights) expected = positional_weights actual = prj.get('weight', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) u_list = [0.9, 0.8, 0.7, 0.6, 0.5, 0.4, 0.3, 0.2] prj.set(U=u_list) expected = np.array([[0.9, 0.8], [0.7, 0.6], [0.5, 0.4], [0.3, 0.2]]) actual = prj.get('U', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) f_delay = lambda d: 0.5 + d prj.set(delay=f_delay) expected = np.array([[0.5, 1.5], [1.5, 0.5], [2.5, 1.5], [3.5, 2.5]]) actual = prj.get('delay', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) # final sanity check expected = np.array([ (0.0, 0.0, 0.0, 0.5, 0.9), (0.0, 1.0, 1.0, 1.5, 0.8), (1.0, 0.0, 2.0, 1.5, 0.7), (1.0, 1.0, 3.0, 0.5, 0.6), (2.0, 0.0, 4.0, 2.5, 0.5), (2.0, 1.0, 5.0, 1.5, 0.4), (3.0, 0.0, 6.0, 3.5, 0.3), (3.0, 1.0, 7.0, 2.5, 0.2), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_array_equal(actual[ind], expected) test_set_synaptic_parameters_fully_connected.__test__ = False @register() def test_set_synaptic_parameters_partially_connected(sim): sim.setup() mpi_rank = sim.rank() p1 = sim.Population(4, sim.IF_cond_exp()) p2 = sim.Population(2, sim.IF_cond_exp()) syn = sim.TsodyksMarkramSynapse(U=0.5, weight=0.123, delay=0.1) prj = sim.Projection(p1, p2, sim.FromListConnector([(0, 0), (3, 0), (1, 1), (1, 0), (2, 1)]), syn) expected = np.array([ (0.0, 0.0, 0.123, 0.1, 0.5), (1.0, 0.0, 0.123, 0.1, 0.5), (1.0, 1.0, 0.123, 0.1, 0.5), (2.0, 1.0, 0.123, 0.1, 0.5), (3.0, 0.0, 0.123, 0.1, 0.5), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_allclose(actual[ind], expected, 1e-15) positional_weights = np.array([[0, nan], [2, 3], [nan, 5], [6, nan]], dtype=float) prj.set(weight=positional_weights) expected = positional_weights actual = prj.get('weight', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) u_list = [0.9, 0.8, 0.7, 0.6, 0.5] prj.set(U=u_list) expected = np.array([[0.9, nan], [0.8, 0.7], [nan, 0.6], [0.5, nan]]) actual = prj.get('U', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) f_delay = lambda d: 0.5 + d prj.set(delay=f_delay) expected = np.array([[0.5, nan], [1.5, 0.5], [nan, 1.5], [3.5, nan]]) actual = prj.get('delay', format='array') if mpi_rank == 0: assert_array_equal(actual, expected) # final sanity check expected = np.array([ (0.0, 0.0, 0.0, 0.5, 0.9), (1.0, 0.0, 2.0, 1.5, 0.8), (1.0, 1.0, 3.0, 0.5, 0.7), (2.0, 1.0, 5.0, 1.5, 0.6), (3.0, 0.0, 6.0, 3.5, 0.5), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_array_equal(actual[ind], expected) test_set_synaptic_parameters_partially_connected.__test__ = False @register() def test_set_synaptic_parameters_multiply_connected(sim): sim.setup() mpi_rank = sim.rank() p1 = sim.Population(4, sim.IF_cond_exp()) p2 = sim.Population(2, sim.IF_cond_exp()) syn = sim.TsodyksMarkramSynapse(U=0.5, weight=0.123, delay=0.1) prj = sim.Projection(p1, p2, sim.FromListConnector([(0, 0), (1, 0), (3, 0), (1, 1), (1, 0), (2, 1)]), syn) expected = np.array([ (0.0, 0.0, 0.123, 0.1, 0.5), (1.0, 0.0, 0.123, 0.1, 0.5), (1.0, 0.0, 0.123, 0.1, 0.5), (1.0, 1.0, 0.123, 0.1, 0.5), (2.0, 1.0, 0.123, 0.1, 0.5), (3.0, 0.0, 0.123, 0.1, 0.5), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_allclose(actual[ind], expected, 1e-15) positional_weights = np.array([[0, nan], [2, 3], [nan, 5], [6, nan]], dtype=float) prj.set(weight=positional_weights) expected = np.array([ (0.0, 0.0, 0.0), (1.0, 0.0, 2.0), (1.0, 0.0, 2.0), (1.0, 1.0, 3.0), (2.0, 1.0, 5.0), (3.0, 0.0, 6.0), ]) actual = np.array(prj.get('weight', format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_allclose(actual[ind], expected, 1e-15) # postponing implementation of this functionality until after 0.8.0 # u_list = [0.9, 0.8, 0.7, 0.6, 0.5, 0.4] # prj.set(U=u_list) # expected = np.array([ # (0.0, 0.0, 0.9), # (1.0, 0.0, 0.8), # (1.0, 0.0, 0.7), # (1.0, 1.0, 0.6), # (2.0, 1.0, 0.5), # (3.0, 0.0, 0.4), # ]) # actual = np.array(prj.get('U', format='list')) # if mpi_rank == 0: # ind = np.lexsort((actual[:, 1], actual[:, 0])) # assert_allclose(actual[ind], expected, 1e-16) f_delay = lambda d: 0.5 + d prj.set(delay=f_delay) expected = np.array([ (0.0, 0.0, 0.5), (1.0, 0.0, 1.5), (1.0, 0.0, 1.5), (1.0, 1.0, 0.5), (2.0, 1.0, 1.5), (3.0, 0.0, 3.5), ]) actual = np.array(prj.get('delay', format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_allclose(actual[ind], expected, 1e-15) # final sanity check expected = np.array([ (0.0, 0.0, 0.0, 0.5, 0.5), (1.0, 0.0, 2.0, 1.5, 0.5), (1.0, 0.0, 2.0, 1.5, 0.5), (1.0, 1.0, 3.0, 0.5, 0.5), (2.0, 1.0, 5.0, 1.5, 0.5), (3.0, 0.0, 6.0, 3.5, 0.5), ]) actual = np.array(prj.get(['weight', 'delay', 'U'], format='list')) if mpi_rank == 0: ind = np.lexsort((actual[:, 1], actual[:, 0])) assert_array_equal(actual[ind], expected) test_set_synaptic_parameters_multiply_connected.__test__ = False @register() def issue505(sim): sim.setup(timestep=0.05, min_delay=0.05) p = sim.Population(2, sim.IF_cond_exp()) projection = sim.Projection(p, p, sim.AllToAllConnector(), sim.TsodyksMarkramSynapse(U=0.543)) U = projection.get('U', format='list', with_address=False) assert_equal(U, [0.543, 0.543, 0.543, 0.543]) delay = projection.get('delay', format='list', with_address=False) assert_equal(delay, [0.05, 0.05, 0.05, 0.05]) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() issue241(sim) issue302(sim) test_set_synaptic_parameters_fully_connected(sim) test_set_synaptic_parameters_partially_connected(sim) issue505(sim)PyNN-0.10.0/test/system/scenarios/test_procedural_api.py000066400000000000000000000017431415343567000233240ustar00rootroot00000000000000 import numpy as np from numpy.testing import assert_allclose from pyNN.utility import init_logging from .registry import register @register() def ticket195(sim): """ Check that the `connect()` function works correctly with single IDs (see http://neuralensemble.org/trac/PyNN/ticket/195) """ init_logging(None, debug=True) sim.setup(timestep=0.01) pre = sim.Population(10, sim.SpikeSourceArray(spike_times=range(1, 10))) post = sim.Population(10, sim.IF_cond_exp()) #sim.connect(pre[0], post[0], weight=0.01, delay=0.1, p=1) sim.connect(pre[0:1], post[0:1], weight=0.01, delay=0.1, p=1) #prj = sim.Projection(pre, post, sim.FromListConnector([(0, 0, 0.01, 0.1)])) post.record(['spikes', 'v']) sim.run(100.0) assert_allclose(post.get_data().segments[0].spiketrains[0].magnitude, np.array([13.4]), 0.5) sim.end() if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() ticket195(sim) PyNN-0.10.0/test/system/scenarios/test_recording.py000066400000000000000000000362461415343567000223150ustar00rootroot00000000000000 import os import pickle import numpy as np import quantities as pq from nose.tools import assert_equal, assert_true from numpy.testing import assert_array_equal, assert_allclose from neo.io import get_io from pyNN.utility import normalized_filename from .registry import register @register() def test_reset_recording(sim): """ Check that record(None) resets the list of things to record. This test injects different levels of current into two neurons. In the first run, we record one of the neurons, in the second we record the other. The main point is to check that the first neuron is not recorded in the second run. """ sim.setup() p = sim.Population(7, sim.IF_cond_exp()) p[3].i_offset = 0.1 p[4].i_offset = 0.2 p[3:4].record('v') sim.run(10.0) sim.reset() p.record(None) p[4:5].record('v') sim.run(10.0) data = p.get_data() sim.end() ti = lambda i: data.segments[i].analogsignals[0].times assert_array_equal(ti(0), ti(1)) assert_array_equal(data.segments[0].analogsignals[0].array_annotations["channel_index"], np.array([3])) assert_array_equal(data.segments[1].analogsignals[0].array_annotations["channel_index"], np.array([4])) vi = lambda i: data.segments[i].analogsignals[0] assert vi(0).shape == vi(1).shape == (101, 1) assert vi(0)[0, 0] == vi(1)[0, 0] == p.initial_values['v'].evaluate(simplify=True) * pq.mV # the first value should be the same assert not (vi(0)[1:, 0] == vi(1)[1:, 0]).any() # none of the others should be, because of different i_offset test_reset_recording.__test__ = False @register() def test_record_vm_and_gsyn_from_assembly(sim): from pyNN.utility import init_logging init_logging(logfile=None, debug=True) dt = 0.1 tstop = 100.0 sim.setup(timestep=dt, min_delay=dt) cells = sim.Population(5, sim.IF_cond_exp()) + sim.Population(6, sim.EIF_cond_exp_isfa_ista()) inputs = sim.Population(5, sim.SpikeSourcePoisson(rate=50.0)) sim.connect(inputs, cells, weight=0.1, delay=0.5, receptor_type='inhibitory') sim.connect(inputs, cells, weight=0.1, delay=0.3, receptor_type='excitatory') cells.record('v') cells[2:9].record(['gsyn_exc', 'gsyn_inh']) # for p in cells.populations: # assert_equal(p.recorders['v'].recorded, set(p.all_cells)) # assert_equal(cells.populations[0].recorders['gsyn'].recorded, set(cells.populations[0].all_cells[2:5])) # assert_equal(cells.populations[1].recorders['gsyn'].recorded, set(cells.populations[1].all_cells[0:4])) sim.run(tstop) data0 = cells.populations[0].get_data().segments[0] data1 = cells.populations[1].get_data().segments[0] data_all = cells.get_data().segments[0] vm_p0 = data0.filter(name='v')[0] vm_p1 = data1.filter(name='v')[0] vm_all = data_all.filter(name='v')[0] gsyn_p0 = data0.filter(name='gsyn_exc')[0] gsyn_p1 = data1.filter(name='gsyn_exc')[0] gsyn_all = data_all.filter(name='gsyn_exc')[0] n_points = int(tstop / dt) + 1 assert_equal(vm_p0.shape, (n_points, 5)) assert_equal(vm_p1.shape, (n_points, 6)) assert_equal(vm_all.shape, (n_points, 11)) assert_equal(gsyn_p0.shape, (n_points, 3)) assert_equal(gsyn_p1.shape, (n_points, 4)) assert_equal(gsyn_all.shape, (n_points, 7)) assert_array_equal(vm_p1[:, 3], vm_all[:, 8]) assert_array_equal(vm_p0.array_annotations["channel_index"], np.arange(5)) assert_array_equal(vm_p1.array_annotations["channel_index"], np.arange(6)) #assert_array_equal(vm_all.array_annotations["channel_index"], np.arange(11)) assert_array_equal(gsyn_p0.array_annotations["channel_index"], np.array([2, 3, 4])) assert_array_equal(gsyn_p1.array_annotations["channel_index"], np.arange(4)) #assert_array_equal(gsyn_all.array_annotations["channel_index"], np.arange(2, 9)) sim.end() test_record_vm_and_gsyn_from_assembly.__test__ = False @register(exclude=["brian2"]) # brian does not support off_grid. To fix? def issue259(sim): """ A test that retrieving data with "clear=True" gives correct spike trains. """ sim.setup(timestep=0.05, spike_precision="off_grid") p = sim.Population(1, sim.SpikeSourceArray(spike_times=[0.075, 10.025, 12.34, 1000.025])) p.record('spikes') sim.run(10.0) spiketrains0 = p.get_data('spikes', clear=True).segments[0].spiketrains print(spiketrains0[0]) sim.run(10.0) spiketrains1 = p.get_data('spikes', clear=True).segments[0].spiketrains print(spiketrains1[0]) sim.run(10.0) spiketrains2 = p.get_data('spikes', clear=True).segments[0].spiketrains print(spiketrains2[0]) sim.end() assert_allclose(spiketrains0[0].rescale(pq.ms).magnitude, np.array([0.075]), 1e-17) assert_allclose(spiketrains1[0].rescale(pq.ms).magnitude, np.array([10.025, 12.34]), 1e-14) assert_equal(spiketrains2[0].size, 0) @register() def test_sampling_interval(sim): """ A test of the sampling_interval argument. """ sim.setup(0.1) p1 = sim.Population(3, sim.IF_cond_exp()) p2 = sim.Population(4, sim.IF_cond_exp()) p1.record('v', sampling_interval=1.0) p2.record('v', sampling_interval=0.5) sim.run(10.0) d1 = p1.get_data().segments[0].analogsignals[0] d2 = p2.get_data().segments[0].analogsignals[0] assert_equal(d1.sampling_period, 1.0 * pq.ms) assert_equal(d1.shape, (11, 3)) assert_equal(d2.sampling_period, 0.5 * pq.ms) assert_equal(d2.shape, (21, 4)) sim.end() test_sampling_interval.__test__ = False @register() def test_mix_procedural_and_oo(sim): # cf Issues #217, #234 fn_proc = "test_write_procedural.pkl" fn_oo = "test_write_oo.pkl" sim.setup(timestep=0.1, min_delay=0.1) cells = sim.Population(5, sim.IF_cond_exp(i_offset=0.2)) sim.record('v', cells, fn_proc) sim.run(10.0) cells.write_data(fn_oo) # explicitly write data sim.end() # implicitly write data using filename provided previously data_proc = get_io(fn_proc).read()[0] data_oo = get_io(fn_oo).read()[0] assert_array_equal(data_proc.segments[0].analogsignals[0], data_oo.segments[0].analogsignals[0]) os.remove(fn_proc) os.remove(fn_oo) test_mix_procedural_and_oo.__test__ = False @register(exclude=['brian2']) # todo: known to fail with Brian, but should work def test_record_with_filename(sim): """ Test to ensure that Simulator and Population recording work properly The following 12 scenarios are explored: Note: var1 = "spikes", var2 = "v" 1) sim.record() i) cell[0] a) 2 parameters (2vars) (scenario 1) b) parameter1 (var1) (scenario 2) c) parameter2 (var2) (scenario 3) ii) cell[1] a) 2 parameters (2vars) (scenario 4) b) parameter1 (var1) (scenario 5) c) parameter2 (var2) (scenario 6) iii) population a) 2 parameters (2vars) (scenario 7) b) parameter1 (var1) (scenario 8) c) parameter2 (var2) (scenario 9) 2) pop.record() - always records for a population; not a single cell a) 2 parameters (2vars) (scenario 10) b) parameter1 (var1) (scenario 11) c) parameter2 (var2) (scenario 12) cf Issues #449, #490, #491 """ # START ***** defining methods needed for test ***** def get_file_data(filename): # method to access pickled file and retrieve data data = [] with (open(filename, "rb")) as openfile: while True: try: data.append(pickle.load(openfile)) except EOFError: break return data def eval_num_cells(data): # scan data object to evaluate number of cells; returns 4 values # nCells : # of cells in analogsignals (if "v" recorded) # nspikes1: # of spikes in first recorded cell # nspikes2: # of spikes in second recorded cell (if exists) # -- if any parameter absent, return -1 as its value # annot_bool # true if specified annotation exists; false otherwise try: nCells = data[0].segments[0].analogsignals[0].shape[1] except: nCells = -1 try: nspikes1 = data[0].segments[0].spiketrains[0].shape[0] except: nspikes1 = -1 try: nspikes2 = data[0].segments[0].spiketrains[1].shape[0] except: nspikes2 = -1 if 'script_name' in data[0].annotations.keys(): annot_bool = True else: annot_bool = False return (nCells, nspikes1, nspikes2, annot_bool) # END ***** defining methods needed for test ***** sim_dt = 0.1 sim.setup(min_delay=1.0, timestep = sim_dt) # creating a population of two cells; only cell[0] gets stimulus # hence only cell[0] will have entries for spiketrains cells = sim.Population(2, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0)) steady = sim.DCSource(amplitude=2.5, start=25.0, stop=75.0) cells[0].inject(steady) # specify appropriate filenames for output files filename_sim_cell1_2vars = normalized_filename("Results", "sim_cell1_2vars", "pkl", sim.__name__) filename_sim_cell1_var1 = normalized_filename("Results", "sim_cell1_var1", "pkl", sim.__name__) filename_sim_cell1_var2 = normalized_filename("Results", "sim_cell1_var2", "pkl", sim.__name__) filename_sim_cell2_2vars = normalized_filename("Results", "sim_cell2_2vars", "pkl", sim.__name__) filename_sim_cell2_var1 = normalized_filename("Results", "sim_cell2_var1", "pkl", sim.__name__) filename_sim_cell2_var2 = normalized_filename("Results", "sim_cell2_var2", "pkl", sim.__name__) filename_sim_popl_2vars = normalized_filename("Results", "sim_popl_2vars", "pkl", sim.__name__) filename_sim_popl_var1 = normalized_filename("Results", "sim_popl_var1", "pkl", sim.__name__) filename_sim_popl_var2 = normalized_filename("Results", "sim_popl_var2", "pkl", sim.__name__) filename_rec_2vars = normalized_filename("Results", "rec_2vars", "pkl", sim.__name__) filename_rec_var1 = normalized_filename("Results", "rec_var1", "pkl", sim.__name__) filename_rec_var2 = normalized_filename("Results", "rec_var2", "pkl", sim.__name__) # instruct pynn to record as per above scenarios sim.record(["spikes", "v"], cells[0], filename_sim_cell1_2vars, annotations={'script_name': __file__}) sim.record(["spikes"], cells[0], filename_sim_cell1_var1, annotations={'script_name': __file__}) sim.record(["v"], cells[0], filename_sim_cell1_var2, annotations={'script_name': __file__}) sim.record(["spikes", "v"], cells[1], filename_sim_cell2_2vars, annotations={'script_name': __file__}) sim.record(["spikes"], cells[1], filename_sim_cell2_var1, annotations={'script_name': __file__}) sim.record(["v"], cells[1], filename_sim_cell2_var2, annotations={'script_name': __file__}) sim.record(["spikes", "v"], cells, filename_sim_popl_2vars, annotations={'script_name': __file__}) sim.record(["spikes"], cells, filename_sim_popl_var1, annotations={'script_name': __file__}) sim.record(["v"], cells, filename_sim_popl_var2, annotations={'script_name': __file__}) cells.record(["spikes", "v"], to_file=filename_rec_2vars) cells.record(["spikes"], to_file=filename_rec_var1) cells.record(["v"], to_file=filename_rec_var2) sim.run(100.0) sim.end() # retrieve data from the created files, and perform appropriate checks # scenario 1 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell1_2vars)) assert_true (nCells == 1) assert_true (nspikes1 > 0) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 2 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell1_var1)) assert_true (nCells == -1) assert_true (nspikes1 > 0) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 3 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell1_var2)) assert_true (nCells == 1) assert_true (nspikes1 == -1) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 4 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell2_2vars)) assert_true (nCells == 1) assert_true (nspikes1 == 0) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 5 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell2_var1)) assert_true (nCells == -1) assert_true (nspikes1 == 0) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 6 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_cell2_var2)) assert_true (nCells == 1) assert_true (nspikes1 == -1) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 7 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_popl_2vars)) assert_true (nCells == 2) assert_true (nspikes1 > 0) assert_true (nspikes2 == 0) assert_true (annot_bool) # scenario 8 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_popl_var1)) assert_true (nCells == -1) assert_true (nspikes1 > 0) assert_true (nspikes2 == 0) assert_true (annot_bool) # scenario 9 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_sim_popl_var2)) assert_true (nCells == 2) assert_true (nspikes1 == -1) assert_true (nspikes2 == -1) assert_true (annot_bool) # scenario 10 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_rec_2vars)) assert_true (nCells == 2) assert_true (nspikes1 > 0) assert_true (nspikes2 == 0) assert_true (annot_bool) # scenario 11 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_rec_var1)) assert_true (nCells == -1) assert_true (nspikes1 > 0) assert_true (nspikes2 == 0) assert_true (annot_bool) # scenario 12 nCells, nspikes1, nspikes2, annot_bool = eval_num_cells(get_file_data(filename_rec_var2)) assert_true (nCells == 2) assert_true (nspikes1 == -1) assert_true (nspikes2 == -1) assert_true (annot_bool) test_record_with_filename.__test__ = False @register() def issue499(sim): """ Test to check that sim.end() does not erase the recorded data """ sim.setup(min_delay=1.0, timestep = 0.1) cells = sim.Population(1, sim.IF_curr_exp()) dcsource = sim.DCSource(amplitude=0.5, start=20, stop=80) cells[0].inject(dcsource) cells.record('v') sim.run(50.0) sim.end() vm = cells.get_data().segments[0].filter(name="v")[0] v_dc = vm[:, 0] assert_true (len(v_dc)!=0) if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() test_reset_recording(sim) test_record_vm_and_gsyn_from_assembly(sim) issue259(sim) test_sampling_interval(sim) test_mix_procedural_and_oo(sim) test_record_with_filename(sim) issue499(sim) PyNN-0.10.0/test/system/scenarios/test_simulation_control.py000066400000000000000000000075341415343567000242630ustar00rootroot00000000000000 from nose.tools import assert_almost_equal, assert_raises from numpy.testing import assert_array_equal, assert_array_almost_equal, assert_allclose from .registry import register @register() def test_reset(sim): """ Run the same simulation n times without recreating the network, and check the results are the same each time. """ repeats = 3 dt = 1 sim.setup(timestep=dt, min_delay=dt, t_flush=10.0) p = sim.Population(1, sim.IF_curr_exp(i_offset=0.1)) p.record('v') for i in range(repeats): sim.run(10.0) sim.reset() data = p.get_data(clear=False) sim.end() assert len(data.segments) == repeats for segment in data.segments[1:]: assert_array_almost_equal(segment.analogsignals[0], data.segments[0].analogsignals[0], 10) test_reset.__test__ = False @register() def test_reset_with_clear(sim): """ Run the same simulation n times without recreating the network, and check the results are the same each time. """ repeats = 3 dt = 1 sim.setup(timestep=dt, min_delay=dt, t_flush=10.0) p = sim.Population(1, sim.IF_curr_exp(i_offset=0.1)) p.record('v') data = [] for i in range(repeats): sim.run(10.0) data.append(p.get_data(clear=True)) sim.reset() sim.end() for rec in data: assert len(rec.segments) == 1 assert_allclose(rec.segments[0].analogsignals[0].magnitude, data[0].segments[0].analogsignals[0].magnitude, 1e-11) test_reset_with_clear.__test__ = False @register() def test_reset_with_spikes(sim): """ Run the same simulation n times without recreating the network, and check the results are the same each time. """ repeats = 3 dt = 0.1 sim.setup(timestep=dt, min_delay=dt, t_flush=200.0) p1 = sim.Population(2, sim.SpikeSourceArray(spike_times=[ [1.2, 3.8, 9.2], [1.5, 1.9, 2.7, 4.8, 6.8], ])) p2 = sim.Population(2, sim.IF_curr_exp()) p2.record('v') prj = sim.Projection(p1, p2, sim.AllToAllConnector(), sim.StaticSynapse(weight=0.5, delay=0.5)) for i in range(repeats): sim.run(10.0) sim.reset() data = p2.get_data(clear=False) sim.end() assert len(data.segments) == repeats for segment in data.segments[1:]: assert_array_almost_equal(segment.analogsignals[0], data.segments[0].analogsignals[0], 10) test_reset_with_spikes.__test__ = False @register() def test_setup(sim): """ Run the same simulation n times, recreating the network each time, and check the results are the same each time. """ n = 3 data = [] dt = 1 for i in range(n): sim.setup(timestep=dt, min_delay=dt) p = sim.Population(1, sim.IF_curr_exp(i_offset=0.1)) p.record('v') sim.run(10.0) data.append(p.get_data()) sim.end() assert len(data) == n for block in data: assert len(block.segments) == 1 signals = block.segments[0].analogsignals assert len(signals) == 1 assert_array_equal(signals[0], data[0].segments[0].analogsignals[0]) test_setup.__test__ = False @register() def test_run_until(sim): sim.setup(timestep=0.1) p = sim.Population(1, sim.IF_cond_exp()) sim.run_until(12.7) assert_almost_equal(sim.get_current_time(), 12.7, 10) sim.run_until(12.7) assert_almost_equal(sim.get_current_time(), 12.7, 10) sim.run_until(99.9) assert_almost_equal(sim.get_current_time(), 99.9, 10) assert_raises(ValueError, sim.run_until, 88.8) sim.end() test_run_until.__test__ = False if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator() test_reset(sim) test_reset_with_clear(sim) test_setup(sim) test_run_until(sim) PyNN-0.10.0/test/system/scenarios/test_synapse_types.py000066400000000000000000000043601415343567000232370ustar00rootroot00000000000000 import numpy as np try: import scipy have_scipy = True except ImportError: have_scipy = False from nose.tools import assert_equal, assert_less, assert_greater, assert_not_equal from .registry import register @register(exclude=['brian2']) def test_simple_stochastic_synapse(sim, plot_figure=False): # in this test we connect sim.setup(min_delay=0.5) t_stop = 1000.0 spike_times = np.arange(2.5, t_stop, 5.0) source = sim.Population(1, sim.SpikeSourceArray(spike_times=spike_times)) neurons = sim.Population(4, sim.IF_cond_exp(tau_syn_E=1.0)) synapse_type = sim.SimpleStochasticSynapse(weight=0.5, p=np.array([[0.0, 0.5, 0.5, 1.0]])) connections = sim.Projection(source, neurons, sim.AllToAllConnector(), synapse_type=synapse_type) source.record('spikes') neurons.record('gsyn_exc') sim.run(t_stop) data = neurons.get_data().segments[0] gsyn = data.analogsignals[0].rescale('uS') if plot_figure: import matplotlib.pyplot as plt for i in range(neurons.size): plt.subplot(neurons.size, 1, i+1) plt.plot(gsyn.times, gsyn[:, i]) plt.savefig("test_simple_stochastic_synapse_%s.png" % sim.__name__) print(data.analogsignals[0].units) crossings = [] for i in range(neurons.size): crossings.append( gsyn.times[:-1][np.logical_and(gsyn.magnitude[:-1, i] < 0.4, 0.4 < gsyn.magnitude[1:, i])]) assert_equal(crossings[0].size, 0) assert_less(crossings[1].size, 0.6*spike_times.size) assert_greater(crossings[1].size, 0.4*spike_times.size) assert_equal(crossings[3].size, spike_times.size) try: assert_not_equal(crossings[1], crossings[2]) except ValueError: assert not (crossings[1] == crossings[2]).all() print(crossings[1].size / spike_times.size) return data test_simple_stochastic_synapse.__test__ = False if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator(("--plot-figure", {"help": "generate a figure", "action": "store_true"})) test_simple_stochastic_synapse(sim, plot_figure=args.plot_figure) PyNN-0.10.0/test/system/scenarios/ticket166.py000066400000000000000000000034671415343567000210210ustar00rootroot00000000000000 import numpy as np from .registry import register @register() def ticket166(sim, plot_figure=False): """ Check that changing the spike_times of a SpikeSourceArray mid-simulation works (see http://neuralensemble.org/trac/PyNN/ticket/166) """ dt = 0.1 # ms t_step = 100.0 # ms lag = 3.0 # ms sim.setup(timestep=dt, min_delay=dt) spikesources = sim.Population(2, sim.SpikeSourceArray()) cells = sim.Population(2, sim.IF_cond_exp()) syn = sim.StaticSynapse(weight=0.01) conn = sim.Projection(spikesources, cells, sim.OneToOneConnector(), syn) cells.record('v') spiketimes = np.arange(2.0, t_step, t_step / 13.0) spikesources[0].spike_times = spiketimes spikesources[1].spike_times = spiketimes + lag t = sim.run(t_step) # both neurons depolarized by synaptic input t = sim.run(t_step) # no more synaptic input, neurons decay spiketimes += 2 * t_step spikesources[0].spike_times = spiketimes # note we add no new spikes to the second source t = sim.run(t_step) # first neuron gets depolarized again vm = cells.get_data().segments[0].analogsignals[0] final_v_0 = vm[-1, 0] final_v_1 = vm[-1, 1] sim.end() if plot_figure: import matplotlib.pyplot as plt plt.plot(vm.times, vm[:, 0]) plt.plot(vm.times, vm[:, 1]) plt.savefig("ticket166_%s.png" % sim.__name__) assert final_v_0 > -60.0 # first neuron has been depolarized again assert final_v_1 < -64.99 # second neuron has decayed back towards rest if __name__ == '__main__': from pyNN.utility import get_simulator sim, args = get_simulator(("--plot-figure", {"help": "generate a figure", "action": "store_true"})) ticket166(sim, plot_figure=args.plot_figure) PyNN-0.10.0/test/system/test_brian2.py000066400000000000000000000073011415343567000175160ustar00rootroot00000000000000from nose.plugins.skip import SkipTest from nose.tools import assert_equal import numpy as np from numpy.testing import assert_array_equal from .scenarios.registry import registry try: import pyNN.brian2 have_brian2 = True except ImportError: have_brian2 = False def test_scenarios(): for scenario in registry: if "brian2" not in scenario.exclude: scenario.description = "{}(brian2)".format(scenario.__name__) if have_brian2: yield scenario, pyNN.brian2 else: raise SkipTest def test_ticket235(): if not have_brian2: raise SkipTest pynnn = pyNN.brian2 pynnn.setup() p1 = pynnn.Population(9, pynnn.IF_curr_alpha(), structure=pynnn.space.Grid2D()) p2 = pynnn.Population(9, pynnn.IF_curr_alpha(), structure=pynnn.space.Grid2D()) p1.record('spikes', to_file=False) p2.record('spikes', to_file=False) prj1_2 = pynnn.Projection(p1, p2, pynnn.OneToOneConnector( ), pynnn.StaticSynapse(weight=10.0), receptor_type='excitatory') # we note that the connectivity is as expected: a uniform diagonal prj1_2.get('weight', format='array') src = pynnn.DCSource(amplitude=70) src.inject_into(p1[:]) pynnn.run(50) n_spikes_p1 = p1.get_spike_counts() # We see that both populations have fired uniformly as expected: n_spikes_p2 = p2.get_spike_counts() for n in n_spikes_p1.values(): assert_equal(n, n_spikes_p1[p1[1]]) for n in n_spikes_p2.values(): assert_equal(n, n_spikes_p2[p2[1]]) # With this new setup, only the second p2 unit should fire: # prj1_2.set(weight=[0, 20, 0, 0, 0, 0, 0, 0, 0]) new_weights = np.where(np.eye(9), 0, np.nan) new_weights[1, 1] = 20.0 prj1_2.set(weight=new_weights) # This looks good: prj1_2.get('weight', format='array') pynnn.run(50) n_spikes_p1 = p1.get_spike_counts() for n in n_spikes_p1.values(): assert_equal(n, n_spikes_p1[p1[1]]) # p2[1] should be ahead in spikes count, and others should not have # fired more. It is not what I observe: n_spikes_p2 = p2.get_spike_counts() assert n_spikes_p2[p2[1]] > n_spikes_p2[p2[0]] def test_tsodyks_markram_synapse(): if not have_brian2: raise SkipTest sim = pyNN.brian2 sim.setup() spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) neurons = sim.Population(5, sim.IF_cond_exp( e_rev_I=-75, tau_syn_I=np.arange(0.2, 0.7, 0.1))) synapse_type = sim.TsodyksMarkramSynapse(U=0.04, tau_rec=100.0, tau_facil=1000.0, weight=0.01, delay=0.5) connector = sim.AllToAllConnector() prj = sim.Projection(spike_source, neurons, connector, receptor_type='inhibitory', synapse_type=synapse_type) neurons.record('gsyn_inh') sim.run(100.0) tau_psc = prj._brian2_synapses[0][0].tau_syn_ * 1e3 # s --> ms assert_array_equal(tau_psc, np.arange(0.2, 0.7, 0.1)) def test_issue648(): """ https://github.com/NeuralEnsemble/PyNN/issues/648 For a population of size 2: cells[0].inject(dc_source) cells[1].inject(dc_source) should give the same results as: cells.inject(dc_source) """ if not have_brian2: raise SkipTest sim = pyNN.brian2 sim.setup() cells = sim.Population(2, sim.IF_curr_exp(v_rest = -65.0, v_thresh=-55.0, tau_refrac=5.0, i_offset=-1.0)) dc_source = sim.DCSource(amplitude=0.5, start=25, stop=50) cells[0].inject(dc_source) cells[1].inject(dc_source) cells.record(['v']) sim.run(100) PyNN-0.10.0/test/system/test_hardware_brainscales.py000066400000000000000000000111221415343567000225000ustar00rootroot00000000000000import logging import unittest from nose.plugins.skip import SkipTest from numpy import nan_to_num try: import pyNN.hardware.brainscales as sim have_hardware_brainscales = True except ImportError: have_hardware_brainscales = False logger = logging.getLogger() logger.setLevel(logging.DEBUG) class HardwareTest(unittest.TestCase): def setUp(self): if not have_hardware_brainscales: raise SkipTest extra = { 'loglevel': 0, 'ignoreHWParameterRanges': True, 'useSystemSim': True, 'hardware': sim.hardwareSetup['one-hicann'] } sim.setup(**extra) def test_IF_cond_exp_default_values(self): ifcell = sim.IF_cond_exp() def test_IF_cond_exp_default_values2(self): ifcell = sim.IF_cond_exp() def test_SpikeSourceArray(self): from pyNN.utility.plotting import Figure, Panel spike_times = [50.] p = sim.Population(3, sim.SpikeSourceArray(spike_times=spike_times)) p2 = sim.Population(3, sim.Hardware_IF_cond_exp()) syn = sim.StaticSynapse(weight=0.012) con = sim.Projection(p, p2, connector=sim.OneToOneConnector(), synapse_type=syn, receptor_type='excitatory') spike_times_g = p.get('spike_times') p2.record('v') sim.run(100.0) weights = nan_to_num(con.get('weight', format="array")) print(weights) data = p2.get_data().segments[0] vm = data.filter(name="v")[0] print(vm) Figure( Panel(weights, data_labels=[ "ext->cell"], line_properties=[{'xticks': True, 'yticks': True, 'cmap': 'Greys'}]), Panel(vm, ylabel="Membrane potential (mV)", data_labels=[ "excitatory", "excitatory"], line_properties=[{'xticks': True, 'yticks': True}]), ).save("result") # def test_set_parameters(self): #p = sim.Population(3, sim.SpikeSourceArray()) #p2 = sim.Population(3, sim.Hardware_IF_cond_exp()) #syn = sim.StaticSynapse(weight=0.012) #con = sim.Projection(p, p2, connector = sim.OneToOneConnector(), synapse_type=syn,receptor_type='excitatory') #p[0].set_parameters(spike_times=Sequence([1., 2., 3., 40.])) #p[1].set_parameters(spike_times=Sequence([2., 3., 4., 50.])) #p[2].set_parameters(spike_times=Sequence([3., 4., 5., 50.])) #spike_times = p.get('spike_times') #self.assertEqual(spike_times.size, 3) #assert_array_equal(spike_times[1], Sequence([2, 3, 4, 50])) # p2.record('v') # sim.run(100.0) #weights = nan_to_num(con.get('weight', format="array")) # print weights #data = p2.get_data().segments[0] #vm = data.filter(name="v")[0] # print vm # Figure( #Panel(weights,data_labels=["ext->cell"], line_properties=[{'xticks':True, 'yticks':True, 'cmap':'Greys'}]), #Panel(vm, ylabel="Membrane potential (mV)", data_labels=["excitatory", "excitatory"], line_properties=[{'xticks': True, 'yticks':True}]), # ).save("result") # def test_scenarios(): #extra = {'loglevel':0, 'useSystemSim': True} #extra['hardware'] = sim.hardwareSetup['small'] # for scenario in registry: # if "hardware.brainscales" not in scenario.exclude: #scenario.description = scenario.__name__ # if have_hardware_brainscales: # sim.setup(**extra) # yield scenario, sim # sim.end() # else: #raise SkipTest def test_restart_loop(): if not have_hardware_brainscales: raise SkipTest extra = {'loglevel': 0, 'useSystemSim': True, 'hardware': sim.hardwareSetup['one-hicann']} sim.setup(**extra) sim.end() sim.setup(**extra) sim.end() sim.setup(**extra) sim.run(10.0) sim.end() sim.setup(**extra) sim.run(10.0) sim.end() # def test_several_runs(): if not have_hardware_brainscales: raise SkipTest #extra = {'loglevel':0, 'useSystemSim': True, 'hardware': sim.hardwareSetup['one-hicann']} # sim.setup(**extra) # sim.run(10.0) # sim.run(10.0) # sim.end() def test_sim_without_clearing(): if not have_hardware_brainscales: raise SkipTest extra = {'loglevel': 0, 'useSystemSim': True, 'hardware': sim.hardwareSetup['one-hicann']} sim.setup(**extra) def test_sim_without_setup(): if not have_hardware_brainscales: raise SkipTest sim.end() if __name__ == '__main__': # test_scenarios() # test_restart_loop() # test_sim_without_clearing() test_sim_without_setup() # test_several_runs() PyNN-0.10.0/test/system/test_nest.py000066400000000000000000000260461415343567000173210ustar00rootroot00000000000000 from nose.plugins.skip import SkipTest from nose.tools import assert_equal, assert_not_equal, assert_raises import numpy as np from numpy.testing import assert_array_equal try: import pyNN.nest have_nest = True except ImportError: have_nest = False from .scenarios.registry import registry from pyNN.utility import init_logging from pyNN.random import RandomDistribution def test_scenarios(): for scenario in registry: if "nest" not in scenario.exclude: scenario.description = "{}(nest)".format(scenario.__name__) if have_nest: yield scenario, pyNN.nest else: raise SkipTest def test_record_native_model(): if not have_nest: raise SkipTest nest = pyNN.nest from pyNN.random import RandomDistribution init_logging(logfile=None, debug=True) nest.setup() parameters = {'tau_m': 17.0} n_cells = 10 p1 = nest.Population(n_cells, nest.native_cell_type("ht_neuron")(**parameters)) p1.initialize(V_m=-70.0, Theta=-50.0) p1.set(theta_eq=-51.5) #assert_array_equal(p1.get('theta_eq'), -51.5*np.ones((10,))) assert_equal(p1.get('theta_eq'), -51.5) print(p1.get('tau_m')) p1.set(tau_m=RandomDistribution('uniform', low=15.0, high=20.0)) print(p1.get('tau_m')) current_source = nest.StepCurrentSource(times=[50.0, 110.0, 150.0, 210.0], amplitudes=[0.01, 0.02, -0.02, 0.01]) p1.inject(current_source) p2 = nest.Population(1, nest.native_cell_type("poisson_generator")(rate=200.0)) print("Setting up recording") p2.record('spikes') p1.record('V_m') connector = nest.AllToAllConnector() syn = nest.StaticSynapse(weight=0.001) prj_ampa = nest.Projection(p2, p1, connector, syn, receptor_type='AMPA') tstop = 250.0 nest.run(tstop) vm = p1.get_data().segments[0].analogsignals[0] n_points = int(tstop / nest.get_time_step()) + 1 assert_equal(vm.shape, (n_points, n_cells)) assert vm.max() > 0.0 # should have some spikes def test_native_stdp_model(): if not have_nest: raise SkipTest nest = pyNN.nest from pyNN.utility import init_logging init_logging(logfile=None, debug=True) nest.setup() p1 = nest.Population(10, nest.IF_cond_exp()) p2 = nest.Population(10, nest.SpikeSourcePoisson()) stdp_params = {'Wmax': 50.0, 'lambda': 0.015, 'weight': 0.001} stdp = nest.native_synapse_type("stdp_synapse")(**stdp_params) connector = nest.AllToAllConnector() prj = nest.Projection(p2, p1, connector, receptor_type='excitatory', synapse_type=stdp) def test_ticket240(): if not have_nest: raise SkipTest nest = pyNN.nest nest.setup(threads=4) parameters = {'tau_m': 17.0} p1 = nest.Population(4, nest.IF_curr_exp()) p2 = nest.Population(5, nest.native_cell_type("ht_neuron")(**parameters)) conn = nest.AllToAllConnector() syn = nest.StaticSynapse(weight=1.0) # This should be a nonstandard receptor type but I don't know of one to use. prj = nest.Projection(p1, p2, conn, syn, receptor_type='AMPA') connections = prj.get(('weight',), format='list') assert len(connections) > 0 def test_ticket244(): if not have_nest: raise SkipTest nest = pyNN.nest nest.setup(threads=4) p1 = nest.Population(4, nest.IF_curr_exp()) p1.record('spikes') poisson_generator = nest.Population(3, nest.SpikeSourcePoisson(rate=1000.0)) conn = nest.OneToOneConnector() syn = nest.StaticSynapse(weight=1.0) nest.Projection(poisson_generator, p1.sample(3), conn, syn, receptor_type="excitatory") nest.run(15) p1.get_data() def test_ticket236(): """Calling get_spike_counts() in the middle of a run should not stop spike recording""" if not have_nest: raise SkipTest pynnn = pyNN.nest pynnn.setup() p1 = pynnn.Population(2, pynnn.IF_curr_alpha(), structure=pynnn.space.Grid2D()) p1.record('spikes', to_file=False) src = pynnn.DCSource(amplitude=70) src.inject_into(p1[:]) pynnn.run(50) s1 = p1.get_spike_counts() # as expected, {1: 124, 2: 124} pynnn.run(50) s2 = p1.get_spike_counts() # unexpectedly, still {1: 124, 2: 124} assert s1[p1[0]] < s2[p1[0]] def test_issue237(): if not have_nest: raise SkipTest sim = pyNN.nest n_exc = 10 sim.setup() exc_noise_in_exc = sim.Population(n_exc, sim.SpikeSourcePoisson, {'rate': 1000.}) exc_cells = sim.Population(n_exc, sim.IF_cond_exp()) exc_noise_connector = sim.OneToOneConnector() noise_ee_prj = sim.Projection(exc_noise_in_exc, exc_cells, exc_noise_connector, receptor_type="excitatory") noise_ee_prj.set(weight=1e-3) def test_random_seeds(): if not have_nest: raise SkipTest sim = pyNN.nest data = [] for seed in (854947309, 470924491): sim.setup(threads=1, rng_seed=seed) p = sim.Population(3, sim.SpikeSourcePoisson(rate=100.0)) p.record('spikes') sim.run(100) data.append(p.get_data().segments[0].spiketrains) assert_not_equal(*data) def test_tsodyks_markram_synapse(): if not have_nest: raise SkipTest import nest sim = pyNN.nest sim.setup() spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) neurons = sim.Population(5, sim.IF_cond_exp( e_rev_I=-75, tau_syn_I=np.arange(0.2, 0.7, 0.1))) synapse_type = sim.TsodyksMarkramSynapse(U=0.04, tau_rec=100.0, tau_facil=1000.0, weight=0.01, delay=0.5) connector = sim.AllToAllConnector() prj = sim.Projection(spike_source, neurons, connector, receptor_type='inhibitory', synapse_type=synapse_type) neurons.record('gsyn_inh') sim.run(100.0) connections = nest.GetConnections(nest.NodeCollection(list(prj._sources)), synapse_model=prj.nest_synapse_model) tau_psc = np.array(nest.GetStatus(connections, 'tau_psc')) assert_array_equal(tau_psc, np.arange(0.2, 0.7, 0.1)) def test_native_electrode_types(): """ Test of NativeElectrodeType class. (See issue #506)""" if not have_nest: raise SkipTest sim = pyNN.nest dt = 0.1 sim.setup(timestep=0.1, min_delay=0.1) current_sources = [sim.DCSource(amplitude=0.5, start=50.0, stop=400.0), sim.native_electrode_type('dc_generator')( amplitude=500.0, start=50.0 - dt, stop=400.0 - dt), sim.StepCurrentSource(times=[50.0, 210.0, 250.0, 410.0], amplitudes=[0.4, 0.6, -0.2, 0.2]), sim.native_electrode_type('step_current_generator')( amplitude_times=[50.0 - dt, 210.0 - dt, 250.0 - dt, 410.0 - dt], amplitude_values=[400.0, 600.0, -200.0, 200.0]), sim.ACSource(start=50.0, stop=450.0, amplitude=0.2, offset=0.1, frequency=10.0, phase=180.0), sim.native_electrode_type('ac_generator')( start=50.0 - dt, stop=450.0 - dt, amplitude=200.0, offset=100.0, frequency=10.0, phase=180.0), sim.NoisyCurrentSource(mean=0.5, stdev=0.2, start=50.0, stop=450.0, dt=1.0), sim.native_electrode_type('noise_generator')( mean=500.0, std=200.0, start=50.0 - dt, stop=450.0 - dt, dt=1.0), ] n_cells = len(current_sources) cells = sim.Population(n_cells, sim.IF_curr_exp(v_thresh=-55.0, tau_refrac=5.0, tau_m=10.0)) for cell, current_source in zip(cells, current_sources): cell.inject(current_source) cells.record('v') sim.run(500) vm = cells.get_data().segments[0].filter(name="v")[0] assert_array_equal(vm[:, 0].magnitude, vm[:, 1].magnitude) assert_array_equal(vm[:, 2].magnitude, vm[:, 3].magnitude) def test_issue529(): # A combination of NEST Common synapse properties and FromListConnector doesn't work if not have_nest: raise SkipTest import nest sim = pyNN.nest sim.setup() iaf_neuron = sim.native_cell_type('iaf_psc_exp') poisson = sim.native_cell_type('poisson_generator') p1 = sim.Population(10, iaf_neuron(tau_m=20.0, tau_syn_ex=3., tau_syn_in=3.)) p2 = sim.Population(10, iaf_neuron(tau_m=20.0, tau_syn_ex=3., tau_syn_in=3.)) nest.SetStatus(p2.node_collection, {'tau_minus': 20.}) stdp = sim.native_synapse_type("stdp_synapse_hom")(**{ 'lambda': 0.005, 'mu_plus': 0., 'mu_minus': 0., 'alpha': 1.1, 'tau_plus': 20., 'Wmax': 10., }) W = np.random.rand(5) connections = [ (0, 0, W[0]), (0, 1, W[1]), (0, 2, W[2]), (1, 5, W[3]), (6, 1, W[4]), ] ee_connector = sim.FromListConnector(connections, column_names=["weight"]) prj_plastic = sim.Projection( p1, p2, ee_connector, receptor_type='excitatory', synapse_type=stdp) def test_issue662a(): """Setting tau_minus to a random distribution fails...""" if not have_nest: raise SkipTest import nest sim = pyNN.nest sim.setup() p1 = sim.Population(5, sim.SpikeSourcePoisson(rate=100.0)) p2 = sim.Population(10, sim.IF_cond_exp()) syn = sim.STDPMechanism( timing_dependence=sim.SpikePairRule( A_plus=0.2, A_minus=0.1, tau_minus=RandomDistribution('uniform', (20, 40)), tau_plus=RandomDistribution('uniform', (10, 20)) ), weight_dependence=sim.AdditiveWeightDependence(w_min=0.0, w_max=0.01) ) assert_raises(ValueError, sim.Projection, p1, p2, sim.AllToAllConnector(), synapse_type=syn, receptor_type='excitatory') def test_issue662b(): """Setting tau_minus to a random distribution fails...""" if not have_nest: raise SkipTest import nest sim = pyNN.nest sim.setup(min_delay=0.5) p1 = sim.Population(5, sim.SpikeSourcePoisson(rate=100.0)) p2 = sim.Population(10, sim.IF_cond_exp()) syn = sim.STDPMechanism( timing_dependence=sim.SpikePairRule( A_plus=0.2, A_minus=0.1, tau_minus=30, tau_plus=RandomDistribution('uniform', (10, 20)) ), weight_dependence=sim.AdditiveWeightDependence(w_min=0.0, w_max=0.01), weight=0.005 ) connections = sim.Projection(p1, p2, sim.AllToAllConnector(), synapse_type=syn, receptor_type='inhibitory') connections.set(tau_minus=25) # RandomDistribution('uniform', (20,40))) # todo: check this worked assert_raises(ValueError, connections.set, tau_minus=RandomDistribution('uniform', (20, 40))) if __name__ == '__main__': #data = test_random_seeds() test_native_electrode_types() PyNN-0.10.0/test/system/test_neuroml.py000066400000000000000000000020271415343567000200220ustar00rootroot00000000000000from nose.plugins.skip import SkipTest from nose.tools import assert_equal from .scenarios.registry import registry import numpy as np try: import pyNN.neuroml have_neuroml = True except ImportError: have_neuroml = False ''' ### Need to go through each of these and check which tests are appropriate def test_scenarios(): for scenario in registry: if "neuroml" not in scenario.exclude: scenario.description = "{}(neuroml)".format(scenario.__name__) if have_neuroml: yield scenario, pyNN.neuroml else: raise SkipTest''' def test_save_validate_network(): if not have_neuroml: raise SkipTest sim = pyNN.neuroml reference='Test0' sim.setup(reference=reference) spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) neurons = sim.Population(5, sim.IF_cond_exp(e_rev_I=-75)) sim.end() from neuroml.utils import validate_neuroml2 validate_neuroml2('%s.net.nml'%reference) PyNN-0.10.0/test/system/test_neuron.py000066400000000000000000000213341415343567000176510ustar00rootroot00000000000000import os from nose.plugins.skip import SkipTest from .scenarios.registry import registry from nose.tools import assert_equal, assert_almost_equal from numpy.testing import assert_array_equal from pyNN.random import RandomDistribution from pyNN.utility import init_logging import quantities as pq import numpy as np try: import pyNN.neuron from pyNN.neuron.cells import _new_property, NativeCellType from nrnutils import Mechanism, Section, DISTAL have_neuron = True except ImportError: have_neuron = False skip_ci = False if "JENKINS_SKIP_TESTS" in os.environ: skip_ci = os.environ["JENKINS_SKIP_TESTS"] == "1" def test_scenarios(): for scenario in registry: if "neuron" not in scenario.exclude: scenario.description = "{}(neuron)".format(scenario.__name__) if have_neuron: yield scenario, pyNN.neuron else: raise SkipTest def test_ticket168(): """ Error setting firing rate of `SpikeSourcePoisson` after `reset()` in NEURON http://neuralensemble.org/trac/PyNN/ticket/168 """ if not have_neuron: raise SkipTest pynn = pyNN.neuron pynn.setup() cell = pynn.Population(1, pynn.SpikeSourcePoisson(), label="cell") cell[0].rate = 12 pynn.run(10.) pynn.reset() cell[0].rate = 12 pynn.run(10.) assert_almost_equal(pynn.get_current_time(), 10.0, places=11) assert_equal(cell[0]._cell.interval, 1000.0 / 12.0) class SimpleNeuron(object): def __init__(self, **parameters): # define ion channel parameters leak = Mechanism('pas', e=-65, g=parameters['g_leak']) hh = Mechanism('hh', gl=parameters['g_leak'], el=-65, gnabar=parameters['gnabar'], gkbar=parameters['gkbar']) # create cable sections self.soma = Section(L=30, diam=30, mechanisms=[hh]) self.apical = Section(L=600, diam=2, nseg=5, mechanisms=[leak], parent=self.soma, connection_point=DISTAL) self.basilar = Section(L=600, diam=2, nseg=5, mechanisms=[leak], parent=self.soma) self.axon = Section(L=1000, diam=1, nseg=37, mechanisms=[hh]) # synaptic input self.apical.add_synapse('ampa', 'Exp2Syn', e=0.0, tau1=0.1, tau2=5.0) # needed for PyNN self.source_section = self.soma self.source = self.soma(0.5)._ref_v self.parameter_names = ('g_leak', 'gnabar', 'gkbar') self.traces = {} self.recording_time = False def _set_g_leak(self, value): for sec in (self.apical, self.basilar): for seg in sec: seg.pas.g = value for sec in (self.soma, self.axon): for seg in sec: seg.hh.gl = value def _get_g_leak(self): return self.apical(0.5).pas.g g_leak = property(fget=_get_g_leak, fset=_set_g_leak) def _set_gnabar(self, value): for sec in (self.soma, self.axon): for seg in sec: seg.hh.gnabar = value def _get_gnabar(self): return self.soma(0.5).hh.gnabar gnabar = property(fget=_get_gnabar, fset=_set_gnabar) def _set_gkbar(self, value): for sec in (self.soma, self.axon): for seg in sec: seg.hh.gkbar = value def _get_gkbar(self): return self.soma(0.5).hh.gkbar gkbar = property(fget=_get_gkbar, fset=_set_gkbar) def memb_init(self): """needed for PyNN""" for sec in (self.soma, self.axon, self.apical, self.basilar): for seg in sec: seg.v = self.v_init if have_neuron: class SimpleNeuronType(NativeCellType): default_parameters = {'g_leak': 0.0002, 'gkbar': 0.036, 'gnabar': 0.12} default_initial_values = {'v': -65.0} # this is not good - over-ride Population.can_record()? recordable = ['apical(1.0).v', 'soma(0.5).ina'] units = {'apical(1.0).v': 'mV', 'soma(0.5).ina': 'mA/cm**2'} receptor_types = ['apical.ampa'] model = SimpleNeuron def test_electrical_synapse(): raise SkipTest("Skipping test for now as it produces a segmentation fault") if skip_ci: raise SkipTest("Skipping test on CI server as it produces a segmentation fault") p1 = pyNN.neuron.Population(4, pyNN.neuron.standardmodels.cells.HH_cond_exp()) p2 = pyNN.neuron.Population(4, pyNN.neuron.standardmodels.cells.HH_cond_exp()) syn = pyNN.neuron.ElectricalSynapse(weight=1.0) C = pyNN.connectors.FromListConnector(np.array([[0, 0, 1.0], [0, 1, 1.0], [2, 2, 1.0], [3, 2, 1.0]]), column_names=['weight']) prj = pyNN.neuron.Projection(p1, p2, C, syn, source='source_section.gap', receptor_type='source_section.gap') current_source = pyNN.neuron.StepCurrentSource(amplitudes=[1.0], times=[100]) p1[0:1].inject(current_source) p2[2:3].inject(current_source) p1.record('v') p2.record('v') pyNN.neuron.run(200) p1_trace = p1.get_data(('v',)).segments[0].analogsignals[0] p2_trace = p2.get_data(('v',)).segments[0].analogsignals[0] # Check the local forward connection assert p2_trace[:, 0].max() - p2_trace[:, 0].min() > 50 # Check the remote forward connection assert p2_trace[:, 1].max() - p2_trace[:, 1].min() > 50 # Check the local backward connection assert p1_trace[:, 2].max() - p2_trace[:, 2].min() > 50 # Check the remote backward connection assert p1_trace[:, 3].max() - p2_trace[:, 3].min() > 50 def test_record_native_model(): if not have_neuron: raise SkipTest nrn = pyNN.neuron init_logging(logfile=None, debug=True) nrn.setup() parameters = {'g_leak': 0.0003} p1 = nrn.Population(10, SimpleNeuronType(**parameters)) print(p1.get('g_leak')) p1.rset('gnabar', RandomDistribution('uniform', low=0.10, high=0.14)) print(p1.get('gnabar')) p1.initialize(v=-63.0) current_source = nrn.StepCurrentSource(times=[50.0, 110.0, 150.0, 210.0], amplitudes=[0.4, 0.6, -0.2, 0.2]) p1.inject(current_source) p2 = nrn.Population(1, nrn.SpikeSourcePoisson(rate=100.0)) p1.record(['apical(1.0).v', 'soma(0.5).ina']) connector = nrn.AllToAllConnector() syn = nrn.StaticSynapse(weight=0.1) prj_alpha = nrn.Projection(p2, p1, connector, syn, receptor_type='apical.ampa') nrn.run(250.0) data = p1.get_data().segments[0].analogsignals assert_equal(len(data), 2) # one array per variable names = set(sig.name for sig in data) assert_equal(names, set(('apical(1.0).v', 'soma(0.5).ina'))) apical_v = [sig for sig in data if sig.name == 'apical(1.0).v'][0] soma_i = [sig for sig in data if sig.name == 'soma(0.5).ina'][0] assert_equal(apical_v.sampling_rate, 10.0 * pq.kHz) assert_equal(apical_v.units, pq.mV) assert_equal(soma_i.units, pq.mA / pq.cm**2) assert_equal(apical_v.t_start, 0.0 * pq.ms) # would prefer if it were 250.0, but this is a fundamental Neo issue assert_equal(apical_v.t_stop, 250.1 * pq.ms) assert_equal(apical_v.shape, (2501, 10)) return data def test_tsodyks_markram_synapse(): if not have_neuron: raise SkipTest sim = pyNN.neuron sim.setup() spike_source = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) neurons = sim.Population(5, sim.IF_cond_exp( e_rev_I=-75, tau_syn_I=np.arange(0.2, 0.7, 0.1))) synapse_type = sim.TsodyksMarkramSynapse(U=0.04, tau_rec=100.0, tau_facil=1000.0, weight=0.01, delay=0.5) connector = sim.AllToAllConnector() prj = sim.Projection(spike_source, neurons, connector, receptor_type='inhibitory', synapse_type=synapse_type) neurons.record('gsyn_inh') sim.run(100.0) tau_psc = np.array([c.weight_adjuster.tau_syn for c in prj.connections]) assert_array_equal(tau_psc, np.arange(0.2, 0.7, 0.1)) def test_artificial_cells(): if not have_neuron: raise SkipTest sim = pyNN.neuron sim.setup() input = sim.Population(1, sim.SpikeSourceArray(spike_times=np.arange(10, 100, 10))) p1 = sim.Population(3, sim.IntFire1(tau=10, refrac=2)) p2 = sim.Population(3, sim.IntFire2()) p3 = sim.Population(3, sim.IntFire4()) projections = [] for p in (p1, p2, p3): projections.append( sim.Projection(input, p, sim.AllToAllConnector(), sim.StaticSynapse(weight=0.1, delay=0.5), receptor_type="default") ) p.record('m') sim.run(100.0) PyNN-0.10.0/test/system/test_serialization.py000066400000000000000000000040461415343567000212210ustar00rootroot00000000000000 from nose.plugins.skip import SkipTest from numpy.testing import assert_array_almost_equal from pyNN.random import RandomDistribution as RD from pyNN.network import Network from pyNN.serialization import export_to_sonata, import_from_sonata, asciify try: import h5py HAVE_H5PY = True except ImportError: HAVE_H5PY = False try: import pyNN.nest as sim HAVE_NEST = True except ImportError: HAVE_NEST = False def test(): if not HAVE_H5PY and HAVE_NEST: raise SkipTest sim.setup() p1 = sim.Population(10, sim.IF_cond_exp( v_rest=-65, tau_m=lambda i: 10 + 0.1*i, cm=RD('normal', (0.5, 0.05))), label="population_one") p2 = sim.Population(20, sim.IF_curr_alpha( v_rest=-64, tau_m=lambda i: 11 + 0.1*i), label="population_two") prj = sim.Projection(p1, p2, sim.FixedProbabilityConnector(p_connect=0.5), synapse_type=sim.StaticSynapse(weight=RD('uniform', [0.0, 0.1]), delay=0.5), receptor_type='excitatory') net = Network(p1, p2, prj) export_to_sonata(net, "tmp_serialization_test", overwrite=True) net2 = import_from_sonata("tmp_serialization_test/circuit_config.json", sim) for orig_population in net.populations: imp_population = net2.get_component(orig_population.label) assert orig_population.size == imp_population.size for name in orig_population.celltype.default_parameters: assert_array_almost_equal(orig_population.get(name), imp_population.get(name), 12) w1 = prj.get('weight', format='array') prj2 = net2.get_component(asciify(prj.label).decode('utf-8') + "-0") w2 = prj2.get('weight', format='array') assert_array_almost_equal(w1, w2, 12) if __name__ == "__main__": test() PyNN-0.10.0/test/unittests/000077500000000000000000000000001415343567000154455ustar00rootroot00000000000000PyNN-0.10.0/test/unittests/__init__.py000066400000000000000000000000001415343567000175440ustar00rootroot00000000000000PyNN-0.10.0/test/unittests/mocks.py000066400000000000000000000037231415343567000171400ustar00rootroot00000000000000""" Mock classes for unit tests :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from pyNN import random import numpy as np class MockRNG(random.WrappedRNG): rng = None def __init__(self, start=0.0, delta=1, parallel_safe=True): random.WrappedRNG.__init__(self, parallel_safe=parallel_safe) self.start = start self.delta = delta def _next(self, distribution, n, parameters): if distribution == "uniform_int": return self._next_int(n, parameters) elif distribution == "binomial": return self._next_binomial(n, parameters) s = self.start self.start += n * self.delta return s + self.delta * np.arange(n) def _next_int(self, n, parameters): low, high = parameters["low"], parameters["high"] s = int(self.start) self.start += n * self.delta x = s + self.delta * np.arange(n) return x % (high - low) + low def _next_binomial(self, n, parameters): return self._next_int(n, {"low": 0, "high": parameters["n"]}) def permutation(self, arr): return arr[::-1] class MockRNG2(random.WrappedRNG): rng = None def __init__(self, numbers, parallel_safe=True): random.WrappedRNG.__init__(self, parallel_safe=parallel_safe) self.numbers = numbers self.i = 0 def _next(self, distribution, n, parameters): x = self.numbers[self.i:self.i + n] self.i += n return x def permutation(self, arr): return arr[::-1] class MockRNG3(random.WrappedRNG): """ returns [1, 0, 0, 0,..] """ rng = None def __init__(self, parallel_safe=True): random.WrappedRNG.__init__(self, parallel_safe=parallel_safe) def _next(self, distribution, n, parameters): x = np.zeros(n) x.dtype = int x[0] = 1 return x def permutation(self, arr): return arr[::-1] PyNN-0.10.0/test/unittests/test_assembly.py000066400000000000000000000536661415343567000207150ustar00rootroot00000000000000""" Tests of the common implementation of the Assembly class, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import numpy as np import sys import quantities as pq from numpy.testing import assert_array_equal, assert_array_almost_equal try: from unittest.mock import Mock, patch except ImportError: from mock import Mock, patch from .mocks import MockRNG import pyNN.mock as sim from pyNN.parameters import Sequence def setUp(): pass def tearDown(): pass class AssemblyTest(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(**extra) def tearDown(self, sim=sim): sim.end() def test_create_with_zero_populations(self, sim=sim): a = sim.Assembly() self.assertEqual(a.populations, []) self.assertIsInstance(a.label, str) def test_create_with_one_population(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p) self.assertEqual(a.populations, [p]) self.assertIsInstance(a.label, str) def test_create_with_two_populations(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") self.assertEqual(a.populations, [p1, p2]) self.assertEqual(a.label, "test") def test_create_with_non_population_should_raise_Exception(self, sim=sim): self.assertRaises(TypeError, sim.Assembly, [1, 2, 3]) def test_size_property(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") self.assertEqual(a.size, p1.size + p2.size) def test_positions_property(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") assert_array_equal(a.positions, np.concatenate((p1.positions, p2.positions), axis=1)) def test__len__(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") self.assertEqual(len(a), len(p1) + len(p2)) def test_local_cells(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") assert_array_equal(a.local_cells, np.append(p1.local_cells, p2.local_cells)) def test_all_cells(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") assert_array_equal(a.all_cells, np.append(p1.all_cells, p2.all_cells)) def test_iter(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") assembly_ids = [id for id in a] def test__add__population(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a1 = sim.Assembly(p1) self.assertEqual(a1.populations, [p1]) a2 = a1 + p2 self.assertEqual(a1.populations, [p1]) self.assertEqual(a2.populations, [p1, p2]) def test__add__assembly(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a1 = sim.Assembly(p1, p2) a2 = sim.Assembly(p2, p3) a3 = a1 + a2 self.assertEqual(a3.populations, [p1, p2, p3]) # or do we want [p1, p2, p3]? def test_add_inplace_population(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1) a += p2 self.assertEqual(a.populations, [p1, p2]) def test_add_inplace_assembly(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a1 = sim.Assembly(p1, p2) a2 = sim.Assembly(p2, p3) a1 += a2 self.assertEqual(a1.populations, [p1, p2, p3]) def test_add_invalid_object(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2) self.assertRaises(TypeError, a.__add__, 42) self.assertRaises(TypeError, a.__iadd__, 42) def test_initialize(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2) v_init = -54.3 a.initialize(v=v_init) assert_array_equal(p2.initial_values['v'].evaluate(simplify=True), v_init) def test_describe(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2) self.assertIsInstance(a.describe(), str) self.assertIsInstance(a.describe(template=None), dict) def test_get_population(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p1.label = "pop1" p2 = sim.Population(11, sim.IF_cond_exp()) p2.label = "pop2" a = sim.Assembly(p1, p2) self.assertEqual(a.get_population("pop1"), p1) self.assertEqual(a.get_population("pop2"), p2) self.assertRaises(KeyError, a.get_population, "foo") def test_all_cells(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, p3) self.assertEqual(a.all_cells.size, p1.all_cells.size + p2.all_cells.size + p3.all_cells.size) self.assertEqual(a.all_cells[0], p1.all_cells[0]) self.assertEqual(a.all_cells[-1], p3.all_cells[-1]) assert_array_equal(a.all_cells, np.append(p1.all_cells, (p2.all_cells, p3.all_cells))) def test_local_cells(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, p3) self.assertEqual(a.local_cells.size, p1.local_cells.size + p2.local_cells.size + p3.local_cells.size) self.assertEqual(a.local_cells[0], p1.local_cells[0]) self.assertEqual(a.local_cells[-1], p3.local_cells[-1]) assert_array_equal(a.local_cells, np.append( p1.local_cells, (p2.local_cells, p3.local_cells))) def test_mask_local(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, p3) self.assertEqual(a._mask_local.size, p1._mask_local.size + p2._mask_local.size + p3._mask_local.size) self.assertEqual(a._mask_local[0], p1._mask_local[0]) self.assertEqual(a._mask_local[-1], p3._mask_local[-1]) assert_array_equal(a._mask_local, np.append( p1._mask_local, (p2._mask_local, p3._mask_local))) assert_array_equal(a.local_cells, a.all_cells[a._mask_local]) def test_save_positions(self, sim=sim): import os p1 = sim.Population(2, sim.IF_cond_exp()) p2 = sim.Population(2, sim.IF_cond_exp()) p1.positions = np.arange(0, 6).reshape((2, 3)).T p2.positions = np.arange(6, 12).reshape((2, 3)).T a = sim.Assembly(p1, p2, label="test") output_file = Mock() a.save_positions(output_file) assert_array_equal(output_file.write.call_args[0][0], np.array([[0, 0, 1, 2], [1, 3, 4, 5], [2, 6, 7, 8], [3, 9, 10, 11]])) self.assertEqual(output_file.write.call_args[0][1], {'assembly': a.label}) def test_repr(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, p3) self.assertIsInstance(repr(a), str) def test_ids_should_not_be_counted_twice(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) pv1 = p[0:5] a1 = sim.Assembly(p, pv1) self.assertEqual(a1.size, p.size) #a2 = sim.Assembly(pv1, p) #self.assertEqual(a2.size, p.size) #pv2 = p[3:7] #a3 = sim.Assembly(pv1, pv2) #self.assertEqual(a3.size, 7) def test_all_iterator(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p1, p2, p3) assert hasattr(a.all(), "next") or hasattr(a.all(), "__next__") # 2nd form is for Py3 ids = list(a.all()) self.assertEqual(ids, p1.all_cells.tolist() + p2.all_cells.tolist() + p3.all_cells.tolist()) def test__homogeneous_synapses(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) a1 = sim.Assembly(p1, p2) self.assertTrue(a1._homogeneous_synapses) def test__non_homogeneous_synapses(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(3, sim.IF_curr_exp()) a2 = sim.Assembly(p1, p3) self.assertFalse(a2._homogeneous_synapses) def test_conductance_based(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) a1 = sim.Assembly(p1, p2) self.assertTrue(a1.conductance_based) def test_not_conductance_based(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p3 = sim.Population(3, sim.IF_curr_exp()) a2 = sim.Assembly(p1, p3) self.assertFalse(a2.conductance_based) def test_first_and_last_id(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) self.assertEqual(a.first_id, p1[0]) self.assertEqual(a.last_id, p3[-1]) def test_id_to_index(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) self.assertEqual(a.id_to_index(p3[0]), 0) self.assertEqual(a.id_to_index(p1[0]), 3) self.assertEqual(a.id_to_index(p2[0]), 14) assert_array_equal(a.id_to_index([p1[0], p2[0], p3[0]]), [3, 14, 0]) def test_id_to_index_with_nonexistent_id(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) self.assertRaises(IndexError, a.id_to_index, p3.last_id + 1) def test_getitem_int(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) self.assertEqual(a[0], p3[0]) self.assertEqual(a[3], p1[0]) self.assertEqual(a[14], p2[0]) def test_getitem_slice(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) a1 = a[0:3] self.assertIsInstance(a1, sim.Assembly) self.assertEqual(len(a1.populations), 1) assert_array_equal(a1.populations[0].all_cells, p3[:].all_cells) a2 = a[2:8] self.assertEqual(len(a2.populations), 2) assert_array_equal(a2.populations[0].all_cells, p3[2:].all_cells) assert_array_equal(a2.populations[1].all_cells, p1[:5].all_cells) def test_getitem_array(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) a1 = a[3, 5, 6, 10] self.assertIsInstance(a1, sim.Assembly) self.assertEqual(len(a1.populations), 1) assert_array_equal(a1.populations[0].all_cells, [p1[0], p1[2], p1[3], p1[7]]) a2 = a[17, 2, 10, 11, 19] self.assertEqual(len(a2.populations), 3) assert_array_equal(a2.populations[0].all_cells, p3[2:].all_cells) assert_array_equal(a2.populations[1].all_cells, p1[7:9].all_cells) assert_array_equal(a2.populations[2].all_cells, p2[3, 5].all_cells) def test_sample(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.IF_curr_exp()) a = sim.Assembly(p3, p1, p2) a1 = a.sample(10, rng=MockRNG()) # MockRNG.permutation reverses the order self.assertEqual(len(a1.populations), 2) assert_array_equal(a1.populations[0].all_cells, sorted(p1[11:6:-1])) assert_array_equal(a1.populations[1].all_cells, sorted(p2[6::-1])) def test_get_data_with_gather(self, sim=sim): t1 = 12.3 t2 = 13.4 t3 = 14.5 p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(6, sim.IF_cond_alpha()) p3 = sim.Population(3, sim.EIF_cond_exp_isfa_ista()) a = sim.Assembly(p3, p1, p2) a.record('v') sim.run(t1) # what if we call p.record between two run statements? # would be nice to get an AnalogSignal with a non-zero t_start # but then need to make sure we get the right initial value sim.run(t2) sim.reset() a.record('spikes') p3.record('w') sim.run(t3) data = a.get_data(gather=True) self.assertEqual(len(data.segments), 2) seg0 = data.segments[0] self.assertEqual(len(seg0.analogsignals), 1) v = seg0.filter(name='v')[0] self.assertEqual(v.name, 'v') num_points = int(round((t1 + t2) / sim.get_time_step())) + 1 self.assertEqual(v.shape, (num_points, a.size)) self.assertEqual(v.t_start, 0.0 * pq.ms) self.assertEqual(v.units, pq.mV) self.assertEqual(v.sampling_period, 0.1 * pq.ms) self.assertEqual(len(seg0.spiketrains), 0) seg1 = data.segments[1] self.assertEqual(len(seg1.analogsignals), 2) w = seg1.filter(name='w')[0] self.assertEqual(w.name, 'w') num_points = int(round(t3 / sim.get_time_step())) + 1 self.assertEqual(w.shape, (num_points, p3.size)) self.assertEqual(v.t_start, 0.0) self.assertEqual(len(seg1.spiketrains), a.size) # assert_array_equal(seg1.spiketrains[7], # np.array([a.first_id+7, a.first_id+7+5]) % t3) def test_printSpikes(self, sim=sim): # TODO: implement assert_deprecated p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record('spikes') sim.run(10.0) a.write_data = Mock() a.printSpikes("foo.txt") a.write_data.assert_called_with('foo.txt', 'spikes', True) def test_getSpikes(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record('spikes') sim.run(10.0) a.get_data = Mock() a.getSpikes() a.get_data.assert_called_with('spikes', True) def test_print_v(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record_v() sim.run(10.0) a.write_data = Mock() a.print_v("foo.txt") a.write_data.assert_called_with('foo.txt', 'v', True) def test_get_v(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record_v() sim.run(10.0) a.get_data = Mock() a.get_v() a.get_data.assert_called_with('v', True) def test_print_gsyn(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record_gsyn() sim.run(10.0) a.write_data = Mock() a.print_gsyn("foo.txt") a.write_data.assert_called_with('foo.txt', ['gsyn_exc', 'gsyn_inh'], True) def test_get_gsyn(self, sim=sim): p1 = sim.Population(11, sim.IF_cond_exp()) p2 = sim.Population(11, sim.IF_cond_exp()) a = sim.Assembly(p1, p2, label="test") a.record_gsyn() sim.run(10.0) a.get_data = Mock() a.get_gsyn() a.get_data.assert_called_with(['gsyn_exc', 'gsyn_inh'], True) def test_get_multiple_homogeneous_params_with_gather(self, sim=sim): p1 = sim.Population(4, sim.IF_cond_exp( **{'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7})) p2 = sim.Population(4, sim.IF_curr_exp( **{'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7})) a = p1 + p2 tau_syn_E, tau_m = a.get(('tau_syn_E', 'tau_m'), gather=True) self.assertIsInstance(tau_syn_E, float) self.assertEqual(tau_syn_E, 0.987) self.assertAlmostEqual(tau_m, 12.3) def test_get_single_param_with_gather(self, sim=sim): p1 = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=0.987, tau_syn_I=0.7)) p2 = sim.Population(3, sim.IF_cond_exp(tau_m=23.4, tau_syn_E=0.987, tau_syn_I=0.7)) a = p1 + p2 tau_syn_E = a.get('tau_syn_E', gather=True) self.assertAlmostEqual(tau_syn_E, 0.987, places=6) tau_m = a.get('tau_m', gather=True) assert_array_equal(tau_m, np.array([12.3, 12.3, 12.3, 12.3, 23.4, 23.4, 23.4])) def test_get_multiple_inhomogeneous_params_with_gather(self, sim=sim): p1 = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=[0.987, 0.988, 0.989, 0.990], tau_syn_I=lambda i: 0.5 + 0.1 * i)) p2 = sim.Population(3, sim.EIF_cond_exp_isfa_ista(tau_m=12.3, tau_syn_E=[0.991, 0.992, 0.993], tau_syn_I=lambda i: 0.5 + 0.1 * (i + 4))) a = p1 + p2 tau_syn_E, tau_m, tau_syn_I = a.get(('tau_syn_E', 'tau_m', 'tau_syn_I'), gather=True) self.assertIsInstance(tau_m, float) self.assertIsInstance(tau_syn_E, np.ndarray) assert_array_equal(tau_syn_E, np.array( [0.987, 0.988, 0.989, 0.990, 0.991, 0.992, 0.993])) self.assertAlmostEqual(tau_m, 12.3) assert_array_almost_equal(tau_syn_I, np.array( [0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.1]), decimal=12) def test_get_multiple_params_no_gather(self, sim=sim): sim.simulator.state.num_processes = 2 sim.simulator.state.mpi_rank = 1 p1 = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=[0.987, 0.988, 0.989, 0.990], i_offset=lambda i: -0.2 * i)) p2 = sim.Population(3, sim.IF_curr_exp(tau_m=12.3, tau_syn_E=[0.991, 0.992, 0.993], i_offset=lambda i: -0.2 * (i + 4))) a = p1 + p2 tau_syn_E, tau_m, i_offset = a.get(('tau_syn_E', 'tau_m', 'i_offset'), gather=False) self.assertIsInstance(tau_m, float) self.assertIsInstance(tau_syn_E, np.ndarray) assert_array_equal(tau_syn_E, np.array([0.988, 0.990, 0.992])) self.assertEqual(tau_m, 12.3) assert_array_almost_equal(i_offset, np.array([-0.2, -0.6, -1.0, ]), decimal=12) sim.simulator.state.num_processes = 1 sim.simulator.state.mpi_rank = 0 def test_get_sequence_param(self, sim=sim): p1 = sim.Population(3, sim.SpikeSourceArray(spike_times=[Sequence([1, 2, 3, 4]), Sequence([2, 3, 4, 5]), Sequence([3, 4, 5, 6])])) p2 = sim.Population(2, sim.SpikeSourceArray(spike_times=[Sequence([4, 5, 6, 7]), Sequence([5, 6, 7, 8])])) a = p1 + p2 spike_times = a.get('spike_times') self.assertEqual(spike_times.size, 5) assert_array_equal(spike_times[3], Sequence([4, 5, 6, 7])) def test_inject(self, sim=sim): p1 = sim.Population(3, sim.IF_curr_alpha()) p2 = sim.Population(5, sim.IF_cond_exp()) a = p1 + p2 cs = Mock() a.inject(cs) meth, args, kwargs = cs.method_calls[0] self.assertEqual(meth, "inject_into") self.assertEqual(args, (p1,)) meth, args, kwargs = cs.method_calls[1] self.assertEqual(meth, "inject_into") self.assertEqual(args, (p2,)) def test_mean_spike_count(self, sim=sim): p1 = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p2 = sim.Population(37, sim.IF_cond_alpha()) a = p1 + p2 p1.record('spikes') p2.record('spikes') # a.record('spikes') sim.run(100.0) # mock backend always produces two spikes per population self.assertEqual(a.mean_spike_count(), 2.0) if __name__ == '__main__': unittest.main() PyNN-0.10.0/test/unittests/test_brian.py000066400000000000000000000024311415343567000201510ustar00rootroot00000000000000try: import pyNN.brian as sim import brian except ImportError: brian = False import unittest import numpy as np from numpy.testing import assert_array_equal class MockConnector(object): def connect(self, projection): pass @unittest.skipUnless(brian, "Requires Brian") class TestProjection(unittest.TestCase): def setUp(self): sim.setup() self.syn = sim.StaticSynapse(weight=0.123, delay=0.5) def test_partitioning(self): p1 = sim.Population(5, sim.IF_cond_exp()) p2 = sim.Population(7, sim.IF_cond_exp()) a = p1 + p2[1:4] # [0 2 3 4 5][x 1 2 3 x x x] prj = sim.Projection(a, a, MockConnector(), synapse_type=self.syn) presynaptic_indices = np.array([0, 3, 4, 6, 7]) partitions = prj._partition(presynaptic_indices) self.assertEqual(len(partitions), 2) assert_array_equal(partitions[0], np.array([0, 3, 4])) assert_array_equal(partitions[1], np.array([2, 3])) # [0 1 2 3 4][x 1 2 3 x] self.assertEqual(prj._localize_index(0), (0, 0)) self.assertEqual(prj._localize_index(3), (0, 3)) self.assertEqual(prj._localize_index(5), (1, 1)) self.assertEqual(prj._localize_index(7), (1, 3)) if __name__ == '__main__': unittest.main() PyNN-0.10.0/test/unittests/test_connectors_parallel.py000066400000000000000000001345141415343567000231170ustar00rootroot00000000000000""" Tests of the Connector classes, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest from pyNN import connectors, random, errors, space, recording import numpy as np import os import sys from numpy.testing import assert_array_equal, assert_array_almost_equal from .mocks import MockRNG, MockRNG2 import pyNN.mock as sim orig_mpi_get_config = random.get_mpi_config def setUp(): random.get_mpi_config = lambda: (0, 2) def tearDown(): random.get_mpi_config = orig_mpi_get_config class TestOneToOneConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(num_processes=2, rank=0, **extra) self.p1 = sim.Population(5, sim.IF_cond_exp()) self.p2 = sim.Population(5, sim.HH_cond_exp()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def tearDown(self, sim=sim): sim.end() def test_connect_with_scalar_weights_and_delays(self, sim=sim): C = connectors.OneToOneConnector(safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(1, 1, 5.0, 0.5), (3, 3, 5.0, 0.5)]) def test_connect_with_random_weights(self, sim=sim): rd = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(delta=1.0)) syn = sim.StaticSynapse(weight=rd, delay=0.5) C = connectors.OneToOneConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(1, 1, 1.0, 0.5), (3, 3, 3.0, 0.5)]) class TestAllToAllConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(num_processes=2, rank=1, min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p1._mask_local, np.array([0, 1, 0, 1], dtype=bool)) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_connect_with_scalar_weights_and_delays(self, sim=sim): C = connectors.AllToAllConnector(safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 5.0, 0.5), (1, 1, 5.0, 0.5), (2, 1, 5.0, 0.5), (3, 1, 5.0, 0.5), (0, 3, 5.0, 0.5), (1, 3, 5.0, 0.5), (2, 3, 5.0, 0.5), (3, 3, 5.0, 0.5)]) nan = np.nan assert_array_equal(prj.get('weight', format='array', gather=False), np.array([[nan, 5.0, nan, 5.0, nan], [nan, 5.0, nan, 5.0, nan], [nan, 5.0, nan, 5.0, nan], [nan, 5.0, nan, 5.0, nan]])) def test_connect_with_array_weights(self, sim=sim): C = connectors.AllToAllConnector(safe=False) syn = sim.StaticSynapse(weight=np.arange(0.0, 2.0, 0.1).reshape(4, 5), delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) assert_array_almost_equal( # use gather False because we are faking the MPI np.array(prj.get(["weight", "delay"], format='list', gather=False)), np.array([(0, 1, 0.1, 0.5), (1, 1, 0.6, 0.5), (2, 1, 1.1, 0.5), (3, 1, 1.6, 0.5), (0, 3, 0.3, 0.5), (1, 3, 0.8, 0.5), (2, 3, 1.3, 0.5), (3, 3, 1.8, 0.5)])) nan = np.nan assert_array_almost_equal(prj.get('weight', format='array', gather=False), np.array([[nan, 0.1, nan, 0.3, nan], [nan, 0.6, nan, 0.8, nan], [nan, 1.1, nan, 1.3, nan], [nan, 1.6, nan, 1.8, nan]]), 9) def test_connect_with_random_weights_parallel_safe(self, sim=sim): rd = random.RandomDistribution( 'uniform', (0, 1), rng=MockRNG(delta=1.0, parallel_safe=True)) syn = sim.StaticSynapse(weight=rd, delay=0.5) C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) # note that the outer loop is over the post-synaptic cells, the inner loop over the pre-synaptic self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 4.0, 0.5), (1, 1, 5.0, 0.5), (2, 1, 6.0, 0.5), (3, 1, 7.0, 0.5), (0, 3, 12.0, 0.5), (1, 3, 13.0, 0.5), (2, 3, 14.0, 0.5), (3, 3, 15.0, 0.5)]) nan = np.nan assert_array_almost_equal(prj.get('weight', format='array', gather=False), np.array([[nan, 4.0, nan, 12.0, nan], [nan, 5.0, nan, 13.0, nan], [nan, 6.0, nan, 14.0, nan], [nan, 7.0, nan, 15.0, nan]]), 9) def test_connect_with_distance_dependent_weights(self, sim=sim): d_expr = "d+100" syn = sim.StaticSynapse(weight=d_expr, delay=0.5) C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 101.0, 0.5), (1, 1, 100.0, 0.5), (2, 1, 101.0, 0.5), (3, 1, 102.0, 0.5), (0, 3, 103.0, 0.5), (1, 3, 102.0, 0.5), (2, 3, 101.0, 0.5), (3, 3, 100.0, 0.5)]) def test_connect_with_distance_dependent_weights_and_delays(self, sim=sim): syn = sim.StaticSynapse(weight="d+100", delay="0.2+2*d") C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 101.0, 2.2), (1, 1, 100.0, 0.2), (2, 1, 101.0, 2.2), (3, 1, 102.0, 4.2), (0, 3, 103.0, 6.2), (1, 3, 102.0, 4.2), (2, 3, 101.0, 2.2), (3, 3, 100.0, 0.2)]) def test_connect_with_delays_None(self, sim=sim): syn = sim.StaticSynapse(weight=0.1, delay=None) C = connectors.AllToAllConnector() assert C.safe assert C.allow_self_connections prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False)[ 0][3], prj._simulator.state.min_delay) @unittest.skip('skipping this test until refactoring of delay checks is complete') def test_connect_with_delays_too_small(self, sim=sim): C = connectors.AllToAllConnector(safe=True) syn = sim.StaticSynapse(weight=0.1, delay=0.0) self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) @unittest.skip('skipping this tests until refactoring of delay checks is complete') def test_connect_with_list_delays_too_small(self, sim=sim): delays = np.ones((self.p1.size, self.p2.size), float) delays[2, 3] = sim.Projection._simulator.state.min_delay - 0.01 syn = sim.StaticSynapse(weight=0.1, delay=delays) C = connectors.AllToAllConnector() self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) class TestFixedProbabilityConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_connect_with_default_args(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.85, rng=MockRNG(delta=0.1, parallel_safe=True)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the # first 9 are created (0,0), (1,0), (2,0), (3,0), (0,1), (1,1), (2,1), (3,1), (0,2) # of these, (0,1), (1,1), (2,1), (3,1) are created on this node self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123)]) def test_connect_with_default_args_again(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.5, rng=MockRNG2(1 - np.array([1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1]), parallel_safe=True)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the following # are created (0,0), (3,0), (3,1), (0,2), (1,2), (0,3), (2,3), (0,4), (1,4), (3,4) # of these, (3,1), (0,3), (2,3) are created on this node # (note that the outer loop is over post-synaptic cells (columns), the inner loop over pre-synaptic (rows)) nan = np.nan assert_array_almost_equal(prj.get('delay', format='array', gather=False), np.array([[nan, nan, nan, 0.123, nan], [nan, nan, nan, nan, nan], [nan, nan, nan, 0.123, nan], [nan, 0.123, nan, nan, nan]]), 9) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(3, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (2, 3, 0.0, 0.123)]) def test_connect_with_probability_1(self, sim=sim): # see https://github.com/NeuralEnsemble/PyNN/issues/309 C = connectors.FixedProbabilityConnector(p_connect=1, rng=MockRNG(delta=0.01, parallel_safe=True)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 connections, only some of which are created on this node self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123) ]) def test_connect_with_weight_function(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.85, rng=MockRNG(delta=0.1)) syn = sim.StaticSynapse(weight=lambda d: 0.1 * d) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.1, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.1, 0.123), (3, 1, 0.2, 0.123)]) def test_connect_with_random_delays_parallel_safe(self, sim=sim): rd = random.RandomDistribution('uniform', low=0.1, high=1.1, rng=MockRNG(start=1.0, delta=0.2, parallel_safe=True)) syn = sim.StaticSynapse(delay=rd) C = connectors.FixedProbabilityConnector(p_connect=0.5, rng=MockRNG2(1 - np.array([1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1]), parallel_safe=True)) prj = sim.Projection(self.p1, self.p2, C, syn) nan = np.nan assert_array_almost_equal(prj.get('delay', format='array', gather=False), np.array([[nan, nan, nan, 2.0, nan], [nan, nan, nan, nan, nan], [nan, nan, nan, 2.2, nan], [nan, 1.4, nan, nan, nan]]), 9) # def test_connect_with_random_delays_parallel_unsafe(self, sim=sim): # rd = random.RandomDistribution('uniform', [0.1, 1.1], rng=MockRNG(start=1.0, delta=0.2, parallel_safe=False)) # syn = sim.StaticSynapse(delay=rd) # C = connectors.FixedProbabilityConnector(p_connect=0.5, # rng=MockRNG2(1 - np.array([1, 0, 0, 1, # 0, 0, 0, 1, # 1, 1, 0, 0, # 1, 0, 1, 0, # 1, 1, 0, 1]), # parallel_safe=False)) # prj = sim.Projection(self.p1, self.p2, C, syn) # nan = np.nan # assert_array_almost_equal(prj.get('delay', format='array', gather=False), # np.array([[nan, nan, nan, 1.2, nan], # [nan, nan, nan, nan, nan], # [nan, nan, nan, 1.4, nan], # [nan, 1.0, nan, nan, nan]]), # 9) class TestDistanceDependentProbabilityConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_connect_with_default_args(self, sim=sim): C = connectors.DistanceDependentProbabilityConnector(d_expression="d<1.5", rng=MockRNG(delta=0.01)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Only those with a sufficiently small distance # are created self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123)]) class TestFromListConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_connect_with_valid_list(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), # local (2, 2, 0.4, 0.13), (0, 1, 0.5, 0.14), # local ] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14), (2, 3, 0.3, 0.12)]) def test_connect_with_out_of_range_index(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), # local (5, 1, 0.4, 0.13), # NON-EXISTENT (0, 1, 0.5, 0.14), # local ] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) def test_with_plastic_synapse(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1, 100, 400), (3, 0, 0.2, 0.11, 101, 500), (2, 3, 0.3, 0.12, 102, 600), # local (2, 2, 0.4, 0.13, 103, 700), (0, 1, 0.5, 0.14, 104, 800), # local ] C = connectors.FromListConnector(connection_list, column_names=[ "weight", "delay", "U", "tau_rec"]) syn = sim.TsodyksMarkramSynapse(U=99, tau_facil=88.8) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay", "tau_facil", "tau_rec", "U"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14, 88.8, 800.0, 104.0), (2, 3, 0.3, 0.12, 88.8, 600.0, 102.0)]) def test_with_stdp_synapse(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1, 10.0, 0.4), (3, 0, 0.2, 0.11, 10.1, 0.5), (2, 3, 0.3, 0.12, 10.2, 0.6), # local (2, 2, 0.4, 0.13, 10.3, 0.7), (0, 1, 0.5, 0.14, 10.4, 0.8), # local ] C = connectors.FromListConnector(connection_list, column_names=[ "weight", "delay", "tau_plus", "w_max"]) syn = sim.STDPMechanism(timing_dependence=sim.SpikePairRule(tau_plus=12.3, tau_minus=33.3), weight_dependence=sim.MultiplicativeWeightDependence(w_max=1.11), weight=0.321, delay=0.2) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay", "tau_plus", "tau_minus", "w_max"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14, 10.4, 33.3, 0.8), (2, 3, 0.3, 0.12, 10.2, 33.3, 0.6)]) class TestFromFileConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) self.connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), # local (2, 2, 0.4, 0.13), (0, 1, 0.5, 0.14), # local ] def tearDown(self, sim=sim): for path in ("test.connections", "test.connections.1", "test.connections.2"): if os.path.exists(path): try: os.remove(path) except PermissionError: pass def test_connect_with_standard_text_file_not_distributed(self, sim=sim): np.savetxt("test.connections", self.connection_list) C = connectors.FromFileConnector("test.connections", distributed=False) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14), (2, 3, 0.3, 0.12)]) def test_connect_with_standard_text_file_distributed(self, sim=sim): local_connection_list = [c for c in self.connection_list if c[1] % 2 == 1] np.savetxt("test.connections.1", local_connection_list) C = connectors.FromFileConnector("test.connections", distributed=True) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14), (2, 3, 0.3, 0.12)]) def test_with_plastic_synapses_not_distributed(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1, 100, 100), (3, 0, 0.2, 0.11, 110, 99), (2, 3, 0.3, 0.12, 120, 98), # local (2, 2, 0.4, 0.13, 130, 97), (0, 1, 0.5, 0.14, 140, 96), # local ] file = recording.files.StandardTextFile("test.connections.2", mode='wb') file.write(connection_list, {"columns": ["i", "j", "weight", "delay", "U", "tau_rec"]}) C = connectors.FromFileConnector("test.connections.2", distributed=False) syn = sim.TsodyksMarkramSynapse(tau_facil=88.8) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay", "U", "tau_rec", "tau_facil"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.5, 0.14, 140.0, 96.0, 88.8), (2, 3, 0.3, 0.12, 120.0, 98.0, 88.8)]) class TestFixedNumberPostConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_with_n_smaller_than_population_size(self, sim=sim): C = connectors.FixedNumberPostConnector(n=3, rng=MockRNG(delta=1)) syn = sim.StaticSynapse(weight="0.5*d") prj = sim.Projection(self.p1, self.p2, C, syn) # MockRNG.permutation(arr) returns the reverse of arr, so each pre neuron will connect to neurons 4, 3, 2 # however, only neuron 3 is on the "local" (fake MPI) node self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 3, 1.5, 0.123), (1, 3, 1.0, 0.123), (2, 3, 0.5, 0.123), (3, 3, 0.0, 0.123)]) def test_with_n_larger_than_population_size(self, sim=sim): C = connectors.FixedNumberPostConnector(n=7, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # each pre neuron will connect to all post neurons (population size 5 is less than n), then to 4, 3 (MockRNG.permutation) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (3, 3, 0.0, 0.123)]) def test_with_n_larger_than_population_size_no_self_connections(self, sim=sim): C = connectors.FixedNumberPostConnector( n=7, allow_self_connections=False, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) # connections as follows: (pre - list of post) # 0 - 1 2 3 4 4 3 2 # 1 - 0 2 3 4 4 3 2 # 2 - 0 1 3 4 4 3 1 # 3 - 0 1 2 4 4 2 1 # 4 - 0 1 2 3 3 2 1 self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), ]) def test_with_replacement(self, sim=sim): C = connectors.FixedNumberPostConnector(n=3, with_replacement=True, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 0 - 0 1 2 # 1 - 3 4 0 # 2 - 1 2 3 # 3 - 4 0 1 self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123)]) def test_with_replacement_with_variable_n(self, sim=sim): n = random.RandomDistribution('binomial', (5, 0.5), rng=MockRNG(start=1, delta=2)) # should give (1, 3, 0, 2) C = connectors.FixedNumberPostConnector(n=n, with_replacement=True, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 0 - 0 # 1 - 1 2 3 # 2 - # 3 - 4 0 self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(1, 1, 0.0, 0.123), (1, 3, 0.0, 0.123)]) def test_with_replacement_no_self_connections(self, sim=sim): C = connectors.FixedNumberPostConnector(n=3, with_replacement=True, allow_self_connections=False, rng=MockRNG(start=2, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) # 0 - 2 3 4 # 1 - 0 2 3 # 2 - 4 0 1 # 3 - 2 4 0 # 4 - 1 2 3 self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(2, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (4, 3, 0.0, 0.123)]) class TestFixedNumberPreConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_with_n_smaller_than_population_size(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, rng=MockRNG(delta=1)) syn = sim.StaticSynapse(weight="0.1*d") prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(3, 1, 0.2, 0.123), (2, 1, 0.1, 0.123), (1, 1, 0.0, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.1, 0.123), (1, 3, 0.2, 0.123), ]) def test_with_n_larger_than_population_size(self, sim=sim): C = connectors.FixedNumberPreConnector(n=7, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), ]) def test_with_n_larger_than_population_size_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector( n=7, allow_self_connections=False, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), ]) def test_with_replacement(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=True, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [ # (0, 0, 0.0, 0.123), #(1, 0, 0.0, 0.123), #(2, 0, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), #(2, 2, 0.0, 0.123), #(3, 2, 0.0, 0.123), #(0, 2, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), ]) def test_with_replacement_with_variable_n(self, sim=sim): n = random.RandomDistribution('binomial', (5, 0.5), rng=MockRNG(start=1, delta=2)) # should give (1, 3, 0, 2, 4) C = connectors.FixedNumberPreConnector(n=n, with_replacement=True, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [ # (0, 0, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123)]) def test_with_replacement_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=True, allow_self_connections=False, rng=MockRNG(start=2, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [ # (2, 0, 0.0, 0.123), # [2, 3, 4] --> [2, 3, 4] #(3, 0, 0.0, 0.123), #(4, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), # [0, 1, 2] --> [0, 3, 2] #(1, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), # (4, 2, 0.0, 0.123), # [4, 0, 1] --> [4, 0, 1] #(0, 2, 0.0, 0.123), #(1, 2, 0.0, 0.123), (2, 3, 0.0, 0.123), # [2, 3, 4] --> [2, 0, 4] #(3, 3, 0.0, 0.123), (0, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), ]) def test_no_replacement_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=False, allow_self_connections=False, rng=MockRNG(start=2, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(4, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (4, 3, 0.0, 0.123), #(3, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), ]) def test_with_replacement_parallel_unsafe(self, sim=sim): C = connectors.FixedNumberPreConnector( n=3, with_replacement=True, rng=MockRNG(delta=1, parallel_safe=False)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 3, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), ]) def test_no_replacement_parallel_unsafe(self, sim=sim): C = connectors.FixedNumberPreConnector( n=3, with_replacement=False, rng=MockRNG(delta=1, parallel_safe=False)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), ]) class TestArrayConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(3, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(4, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([1, 0, 1, 0], dtype=bool)) def test_connect_with_scalar_weights_and_delays(self, sim=sim): connections = np.array([ [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0], ], dtype=bool) C = connectors.ArrayConnector(connections, safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(1, 0, 5.0, 0.5), (0, 2, 5.0, 0.5), (2, 2, 5.0, 0.5)]) def test_connect_with_random_weights_parallel_safe(self, sim=sim): rd_w = random.RandomDistribution( 'uniform', (0, 1), rng=MockRNG(delta=1.0, parallel_safe=True)) rd_d = random.RandomDistribution('uniform', (0, 1), rng=MockRNG( start=1.0, delta=0.1, parallel_safe=True)) syn = sim.StaticSynapse(weight=rd_w, delay=rd_d) connections = np.array([ [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0], ], dtype=bool) C = connectors.ArrayConnector(connections, safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(1, 0, 0.0, 1.0), (0, 2, 3.0, 1.3), (2, 2, 4.0, 1.4000000000000001)]) # better to do an "almost-equal" check class TestCloneConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) connection_list = [ (0, 0, 0.0, 1.0), (3, 0, 0.0, 1.0), (2, 3, 0.0, 1.0), # local (2, 2, 0.0, 1.0), (0, 1, 0.0, 1.0), # local ] list_connector = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() self.ref_prj = sim.Projection(self.p1, self.p2, list_connector, syn) self.orig_gather_dict = recording.gather_dict # create reference to original function # The gather_dict function in recording needs to be temporarily replaced so it can work with # a mock version of the function to avoid it throwing an mpi4py import error when setting # the rank in pyNN.mock by hand to > 1 def mock_gather_dict(D, all=False): return D recording.gather_dict = mock_gather_dict def tearDown(self, sim=sim): # restore original gather_dict function recording.gather_dict = self.orig_gather_dict def test_connect(self, sim=sim): syn = sim.StaticSynapse(weight=5.0, delay=0.5) C = connectors.CloneConnector(self.ref_prj) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 1, 5.0, 0.5), (2, 3, 5.0, 0.5)]) def test_connect_with_pre_post_mismatch(self, sim=sim): syn = sim.StaticSynapse() C = connectors.CloneConnector(self.ref_prj) p3 = sim.Population(5, sim.IF_cond_exp(), structure=space.Line()) self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, p3, C, syn) class TestIndexBasedProbabilityConnector(unittest.TestCase): class IndexBasedProbability(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array((i + j) % 3 == 0, dtype=float) class IndexBasedWeights(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array(i * j + 1, dtype=float) class IndexBasedDelays(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array(i + j + 1, dtype=float) def setUp(self, sim=sim, **extra): sim.setup(num_processes=2, rank=1, min_delay=0.123, **extra) self.p1 = sim.Population(5, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([1, 0, 1, 0, 1], dtype=bool)) def test_connect_with_scalar_weights_and_delays(self, sim=sim): syn = sim.StaticSynapse(weight=1.0, delay=2) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 0, 1, 2), (3, 0, 1, 2), (1, 2, 1, 2), (4, 2, 1, 2), (2, 4, 1, 2)]) def test_connect_with_index_based_weights(self, sim=sim): syn = sim.StaticSynapse(weight=self.IndexBasedWeights(), delay=2) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 0, 1, 2), (3, 0, 1, 2), (1, 2, 3, 2), (4, 2, 9, 2), (2, 4, 9, 2)]) def test_connect_with_index_based_delays(self, sim=sim): syn = sim.StaticSynapse(weight=1.0, delay=self.IndexBasedDelays()) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 0, 1, 1), (3, 0, 1, 4), (1, 2, 1, 4), (4, 2, 1, 7), (2, 4, 1, 7)]) class TestDisplacementDependentProbabilityConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(num_processes=2, rank=1, min_delay=0.123, **extra) self.p1 = sim.Population(9, sim.IF_cond_exp(), structure=space.Grid2D(aspect_ratio=1.0, dx=1.0, dy=1.0)) self.p2 = sim.Population(9, sim.HH_cond_exp(), structure=space.Grid2D(aspect_ratio=1.0, dx=1.0, dy=1.0)) assert_array_equal(self.p2._mask_local, np.array( [1, 0, 1, 0, 1, 0, 1, 0, 1], dtype=bool)) def test_connect(self, sim=sim): syn = sim.StaticSynapse(weight=1.0, delay=2) def displacement_expression(d): return 0.5 * ((d[0] >= -1) * (d[0] <= 2)) + 0.25 * (d[1] >= 0) * (d[1] <= 1) C = connectors.DisplacementDependentProbabilityConnector(displacement_expression, rng=MockRNG(delta=0.01)) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list', gather=False), # use gather False because we are faking the MPI [(0, 0, 1.0, 2.0), (1, 0, 1.0, 2.0), (2, 0, 1.0, 2.0), (3, 0, 1.0, 2.0), (4, 0, 1.0, 2.0), (5, 0, 1.0, 2.0), (6, 0, 1.0, 2.0), (0, 2, 1.0, 2.0), (1, 2, 1.0, 2.0), (2, 2, 1.0, 2.0), (3, 2, 1.0, 2.0), (4, 2, 1.0, 2.0), (5, 2, 1.0, 2.0), (0, 4, 1.0, 2.0), (1, 4, 1.0, 2.0), (2, 4, 1.0, 2.0), (3, 4, 1.0, 2.0), (4, 4, 1.0, 2.0), (5, 4, 1.0, 2.0), (6, 4, 1.0, 2.0), (7, 4, 1.0, 2.0), (8, 4, 1.0, 2.0), (0, 6, 1.0, 2.0), (3, 6, 1.0, 2.0), (6, 6, 1.0, 2.0), (1, 8, 1.0, 2.0), (2, 8, 1.0, 2.0)]) class TestFixedTotalNumberConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=2, rank=1, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([0, 1, 0, 1, 0], dtype=bool)) def test_1(self): C = connectors.FixedTotalNumberConnector(n=12, rng=random.NumpyRNG()) syn = sim.StaticSynapse(weight="0.5*d") prj = sim.Projection(self.p1, self.p2, C, syn) connections = prj.get(["weight", "delay"], format='list', gather=False) self.assertLess(len(connections), 12) # unlikely to be 12, since we have 2 MPI nodes self.assertGreater(len(connections), 0) # unlikely to be 0 PyNN-0.10.0/test/unittests/test_connectors_serial.py000066400000000000000000001370361415343567000226040ustar00rootroot00000000000000""" Tests of the Connector classes, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest from pyNN import connectors, random, errors, space, recording import numpy as np from numpy import nan import os import sys from numpy.testing import assert_array_equal, assert_array_almost_equal from .mocks import MockRNG, MockRNG2, MockRNG3 import pyNN.mock as sim orig_mpi_get_config = random.get_mpi_config def setUp(): random.get_mpi_config = lambda: (0, 2) def tearDown(): random.get_mpi_config = orig_mpi_get_config class TestOneToOneConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(**extra) self.p1 = sim.Population(5, sim.IF_cond_exp()) self.p2 = sim.Population(5, sim.HH_cond_exp()) def tearDown(self, sim=sim): sim.end() def test_connect_with_scalar_weights_and_delays(self, sim=sim): C = connectors.OneToOneConnector(safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 5.0, 0.5), (1, 1, 5.0, 0.5), (2, 2, 5.0, 0.5), (3, 3, 5.0, 0.5), (4, 4, 5.0, 0.5)]) def test_connect_with_random_weights(self, sim=sim): rd = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(delta=1.0)) syn = sim.StaticSynapse(weight=rd, delay=0.5) C = connectors.OneToOneConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.5), (1, 1, 1.0, 0.5), (2, 2, 2.0, 0.5), (3, 3, 3.0, 0.5), (4, 4, 4.0, 0.5)]) class TestAllToAllConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_with_scalar_weights_and_delays(self, sim=sim): C = connectors.AllToAllConnector(safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 5.0, 0.5), (1, 0, 5.0, 0.5), (2, 0, 5.0, 0.5), (3, 0, 5.0, 0.5), (0, 1, 5.0, 0.5), (1, 1, 5.0, 0.5), (2, 1, 5.0, 0.5), (3, 1, 5.0, 0.5), (0, 2, 5.0, 0.5), (1, 2, 5.0, 0.5), (2, 2, 5.0, 0.5), (3, 2, 5.0, 0.5), (0, 3, 5.0, 0.5), (1, 3, 5.0, 0.5), (2, 3, 5.0, 0.5), (3, 3, 5.0, 0.5), (0, 4, 5.0, 0.5), (1, 4, 5.0, 0.5), (2, 4, 5.0, 0.5), (3, 4, 5.0, 0.5)]) assert_array_equal(prj.get('weight', format='array'), np.array([[5.0, 5.0, 5.0, 5.0, 5.0], [5.0, 5.0, 5.0, 5.0, 5.0], [5.0, 5.0, 5.0, 5.0, 5.0], [5.0, 5.0, 5.0, 5.0, 5.0]])) def test_connect_with_array_weights(self, sim=sim): C = connectors.AllToAllConnector(safe=False) syn = sim.StaticSynapse(weight=np.arange(0.0, 2.0, 0.1).reshape(4, 5), delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) assert_array_almost_equal( np.array(prj.get(["weight", "delay"], format='list')), np.array([ (0, 0, 0.0, 0.5), (1, 0, 0.5, 0.5), (2, 0, 1.0, 0.5), (3, 0, 1.5, 0.5), (0, 1, 0.1, 0.5), (1, 1, 0.6, 0.5), (2, 1, 1.1, 0.5), (3, 1, 1.6, 0.5), (0, 2, 0.2, 0.5), (1, 2, 0.7, 0.5), (2, 2, 1.2, 0.5), (3, 2, 1.7, 0.5), (0, 3, 0.3, 0.5), (1, 3, 0.8, 0.5), (2, 3, 1.3, 0.5), (3, 3, 1.8, 0.5), (0, 4, 0.4, 0.5), (1, 4, 0.9, 0.5), (2, 4, 1.4, 0.5), (3, 4, 1.9, 0.5)] ) ) assert_array_almost_equal(prj.get('weight', format='array'), np.array([[0., 0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8, 0.9], [1., 1.1, 1.2, 1.3, 1.4], [1.5, 1.6, 1.7, 1.8, 1.9]]), 9) def test_connect_with_random_weights_parallel_safe(self, sim=sim): rd = random.RandomDistribution( 'uniform', (0, 1), rng=MockRNG(delta=1.0, parallel_safe=True)) syn = sim.StaticSynapse(weight=rd, delay=0.5) C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) # note that the outer loop is over the post-synaptic cells, the inner loop over the pre-synaptic self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.5), (1, 0, 1.0, 0.5), (2, 0, 2.0, 0.5), (3, 0, 3.0, 0.5), (0, 1, 4.0, 0.5), (1, 1, 5.0, 0.5), (2, 1, 6.0, 0.5), (3, 1, 7.0, 0.5), (0, 2, 8.0, 0.5), (1, 2, 9.0, 0.5), (2, 2, 10.0, 0.5), (3, 2, 11.0, 0.5), (0, 3, 12.0, 0.5), (1, 3, 13.0, 0.5), (2, 3, 14.0, 0.5), (3, 3, 15.0, 0.5), (0, 4, 16.0, 0.5), (1, 4, 17.0, 0.5), (2, 4, 18.0, 0.5), (3, 4, 19.0, 0.5)]) assert_array_almost_equal(prj.get('weight', format='array'), np.array([[0., 4., 8., 12., 16.], [1., 5., 9., 13., 17.], [2., 6., 10., 14., 18.], [3., 7., 11., 15., 19.]]), 9) def test_connect_with_distance_dependent_weights(self, sim=sim): d_expr = "d+100" syn = sim.StaticSynapse(weight=d_expr, delay=0.5) C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 100.0, 0.5), (1, 0, 101.0, 0.5), (2, 0, 102.0, 0.5), (3, 0, 103.0, 0.5), (0, 1, 101.0, 0.5), (1, 1, 100.0, 0.5), (2, 1, 101.0, 0.5), (3, 1, 102.0, 0.5), (0, 2, 102.0, 0.5), (1, 2, 101.0, 0.5), (2, 2, 100.0, 0.5), (3, 2, 101.0, 0.5), (0, 3, 103.0, 0.5), (1, 3, 102.0, 0.5), (2, 3, 101.0, 0.5), (3, 3, 100.0, 0.5), (0, 4, 104.0, 0.5), (1, 4, 103.0, 0.5), (2, 4, 102.0, 0.5), (3, 4, 101.0, 0.5)]) def test_connect_with_distance_dependent_weights_and_delays(self, sim=sim): syn = sim.StaticSynapse(weight="d+100", delay="0.2+2*d") C = connectors.AllToAllConnector(safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 100.0, 0.2), (1, 0, 101.0, 2.2), (2, 0, 102.0, 4.2), (3, 0, 103.0, 6.2), (0, 1, 101.0, 2.2), (1, 1, 100.0, 0.2), (2, 1, 101.0, 2.2), (3, 1, 102.0, 4.2), (0, 2, 102.0, 4.2), (1, 2, 101.0, 2.2), (2, 2, 100.0, 0.2), (3, 2, 101.0, 2.2), (0, 3, 103.0, 6.2), (1, 3, 102.0, 4.2), (2, 3, 101.0, 2.2), (3, 3, 100.0, 0.2), (0, 4, 104.0, 8.2), (1, 4, 103.0, 6.2), (2, 4, 102.0, 4.2), (3, 4, 101.0, 2.2)]) def test_connect_with_delays_None(self, sim=sim): syn = sim.StaticSynapse(weight=0.1, delay=None) C = connectors.AllToAllConnector() assert C.safe assert C.allow_self_connections prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list') [0][3], prj._simulator.state.min_delay) @unittest.skip('skipping this tests until I figure out how I want to refactor checks') def test_connect_with_delays_too_small(self, sim=sim): C = connectors.AllToAllConnector() syn = sim.StaticSynapse(weight=0.1, delay=0.0) self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) @unittest.skip('skipping this tests until I figure out how I want to refactor checks') def test_connect_with_list_delays_too_small(self, sim=sim): delays = np.ones((self.p1.size, self.p2.size), float) delays[2, 3] = sim.Projection._simulator.state.min_delay - 0.01 syn = sim.StaticSynapse(weight=0.1, delay=delays) C = connectors.AllToAllConnector() self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) class TestFixedProbabilityConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_with_default_args(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.85, rng=MockRNG(delta=0.1, parallel_safe=True)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the # first 9 are created (0,0), (1,0), (2,0), (3,0), (0,1), (1,1), (2,1), (3,1), (0,2) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 2, 0.0, 0.123) ]) def test_connect_with_default_args_again(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.5, rng=MockRNG2(1 - np.array([1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1]), parallel_safe=True)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the following # are created (0,0), (3,0), (3,1), (0,2), (1,2), (0,3), (2,3), (0,4), (1,4), (3,4) # (note that the outer loop is over post-synaptic cells (columns), the inner loop over pre-synaptic (rows)) assert_array_almost_equal(prj.get('delay', format='array'), np.array([[0.123, nan, 0.123, 0.123, 0.123], [nan, nan, 0.123, nan, 0.123], [nan, nan, nan, 0.123, nan], [0.123, 0.123, nan, nan, 0.123]]), 9) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (0, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (3, 4, 0.0, 0.123) ]) def test_connect_with_probability_one(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=1.) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (2, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), ]) def test_connect_weight_function_and_one_post_synaptic_neuron_not_connected(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.8, rng=MockRNG(delta=0.05)) syn = sim.StaticSynapse(weight=lambda d: 0.1 * d) prj = sim.Projection(self.p1, self.p2, C, syn) assert_array_almost_equal(prj.get(["weight", "delay"], format='array'), np.array([ [[0., 0.1, 0.2, 0.3, nan], [0.1, 0., 0.1, 0.2, nan], [0.2, 0.1, 0.0, 0.1, nan], [0.3, 0.2, 0.1, 0.0, nan]], [[0.123, 0.123, 0.123, 0.123, nan], [0.123, 0.123, 0.123, 0.123, nan], [0.123, 0.123, 0.123, 0.123, nan], [0.123, 0.123, 0.123, 0.123, nan]] ]), 9) def test_connect_with_weight_function(self, sim=sim): C = connectors.FixedProbabilityConnector(p_connect=0.85, rng=MockRNG(delta=0.1)) syn = sim.StaticSynapse(weight=lambda d: 0.1 * d) prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the # first 9 are created (0,0), (1,0), (2,0), (3,0), (0,1), (1,1), (2,1), (3,1), (0,2) assert_array_almost_equal(prj.get(["weight", "delay"], format='array'), np.array([ [[0., 0.1, 0.2, nan, nan], [0.1, 0., nan, nan, nan], [0.2, 0.1, nan, nan, nan], [0.3, 0.2, nan, nan, nan]], [[0.123, 0.123, 0.123, nan, nan], [0.123, 0.123, nan, nan, nan], [0.123, 0.123, nan, nan, nan], [0.123, 0.123, nan, nan, nan]] ]), 9) def test_connect_with_random_delays_parallel_safe(self, sim=sim): rd = random.RandomDistribution('uniform', low=0.1, high=1.1, rng=MockRNG(start=1.0, delta=0.2, parallel_safe=True)) syn = sim.StaticSynapse(delay=rd) C = connectors.FixedProbabilityConnector(p_connect=0.5, rng=MockRNG2(1 - np.array([1, 0, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 1, 1, 0, 1]), parallel_safe=True)) prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Due to the mock RNG, only the following # are created (0,0), (3,0), (3,1), (0,2), (1,2), (0,3), (2,3), (0,4), (1,4), (3,4) # (note that the outer loop is over post-synaptic cells (columns), the inner loop over pre-synaptic (rows)) assert_array_almost_equal(prj.get('delay', format='array'), np.array([[1.0, nan, 1.6, 2.0, 2.4], [nan, nan, 1.8, nan, 2.6], [nan, nan, nan, 2.2, nan], [1.2, 1.4, nan, nan, 2.8]]), 9) class TestDistanceDependentProbabilityConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_with_default_args(self, sim=sim): C = connectors.DistanceDependentProbabilityConnector(d_expression="d<1.5", rng=MockRNG(delta=0.01)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) # 20 possible connections. Only those with a sufficiently small distance # are created self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (1, 2, 0.0, 0.123), (2, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (3, 4, 0.0, 0.123)]) class TestFromListConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_unique_connection_neuron_0_to_neuron_0(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.18) ] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.1, 0.18)]) def test_connect_with_empty_list(self, sim=sim): connection_list = [] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), []) def test_connect_with_valid_list(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.18), (3, 0, 0.2, 0.17), (2, 3, 0.3, 0.16), # local (2, 2, 0.4, 0.15), (0, 1, 0.5, 0.14), # local ] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.1, 0.18), (3, 0, 0.2, 0.17), (0, 1, 0.5, 0.14), (2, 2, 0.4, 0.15), (2, 3, 0.3, 0.16)]) def test_connect_with_out_of_range_index(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), # local (5, 1, 0.4, 0.13), # NON-EXISTENT (0, 1, 0.5, 0.14), # local ] C = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p2, C, syn) def test_with_plastic_synapse(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1, 100, 400), (3, 0, 0.2, 0.11, 101, 500), (2, 3, 0.3, 0.12, 102, 600), # local (2, 2, 0.4, 0.13, 103, 700), (0, 1, 0.5, 0.14, 104, 800), # local ] C = connectors.FromListConnector(connection_list, column_names=[ "weight", "delay", "U", "tau_rec"]) syn = sim.TsodyksMarkramSynapse(U=99, tau_facil=88.8) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay", "tau_facil", "tau_rec", "U"], format='list'), [(0, 0, 0.1, 0.1, 88.8, 400.0, 100.0), (3, 0, 0.2, 0.11, 88.8, 500.0, 101.0), (0, 1, 0.5, 0.14, 88.8, 800.0, 104.0), (2, 2, 0.4, 0.13, 88.8, 700.0, 103.0), (2, 3, 0.3, 0.12, 88.8, 600.0, 102.0)]) class TestFromFileConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) self.connection_list = [ (0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (2, 3, 0.3, 0.12), # local (2, 2, 0.4, 0.13), (0, 1, 0.5, 0.14), # local ] def tearDown(self, sim=sim): sim.end() for path in ("test.connections", "test.connections.1", "test.connections.2"): if os.path.exists(path): os.remove(path) def test_connect_with_standard_text_file_not_distributed(self, sim=sim): np.savetxt("test.connections", self.connection_list) C = connectors.FromFileConnector("test.connections", distributed=False) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.1, 0.1), (3, 0, 0.2, 0.11), (0, 1, 0.5, 0.14), (2, 2, 0.4, 0.13), (2, 3, 0.3, 0.12)]) def test_with_plastic_synapses_not_distributed(self, sim=sim): connection_list = [ (0, 0, 0.1, 0.1, 100, 100), (3, 0, 0.2, 0.11, 110, 99), (2, 3, 0.3, 0.12, 120, 98), # local (2, 2, 0.4, 0.13, 130, 97), (0, 1, 0.5, 0.14, 140, 96), # local ] file = recording.files.StandardTextFile("test.connections.2", mode='wb') file.write(connection_list, {"columns": ["i", "j", "weight", "delay", "U", "tau_rec"]}) C = connectors.FromFileConnector("test.connections.2", distributed=False) syn = sim.TsodyksMarkramSynapse(tau_facil=88.8) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay", "U", "tau_rec", "tau_facil"], format='list'), [(0, 0, 0.1, 0.1, 100.0, 100.0, 88.8), (3, 0, 0.2, 0.11, 110.0, 99.0, 88.8), (0, 1, 0.5, 0.14, 140.0, 96.0, 88.8), (2, 2, 0.4, 0.13, 130.0, 97.0, 88.8), (2, 3, 0.3, 0.12, 120.0, 98.0, 88.8)]) class TestFixedNumberPreConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_with_n_smaller_than_population_size(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, rng=MockRNG(delta=1)) syn = sim.StaticSynapse(weight="0.1*d") prj = sim.Projection(self.p1, self.p2, C, syn) rec = prj.get(["weight", "delay"], format='list') assert_array_almost_equal([list(r) for r in rec], [(3, 0, 0.3, 0.123), (2, 0, 0.2, 0.123), (1, 0, 0.1, 0.123), (3, 1, 0.2, 0.123), (2, 1, 0.1, 0.123), (1, 1, 0.0, 0.123), (3, 2, 0.1, 0.123), (2, 2, 0.0, 0.123), (1, 2, 0.1, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.1, 0.123), (1, 3, 0.2, 0.123), (3, 4, 0.1, 0.123), (2, 4, 0.2, 0.123), (1, 4, 0.3, 0.123)]) def test_with_n_larger_than_population_size(self, sim=sim): C = connectors.FixedNumberPreConnector(n=7, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (2, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (2, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (1, 4, 0.0, 0.123)]) def test_with_n_larger_than_population_size_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector( n=7, allow_self_connections=False, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (4, 0, 0.0, 0.123), (4, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (4, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (4, 2, 0.0, 0.123), (4, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (1, 4, 0.0, 0.123)]) def test_with_replacement(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=True, rng=MockRNG(delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), ]) def test_with_replacement_with_neuron_0_connecting_neuron_0(self, sim=sim): n = random.RandomDistribution('binomial', (5, 0.5), rng=MockRNG3()) C = connectors.FixedNumberPreConnector( n=n, with_replacement=True, rng=MockRNG(start=0, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), ]) def test_with_replacement_with_variable_n(self, sim=sim): n = random.RandomDistribution('binomial', (5, 0.5), rng=MockRNG(start=1, delta=2)) # should give (1, 3, 0, 2, 4) C = connectors.FixedNumberPreConnector( n=n, with_replacement=True, rng=MockRNG(start=0, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123) ]) # TOCHECK def test_with_replacement_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=True, allow_self_connections=False, rng=MockRNG(start=2, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(2, 0, 0.0, 0.123), # [2, 3, 4] --> [2, 3, 4] (3, 0, 0.0, 0.123), (4, 0, 0.0, 0.123), (0, 1, 0.0, 0.123), # [0, 1, 2] --> [0, 3, 2] #(1, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (4, 2, 0.0, 0.123), # [4, 0, 1] --> [4, 0, 1] (0, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (2, 3, 0.0, 0.123), # [2, 3, 4] --> [2, 0, 4] #(3, 3, 0.0, 0.123), (0, 3, 0.0, 0.123), (4, 3, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (3, 4, 0.0, 0.123), ]) # TOCHECK def test_no_replacement_no_self_connections(self, sim=sim): C = connectors.FixedNumberPreConnector(n=3, with_replacement=False, allow_self_connections=False, rng=MockRNG(start=2, delta=1)) syn = sim.StaticSynapse() prj = sim.Projection(self.p2, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(4, 0, 0.0, 0.123), (3, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (4, 1, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (4, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (4, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (3, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (1, 4, 0.0, 0.123)]) # TOCHECK def test_with_replacement_parallel_unsafe(self, sim=sim): C = connectors.FixedNumberPreConnector( n=3, with_replacement=True, rng=MockRNG(delta=1, parallel_safe=False)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (3, 1, 0.0, 0.123), (0, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (2, 2, 0.0, 0.123), (3, 2, 0.0, 0.123), (0, 2, 0.0, 0.123), (1, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (3, 3, 0.0, 0.123), (0, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), ]) def test_no_replacement_parallel_unsafe(self, sim=sim): C = connectors.FixedNumberPreConnector( n=3, with_replacement=False, rng=MockRNG(delta=1, parallel_safe=False)) syn = sim.StaticSynapse() prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(3, 0, 0.0, 0.123), (2, 0, 0.0, 0.123), (1, 0, 0.0, 0.123), (3, 1, 0.0, 0.123), (2, 1, 0.0, 0.123), (1, 1, 0.0, 0.123), (3, 2, 0.0, 0.123), (2, 2, 0.0, 0.123), (1, 2, 0.0, 0.123), (3, 3, 0.0, 0.123), (2, 3, 0.0, 0.123), (1, 3, 0.0, 0.123), (3, 4, 0.0, 0.123), (2, 4, 0.0, 0.123), (1, 4, 0.0, 0.123), ]) class TestArrayConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(3, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(4, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_with_scalar_weights_and_delays(self, sim=sim): connections = np.array([ [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0], ], dtype=bool) C = connectors.ArrayConnector(connections, safe=False) syn = sim.StaticSynapse(weight=5.0, delay=0.5) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(1, 0, 5.0, 0.5), (0, 1, 5.0, 0.5), (1, 1, 5.0, 0.5), (0, 2, 5.0, 0.5), (2, 2, 5.0, 0.5), (1, 3, 5.0, 0.5)]) def test_connect_with_random_weights_parallel_safe(self, sim=sim): rd_w = random.RandomDistribution( 'uniform', (0, 1), rng=MockRNG(delta=1.0, parallel_safe=True)) rd_d = random.RandomDistribution('uniform', (0, 1), rng=MockRNG( start=1.0, delta=0.1, parallel_safe=True)) syn = sim.StaticSynapse(weight=rd_w, delay=rd_d) connections = np.array([ [0, 1, 1, 0], [1, 1, 0, 1], [0, 0, 1, 0], ], dtype=bool) C = connectors.ArrayConnector(connections, safe=False) prj = sim.Projection(self.p1, self.p2, C, syn) rec = prj.get(["weight", "delay"], format='list') assert_array_almost_equal([tuple(r) for r in rec], [(1, 0, 0.0, 1.0), (0, 1, 1.0, 1.1), (1, 1, 2.0, 1.2), (0, 2, 3.0, 1.3), (2, 2, 4.0, 1.4), (1, 3, 5.0, 1.5)]) class TestCloneConnector(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(min_delay=0.123, **extra) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) connection_list = [ (0, 0, 0.0, 1.0), (3, 0, 0.0, 1.0), (2, 3, 0.0, 1.0), # local (2, 2, 0.0, 1.0), (0, 1, 0.0, 1.0), # local ] list_connector = connectors.FromListConnector(connection_list) syn = sim.StaticSynapse() self.ref_prj = sim.Projection(self.p1, self.p2, list_connector, syn) self.orig_gather_dict = recording.gather_dict # create reference to original function # The gather_dict function in recording needs to be temporarily replaced so it can work with # a mock version of the function to avoid it throwing an mpi4py import error when setting # the rank in pyNN.mock by hand to > 1 def mock_gather_dict(D, all=False): return D recording.gather_dict = mock_gather_dict def tearDown(self, sim=sim): # restore original gather_dict function recording.gather_dict = self.orig_gather_dict sim.end() def test_connect(self, sim=sim): syn = sim.StaticSynapse(weight=5.0, delay=0.5) C = connectors.CloneConnector(self.ref_prj) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 5.0, 0.5), (3, 0, 5.0, 0.5), (0, 1, 5.0, 0.5), (2, 2, 5.0, 0.5), (2, 3, 5.0, 0.5)]) def test_connect_with_pre_post_mismatch(self, sim=sim): syn = sim.StaticSynapse() C = connectors.CloneConnector(self.ref_prj) p3 = sim.Population(5, sim.IF_cond_exp(), structure=space.Line()) self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, p3, C, syn) class TestIndexBasedProbabilityConnector(unittest.TestCase): class IndexBasedProbability(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array((i + j) % 3 == 0, dtype=float) class IndexBasedWeights(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array(i * j + 1, dtype=float) class IndexBasedDelays(connectors.IndexBasedExpression): def __call__(self, i, j): return np.array(i + j + 1, dtype=float) def setUp(self, sim=sim, **extra): sim.setup(nmin_delay=0.123, **extra) self.p1 = sim.Population(5, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) def tearDown(self, sim=sim): sim.end() def test_connect_with_scalar_weights_and_delays(self, sim=sim): syn = sim.StaticSynapse(weight=1.0, delay=2) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 1., 2), (3, 0, 1., 2), (2, 1, 1., 2), (1, 2, 1., 2), (4, 2, 1., 2), (0, 3, 1., 2), (3, 3, 1., 2), (2, 4, 1., 2)]) def test_connect_with_index_based_weights(self, sim=sim): syn = sim.StaticSynapse(weight=self.IndexBasedWeights(), delay=2) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 1., 2), (3, 0, 1., 2), (2, 1, 3., 2), (1, 2, 3., 2), (4, 2, 9., 2), (0, 3, 1., 2), (3, 3, 10., 2), (2, 4, 9., 2)]) def test_connect_with_index_based_delays(self, sim=sim): syn = sim.StaticSynapse(weight=1.0, delay=self.IndexBasedDelays()) C = connectors.IndexBasedProbabilityConnector(self.IndexBasedProbability()) prj = sim.Projection(self.p1, self.p2, C, syn) self.assertEqual(prj.get(["weight", "delay"], format='list'), [(0, 0, 1., 1), (3, 0, 1., 4), (2, 1, 1., 4), (1, 2, 1., 4), (4, 2, 1., 7), (0, 3, 1., 4), (3, 3, 1., 7), (2, 4, 1., 7)]) #TOCHECK, not included # class TestDisplacementDependentProbabilityConnector(unittest.TestCase): # def setUp(self, sim=sim, **extra): #sim.setup(min_delay=0.123, **extra) # self.p1 = sim.Population(9, sim.IF_cond_exp(), # structure=space.Grid2D(aspect_ratio=1.0, dx=1.0, dy=1.0)) # self.p2 = sim.Population(9, sim.HH_cond_exp(), # structure=space.Grid2D(aspect_ratio=1.0, dx=1.0, dy=1.0)) # def tearDown(self, sim=sim): # sim.end() # def test_connect(self, sim=sim): #syn = sim.StaticSynapse(weight=1.0, delay=2) # def displacement_expression(d): # return 0.5 * ((d[0] >= -1) * (d[0] <= 2)) + 0.25 * (d[1] >= 0) * (d[1] <= 1) # C = connectors.DisplacementDependentProbabilityConnector(displacement_expression, # rng=MockRNG(delta=0.01)) #prj = sim.Projection(self.p1, self.p2, C, syn) # self.assertEqual(prj.get(["weight", "delay"], format='list'), # [(0, 0, 1.0, 2.0), #(1, 0, 1.0, 2.0), #(2, 0, 1.0, 2.0), #(3, 0, 1.0, 2.0), #(4, 0, 1.0, 2.0), #(5, 0, 1.0, 2.0), #(6, 0, 1.0, 2.0), #(0, 2, 1.0, 2.0), #(1, 2, 1.0, 2.0), #(2, 2, 1.0, 2.0), #(3, 2, 1.0, 2.0), #(4, 2, 1.0, 2.0), #(5, 2, 1.0, 2.0), #(0, 4, 1.0, 2.0), #(1, 4, 1.0, 2.0), #(2, 4, 1.0, 2.0), #(3, 4, 1.0, 2.0), #(4, 4, 1.0, 2.0), #(5, 4, 1.0, 2.0), #(6, 4, 1.0, 2.0), #(7, 4, 1.0, 2.0), #(8, 4, 1.0, 2.0), #(0, 6, 1.0, 2.0), #(3, 6, 1.0, 2.0), #(6, 6, 1.0, 2.0), #(1, 8, 1.0, 2.0), # (2, 8, 1.0, 2.0)]) class TestFixedTotalNumberConnector(unittest.TestCase): def setUp(self, sim=sim): sim.setup(num_processes=1, rank=0, min_delay=0.123) self.p1 = sim.Population(4, sim.IF_cond_exp(), structure=space.Line()) self.p2 = sim.Population(5, sim.HH_cond_exp(), structure=space.Line()) assert_array_equal(self.p2._mask_local, np.array([1, 1, 1, 1, 1], dtype=bool)) def test_1(self): C = connectors.FixedTotalNumberConnector(n=12, rng=random.NumpyRNG()) syn = sim.StaticSynapse(weight="0.5*d") prj = sim.Projection(self.p1, self.p2, C, syn) connections = prj.get(["weight", "delay"], format='list', gather=False) self.assertEqual(len(connections), 12) if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_core.py000066400000000000000000000011301415343567000200010ustar00rootroot00000000000000from pyNN.core import is_listlike import numpy as np def test_is_list_like_with_tuple(): assert is_listlike((1, 2, 3)) def test_is_list_like_with_list(): assert is_listlike([1, 2, 3]) def test_is_list_like_with_iterator(): assert not is_listlike(iter((1, 2, 3))) def test_is_list_like_with_set(): assert is_listlike(set((1, 2, 3))) def test_is_list_like_with_numpy_array(): assert is_listlike(np.arange(10)) def test_is_list_like_with_string(): assert not is_listlike("abcdefg") # def test_is_list_like_with_file(): # f = file() # assert not is_listlike(f) PyNN-0.10.0/test/unittests/test_descriptions.py000066400000000000000000000055251415343567000215730ustar00rootroot00000000000000import unittest from pyNN import common, errors, random, standardmodels, space, descriptions import numpy as np try: from unittest.mock import Mock except ImportError: from mock import Mock import os.path class MockTemplateEngine(descriptions.TemplateEngine): render = Mock(return_value="african swallow") get_template = Mock() class DescriptionTest(unittest.TestCase): def test_get_default_template_engine(self): engine = descriptions.get_default_template_engine() assert issubclass(engine, descriptions.TemplateEngine) def test_render_with_no_template(self): context = {'a': 2, 'b': 3} result = descriptions.render(Mock(), None, context) self.assertEqual(result, context) def test_render_with_template(self): engine = MockTemplateEngine context = {'a': 2, 'b': 3} template = "abcdefg" result = descriptions.render(engine, template, context) engine.render.assert_called_with(template, context) self.assertEqual(result, "african swallow") def test_StringTE_get_template(self): result = descriptions.StringTemplateEngine.get_template("$a $b c d") self.assertEqual(result.template, "$a $b c d") def test_StringTE_get_template_from_file(self): filename = "population_default.txt" result = descriptions.StringTemplateEngine.get_template(filename) self.assertNotEqual(result.template, filename) def test_StringTE_render(self): context = {'a': 2, 'b': 3} result = descriptions.StringTemplateEngine.render("$a $b c d", context) self.assertEqual(result, "2 3 c d") @unittest.skipUnless('jinja2' in descriptions.TEMPLATE_ENGINES, "Requires Jinja2") def test_Jinja2TE_get_template_from_file(self): filename = "population_default.txt" result = descriptions.Jinja2TemplateEngine.get_template(filename) self.assertEqual(os.path.basename(result.filename), filename) @unittest.skipUnless('jinja2' in descriptions.TEMPLATE_ENGINES, "Requires Jinja2") def test_Jinja2TE_render(self): context = {'a': 2, 'b': 3} result = descriptions.Jinja2TemplateEngine.render("{{a}} {{b}} c d", context) self.assertEqual(result, "2 3 c d") @unittest.skipUnless('cheetah' in descriptions.TEMPLATE_ENGINES, "Requires Cheetah") def test_CheetahTE_get_template_from_file(self): filename = "population_default.txt" result = descriptions.CheetahTemplateEngine.get_template(filename) # incomplete test @unittest.skipUnless('cheetah' in descriptions.TEMPLATE_ENGINES, "Requires Cheetah") def test_CheetahTE_render(self): context = {'a': 2, 'b': 3} result = descriptions.CheetahTemplateEngine.render("$a $b c d", context) self.assertEqual(result, "2 3 c d") if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_files.py000066400000000000000000000064351415343567000201700ustar00rootroot00000000000000import os from unittest.mock import Mock from textwrap import dedent from nose.tools import assert_equal import numpy as np from numpy.testing import assert_array_equal from pyNN.recording import files builtin_open = open def test__savetxt(): mock_file = Mock() files.open = Mock(return_value=mock_file) files._savetxt(filename="dummy_file", data=[(0, 2.3), (1, 3.4), (2, 4.3)], format="%f", delimiter=" ") target = [(('0.000000 2.300000\n',), {}), (('1.000000 3.400000\n',), {}), (('2.000000 4.300000\n',), {})] assert_equal(mock_file.write.call_args_list, target) files.open = builtin_open def test_create_BaseFile(): files.open = Mock() bf = files.BaseFile("filename", 'r') files.open.assert_called_with("filename", "r", files.DEFAULT_BUFFER_SIZE) files.open = builtin_open def test_del(): files.open = Mock() bf = files.BaseFile("filename", 'r') close_mock = Mock() bf.close = close_mock del bf close_mock.assert_called_with() files.open = builtin_open def test_close(): files.open = Mock() bf = files.BaseFile("filename", 'r') bf.close() bf.fileobj.close.assert_called_with() files.open = builtin_open # def test_StandardTextFile_write(): # files.open = Mock() # stf = files.StandardTextFile("filename", "w") # data=[(0, 2.3),(1, 3.4),(2, 4.3)] # metadata = {'a': 1, 'b': 9.99} # target = [(('# a = 1\n# b = 9.99\n',), {}), # (('0.0\t2.3\n',), {}), # (('1.0\t3.4\n',), {}), # (('2.0\t4.3\n',), {})] # stf.write(data, metadata) # assert_equal(stf.fileobj.write.call_args_list, # target) # files.open = builtin_open def test_StandardTextFile_read(): files.open = Mock() stf = files.StandardTextFile("filename", "w") orig_loadtxt = np.loadtxt np.loadtxt = Mock() stf.read() np.loadtxt.assert_called_with(stf.fileobj) np.loadtxt = orig_loadtxt files.open = builtin_open def test_PickleFile(): pf = files.PickleFile("tmp.pickle", "wb") data = [(0, 2.3), (1, 3.4), (2, 4.3)] metadata = {'a': 1, 'b': 9.99} pf.write(data, metadata) pf.close() pf = files.PickleFile("tmp.pickle", "rb") assert_equal(pf.get_metadata(), metadata) assert_equal(pf.read(), data) pf.close() os.remove("tmp.pickle") # def test_NumpyBinaryFile(): # nbf = files.NumpyBinaryFile("tmp.npz", "w") # data=[(0, 2.3), (1, 3.4), (2, 4.3)] # metadata = {'a': 1, 'b': 9.99} # nbf.write(data, metadata) # nbf.close() # # nbf = files.NumpyBinaryFile("tmp.npz", "r") # assert_equal(nbf.get_metadata(), metadata) # assert_array_equal(nbf.read().flatten(), np.array(data).flatten()) # nbf.close() # # os.remove("tmp.npz") def test_HDF5ArrayFile(): if files.have_hdf5: h5f = files.HDF5ArrayFile("tmp.h5", "w") data = [(0, 2.3), (1, 3.4), (2, 4.3)] metadata = {'a': 1, 'b': 9.99} h5f.write(data, metadata) h5f.close() h5f = files.HDF5ArrayFile("tmp.h5", "r") assert_equal(h5f.get_metadata(), metadata) assert_array_equal(np.array(h5f.read()).flatten(), np.array(data).flatten()) h5f.close() os.remove("tmp.h5") PyNN-0.10.0/test/unittests/test_idmixin.py000066400000000000000000000102361415343567000205210ustar00rootroot00000000000000from pyNN import common, errors, standardmodels from nose.tools import assert_equal, assert_raises class MockStandardCell(standardmodels.StandardCellType): default_parameters = { 'a': 20.0, 'b': -34.9, 'c': 2.2, } translations = standardmodels.build_translations( ('a', 'A'), ('b', 'B'), ('c', 'C', 'c + a', 'C - A')) class MockNativeCell(object): @classmethod def has_parameter(cls, name): return False @classmethod def get_parameter_names(cls): return [] class MockPopulation(object): def __init__(self, standard): if standard: self.celltype = MockStandardCell() else: self.celltype = MockNativeCell() self._is_local_called = False self._positions = {} self._initial_values = {} def id_to_index(self, id): return 1234 def is_local(self, id): self._is_local_called = True return True def _set_cell_position(self, id, pos): self._positions[id] = pos def _get_cell_position(self, id): return (1.2, 3.4, 5.6) def _get_cell_initial_value(self, id, variable): return -65.0 def _set_cell_initial_value(self, id, variable, value): self._initial_values[id] = (variable, value) class MockID(common.IDMixin): def __init__(self, standard_cell): self.parent = MockPopulation(standard=standard_cell) self.foo = "bar" self._parameters = {'A': 76.5, 'B': 23.4, 'C': 100.0} class MockCurrentSource(object): def __init__(self): self._inject_into = [] def inject_into(self, objs): self._inject_into.extend(objs) class Test_IDMixin(): def setup(self): self.id = MockID(standard_cell=True) self.id_ns = MockID(standard_cell=False) # def test_getattr_with_parameter_attr(self): # assert_equal(self.id.a, 76.5) # assert_equal(self.id_ns.A, 76.5) # assert_raises(errors.NonExistentParameterError, self.id.__getattr__, "tau_m") # assert_raises(errors.NonExistentParameterError, self.id_ns.__getattr__, "tau_m") def test_getattr_with_nonparameter_attr(self): assert_equal(self.id.foo, "bar") assert_equal(self.id_ns.foo, "bar") def test_getattr_with_parent_not_set(self): del(self.id.parent) assert_raises(Exception, self.id.__getattr__, "parent") # def test_setattr_with_parameter_attr(self): # self.id.a = 87.6 # self.id_ns.A = 98.7 # assert_equal(self.id.a, 87.6) # assert_equal(self.id_ns.A, 98.7) # def test_set_parameters(self): # assert_raises(errors.NonExistentParameterError, self.id.set_parameters, hello='world') # ##assert_raises(errors.NonExistentParameterError, self.id_ns.set_parameters, hello='world') # self.id.set_parameters(a=12.3, c=77.7) # assert_equal(self.id._parameters, {'A': 12.3, 'B': 23.4, 'C': 90.0}) # def test_get_parameters(self): # assert_equal(self.id.get_parameters(), {'a': 76.5, 'b': 23.4, 'c': 23.5}) def test_celltype_property(self): assert_equal(self.id.celltype.__class__, MockStandardCell) assert_equal(self.id_ns.celltype.__class__, MockNativeCell) def test_is_standard_cell(self): assert self.id.is_standard_cell assert not self.id_ns.is_standard_cell def test_position_property(self): for id in (self.id, self.id_ns): assert_equal(id.position, (1.2, 3.4, 5.6)) id.position = (9, 8, 7) assert_equal(id.parent._positions[id], (9, 8, 7)) def test_local_property(self): for id in (self.id, self.id_ns): assert id.parent._is_local_called is False assert id.local assert id.parent._is_local_called is True def test_inject(self): for id in (self.id, self.id_ns): cs = MockCurrentSource() id.inject(cs) assert_equal(cs._inject_into, [id]) def test_get_initial_value(self): self.id.get_initial_value('v') def test_set_initial_value(self): self.id.set_initial_value('v', -77.7) assert_equal(self.id.parent._initial_values[self.id], ('v', -77.7)) PyNN-0.10.0/test/unittests/test_lowlevelapi.py000066400000000000000000000054611415343567000214070ustar00rootroot00000000000000from pyNN import common from pyNN.common.populations import BasePopulation try: from unittest.mock import Mock except ImportError: from mock import Mock from inspect import isfunction from nose.tools import assert_equal def test_build_create(): population_class = Mock() create_function = common.build_create(population_class) assert isfunction(create_function) p = create_function("cell class", "cell params", n=999) population_class.assert_called_with(999, "cell class", cellparams="cell params") def test_build_connect(): projection_class = Mock() connector_class = Mock(return_value="connector") syn_class = Mock(return_value="syn") connect_function = common.build_connect(projection_class, connector_class, syn_class) assert isfunction(connect_function) prj = connect_function("source", "target", "weight", "delay", "receptor_type", "p", "rng") syn_class.assert_called_with(weight="weight", delay="delay") connector_class.assert_called_with(p_connect="p", rng="rng") projection_class.assert_called_with( "source", "target", "connector", synapse_type="syn", receptor_type="receptor_type") class MockID(common.IDMixin): def as_view(self): return "view" prj = connect_function(MockID(), MockID(), "weight", "delay", "receptor_type", "p", "rng") projection_class.assert_called_with( "view", "view", "connector", synapse_type="syn", receptor_type="receptor_type") def test_set(): cells = BasePopulation() cells.set = Mock() common.set(cells, param="val") cells.set.assert_called_with(param="val") def test_build_record(): simulator = Mock() simulator.state.write_on_end = [] record_function = common.build_record(simulator) assert isfunction(record_function) source = BasePopulation() source.record = Mock() record_function(('v', 'spikes'), source, "filename") source.record.assert_called_with(('v', 'spikes'), to_file="filename", sampling_interval=None) # below check needs to be re-implmented with pyNN.mock # assert_equal(simulator.state.write_on_end, [(source, ('v', 'spikes'), "filename")]) def test_build_record_with_assembly(): simulator = Mock() simulator.state.write_on_end = [] record_function = common.build_record(simulator) assert isfunction(record_function) p1 = BasePopulation() p2 = BasePopulation() common.Assembly._simulator = None source = common.Assembly(p1, p2) source.record = Mock() record_function('foo', source, "filename") source.record.assert_called_with('foo', to_file="filename", sampling_interval=None) # below check needs to be re-implmented with pyNN.mock # assert_equal(simulator.state.write_on_end, [(source, 'foo', "filename")]) # not sure this is what we want - won't file get over-written? PyNN-0.10.0/test/unittests/test_nest.py000066400000000000000000000171471415343567000200410ustar00rootroot00000000000000try: import pyNN.nest as sim import nest except ImportError: nest = False from pyNN.standardmodels import StandardCellType import unittest import numpy as np from numpy.testing import assert_array_equal, assert_array_almost_equal @unittest.skipUnless(nest, "Requires NEST") class TestFunctions(unittest.TestCase): def tearDown(self): sim.setup(verbosity='error') def test_list_standard_models(self): cell_types = sim.list_standard_models() self.assertTrue(len(cell_types) > 10) self.assertIsInstance(cell_types[0], str) def test_setup(self): sim.setup(timestep=0.05, min_delay=0.1, max_delay=1.0, verbosity='debug', spike_precision='off_grid', recording_precision=4, threads=2, rng_seed=873465) ks = nest.GetKernelStatus() self.assertEqual(ks['resolution'], 0.05) self.assertEqual(ks['local_num_threads'], 2) self.assertEqual(ks['rng_seed'], 873465) #self.assertEqual(ks['min_delay'], 0.1) #self.assertEqual(ks['max_delay'], 1.0) self.assertEqual(sim.state.spike_precision, "off_grid") def test_run_0(self, ): # see https://github.com/NeuralEnsemble/PyNN/issues/191 sim.setup(timestep=0.123, min_delay=0.246) sim.run(0) self.assertEqual(sim.get_current_time(), 0.0) @unittest.skipUnless(nest, "Requires NEST") class TestPopulation(unittest.TestCase): def setUp(self): sim.setup() self.p = sim.Population(4, sim.IF_cond_exp(**{'tau_m': 12.3, 'cm': lambda i: 0.987 + 0.01 * i, 'i_offset': np.array([-0.21, -0.20, -0.19, -0.18])})) def test_create_native(self): cell_type = sim.native_cell_type('iaf_psc_alpha') p = sim.Population(3, cell_type()) def test__get_parameters(self): ps = self.p._get_parameters('C_m', 'g_L', 'E_ex', 'I_e') ps.evaluate(simplify=True) assert_array_almost_equal(ps['C_m'], np.array([987, 997, 1007, 1017], float), decimal=12) assert_array_almost_equal(ps['I_e'], np.array([-210, -200, -190, -180], float), decimal=12) self.assertEqual(ps['E_ex'], 0.0) def test_set_parameters(self): self.p.set(tau_m=[15.] * self.p.size) def test_set_parameters_singular(self): self.p[0:1].set(tau_m=[20.]) def test_set_parameters_scalar(self): self.p[0:1].set(tau_m=20.) @unittest.skipUnless(nest, "Requires NEST") class TestProjection(unittest.TestCase): def setUp(self): sim.setup() self.p1 = sim.Population(7, sim.IF_cond_exp()) self.p2 = sim.Population(4, sim.IF_cond_exp()) self.p3 = sim.Population(5, sim.IF_curr_alpha()) self.p4 = sim.Population(1, sim.IF_cond_exp()) self.syn_rnd = sim.StaticSynapse(weight=0.123, delay=0.5) self.syn_a2a = sim.StaticSynapse(weight=0.456, delay=0.4) self.random_connect = sim.FixedNumberPostConnector(n=2) self.all2all = sim.AllToAllConnector() self.native_synapse_type = sim.native_synapse_type("stdp_facetshw_synapse_hom") def test_create_simple(self): prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=self.syn_a2a) def test_create_with_synapse_dynamics(self): prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=sim.TsodyksMarkramSynapse()) def test_create_with_native_synapse(self): """ Native synapse with array-like parameters and CommonProperties. """ prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=self.native_synapse_type()) def test_inhibitory_weight(self): prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=self.syn_rnd, receptor_type="inhibitory") weights_list = prj.get("weight", format="list") for pre, post, weight in weights_list: self.assertTrue(weight > 0.) weights_array = prj.get("weight", format="array") self.assertTrue((weights_array > 0.).all()) prj.set(weight=0.456) weights_list = prj.get("weight", format="list") for pre, post, weight in weights_list: self.assertTrue(weight > 0.) weights_array = prj.get("weight", format="array") self.assertTrue((weights_array > 0.).all()) def test_create_with_homogeneous_common_properties(self): with self.assertRaises(ValueError): # create synapse type with heterogeneous common parameters fromlist = sim.FromListConnector(conn_list=[ (0, 0, 10., 100.), (1, 1, 10., 200.)], column_names=["weight", "Wmax"]) prj = sim.Projection(self.p1, self.p2, fromlist, synapse_type=self.native_synapse_type()) def test_set_array(self): weight = 0.123 prj = sim.Projection(self.p1, self.p2, sim.AllToAllConnector()) weight_array = np.ones(prj.shape) * weight prj.set(weight=weight_array) self.assertTrue((weight_array == prj.get("weight", format="array")).all()) def test_single_postsynaptic_neuron(self): prj = sim.Projection(self.p1, self.p4, sim.AllToAllConnector(), synapse_type=sim.StaticSynapse(weight=0.123)) assert prj.shape == (7, 1) weight = 0.456 prj.set(weight=weight) self.assertEqual(prj.get("weight", format="array")[0], weight) weight_array = np.ones(prj.shape) * weight prj.set(weight=weight_array) self.assertTrue((weight_array == prj.get("weight", format="array")).all()) def test_single_presynaptic_neuron(self): prj = sim.Projection(self.p4, self.p1, sim.AllToAllConnector(), synapse_type=sim.StaticSynapse(weight=0.123)) assert prj.shape == (1, 7) weight = 0.456 prj.set(weight=weight) self.assertEqual(prj.get("weight", format="array")[0][0], weight) weight_array = np.ones(prj.shape) * weight prj.set(weight=weight_array) self.assertTrue((weight_array == prj.get("weight", format="array")).all()) def test_single_presynaptic_and_single_postsynaptic_neuron(self): prj = sim.Projection(self.p4, self.p4, sim.AllToAllConnector(), synapse_type=sim.StaticSynapse(weight=0.123)) assert prj.shape == (1, 1) weight = 0.456 prj.set(weight=weight) self.assertEqual(prj.get("weight", format="array")[0][0], weight) weight_array = np.ones(prj.shape) * weight prj.set(weight=weight_array) self.assertTrue((weight_array == prj.get("weight", format="array")).all()) def test_stdp_set_tau_minus(self): """cf https://github.com/NeuralEnsemble/PyNN/issues/423""" intended_tau_minus = 18.9 stdp_model = sim.STDPMechanism( timing_dependence=sim.SpikePairRule(tau_plus=16.7, tau_minus=intended_tau_minus, A_plus=0.005, A_minus=0.005), weight_dependence=sim.AdditiveWeightDependence(w_min=0.0, w_max=1.0), weight=0.5, delay=1.0, dendritic_delay_fraction=1.0) prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=stdp_model) actual_tau_minus = nest.GetStatus(prj.post.node_collection[0], "tau_minus")[0] self.assertEqual(intended_tau_minus, actual_tau_minus) if __name__ == '__main__': unittest.main() PyNN-0.10.0/test/unittests/test_neuron.py000066400000000000000000000356561415343567000204030ustar00rootroot00000000000000# encoding: utf-8 import os try: from unittest.mock import Mock except ImportError: from mock import Mock try: from neuron import h import pyNN.neuron as sim from pyNN.neuron.standardmodels import electrodes from pyNN.neuron import recording, simulator, cells except ImportError: sim = False h = Mock() from pyNN.common import populations import unittest import numpy as np from numpy.testing import assert_array_equal, assert_array_almost_equal skip_ci = False if "JENKINS_SKIP_TESTS" in os.environ: skip_ci = os.environ["JENKINS_SKIP_TESTS"] == "1" class MockCellClass(object): recordable = ['v', 'spikes', 'gsyn_exc', 'gsyn_inh', 'spam'] parameters = ['romans', 'judeans'] injectable = True @classmethod def has_parameter(cls, name): return name in cls.parameters class MockCell(object): parameter_names = ['romans', 'judeans'] def __init__(self, romans=0, judeans=1): self.source_section = h.Section() self.source = self.source_section(0.5)._ref_v #self.synapse = h.tmgsyn(self.source_section(0.5)) self.record = Mock() self.record_v = Mock() self.record_gsyn = Mock() self.memb_init = Mock() self.excitatory = h.ExpSyn(self.source_section(0.5)) self.inhibitory = None self.romans = romans self.judeans = judeans self.foo_init = -99.9 self.traces = {} def __call__(self, pos): return Mock() class MockSynapseType(object): model = None class MockPlasticSynapseType(object): model = "StdwaSA" postsynaptic_variable = "spikes" class MockStepCurrentSource(object): parameter_names = ['amplitudes', 'times'] def __init__(self, **parameters): self._devices = [] def inject_into(self, cell_list): for cell in cell_list: if cell.local: self._devices += [cell] class MockDCSource(object): parameter_names = ['amplitude', 'start', 'stop'] def __init__(self, **parameters): self._devices = [] def inject_into(self, cell_list): for cell in cell_list: if cell.local: self._devices += [cell] class MockID(int): def __init__(self, n): int.__init__(n) self.local = bool(n % 2) self.celltype = MockCellClass() self._cell = MockCell() class MockPopulation(populations.BasePopulation): celltype = MockCellClass() local_cells = [MockID(44), MockID(33)] all_cells = local_cells label = "mock population" def describe(self): return "mock population" class MockProjection(object): receptor_type = 'excitatory' synapse_type = MockSynapseType() pre = MockPopulation() post = MockPopulation() @unittest.skipUnless(sim, "Requires NEURON") class TestFunctions(unittest.TestCase): def test_load_mechanisms(self): self.assertRaises(Exception, simulator.load_mechanisms, "/tmp") # not found def test_is_point_process(self): section = h.Section() clamp = h.SEClamp(section(0.5)) assert simulator.is_point_process(clamp) section.insert('hh') assert not simulator.is_point_process(section(0.5).hh) def test_native_rng_pick(self): rng = Mock() rng.seed = 28754 rarr = simulator.nativeRNG_pick(100, rng, 'uniform', [-3, 6]) assert isinstance(rarr, np.ndarray) self.assertEqual(rarr.shape, (100,)) assert -3 <= rarr.min() < -2.5 assert 5.5 < rarr.max() < 6 def test_list_standard_models(self): cell_types = sim.list_standard_models() self.assertTrue(len(cell_types) > 10) self.assertIsInstance(cell_types[0], str) def test_setup(self): sim.setup(timestep=0.05, min_delay=0.1, max_delay=1.0) self.assertEqual(h.dt, 0.05) # many more things could be tested here def test_setup_with_cvode(self): sim.setup(timestep=0.05, min_delay=0.1, max_delay=1.0, use_cvode=True, rtol=1e-2, atol=2e-6) self.assertEqual(h.dt, 0.05) self.assertEqual(simulator.state.cvode.rtol(), 1e-2) # many more things could be tested here @unittest.skipUnless(sim, "Requires NEURON") class TestInitializer(unittest.TestCase): def test_initializer_initialize(self): init = simulator.initializer orig_initialize = init._initialize init._initialize = Mock() h.finitialize(-65) self.assertTrue(init._initialize.called) init._initialize = orig_initialize def test_register(self): init = simulator.initializer cell = MockID(22) pop = MockPopulation() init.clear() init.register(cell, pop) self.assertEqual(init.cell_list, [cell]) self.assertEqual(init.population_list, [pop]) def test_initialize(self): init = simulator.initializer cell = MockID(77) pop = MockPopulation() init.register(cell, pop) init._initialize() self.assertTrue(cell._cell.memb_init.called) for pcell in pop.local_cells: self.assertTrue(pcell._cell.memb_init.called) def test_clear(self): init = simulator.initializer init.cell_list = range(10) init.population_list = range(10) init.clear() self.assertEqual(init.cell_list, []) self.assertEqual(init.population_list, []) @unittest.skipUnless(sim, "Requires NEURON") class TestState(unittest.TestCase): def test_register_gid(self): cell = MockCell() simulator.state.register_gid(84568345, cell.source, cell.source_section) def test_dt_property(self): simulator.state.dt = 0.01 self.assertEqual(h.dt, 0.01) self.assertEqual(h.steps_per_ms, 100.0) self.assertEqual(simulator.state.dt, 0.01) # def test_reset(self): # simulator.state.running = True # simulator.state.t = 17 # simulator.state.tstop = 123 # init = simulator.initializer # orig_initialize = init._initialize # init._initialize = Mock() # simulator.state.reset() # self.assertEqual(simulator.state.running, False) # self.assertEqual(simulator.state.t, 0.0) # self.assertEqual(simulator.state.tstop, 0.0) # init._initialize = orig_initialize # def test_run(self): # simulator.state.reset() # simulator.state.run(12.3) # self.assertAlmostEqual(h.t, 12.3, places=11) # simulator.state.run(7.7) # self.assertAlmostEqual(h.t, 20.0, places=11) def test_finalize(self): orig_pc = simulator.state.parallel_context simulator.state.parallel_context = Mock() simulator.state.finalize() self.assertTrue(simulator.state.parallel_context.runworker.called) self.assertTrue(simulator.state.parallel_context.done.called) simulator.state.parallel_context = orig_pc @unittest.skipUnless(sim, "Requires NEURON") class TestPopulation(unittest.TestCase): def setUp(self): sim.setup() self.p = sim.Population(4, sim.IF_cond_exp(**{'tau_m': 12.3, 'cm': lambda i: 0.987 + 0.01 * i, 'i_offset': np.array([-0.21, -0.20, -0.19, -0.18])})) def test__get_parameters(self): ps = self.p._get_parameters('c_m', 'tau_m', 'e_e', 'i_offset') ps.evaluate(simplify=True) assert_array_almost_equal(ps['c_m'], np.array([0.987, 0.997, 1.007, 1.017], float), decimal=12) assert_array_almost_equal(ps['i_offset'], np.array([-0.21, -0.2, -0.19, -0.18], float), decimal=12) self.assertEqual(ps['e_e'], 0.0) @unittest.skipUnless(sim, "Requires NEURON") @unittest.skipIf(skip_ci, "Skipping test on CI server") class TestID(unittest.TestCase): def setUp(self): self.id = simulator.ID(984329856) self.id.parent = MockPopulation() self.id._cell = MockCell() def test_create(self): self.assertEqual(self.id, 984329856) def test_build_cell(self): parameters = {'judeans': 1, 'romans': 0} self.id._build_cell(MockCell, parameters) def test_get_initial_value(self): foo_init = self.id.get_initial_value('foo') self.assertEqual(foo_init, -99.9) # def test_set_initial_value(self): @unittest.skipUnless(sim, "Requires NEURON") class TestConnection(unittest.TestCase): def setUp(self): self.pre = 0 self.post = 1 self.c = simulator.Connection(MockProjection(), self.pre, self.post, weight=0.123, delay=0.321) def test_create(self): c = self.c self.assertEqual(c.presynaptic_index, self.pre) self.assertEqual(c.postsynaptic_index, self.post) def test_setup_plasticity(self): self.c._setup_plasticity(MockPlasticSynapseType(), {'wmax': 0.04, 'dendritic_delay_fraction': 0.234}) def test_weight_property(self): self.c.nc.weight[0] = 0.123 self.assertEqual(self.c.weight, 0.123) self.c.weight = 0.234 self.assertEqual(self.c.nc.weight[0], 0.234) def test_delay_property(self): self.c.nc.delay = 12.3 self.assertEqual(self.c.delay, 12.3) self.c.delay = 23.4 self.assertEqual(self.c.nc.delay, 23.4) def test_w_max_property(self): self.c._setup_plasticity(MockPlasticSynapseType(), {'wmax': 0.04, 'dendritic_delay_fraction': 0}) self.assertEqual(self.c.wmax, 0.04) self.c.wmax = 0.05 self.assertEqual(self.c.weight_adjuster.wmax, 0.05) @unittest.skipUnless(sim, "Requires NEURON") class TestProjection(unittest.TestCase): def setUp(self): sim.setup() self.p1 = sim.Population(7, sim.IF_cond_exp()) self.p2 = sim.Population(4, sim.IF_cond_exp()) self.p3 = sim.Population(5, sim.IF_curr_alpha()) self.syn1 = sim.StaticSynapse(weight=0.123, delay=0.5) self.syn2 = sim.StaticSynapse(weight=0.456, delay=0.4) self.random_connect = sim.FixedNumberPostConnector(n=2) self.all2all = sim.AllToAllConnector() def test_create_simple(self): prj = sim.Projection(self.p1, self.p2, self.all2all, self.syn2) def test_create_with_fast_synapse_dynamics(self): prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=sim.TsodyksMarkramSynapse()) @unittest.skipUnless(sim, "Requires NEURON") class TestCurrentSources(unittest.TestCase): def setUp(self): self.cells = [MockID(n) for n in range(5)] def test_inject_dc(self): cs = electrodes.DCSource() cs.inject_into(self.cells) self.assertEqual(cs.stop, 1e12) self.assertEqual(len(cs._devices), 2) def test_inject_step_current(self): cs = MockStepCurrentSource(amplitudes=[1, 2, 3], times=[0.5, 1.5, 2.5]) cs.inject_into(self.cells) self.assertEqual(len(cs._devices), 2) # 2 local cells # need more assertions about iclamps, vectors @unittest.skipUnless(sim, "Requires NEURON") class TestRecorder(unittest.TestCase): def setUp(self): self.p = sim.Population(2, sim.IF_cond_exp()) self.rec = recording.Recorder(self.p) self.cells = self.p.all_cells # [MockID(22), MockID(29)] def tearDown(self): pass def test__record(self): self.rec._record('v', self.cells) self.rec._record('gsyn_inh', self.cells) self.rec._record('spikes', self.cells) self.assertRaises(Exception, self.rec._record, self.cells) # def test__get_v(self): # self.rv.recorded['v'] = self.cells # self.cells[0]._cell.vtrace = np.arange(-65.0, -64.0, 0.1) # self.cells[1]._cell.vtrace = np.arange(-64.0, -65.0, -0.1) # self.cells[0]._cell.record_times = self.cells[1]._cell.record_times = np.arange(0.0, 1.0, 0.1) # simulator.state.t = simulator.state.dt * len(self.cells[0]._cell.vtrace) # vdata = self.rv._get_current_segment(variables=['v'], filter_ids=None) # self.assertEqual(len(vdata.analogsignals), 1) # assert_array_equal(np.array(vdata.analogsignals[0]), # np.vstack((self.cells[0]._cell.vtrace, self.cells[1]._cell.vtrace)).T) def test__get_spikes(self): self.rec.recorded['spikes'] = self.cells self.cells[0]._cell.spike_times = np.arange(101.0, 111.0) self.cells[1]._cell.spike_times = np.arange(13.0, 23.0) simulator.state.t = 111.0 sdata = self.rec._get_current_segment(variables=['spikes'], filter_ids=None) self.assertEqual(len(sdata.spiketrains), 2) assert_array_equal(np.array(sdata.spiketrains[0]), self.cells[0]._cell.spike_times) # def test__get_gsyn(self): # self.rg.recorded['gsyn_exc'] = self.cells # self.rg.recorded['gsyn_inh'] = self.cells # for cell in self.cells: # cell._cell.gsyn_trace = {} # cell._cell.gsyn_trace['excitatory'] = np.arange(0.01, 0.0199, 0.001) # cell._cell.gsyn_trace['inhibitory'] = np.arange(1.01, 1.0199, 0.001) # cell._cell.gsyn_trace['excitatory_TM'] = np.arange(2.01, 2.0199, 0.001) # cell._cell.gsyn_trace['inhibitory_TM'] = np.arange(4.01, 4.0199, 0.001) # cell._cell.record_times = self.cells[1]._cell.record_times = np.arange(0.0, 1.0, 0.1) # simulator.state.t = simulator.state.dt * len(cell._cell.gsyn_trace['excitatory']) # gdata = self.rg._get_current_segment(variables=['gsyn_exc', 'gsyn_inh'], filter_ids=None) # self.assertEqual(len(gdata.analogsignals), 2) # assert_array_equal(np.array(gdata.analogsignals[0][:,0]), # cell._cell.gsyn_trace['excitatory']) # assert_array_equal(np.array(gdata.analogsignals[1][:,0]), # cell._cell.gsyn_trace['inhibitory']) # def test__local_count(self): self.rec.recorded['spikes'] = self.cells self.cells[0]._cell.spike_times = h.Vector(np.arange(101.0, 111.0)) self.cells[1]._cell.spike_times = h.Vector(np.arange(13.0, 33.0)) self.assertEqual(self.rec._local_count('spikes', filter_ids=None), {self.cells[0]: 10, self.cells[1]: 20}) @unittest.skipUnless(sim, "Requires NEURON") class TestStandardIF(unittest.TestCase): def test_create_cond_exp(self): cell = cells.StandardIF("conductance", "exp", tau_m=12.3, c_m=0.246, v_rest=-67.8) self.assertAlmostEqual(cell.area(), 1e5, places=10) # µm² self.assertEqual(cell(0.5).cm, 0.246) self.assertEqual(cell(0.5).pas.g, 2e-5) def test_get_attributes(self): cell = cells.StandardIF("conductance", "exp", tau_m=12.3, c_m=0.246, v_rest=-67.8) self.assertAlmostEqual(cell.tau_m, 12.3, places=10) self.assertAlmostEqual(cell.c_m, 0.246, places=10) if __name__ == '__main__': unittest.main() PyNN-0.10.0/test/unittests/test_parameters.py000066400000000000000000000351071415343567000212270ustar00rootroot00000000000000""" Tests of the parameters module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import operator import numpy as np from lazyarray import larray from numpy.testing import assert_array_equal from nose.tools import assert_raises, assert_equal from pyNN.parameters import LazyArray, ParameterSpace, Sequence from pyNN import random, errors from .mocks import MockRNG # test LazyArray def test_create_with_int(): A = LazyArray(3, shape=(5,)) assert A.shape == (5,) assert A.evaluate(simplify=True) == 3 def test_create_with_float(): A = LazyArray(3.0, shape=(5,)) assert A.shape == (5,) assert A.evaluate(simplify=True) == 3.0 def test_create_with_list(): A = LazyArray([1, 2, 3], shape=(3,)) assert A.shape == (3,) assert_array_equal(A.evaluate(simplify=True), np.array([1, 2, 3])) def test_create_with_array(): A = LazyArray(np.array([1, 2, 3]), shape=(3,)) assert A.shape == (3,) assert_array_equal(A.evaluate(simplify=True), np.array([1, 2, 3])) def test_create_inconsistent(): assert_raises(ValueError, LazyArray, [1, 2, 3], shape=4) def test_create_with_invalid_string(): A = LazyArray("d+2", shape=3) def test_create_with_invalid_string(): assert_raises(errors.InvalidParameterValueError, LazyArray, "x+y", shape=3) def test_setitem_nonexpanded_same_value(): A = LazyArray(3, shape=(5,)) assert A.evaluate(simplify=True) == 3 A[0] = 3 assert A.evaluate(simplify=True) == 3 def test_setitem_invalid_value(): A = LazyArray(3, shape=(5,)) assert_raises(TypeError, A.__setitem__, "abc") def test_setitem_nonexpanded_different_value(): A = LazyArray(3, shape=(5,)) assert A.evaluate(simplify=True) == 3 A[0] = 4 A[4] = 5 assert_array_equal(A.evaluate(simplify=True), np.array([4, 3, 3, 3, 5])) def test_columnwise_iteration_with_flat_array(): m = LazyArray(5, shape=(4, 3)) # 4 rows, 3 columns cols = [col for col in m.by_column()] assert_equal(cols, [5, 5, 5]) def test_columnwise_iteration_with_structured_array(): input = np.arange(12).reshape((4, 3)) m = LazyArray(input, shape=(4, 3)) # 4 rows, 3 columns cols = [col for col in m.by_column()] assert_array_equal(cols[0], input[:, 0]) assert_array_equal(cols[2], input[:, 2]) def test_columnwise_iteration_with_random_array_parallel_safe_no_mask(): orig_get_mpi_config = random.get_mpi_config # first, with a single MPI node random.get_mpi_config = lambda: (0, 2) input = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(parallel_safe=True)) m = LazyArray(input, shape=(4, 3)) cols_np1 = [col for col in m.by_column()] # now, on one node of two random.get_mpi_config = lambda: (1, 2) input = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(parallel_safe=True)) m = LazyArray(input, shape=(4, 3)) cols_np2_1 = [col for col in m.by_column()] for i in range(3): assert_array_equal(cols_np1[i], cols_np2_1[i]) random.get_mpi_config = orig_get_mpi_config def test_columnwise_iteration_with_function(): def input(i, j): return 2 * i + j m = LazyArray(input, shape=(4, 3)) cols = [col for col in m.by_column()] assert_array_equal(cols[0], np.array([0, 2, 4, 6])) assert_array_equal(cols[1], np.array([1, 3, 5, 7])) assert_array_equal(cols[2], np.array([2, 4, 6, 8])) def test_columnwise_iteration_with_flat_array_and_mask(): m = LazyArray(5, shape=(4, 3)) # 4 rows, 3 columns mask = np.array([True, False, True]) cols = [col for col in m.by_column(mask=mask)] assert_equal(cols, [5, 5]) def test_columnwise_iteration_with_structured_array_and_mask(): input = np.arange(12).reshape((4, 3)) m = LazyArray(input, shape=(4, 3)) # 4 rows, 3 columns mask = np.array([False, True, True]) cols = [col for col in m.by_column(mask=mask)] assert_array_equal(cols[0], input[:, 1]) assert_array_equal(cols[1], input[:, 2]) def test_columnwise_iteration_with_random_array_parallel_safe_with_mask(): orig_get_mpi_config = random.get_mpi_config mask = np.array([False, False, True]) # first, with a single MPI node random.get_mpi_config = lambda: (0, 2) input = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(parallel_safe=True)) m = LazyArray(input, shape=(4, 3)) cols_np1 = [col for col in m.by_column(mask=mask)] # now, on one node of two random.get_mpi_config = lambda: (0, 2) input = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(parallel_safe=True)) m = LazyArray(input, shape=(4, 3)) cols_np2_0 = [col for col in m.by_column(mask=mask)] # now, on the other node of two random.get_mpi_config = lambda: (1, 2) input = random.RandomDistribution('uniform', (0, 1), rng=MockRNG(parallel_safe=True)) m = LazyArray(input, shape=(4, 3)) cols_np2_1 = [col for col in m.by_column(mask=mask)] assert_equal(len(cols_np1), 1) assert_equal(len(cols_np2_0), 1) assert_equal(len(cols_np2_1), 1) assert_array_equal(cols_np1[0], cols_np2_0[0]) random.get_mpi_config = orig_get_mpi_config def test_evaluate_with_flat_array(): m = LazyArray(5, shape=(4, 3)) assert_array_equal(m.evaluate(), 5 * np.ones((4, 3))) def test_evaluate_with_structured_array(): input = np.arange(12).reshape((4, 3)) m = LazyArray(input, shape=(4, 3)) assert_array_equal(m.evaluate(), input) def test_evaluate_with_functional_array(): def input(i, j): return 2 * i + j m = LazyArray(input, shape=(4, 3)) assert_array_equal(m.evaluate(), np.array([[0, 1, 2], [2, 3, 4], [4, 5, 6], [6, 7, 8]])) def test_iadd_with_flat_array(): m = LazyArray(5, shape=(4, 3)) m += 2 assert_array_equal(m.evaluate(), 7 * np.ones((4, 3))) assert_equal(m.base_value, 5) assert_equal(m.evaluate(simplify=True), 7) def test_add_with_flat_array(): m0 = LazyArray(5, shape=(4, 3)) m1 = m0 + 2 assert_equal(m1.evaluate(simplify=True), 7) assert_equal(m0.evaluate(simplify=True), 5) def test_lt_with_flat_array(): m0 = LazyArray(5, shape=(4, 3)) m1 = m0 < 10 assert_equal(m1.evaluate(simplify=True), True) assert_equal(m0.evaluate(simplify=True), 5) def test_lt_with_structured_array(): input = np.arange(12).reshape((4, 3)) m0 = LazyArray(input, shape=(4, 3)) m1 = m0 < 5 assert_array_equal(m1.evaluate(simplify=True), input < 5) def test_structured_array_lt_array(): input = np.arange(12).reshape((4, 3)) m0 = LazyArray(input, shape=(4, 3)) comparison = 5 * np.ones((4, 3)) m1 = m0 < comparison assert_array_equal(m1.evaluate(simplify=True), input < comparison) def test_multiple_operations_with_structured_array(): input = np.arange(12).reshape((4, 3)) m0 = LazyArray(input, shape=(4, 3)) m1 = (m0 + 2) < 5 m2 = (m0 < 5) + 2 assert_array_equal(m1.evaluate(simplify=True), (input + 2) < 5) assert_array_equal(m2.evaluate(simplify=True), (input < 5) + 2) assert_array_equal(m0.evaluate(simplify=True), input) def test_apply_function_to_constant_array(): def f(m): return 2 * m + 3 m0 = LazyArray(5, shape=(4, 3)) m1 = f(m0) assert isinstance(m1, larray) assert_equal(m1.evaluate(simplify=True), 13) # the following tests the internals, not the behaviour # it is just to check I understand what's going on assert_equal(m1.operations, [(operator.mul, 2), (operator.add, 3)]) def test_apply_function_to_structured_array(): def f(m): return 2 * m + 3 input = np.arange(12).reshape((4, 3)) m0 = LazyArray(input, shape=(4, 3)) m1 = f(m0) assert isinstance(m1, larray) assert_array_equal(m1.evaluate(simplify=True), input * 2 + 3) def test_apply_function_to_functional_array(): def input(i, j): return 2 * i + j m0 = LazyArray(input, shape=(4, 3)) def f(m): return 2 * m + 3 m1 = f(m0) assert_array_equal(m1.evaluate(), np.array([[3, 5, 7], [7, 9, 11], [11, 13, 15], [15, 17, 19]])) def test_add_two_constant_arrays(): m0 = LazyArray(5, shape=(4, 3)) m1 = LazyArray(7, shape=(4, 3)) m2 = m0 + m1 assert_equal(m2.evaluate(simplify=True), 12) # the following tests the internals, not the behaviour # it is just to check I understand what's going on assert_equal(m2.base_value, m0.base_value) assert_equal(m2.operations, [(operator.add, m1)]) def test_add_incommensurate_arrays(): m0 = LazyArray(5, shape=(4, 3)) m1 = LazyArray(7, shape=(5, 3)) assert_raises(ValueError, m0.__add__, m1) def test_getitem_from_constant_array(): m = LazyArray(3, shape=(4, 3)) assert m[0, 0] == m[3, 2] == m[-1, 2] == m[-4, 2] == m[2, -3] == 3 assert_raises(IndexError, m.__getitem__, (4, 0)) assert_raises(IndexError, m.__getitem__, (2, -4)) def test_getitem_from_constant_array(): m = LazyArray(3 * np.ones((4, 3)), shape=(4, 3)) assert m[0, 0] == m[3, 2] == m[-1, 2] == m[-4, 2] == m[2, -3] == 3 assert_raises(IndexError, m.__getitem__, (4, 0)) assert_raises(IndexError, m.__getitem__, (2, -4)) class ParameterSpaceTest(unittest.TestCase): def test_evaluate(self): ps = ParameterSpace({'a': [2, 3, 5, 8], 'b': 7, 'c': lambda i: 3 * i + 2}, shape=(4,)) self.assertIsInstance(ps['c'], LazyArray) ps.evaluate() assert_array_equal(ps['c'], np.array([2, 5, 8, 11])) def test_evaluate_with_mask(self): ps = ParameterSpace({'a': [2, 3, 5, 8, 13], 'b': 7, 'c': lambda i: 3 * i + 2}, shape=(5,)) ps.evaluate(mask=[1, 3, 4]) expected = {'a': np.array([3, 8, 13]), 'c': np.array([5, 11, 14]), 'b': np.array([7, 7, 7])} for key in expected: assert_array_equal(expected[key], ps[key]) def test_evaluate_with_mask_2D(self): ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]], 'b': 7, 'c': lambda i, j: 3 * i - 2 * j}, shape=(2, 5)) ps2d.evaluate(mask=(slice(None), [1, 3, 4])) assert_array_equal(ps2d['a'], np.array([[3, 8, 13], [34, 89, 144]])) assert_array_equal(ps2d['c'], np.array([[-2, -6, -8], [1, -3, -5]])) def test_evaluate_with_mask_2D(self): ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]], 'b': 7, 'c': lambda i, j: 3 * i - 2 * j}, shape=(2, 5)) ps2d.evaluate(mask=(slice(None), [1, 3, 4])) assert_array_equal(ps2d['a'], np.array([[3, 8, 13], [34, 89, 144]])) assert_array_equal(ps2d['c'], np.array([[-2, -6, -8], [1, -3, -5]])) def test_iteration(self): ps = ParameterSpace({'a': [2, 3, 5, 8, 13], 'b': 7, 'c': lambda i: 3 * i + 2}, shape=(5,)) ps.evaluate(mask=[1, 3, 4]) self.assertEqual(list(ps), [{'a': 3, 'c': 5, 'b': 7}, {'a': 8, 'c': 11, 'b': 7}, {'a': 13, 'c': 14, 'b': 7}]) def test_iteration_items(self): ps = ParameterSpace({'a': [2, 3, 5, 8, 13], 'b': 7, 'c': lambda i: 3 * i + 2}, shape=(5,)) ps.evaluate(mask=[1, 3, 4]) expected = {'a': np.array([3, 8, 13]), 'c': np.array([5, 11, 14]), 'b': np.array([7, 7, 7])} for key, value in ps.items(): assert_array_equal(expected[key], value) def test_columnwise_iteration(self): ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]], 'b': 7, 'c': lambda i, j: 3 * i - 2 * j}, shape=(2, 5)) ps2d.evaluate(mask=(slice(None), [1, 3, 4])) expected = [{'a': np.array([3, 34]), 'b': np.array([7, 7]), 'c': np.array([-2, 1])}, {'a': np.array([8, 89]), 'b': np.array([7, 7]), 'c': np.array([-6, -3])}, {'a': np.array([13, 144]), 'b': np.array([7, 7]), 'c': np.array([-8, -5])}] for x, y in zip(ps2d.columns(), expected): for key in y: assert_array_equal(x[key], y[key]) def test_columnwise_iteration_single_column(self): ps2d = ParameterSpace({'a': [[2, 3, 5, 8, 13], [21, 34, 55, 89, 144]], 'b': 7, 'c': lambda i, j: 3 * i - 2 * j}, shape=(2, 5)) ps2d.evaluate(mask=(slice(None), 3)) expected = [{'a': np.array([8, 89]), 'b': np.array([7, 7]), 'c': np.array([-6, -3])}] actual = list(ps2d.columns()) for x, y in zip(actual, expected): for key in y: assert_array_equal(x[key], y[key]) def test_create_with_sequence(self): schema = {'a': Sequence} ps = ParameterSpace({'a': Sequence([1, 2, 3])}, schema, shape=(2,)) ps.evaluate() assert_array_equal(ps['a'], np.array( [Sequence([1, 2, 3]), Sequence([1, 2, 3])], dtype=Sequence)) def test_create_with_tuple(self): schema = {'a': Sequence} ps = ParameterSpace({'a': (1, 2, 3)}, schema, shape=(2,)) ps.evaluate() assert_array_equal(ps['a'], np.array( [Sequence([1, 2, 3]), Sequence([1, 2, 3])], dtype=Sequence)) def test_create_with_list_of_sequences(self): schema = {'a': Sequence} ps = ParameterSpace({'a': [Sequence([1, 2, 3]), Sequence([4, 5, 6])]}, schema, shape=(2,)) ps.evaluate() assert_array_equal(ps['a'], np.array( [Sequence([1, 2, 3]), Sequence([4, 5, 6])], dtype=Sequence)) def test_create_with_array_of_sequences(self): schema = {'a': Sequence} ps = ParameterSpace({'a': np.array([Sequence([1, 2, 3]), Sequence([4, 5, 6])], dtype=Sequence)}, schema, shape=(2,)) ps.evaluate() assert_array_equal(ps['a'], np.array( [Sequence([1, 2, 3]), Sequence([4, 5, 6])], dtype=Sequence)) def test_create_with_list_of_lists(self): schema = {'a': Sequence} ps = ParameterSpace({'a': [[1, 2, 3], [4, 5, 6]]}, schema, shape=(2,)) ps.evaluate() assert_array_equal(ps['a'], np.array( [Sequence([1, 2, 3]), Sequence([4, 5, 6])], dtype=Sequence)) if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_population.py000066400000000000000000000612341415343567000212560ustar00rootroot00000000000000""" Tests of the common implementation of the Population class, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import numpy as np import sys from numpy.testing import assert_array_equal, assert_array_almost_equal import quantities as pq try: from unittest.mock import Mock, patch except ImportError: from mock import Mock, patch from .mocks import MockRNG import pyNN.mock as sim from pyNN import random, errors, space from pyNN.parameters import Sequence def setUp(): pass def tearDown(): pass class PopulationTest(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(**extra) def tearDown(self, sim=sim): sim.end() def test_create_with_standard_cell_simple(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) self.assertEqual(p.size, 11) self.assertIsInstance(p.label, str) self.assertIsInstance(p.celltype, sim.IF_cond_exp) self.assertIsInstance(p._structure, space.Line) self.assertEqual(p._positions, None) self.assertEqual(p.initial_values.keys(), p.celltype.default_initial_values.keys()) def test_create_with_parameters(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp(**{'tau_m': 12.3, 'tau_syn_E': lambda i: 0.987 + 0.01 * i, 'tau_syn_I': np.array([0.5, 0.6, 0.7, 0.8])})) tau_syn_E, tau_m, tau_syn_I = p.get(('tau_syn_E', 'tau_m', 'tau_syn_I'), gather=True) assert_array_almost_equal(tau_syn_E, np.array([0.987, 0.997, 1.007, 1.017])) self.assertAlmostEqual(tau_m, 12.3) assert_array_equal(tau_syn_I, np.array([0.5, 0.6, 0.7, 0.8])) # test create native cell # test create native cell with params # test create with structure def test_create_with_implicit_grid(self, sim=sim): p = sim.Population((11,), sim.IF_cond_exp()) self.assertEqual(p.size, 11) self.assertIsInstance(p.structure, space.Line) p = sim.Population((5, 6), sim.IF_cond_exp()) self.assertEqual(p.size, 30) self.assertIsInstance(p.structure, space.Grid2D) p = sim.Population((2, 3, 4), sim.IF_cond_exp()) self.assertEqual(p.size, 24) self.assertIsInstance(p.structure, space.Grid3D) self.assertRaises(Exception, sim.Population, (2, 3, 4, 5), sim.IF_cond_exp()) def test_create_with_empty_spike_source_array(self, sim=sim): # regression test for https://github.com/NeuralEnsemble/PyNN/issues/378 p = sim.Population(11, sim.SpikeSourceArray(spike_times=[])) # def test_create_with_initial_values(): def test_id_to_index(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) self.assertEqual(p.id_to_index(p[0]), 0) self.assertEqual(p.id_to_index(p[10]), 10) def test_id_to_index_with_array(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) assert_array_equal(p.id_to_index(p.all_cells[3:9:2]), np.arange(3, 9, 2)) def test_id_to_index_with_populationview(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) view = p[3:7] self.assertIsInstance(view, sim.PopulationView) assert_array_equal(p.id_to_index(view), np.arange(3, 7)) def test_id_to_index_with_invalid_id(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) self.assertRaises(ValueError, p.id_to_index, p.last_id + 1) self.assertRaises(ValueError, p.id_to_index, p.first_id - 1) def test_id_to_index_with_invalid_ids(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) self.assertRaises(ValueError, p.id_to_index, [p.first_id - 1] + p.all_cells[0:3].tolist()) # def test_id_to_local_index(): # test structure property def test_set_structure(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) p.positions = np.arange(33).reshape(3, 11) new_struct = space.Grid2D() p.structure = new_struct self.assertEqual(p.structure, new_struct) self.assertEqual(p._positions, None) # test positions property def test_get_positions(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) pos1 = np.arange(33).reshape(3, 11) p._structure = Mock() p._structure.generate_positions = Mock(return_value=pos1) self.assertEqual(p._positions, None) assert_array_equal(p.positions, pos1) pos2 = 1 + np.arange(33).reshape(3, 11) p.positions = pos2 assert_array_equal(p.positions, pos2) def test_set_positions(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) assert p._structure is not None new_positions = np.random.uniform(size=(3, 11)) p.positions = new_positions self.assertEqual(p.structure, None) assert_array_equal(p.positions, new_positions) new_positions[0, 0] = 99.9 self.assertNotEqual(p.positions[0, 0], 99.9) def test_position_generator(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) assert_array_equal(p.position_generator(0), p.positions[:, 0]) assert_array_equal(p.position_generator(10), p.positions[:, 10]) assert_array_equal(p.position_generator(-1), p.positions[:, 10]) assert_array_equal(p.position_generator(-11), p.positions[:, 0]) self.assertRaises(IndexError, p.position_generator, 11) self.assertRaises(IndexError, p.position_generator, -12) def test__getitem__int(self, sim=sim): # Should return the correct ID object p = sim.Population(12, sim.IF_cond_exp()) self.assertEqual(p[11], p[0] + 11) self.assertRaises(IndexError, p.__getitem__, 12) self.assertEqual(p[-1], p[11]) def test__getitem__slice(self, sim=sim): # Should return a PopulationView with the correct parent and value # of all_cells p = sim.Population(17, sim.HH_cond_exp()) pv = p[3:9] self.assertEqual(pv.parent, p) assert_array_almost_equal(pv.all_cells, p.all_cells[3:9]) def test__getitem__list(self, sim=sim): p = sim.Population(23, sim.HH_cond_exp()) pv = p[list(range(3, 9))] self.assertEqual(pv.parent, p) assert_array_almost_equal(pv.all_cells, p.all_cells[3:9]) def test__getitem__tuple(self, sim=sim): p = sim.Population(23, sim.HH_cond_exp()) pv = p[(3, 5, 7)] self.assertEqual(pv.parent, p) assert_array_almost_equal(pv.all_cells, p.all_cells[[3, 5, 7]]) def test__getitem__invalid(self, sim=sim): p = sim.Population(23, sim.IF_curr_alpha()) self.assertRaises(TypeError, p.__getitem__, "foo") def test__len__(self, sim=sim): # len(p) should give the global size (all MPI nodes) p = sim.Population(77, sim.IF_cond_exp()) self.assertEqual(len(p), p.size, 77) def test_iter(self, sim=sim): p = sim.Population(6, sim.IF_curr_exp()) itr = p.__iter__() assert hasattr(itr, "next") or hasattr(itr, "__next__") self.assertEqual(len(list(itr)), 6) def test___add__two(self, sim=sim): # adding two populations should give an Assembly p1 = sim.Population(6, sim.IF_curr_exp()) p2 = sim.Population(17, sim.IF_cond_exp()) assembly = p1 + p2 self.assertIsInstance(assembly, sim.Assembly) self.assertEqual(assembly.populations, [p1, p2]) def test___add__three(self, sim=sim): # adding three populations should give an Assembly p1 = sim.Population(6, sim.IF_curr_exp()) p2 = sim.Population(17, sim.IF_cond_exp()) p3 = sim.Population(9, sim.HH_cond_exp()) assembly = p1 + p2 + p3 self.assertIsInstance(assembly, sim.Assembly) self.assertEqual(assembly.populations, [p1, p2, p3]) def test_nearest(self, sim=sim): p = sim.Population(13, sim.IF_cond_exp()) p.positions = np.arange(39).reshape((13, 3)).T self.assertEqual(p.nearest((0.0, 1.0, 2.0)), p[0]) self.assertEqual(p.nearest((3.0, 4.0, 5.0)), p[1]) self.assertEqual(p.nearest((36.0, 37.0, 38.0)), p[12]) self.assertEqual(p.nearest((1.49, 2.49, 3.49)), p[0]) self.assertEqual(p.nearest((1.51, 2.51, 3.51)), p[1]) x, y, z = 4, 5, 6 p = sim.Population((x, y, z), sim.IF_cond_alpha()) self.assertEqual(p.nearest((0.0, 0.0, 0.0)), p[0]) self.assertEqual(p.nearest((0.0, 0.0, 1.0)), p[1]) self.assertEqual(p.nearest((0.0, 1.0, 0.0)), p[z]) self.assertEqual(p.nearest((1.0, 0.0, 0.0)), p[y * z]) self.assertEqual(p.nearest((3.0, 2.0, 1.0)), p[3 * y * z + 2 * z + 1]) self.assertEqual(p.nearest((3.49, 2.49, 1.49)), p[3 * y * z + 2 * z + 1]) self.assertEqual(p.nearest((3.49, 2.49, 1.51)), p[3 * y * z + 2 * z + 2]) # self.assertEqual(p.nearest((3.49,2.49,1.5)), p[3*y*z+2*z+2]) # known to fail #self.assertEqual(p.nearest((2.5,2.5,1.5)), p[3*y*z+3*y+2]) def test_sample(self, sim=sim): p = sim.Population(13, sim.IF_cond_exp()) rng = Mock() rng.permutation = Mock(return_value=np.array( [7, 4, 8, 12, 0, 3, 9, 1, 2, 11, 5, 10, 6])) pv = p.sample(5, rng=rng) assert_array_equal(pv.all_cells, sorted(p.all_cells[[7, 4, 8, 12, 0]])) def test_get_multiple_homogeneous_params_with_gather(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp( **{'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7})) tau_syn_E, tau_m = p.get(('tau_syn_E', 'tau_m'), gather=True) self.assertIsInstance(tau_syn_E, float) self.assertEqual(tau_syn_E, 0.987) self.assertAlmostEqual(tau_m, 12.3) def test_get_single_param_with_gather(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=0.987, tau_syn_I=0.7)) tau_syn_E = p.get('tau_syn_E', gather=True) self.assertEqual(tau_syn_E, 0.987) def test_get_multiple_inhomogeneous_params_with_gather(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=[0.987, 0.988, 0.989, 0.990], tau_syn_I=lambda i: 0.5 + 0.1 * i)) tau_syn_E, tau_m, tau_syn_I = p.get(('tau_syn_E', 'tau_m', 'tau_syn_I'), gather=True) self.assertIsInstance(tau_m, float) self.assertIsInstance(tau_syn_E, np.ndarray) assert_array_equal(tau_syn_E, np.array([0.987, 0.988, 0.989, 0.990])) self.assertAlmostEqual(tau_m, 12.3) assert_array_almost_equal(tau_syn_I, np.array([0.5, 0.6, 0.7, 0.8]), decimal=12) def test_get_multiple_params_no_gather(self, sim=sim): sim.simulator.state.num_processes = 2 sim.simulator.state.mpi_rank = 1 p = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=[0.987, 0.988, 0.989, 0.990], i_offset=lambda i: -0.2 * i)) tau_syn_E, tau_m, i_offset = p.get(('tau_syn_E', 'tau_m', 'i_offset'), gather=False) self.assertIsInstance(tau_m, float) self.assertIsInstance(tau_syn_E, np.ndarray) assert_array_equal(tau_syn_E, np.array([0.988, 0.990])) self.assertEqual(tau_m, 12.3) assert_array_almost_equal(i_offset, np.array([-0.2, -0.6]), decimal=12) sim.simulator.state.num_processes = 1 sim.simulator.state.mpi_rank = 0 def test_get_sequence_param(self, sim=sim): p = sim.Population(3, sim.SpikeSourceArray(spike_times=[Sequence([1, 2, 3, 4]), Sequence([2, 3, 4, 5]), Sequence([3, 4, 5, 6])])) spike_times = p.get('spike_times') self.assertEqual(spike_times.size, 3) assert_array_equal(spike_times[1], Sequence([2, 3, 4, 5])) def test_set(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp, { 'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7}) rng = MockRNG(start=1.21, delta=0.01, parallel_safe=True) p.set(tau_syn_E=random.RandomDistribution('uniform', (0.5, 1.5), rng=rng), tau_m=9.87) tau_m, tau_syn_E, tau_syn_I = p.get(('tau_m', 'tau_syn_E', 'tau_syn_I'), gather=True) assert_array_equal(tau_syn_E, np.array([1.21, 1.22, 1.23, 1.24])) assert_array_almost_equal(tau_m, 9.87 * np.ones((4,))) assert_array_equal(tau_syn_I, 0.7 * np.ones((4,))) def test_set_invalid_name(self, sim=sim): p = sim.Population(9, sim.HH_cond_exp()) self.assertRaises(errors.NonExistentParameterError, p.set, foo=13.2) def test_set_invalid_type(self, sim=sim): p = sim.Population(9, sim.IF_cond_exp()) self.assertRaises(errors.InvalidParameterValueError, p.set, tau_m={}) self.assertRaises(errors.InvalidParameterValueError, p.set, v_reset='bar') def test_set_sequence(self, sim=sim): p = sim.Population(3, sim.SpikeSourceArray()) p.set(spike_times=[Sequence([1, 2, 3, 4]), Sequence([2, 3, 4, 5]), Sequence([3, 4, 5, 6])]) spike_times = p.get('spike_times', gather=True) self.assertEqual(spike_times.size, 3) assert_array_equal(spike_times[1], Sequence([2, 3, 4, 5])) def test_set_array(self, sim=sim): p = sim.Population(5, sim.IF_cond_exp()) p.set(v_thresh=-50.0 + np.arange(5)) assert_array_equal(p.get('v_thresh', gather=True), np.array([-50.0, -49.0, -48.0, -47.0, -46.0])) def test_set_random_distribution_parallel_unsafe(self, sim=sim): orig_rcfg = random.get_mpi_config random.get_mpi_config = lambda: (1, 2) sim.simulator.state.num_processes = 2 sim.simulator.state.mpi_rank = 1 p = sim.Population(4, sim.IF_cond_exp(tau_syn_E=0.987)) rng = MockRNG(start=1.21, delta=0.01, parallel_safe=False) p.set(tau_syn_E=random.RandomDistribution('uniform', (0.8, 1.2), rng=rng)) tau_syn_E = p.get('tau_syn_E', gather=False) assert_array_equal(tau_syn_E, np.array([1.21, 1.22])) random.get_mpi_config = orig_rcfg sim.simulator.state.num_processes = 1 sim.simulator.state.mpi_rank = 0 def test_set_random_distribution_parallel_safe(self, sim=sim): orig_rcfg = random.get_mpi_config random.get_mpi_config = lambda: (1, 2) sim.simulator.state.num_processes = 2 sim.simulator.state.mpi_rank = 1 p = sim.Population(4, sim.IF_cond_exp(tau_syn_E=0.987)) rng = MockRNG(start=1.21, delta=0.01, parallel_safe=True) p.set(tau_syn_E=random.RandomDistribution('uniform', (0.1, 1), rng=rng)) tau_syn_E = p.get('tau_syn_E', gather=False) assert_array_equal(tau_syn_E, np.array([1.22, 1.24])) random.get_mpi_config = orig_rcfg sim.simulator.state.num_processes = 1 sim.simulator.state.mpi_rank = 0 def test_tset(self, sim=sim): p = sim.Population(17, sim.IF_cond_alpha()) p.set = Mock() tau_m = np.linspace(10.0, 20.0, num=p.size) p.tset("tau_m", tau_m) p.set.assert_called_with(tau_m=tau_m) def test_rset(self, sim=sim): p = sim.Population(17, sim.IF_cond_alpha()) p.set = Mock() v_rest = random.RandomDistribution('uniform', low=-70.0, high=-60.0) p.rset("v_rest", v_rest) p.set.assert_called_with(v_rest=v_rest) # def test_set_with_native_rng(): def test_initialize(self, sim=sim): p = sim.Population(17, sim.EIF_cond_exp_isfa_ista()) v_init = np.linspace(-70.0, -60.0, num=p.size) w_init = 0.1 p.initialize(v=v_init, w=w_init) assert_array_equal(p.initial_values['v'].evaluate(simplify=True), v_init) assert_array_equal(p.initial_values['w'].evaluate(simplify=True), w_init) # should call p.record(('v', 'w')) and check that the recorded data starts with the initial value def test_can_record(self, sim=sim): p = sim.Population(17, sim.EIF_cond_exp_isfa_ista()) assert p.can_record('v') assert p.can_record('w') assert p.can_record('gsyn_inh') assert p.can_record('spikes') assert not p.can_record('foo') def test_record_with_single_variable(self, sim=sim): p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p.record('v') sim.run(12.3) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 1) n_values = int(round(12.3 / sim.get_time_step())) + 1 self.assertEqual(data.analogsignals[0].name, 'v') self.assertEqual(data.analogsignals[0].shape, (n_values, p.size)) def test_record_with_multiple_variables(self, sim=sim): p = sim.Population(2, sim.EIF_cond_exp_isfa_ista()) p.record(('v', 'w', 'gsyn_exc')) sim.run(10.0) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 3) n_values = int(round(10.0 / sim.get_time_step())) + 1 names = set(arr.name for arr in data.analogsignals) self.assertEqual(names, set(('v', 'w', 'gsyn_exc'))) for arr in data.analogsignals: self.assertEqual(arr.shape, (n_values, p.size)) def test_record_with_v_and_spikes(self, sim=sim): p = sim.Population(2, sim.EIF_cond_exp_isfa_ista()) p.record(('v', 'spikes')) sim.run(10.0) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 1) n_values = int(round(10.0 / sim.get_time_step())) + 1 names = set(arr.name for arr in data.analogsignals) self.assertEqual(names, set(('v'))) for arr in data.analogsignals: self.assertEqual(arr.shape, (n_values, p.size)) def test_record_v(self, sim=sim): p = sim.Population(2, sim.EIF_cond_exp_isfa_ista()) p.record = Mock() p.record_v("arg1") p.record.assert_called_with('v', "arg1") def test_record_gsyn(self, sim=sim): p = sim.Population(2, sim.EIF_cond_exp_isfa_ista()) p.record = Mock() p.record_gsyn("arg1") p.record.assert_called_with(['gsyn_exc', 'gsyn_inh'], "arg1") def test_record_invalid_variable(self, sim=sim): p = sim.Population(14, sim.IF_curr_alpha()) self.assertRaises(errors.RecordingError, p.record, ('v', 'gsyn_exc')) # can't record gsyn_exc from this celltype # def test_write_data(self, sim=sim): # self.fail() # def test_get_data_with_gather(self, sim=sim): t1 = 12.3 t2 = 13.4 t3 = 14.5 p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p.record('v') sim.run(t1) # what if we call p.record between two run statements? # would be nice to get an AnalogSignal with a non-zero t_start # but then need to make sure we get the right initial value sim.run(t2) sim.reset() p.record('spikes') p.record('w') sim.run(t3) data = p.get_data(gather=True) self.assertEqual(len(data.segments), 2) seg0 = data.segments[0] self.assertEqual(len(seg0.analogsignals), 1) v = seg0.analogsignals[0] self.assertEqual(v.name, 'v') num_points = int(round((t1 + t2) / sim.get_time_step())) + 1 self.assertEqual(v.shape, (num_points, p.size)) self.assertEqual(v.t_start, 0.0 * pq.ms) self.assertEqual(v.units, pq.mV) self.assertEqual(v.sampling_period, 0.1 * pq.ms) self.assertEqual(len(seg0.spiketrains), 0) seg1 = data.segments[1] self.assertEqual(len(seg1.analogsignals), 2) w = seg1.filter(name='w')[0] self.assertEqual(w.name, 'w') num_points = int(round(t3 / sim.get_time_step())) + 1 self.assertEqual(w.shape, (num_points, p.size)) self.assertEqual(v.t_start, 0.0) self.assertEqual(len(seg1.spiketrains), p.size) def test_get_spikes_with_gather(self, sim=sim): t1 = 12.3 t2 = 13.4 t3 = 14.5 p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p.record('v') sim.run(t1) sim.run(t2) sim.reset() p.record('spikes') p.record('w') sim.run(t3) data = p.get_data(gather=True) self.assertEqual(len(data.segments), 2) seg0 = data.segments[0] self.assertEqual(len(seg0.analogsignals), 1) self.assertEqual(len(seg0.spiketrains), 0) seg1 = data.segments[1] self.assertEqual(len(seg1.analogsignals), 2) self.assertEqual(len(seg1.spiketrains), p.size) assert_array_equal(seg1.spiketrains[7], np.array([p.first_id + 7, p.first_id + 7 + 5]) % t3) # def test_get_data_no_gather(self, sim=sim): # self.fail() def test_printSpikes(self, sim=sim): # TODO: implement assert_deprecated p = sim.Population(3, sim.IF_curr_alpha()) p.record('spikes') sim.run(10.0) p.write_data = Mock() p.printSpikes("foo.txt") p.write_data.assert_called_with('foo.txt', 'spikes', True) def test_getSpikes(self, sim=sim): p = sim.Population(3, sim.IF_curr_alpha()) p.record('spikes') sim.run(10.0) p.get_data = Mock() p.getSpikes() p.get_data.assert_called_with('spikes', True) def test_print_v(self, sim=sim): p = sim.Population(3, sim.IF_curr_alpha()) p.record_v() sim.run(10.0) p.write_data = Mock() p.print_v("foo.txt") p.write_data.assert_called_with('foo.txt', 'v', True) def test_get_v(self, sim=sim): p = sim.Population(3, sim.IF_curr_alpha()) p.record_v() sim.run(10.0) p.get_data = Mock() p.get_v() p.get_data.assert_called_with('v', True) def test_print_gsyn(self, sim=sim): p = sim.Population(3, sim.IF_cond_alpha()) p.record_gsyn() sim.run(10.0) p.write_data = Mock() p.print_gsyn("foo.txt") p.write_data.assert_called_with('foo.txt', ['gsyn_exc', 'gsyn_inh'], True) def test_get_gsyn(self, sim=sim): p = sim.Population(3, sim.IF_cond_alpha()) p.record_gsyn() sim.run(10.0) p.get_data = Mock() p.get_gsyn() p.get_data.assert_called_with(['gsyn_exc', 'gsyn_inh'], True) def test_get_spike_counts(self, sim=sim): p = sim.Population(3, sim.EIF_cond_exp_isfa_ista()) p.record('spikes') sim.run(100.0) self.assertEqual(p.get_spike_counts(), {p.all_cells[0]: 2, p.all_cells[1]: 2, p.all_cells[2]: 2}) def test_mean_spike_count(self, sim=sim): p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p.record('spikes') sim.run(100.0) # mock backend always produces two spikes per population self.assertEqual(p.mean_spike_count(), 2.0) # def test_mean_spike_count_on_slave_node(): def test_meanSpikeCount(self, sim=sim): p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) p.record('spikes') sim.run(100.0) p.mean_spike_count = Mock() p.meanSpikeCount() self.assertTrue(p.mean_spike_count.called) def test_inject(self, sim=sim): p = sim.Population(3, sim.IF_curr_alpha()) cs = Mock() p.inject(cs) meth, args, kwargs = cs.method_calls[0] self.assertEqual(meth, "inject_into") self.assertEqual(args, (p,)) def test_inject_into_invalid_celltype(self, sim=sim): p = sim.Population(3, sim.SpikeSourceArray()) self.assertRaises(TypeError, p.inject, Mock()) # def test_save_positions(self, sim=sim): # self.fail() # test describe method def test_describe(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) self.assertIsInstance(p.describe(), str) self.assertIsInstance(p.describe(template=None), dict) def test_save_positions(self, sim=sim): import os p = sim.Population(4, sim.IF_cond_exp(), ) p.positions = np.arange(15, 27).reshape((4, 3)).T output_file = Mock() p.save_positions(output_file) assert_array_equal(output_file.write.call_args[0][0], np.array([[0, 15, 16, 17], [1, 18, 19, 20], [2, 21, 22, 23], [3, 24, 25, 26]])) self.assertEqual(output_file.write.call_args[0][1], {'population': p.label}) if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_populationview.py000066400000000000000000000560231415343567000221510ustar00rootroot00000000000000""" Tests of the common implementation of the PopulationView class, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import numpy as np import sys from numpy.testing import assert_array_equal, assert_array_almost_equal import quantities as pq try: from unittest.mock import Mock, patch except ImportError: from mock import Mock, patch from .mocks import MockRNG import pyNN.mock as sim from pyNN import random, errors, space from pyNN.parameters import Sequence def setUp(): pass def tearDown(): pass class PopulationViewTest(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(**extra) def tearDown(self, sim=sim): sim.end() # test create with population parent and mask selector def test_create_with_slice_selector(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) mask = slice(3, 9, 2) pv = sim.PopulationView(parent=p, selector=mask) self.assertEqual(pv.parent, p) self.assertEqual(pv.size, 3) self.assertEqual(pv.mask, mask) assert_array_equal(pv.all_cells, np.array( [p.all_cells[3], p.all_cells[5], p.all_cells[7]])) #assert_array_equal(pv.local_cells, np.array([p.all_cells[3]])) #assert_array_equal(pv._mask_local, np.array([1,0,0], dtype=bool)) self.assertEqual(pv.celltype, p.celltype) self.assertEqual(pv.first_id, p.all_cells[3]) self.assertEqual(pv.last_id, p.all_cells[7]) def test_create_with_boolean_array_selector(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) mask = np.array([0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0], dtype=bool) pv = sim.PopulationView(parent=p, selector=mask) assert_array_equal(pv.all_cells, np.array( [p.all_cells[3], p.all_cells[5], p.all_cells[7]])) #assert_array_equal(pv.mask, mask) def test_create_with_index_array_selector(self, sim=sim): p = sim.Population(11, sim.IF_cond_alpha()) mask = np.array([3, 5, 7]) pv = sim.PopulationView(parent=p, selector=mask) assert_array_equal(pv.all_cells, np.array( [p.all_cells[3], p.all_cells[5], p.all_cells[7]])) assert_array_equal(pv.mask, mask) # test create with populationview parent and mask selector def test_create_with_slice_selector(self, sim=sim): p = sim.Population(11, sim.HH_cond_exp()) mask1 = slice(0, 9, 1) pv1 = sim.PopulationView(parent=p, selector=mask1) assert_array_equal(pv1.all_cells, p.all_cells[0:9]) mask2 = slice(3, 9, 2) pv2 = sim.PopulationView(parent=pv1, selector=mask2) # or would it be better to resolve the parent chain up to an actual Population? self.assertEqual(pv2.parent, pv1) assert_array_equal(pv2.all_cells, np.array( [p.all_cells[3], p.all_cells[5], p.all_cells[7]])) #assert_array_equal(pv2._mask_local, np.array([1,0,0], dtype=bool)) # test initial values property def test_structure_property(self, sim=sim): p = sim.Population(11, sim.SpikeSourcePoisson()) mask = slice(3, 9, 2) pv = sim.PopulationView(parent=p, selector=mask) self.assertEqual(pv.structure, p.structure) # test positions property def test_get_positions(self, sim=sim): p = sim.Population(11, sim.IF_curr_exp()) ppos = np.random.uniform(size=(3, 11)) p._positions = ppos pv = sim.PopulationView(parent=p, selector=slice(3, 9, 2)) assert_array_equal(pv.positions, np.array([ppos[:, 3], ppos[:, 5], ppos[:, 7]]).T) def test_id_to_index(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) pv = p[2, 5, 7, 8] self.assertEqual(pv.id_to_index(pv[0]), 0) self.assertEqual(pv.id_to_index(pv[3]), 3) self.assertEqual(pv.id_to_index(p[2]), 0) self.assertEqual(pv.id_to_index(p[8]), 3) def test_id_to_index_with_array(self, sim=sim): p = sim.Population(121, sim.IF_curr_alpha()) pv = p[2, 5, 7, 8, 19, 37, 49, 82, 83, 99] assert_array_equal(pv.id_to_index(pv.all_cells[3:9:2]), np.arange(3, 9, 2)) def test_id_to_index_with_invalid_id(self, sim=sim): p = sim.Population(11, sim.IF_curr_alpha()) pv = p[2, 5, 7, 8] self.assertRaises(IndexError, pv.id_to_index, p[0]) self.assertRaises(IndexError, pv.id_to_index, p[9]) # def test_id_to_index_with_invalid_ids(self, sim=sim): # p = sim.Population(11, sim.IF_curr_alpha()) # pv = p[2, 5, 7, 8] # self.assertRaises(IndexError, pv.id_to_index, p.all_cells[[2, 5, 6]]) # currently failing # def test_id_to_local_index(): # test structure property def test_set_structure(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp(), structure=space.Grid2D()) pv = p[2, 5, 7, 8] new_struct = space.Line() def set_struct(struct): pv.structure = struct self.assertRaises(AttributeError, set_struct, new_struct) # test positions property def test_get_positions(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) pos = np.arange(33).reshape(3, 11) p.positions = pos pv = p[2, 5, 7, 8] assert_array_equal(pv.positions, pos[:, [2, 5, 7, 8]]) def test_position_generator(self, sim=sim): p = sim.Population(11, sim.IF_cond_exp()) pv = p[2, 5, 7, 8] assert_array_equal(pv.position_generator(0), p.positions[:, 2]) assert_array_equal(pv.position_generator(3), p.positions[:, 8]) assert_array_equal(pv.position_generator(-1), p.positions[:, 8]) assert_array_equal(pv.position_generator(-4), p.positions[:, 2]) self.assertRaises(IndexError, pv.position_generator, 4) self.assertRaises(IndexError, pv.position_generator, -5) def test__getitem__int(self, sim=sim): # Should return the correct ID object p = sim.Population(12, sim.IF_cond_exp()) pv = p[1, 5, 6, 8, 11] self.assertEqual(pv[0], p[1], 42) self.assertEqual(pv[4], p[11], 53) self.assertRaises(IndexError, pv.__getitem__, 6) self.assertEqual(pv[-1], p[11], 53) def test__getitem__slice(self, sim=sim): # Should return a PopulationView with the correct parent and value # of all_cells p = sim.Population(17, sim.HH_cond_exp()) pv1 = p[1, 5, 6, 8, 11, 12, 15, 16] pv2 = pv1[2:6] self.assertEqual(pv2.parent, pv1) self.assertEqual(pv2.grandparent, p) assert_array_equal(pv2.all_cells, pv1.all_cells[[2, 3, 4, 5]]) assert_array_equal(pv2.all_cells, p.all_cells[[6, 8, 11, 12]]) def test__getitem__list(self, sim=sim): p = sim.Population(23, sim.HH_cond_exp()) pv1 = p[1, 5, 6, 8, 11, 12, 15, 16, 19, 20] pv2 = pv1[list(range(3, 8))] self.assertEqual(pv2.parent, pv1) assert_array_almost_equal(pv2.all_cells, p.all_cells[[8, 11, 12, 15, 16]]) def test__getitem__tuple(self, sim=sim): p = sim.Population(23, sim.HH_cond_exp()) pv1 = p[1, 5, 6, 8, 11, 12, 15, 16, 19, 20] pv2 = pv1[(3, 5, 7)] self.assertEqual(pv2.parent, pv1) assert_array_almost_equal(pv2.all_cells, p.all_cells[[8, 12, 16]]) def test__getitem__invalid(self, sim=sim): p = sim.Population(23, sim.IF_curr_alpha()) pv = p[1, 5, 6, 8, 11, 12, 15, 16, 19, 20] self.assertRaises(TypeError, pv.__getitem__, "foo") def test__len__(self, sim=sim): # len(p) should give the global size (all MPI nodes) p = sim.Population(77, sim.IF_cond_exp()) pv = p[1, 5, 6, 8, 11, 12, 15, 16, 19, 20] self.assertEqual(len(pv), pv.size, 10) def test_iter(self, sim=sim): p = sim.Population(33, sim.IF_curr_exp()) pv = p[1, 5, 6, 8, 11, 12] itr = pv.__iter__() assert hasattr(itr, "next") or hasattr(itr, "__next__") self.assertEqual(len(list(itr)), 6) def test___add__two(self, sim=sim): # adding two population views should give an Assembly pv1 = sim.Population(6, sim.IF_curr_exp())[2, 3, 5] pv2 = sim.Population(17, sim.IF_cond_exp())[4, 2, 16] assembly = pv1 + pv2 self.assertIsInstance(assembly, sim.Assembly) self.assertEqual(assembly.populations, [pv1, pv2]) def test___add__three(self, sim=sim): # adding three population views should give an Assembly pv1 = sim.Population(6, sim.IF_curr_exp())[0:3] pv2 = sim.Population(17, sim.IF_cond_exp())[1, 5, 14] pv3 = sim.Population(9, sim.HH_cond_exp())[3:8] assembly = pv1 + pv2 + pv3 self.assertIsInstance(assembly, sim.Assembly) self.assertEqual(assembly.populations, [pv1, pv2, pv3]) def test_nearest(self, sim=sim): p = sim.Population(13, sim.IF_cond_exp()) p.positions = np.arange(39).reshape((13, 3)).T pv = p[0, 2, 5, 11] self.assertEqual(pv.nearest((0.0, 1.0, 2.0)), pv[0]) self.assertEqual(pv.nearest((3.0, 4.0, 5.0)), pv[0]) self.assertEqual(pv.nearest((36.0, 37.0, 38.0)), pv[3]) self.assertEqual(pv.nearest((1.49, 2.49, 3.49)), pv[0]) self.assertEqual(pv.nearest((1.51, 2.51, 3.51)), pv[0]) def test_sample(self, sim=sim): p = sim.Population(13, sim.IF_cond_exp()) pv1 = p[0, 3, 7, 10, 12] rng = Mock() rng.permutation = Mock(return_value=np.array([3, 1, 0, 2, 4])) pv2 = pv1.sample(3, rng=rng) assert_array_equal(pv2.all_cells, sorted(p.all_cells[[10, 3, 0]])) def test_get_multiple_homogeneous_params_with_gather(self, sim=sim): p = sim.Population(10, sim.IF_cond_exp, { 'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7}) pv = p[3:7] tau_syn_E, tau_m = pv.get(('tau_syn_E', 'tau_m'), gather=True) self.assertEqual(tau_syn_E, 0.987) self.assertAlmostEqual(tau_m, 12.3) def test_get_single_homogeneous_param_with_gather(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp, { 'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7}) pv = p[:] tau_syn_E = pv.get('tau_syn_E', gather=True) self.assertEqual(tau_syn_E, 0.987) def test_get_multiple_inhomogeneous_params_with_gather(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp(tau_m=12.3, tau_syn_E=[0.987, 0.988, 0.989, 0.990], tau_syn_I=lambda i: 0.5 + 0.1 * i)) pv = p[0, 1, 3] tau_syn_E, tau_m, tau_syn_I = pv.get(('tau_syn_E', 'tau_m', 'tau_syn_I'), gather=True) self.assertIsInstance(tau_m, float) self.assertIsInstance(tau_syn_E, np.ndarray) assert_array_equal(tau_syn_E, np.array([0.987, 0.988, 0.990])) self.assertAlmostEqual(tau_m, 12.3) assert_array_almost_equal(tau_syn_I, np.array([0.5, 0.6, 0.8]), decimal=12) # def test_get_multiple_params_no_gather(self, sim=sim): def test_get_sequence_param(self, sim=sim): p = sim.Population(3, sim.SpikeSourceArray, {'spike_times': [Sequence([1, 2, 3, 4]), Sequence([2, 3, 4, 5]), Sequence([3, 4, 5, 6])]}) pv = p[1:] spike_times = pv.get('spike_times') self.assertEqual(spike_times.size, 2) assert_array_equal(spike_times[1], Sequence([3, 4, 5, 6])) def test_set(self, sim=sim): p = sim.Population(4, sim.IF_cond_exp, { 'tau_m': 12.3, 'tau_syn_E': 0.987, 'tau_syn_I': 0.7}) pv = p[:3] rng = MockRNG(start=1.21, delta=0.01, parallel_safe=True) pv.set(tau_syn_E=random.RandomDistribution('uniform', (0.8, 1.2), rng=rng), tau_m=9.87) tau_m, tau_syn_E, tau_syn_I = p.get(('tau_m', 'tau_syn_E', 'tau_syn_I'), gather=True) assert_array_equal(tau_syn_E, np.array([1.21, 1.22, 1.23, 0.987])) assert_array_almost_equal(tau_m, np.array([9.87, 9.87, 9.87, 12.3])) assert_array_equal(tau_syn_I, 0.7 * np.ones((4,))) tau_m, tau_syn_E, tau_syn_I = pv.get(('tau_m', 'tau_syn_E', 'tau_syn_I'), gather=True) assert_array_equal(tau_syn_E, np.array([1.21, 1.22, 1.23])) assert_array_almost_equal(tau_m, np.array([9.87, 9.87, 9.87])) assert_array_equal(tau_syn_I, 0.7 * np.ones((3,))) def test_set_invalid_name(self, sim=sim): p = sim.Population(9, sim.HH_cond_exp()) pv = p[3:5] self.assertRaises(errors.NonExistentParameterError, pv.set, foo=13.2) def test_set_invalid_type(self, sim=sim): p = sim.Population(9, sim.IF_cond_exp()) pv = p[::3] self.assertRaises(errors.InvalidParameterValueError, pv.set, tau_m={}) self.assertRaises(errors.InvalidParameterValueError, pv.set, v_reset='bar') def test_set_sequence(self, sim=sim): p = sim.Population(5, sim.SpikeSourceArray()) pv = p[0, 2, 4] pv.set(spike_times=[Sequence([1, 2, 3, 4]), Sequence([2, 3, 4, 5]), Sequence([3, 4, 5, 6])]) spike_times = p.get('spike_times', gather=True) self.assertEqual(spike_times.size, 5) assert_array_equal(spike_times[1], Sequence([])) assert_array_equal(spike_times[2], Sequence([2, 3, 4, 5])) def test_set_array(self, sim=sim): p = sim.Population(5, sim.IF_cond_exp, {'v_thresh': -54.3}) pv = p[2:] pv.set(v_thresh=-50.0 + np.arange(3)) assert_array_equal(p.get('v_thresh', gather=True), np.array([-54.3, -54.3, -50.0, -49.0, -48.0])) def test_tset(self, sim=sim): p = sim.Population(17, sim.IF_cond_alpha()) pv = p[::4] pv.set = Mock() tau_m = np.linspace(10.0, 20.0, num=pv.size) pv.tset("tau_m", tau_m) pv.set.assert_called_with(tau_m=tau_m) def test_rset(self, sim=sim): p = sim.Population(17, sim.IF_cond_alpha()) pv = p[::4] pv.set = Mock() v_rest = random.RandomDistribution('uniform', low=-70.0, high=-60.0) pv.rset("v_rest", v_rest) pv.set.assert_called_with(v_rest=v_rest) # def test_set_with_native_rng(): # def test_initialize(self, sim=sim): # p = sim.Population(7, sim.EIF_cond_exp_isfa_ista, # initial_values={'v': -65.4, 'w': 0.0}) # pv = p[::2] # # v_init = np.linspace(-70.0, -67.0, num=pv.size) # w_init = 0.1 # pv.initialize(v=v_init, w=w_init) # assert_array_equal(p.initial_values['v'].evaluate(simplify=True), # np.array([-70.0, -65.4, -69.0, -65.4, -68.0, -65.4, -67.0])) # assert_array_equal(p.initial_values['w'].evaluate(simplify=True), # np.array([0.1, 0.0, 0.1, 0.0, 0.1, 0.0, 0.1])) # # should call p.record(('v', 'w')) and check that the recorded data starts with the initial value def test_can_record(self, sim=sim): pv = sim.Population(17, sim.EIF_cond_exp_isfa_ista())[::2] assert pv.can_record('v') assert pv.can_record('w') assert pv.can_record('gsyn_inh') assert pv.can_record('spikes') assert not pv.can_record('foo') def test_record_with_single_variable(self, sim=sim): p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) pv = p[0, 4, 6, 13] pv.record('v') sim.run(12.3) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 1) n_values = int(round(12.3 / sim.get_time_step())) + 1 self.assertEqual(data.analogsignals[0].name, 'v') self.assertEqual(data.analogsignals[0].shape, (n_values, pv.size)) def test_record_with_multiple_variables(self, sim=sim): p = sim.Population(4, sim.EIF_cond_exp_isfa_ista()) pv = p[0, 3] pv.record(('v', 'w', 'gsyn_exc')) sim.run(10.0) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 3) n_values = int(round(10.0 / sim.get_time_step())) + 1 names = set(arr.name for arr in data.analogsignals) self.assertEqual(names, set(('v', 'w', 'gsyn_exc'))) for arr in data.analogsignals: self.assertEqual(arr.shape, (n_values, pv.size)) def test_record_with_v_spikes(self, sim=sim): p = sim.Population(4, sim.EIF_cond_exp_isfa_ista()) pv = p[0, 3] pv.record(('v', 'spikes')) sim.run(10.0) data = p.get_data(gather=True).segments[0] self.assertEqual(len(data.analogsignals), 1) n_values = int(round(10.0 / sim.get_time_step())) + 1 names = set(arr.name for arr in data.analogsignals) self.assertEqual(names, set(('v'))) for arr in data.analogsignals: self.assertEqual(arr.shape, (n_values, pv.size)) def test_record_v(self, sim=sim): pv = sim.Population(2, sim.EIF_cond_exp_isfa_ista())[0:1] pv.record = Mock() pv.record_v("arg1") pv.record.assert_called_with('v', "arg1") def test_record_gsyn(self, sim=sim): pv = sim.Population(2, sim.EIF_cond_exp_isfa_ista())[1:] pv.record = Mock() pv.record_gsyn("arg1") pv.record.assert_called_with(['gsyn_exc', 'gsyn_inh'], "arg1") def test_record_invalid_variable(self, sim=sim): pv = sim.Population(14, sim.IF_curr_alpha())[::3] self.assertRaises(errors.RecordingError, pv.record, ('v', 'gsyn_exc')) # can't record gsyn_exc from this celltype # def test_write_data(self, sim=sim): # self.fail() # def test_get_data_with_gather(self, sim=sim): t1 = 12.3 t2 = 13.4 t3 = 14.5 p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) pv = p[::3] pv.record('v') sim.run(t1) # what if we call p.record between two run statements? # would be nice to get an AnalogSignal with a non-zero t_start # but then need to make sure we get the right initial value sim.run(t2) sim.reset() pv.record('spikes') pv.record('w') sim.run(t3) data = p.get_data(gather=True) self.assertEqual(len(data.segments), 2) seg0 = data.segments[0] self.assertEqual(len(seg0.analogsignals), 1) v = seg0.analogsignals[0] self.assertEqual(v.name, 'v') num_points = int(round((t1 + t2) / sim.get_time_step())) + 1 self.assertEqual(v.shape, (num_points, pv.size)) self.assertEqual(v.t_start, 0.0 * pq.ms) self.assertEqual(v.units, pq.mV) self.assertEqual(v.sampling_period, 0.1 * pq.ms) self.assertEqual(len(seg0.spiketrains), 0) seg1 = data.segments[1] self.assertEqual(len(seg1.analogsignals), 2) w = seg1.filter(name='w')[0] self.assertEqual(w.name, 'w') num_points = int(round(t3 / sim.get_time_step())) + 1 self.assertEqual(w.shape, (num_points, pv.size)) self.assertEqual(v.t_start, 0.0) self.assertEqual(len(seg1.spiketrains), pv.size) def test_get_data_with_gather(self, sim=sim): t1 = 12.3 t2 = 13.4 t3 = 14.5 p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) pv = p[::3] pv.record('v') sim.run(t1) # what if we call p.record between two run statements? # would be nice to get an AnalogSignal with a non-zero t_start # but then need to make sure we get the right initial value sim.run(t2) sim.reset() pv.record('spikes') pv.record('w') sim.run(t3) data = p.get_data(gather=True) self.assertEqual(len(data.segments), 2) seg0 = data.segments[0] self.assertEqual(len(seg0.analogsignals), 1) self.assertEqual(len(seg0.spiketrains), 0) seg1 = data.segments[1] self.assertEqual(len(seg1.analogsignals), 2) self.assertEqual(len(seg1.spiketrains), pv.size) assert_array_equal(seg1.spiketrains[2], np.array([p.first_id + 6, p.first_id + 6 + 5]) % t3) # def test_get_data_no_gather(self, sim=sim): # self.fail() def test_get_spike_counts(self, sim=sim): p = sim.Population(5, sim.EIF_cond_exp_isfa_ista()) pv = p[0, 1, 4] pv.record('spikes') sim.run(100.0) self.assertEqual(p.get_spike_counts(), {p.all_cells[0]: 2, p.all_cells[1]: 2, p.all_cells[4]: 2}) def test_mean_spike_count(self, sim=sim): p = sim.Population(14, sim.EIF_cond_exp_isfa_ista()) pv = p[2::3] pv.record('spikes') sim.run(100.0) self.assertEqual(p.mean_spike_count(), 2.0) # def test_mean_spike_count_on_slave_node(): def test_inject(self, sim=sim): pv = sim.Population(3, sim.IF_curr_alpha())[1, 2] cs = Mock() pv.inject(cs) meth, args, kwargs = cs.method_calls[0] self.assertEqual(meth, "inject_into") self.assertEqual(args, (pv,)) def test_inject_into_invalid_celltype(self, sim=sim): pv = sim.Population(3, sim.SpikeSourceArray())[:2] self.assertRaises(TypeError, pv.inject, Mock()) # def test_save_positions(self, sim=sim): # self.fail() # test describe method def test_describe(self, sim=sim): pv = sim.Population(11, sim.IF_cond_exp())[::4] self.assertIsInstance(pv.describe(), str) self.assertIsInstance(pv.describe(template=None), dict) def test_index_in_grandparent(self, sim=sim): pv1 = sim.Population(11, sim.IF_cond_exp())[0, 1, 3, 4, 6, 7, 9] pv2 = pv1[2, 3, 5, 6] assert_array_equal(pv1.index_in_grandparent([2, 4, 6]), np.array([3, 6, 9])) assert_array_equal(pv2.index_in_grandparent([0, 1, 3]), np.array([3, 4, 9])) def test_index_from_parent_index(self, sim=sim): parent = sim.Population(20, sim.IF_cond_exp()) # test with slice mask pv1 = parent[2:16:3] assert_array_equal( pv1.index_from_parent_index(np.array([2, 5, 8, 11, 14])), np.array([0, 1, 2, 3, 4]) ) self.assertEqual(pv1.index_from_parent_index(11), 3) # test with array mask pv2 = parent[np.array([1, 2, 3, 5, 8, 13])] assert_array_equal( pv2.index_from_parent_index(np.array([2, 5, 13])), np.array([1, 3, 5]) ) def test_save_positions(self, sim=sim): import os p = sim.Population(7, sim.IF_cond_exp()) p.positions = np.arange(15, 36).reshape((7, 3)).T pv = p[2, 4, 5] output_file = Mock() pv.save_positions(output_file) assert_array_equal(output_file.write.call_args[0][0], np.array([[0, 21, 22, 23], [1, 27, 28, 29], [2, 30, 31, 32]])) self.assertEqual(output_file.write.call_args[0][1], {'population': pv.label}) if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_projection.py000066400000000000000000000377031415343567000212440ustar00rootroot00000000000000""" Tests of the common implementation of the Projection class, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import numpy as np import os import sys from numpy.testing import assert_array_equal try: from unittest.mock import Mock, patch except ImportError: from mock import Mock, patch from .mocks import MockRNG import pyNN.mock as sim from pyNN import random, errors, space, standardmodels from pyNN.parameters import Sequence def _sort_by_column(A, col): A = np.array(A) array_index = np.argsort(A[:, col], kind='mergesort') return A[array_index] def setUp(): pass class ProjectionTest(unittest.TestCase): def setUp(self, sim=sim, **extra): sim.setup(**extra) self.p1 = sim.Population(7, sim.IF_cond_exp()) self.p2 = sim.Population(4, sim.IF_cond_exp()) self.p3 = sim.Population(5, sim.IF_curr_alpha()) self.syn1 = sim.StaticSynapse(weight=0.006, delay=0.5) self.random_connect = sim.FixedNumberPostConnector(n=2) self.syn2 = sim.StaticSynapse(weight=0.007, delay=0.4) self.all2all = sim.AllToAllConnector() self.syn3 = sim.TsodyksMarkramSynapse(weight=0.012, delay=0.6, U=0.2, tau_rec=50) def tearDown(self, sim=sim): sim.end() def test_create_simple(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) def test_create_with_presynaptic_assembly(self, sim=sim): prj = sim.Projection(self.p1 + self.p2, self.p2, connector=self.all2all, synapse_type=self.syn2) def test_create_with_homogeneous_postsynaptic_assembly(self, sim=sim): prj = sim.Projection(self.p1, self.p1 + self.p2, connector=self.all2all, synapse_type=self.syn2) def test_create_with_inhomogeneous_postsynaptic_assembly(self, sim=sim): self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, self.p1 + self.p3, connector=self.all2all, synapse_type=self.syn2) def test_create_with_fast_synapse_dynamics(self, sim=sim): depressing = sim.TsodyksMarkramSynapse(U=0.5, tau_rec=80.0, tau_facil=0.0) prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=depressing) def test_create_with_invalid_type(self, sim=sim): self.assertRaises(errors.ConnectionError, sim.Projection, self.p1, "foo", connector=self.all2all, synapse_type=self.syn2) def test_create_with_default_receptor_type(self, sim=sim): prj = sim.Projection(self.p1, self.p3, connector=self.all2all, synapse_type=sim.StaticSynapse()) self.assertEqual(prj.receptor_type, "excitatory") prj = sim.Projection(self.p1, self.p3, connector=self.all2all, synapse_type=sim.TsodyksMarkramSynapse(weight=0.5)) self.assertEqual(prj.receptor_type, "excitatory") prj = sim.Projection(self.p1, self.p3, connector=self.all2all, synapse_type=sim.StaticSynapse(weight=lambda d: -0.1 * d)) self.assertEqual(prj.receptor_type, "inhibitory") def test_size_with_gather(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) self.assertEqual(prj.size(gather=True), self.p1.size * self.p2.size) # Need to extend the mock backend before setting synaptic parameters can be properly tested # def test_set_weights(self, sim=sim): # prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) # prj.set(weight=0.789) # weights = prj.get("weight", format="array", gather=False) # use gather False because we are faking the MPI # target = 0.789*np.ones((self.p1.size, self.p2.size)) # assert_array_equal(weights, target) # def test_randomize_weights(self, sim=sim): # orig_len = sim.Projection.__len__ # sim.Projection.__len__ = Mock(return_value=42) # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.set = Mock() # rd = Mock() # rd.next = Mock(return_value=777) # prj.randomizeWeights(rd) # rd.next.assert_called_with(len(prj)) # prj.set.assert_called_with('weight', 777) # sim.Projection.__len__ = orig_len # # def test_set_delays(self, sim=sim): # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.set = Mock() # prj.setDelays(0.5) # prj.set.assert_called_with('delay', 0.5) # # def test_randomize_delays(self, sim=sim): # orig_len = sim.Projection.__len__ # sim.Projection.__len__ = Mock(return_value=42) # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.set = Mock() # rd = Mock() # rd.next = Mock(return_value=777) # prj.randomizeDelays(rd) # rd.next.assert_called_with(len(prj)) # prj.set.assert_called_with('delay', 777) # sim.Projection.__len__ = orig_len # # def test_set_synapse_dynamics_param(self, sim=sim): # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.set = Mock() # prj.setComposedSynapseType('U', 0.5) # prj.set.assert_called_with('U', 0.5) # def test_get_weights_as_list(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) weights = prj.get("weight", format="list") weights = _sort_by_column(weights, 1)[:5] target = np.array( [(0, 0, 0.007), (1, 0, 0.007), (2, 0, 0.007), (3, 0, 0.007), (4, 0, 0.007), ]) assert_array_equal(weights, target) def test_get_weights_as_list_no_address(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) weights = prj.get("weight", format="list", with_address=False)[:5] target = 0.007 * np.ones((5,)) assert_array_equal(weights, target) def test_get_weights_as_array(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) # use gather False because we are faking the MPI weights = prj.get("weight", format="array", gather=False) target = 0.007 * np.ones((self.p1.size, self.p2.size)) assert_array_equal(weights, target) def test_get_weights_as_array_with_multapses(self, sim=sim): C = sim.FixedNumberPreConnector(n=7, rng=MockRNG(delta=1)) prj = sim.Projection(self.p2, self.p3, C, synapse_type=self.syn1) # because we use a fake RNG, it is always the last three presynaptic cells which receive the double connection target = np.array([ [0.006, 0.006, 0.006, 0.006, 0.006], [0.012, 0.012, 0.012, 0.012, 0.012], [0.012, 0.012, 0.012, 0.012, 0.012], [0.012, 0.012, 0.012, 0.012, 0.012], ]) # use gather False because we are faking the MPI weights = prj.get("weight", format="array", gather=False) assert_array_equal(weights, target) def test_get_weights_as_array_with_multapses_min(self, sim=sim): C = sim.FixedNumberPreConnector(n=7, rng=MockRNG(delta=1)) prj = sim.Projection(self.p2, self.p3, C, synapse_type=self.syn1) target = np.array([ [0.006, 0.006, 0.006, 0.006, 0.006], [0.006, 0.006, 0.006, 0.006, 0.006], [0.006, 0.006, 0.006, 0.006, 0.006], [0.006, 0.006, 0.006, 0.006, 0.006], ]) # use gather False because we are faking the MPI weights = prj.get("weight", format="array", gather=False, multiple_synapses='min') assert_array_equal(weights, target) def test_synapse_with_lambda_parameter(self, sim=sim): syn = sim.StaticSynapse(weight=lambda d: 0.01 + 0.001 * d) prj = sim.Projection(self.p1, self.p2, self.all2all, synapse_type=syn) def test_parameter_StaticSynapse_random_distribution(self, sim=sim): weight = random.RandomDistribution( 'uniform', low=0.005, high=0.015, rng=MockRNG(start=0.01, delta=0.001)) syn = sim.StaticSynapse(weight=weight) self.assertEqual(weight.next(), 0.01) def test_parameter_TsodyksMarkramSynapse_random_distribution(self, sim=sim): U_distr = random.RandomDistribution( 'uniform', low=0.4, high=0.6, rng=MockRNG(start=0.5, delta=0.001)) depressing = sim.TsodyksMarkramSynapse( U=U_distr, tau_rec=lambda d: 80.0 + d, tau_facil=0.0) self.assertEqual(U_distr.next(), 0.5) def test_get_plasticity_attribute_as_list(self, sim=sim): U_distr = random.RandomDistribution( 'uniform', low=0.4, high=0.6, rng=MockRNG(start=0.5, delta=0.001)) depressing = sim.TsodyksMarkramSynapse( U=U_distr, tau_rec=lambda d: 80.0 + d, tau_facil=0.0) prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=depressing) U = prj.get("U", format="list") U = _sort_by_column(U, 1)[:5] U_target = np.array( [(0, 0, 0.5), (1, 0, 0.501), (2, 0, 0.502), (3, 0, 0.503), (4, 0, 0.504), ]) assert_array_equal(U, U_target) tau_rec = prj.get("tau_rec", format="list") tau_rec = _sort_by_column(tau_rec, 1)[:5] tau_rec_target = np.array( [(0, 0, 80), (1, 0, 81), (2, 0, 82), (3, 0, 83), (4, 0, 84), ]) assert_array_equal(tau_rec, tau_rec_target) # def test_get_delays(self, sim=sim): # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.get = Mock() # prj.getDelays(format='list', gather=False) # prj.get.assert_called_with('delay', 'list') def test_save_connections_with_gather(self, sim=sim): filename = "test.connections" if os.path.exists(filename): os.remove(filename) prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn3) prj.save('connections', filename, gather=True) assert os.path.exists(filename) os.remove(filename) # def test_print_weights_as_list(self, sim=sim): # filename = "test.weights" # if os.path.exists(filename): # os.remove(filename) # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.get = Mock(return_value=range(5)) # prj.printWeights(filename, format='list', gather=False) # prj.get.assert_called_with('weight', format='list', gather=False) # assert os.path.exists(filename) # os.remove(filename) # # def test_print_weights_as_array(self, sim=sim): # filename = "test.weights" # if os.path.exists(filename): # os.remove(filename) # p1 = sim.Population(7, sim.IF_cond_exp) # p2 = sim.Population(7, sim.IF_cond_exp) # prj = sim.Projection(p1, p2, connector=Mock()) # prj.get = Mock(return_value=np.arange(5.0)) # prj.printWeights(filename, format='array', gather=False) # prj.get.assert_called_with('weight', format='array', gather=False) # assert os.path.exists(filename) # os.remove(filename) def test_describe(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) self.assertIsInstance(prj.describe(engine='string'), str) self.assertIsInstance(prj.describe(template=None), dict) def test_weightHistogram(self, sim=sim): prj = sim.Projection(self.p1, self.p2, connector=self.all2all, synapse_type=self.syn2) n, bins = prj.weightHistogram(min=0.0, max=0.05) assert_array_equal(bins, np.linspace(0, 0.05, num=11)) assert_array_equal(n, np.array([0, prj.size(), 0, 0, 0, 0, 0, 0, 0, 0])) class CheckTest(unittest.TestCase): def setUp(self, sim=sim, **extra): self.MIN_DELAY = 0.123 sim.setup(num_processes=2, rank=1, min_delay=self.MIN_DELAY, **extra) self.p1 = sim.Population(7, sim.IF_cond_exp()) self.p2 = sim.Population(4, sim.IF_cond_exp()) self.p3 = sim.Population(5, sim.IF_curr_alpha()) self.projections = { "cond": {}, "curr": {} } for psr, post in (("cond", self.p2), ("curr", self.p3)): for rt in ("excitatory", "inhibitory"): self.projections[psr][rt] = sim.Projection( self.p1, post, sim.AllToAllConnector(safe=True), sim.StaticSynapse(), receptor_type=rt ) def test_check_weights_with_scalar(self, sim=sim): # positive weight for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], ]: standardmodels.check_weights(4.3, prj) for prj in [ self.projections["curr"]["inhibitory"], ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, 4.3, prj) # negative weight for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, -4.3, prj) for prj in [ self.projections["curr"]["inhibitory"], ]: standardmodels.check_weights(-4.3, prj) def test_check_weights_with_array(self, sim=sim): # all positive weights w = np.arange(10) for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], ]: standardmodels.check_weights(w, prj) for prj in [ self.projections["curr"]["inhibitory"], ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, w, prj) # all negative weights w = np.arange(-10, 0) for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, w, prj) for prj in [ self.projections["curr"]["inhibitory"], ]: standardmodels.check_weights(w, prj) # mixture of positive and negative weights w = np.arange(-5, 5) for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], self.projections["curr"]["inhibitory"] ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, w, prj) def test_check_weights_with_invalid_value(self, sim=sim): w = "butterflies" for prj in [ self.projections["cond"]["excitatory"], self.projections["curr"]["excitatory"], self.projections["cond"]["inhibitory"], self.projections["curr"]["inhibitory"] ]: self.assertRaises(errors.ConnectionError, standardmodels.check_weights, w, prj) PyNN-0.10.0/test/unittests/test_random.py000066400000000000000000000232151415343567000203410ustar00rootroot00000000000000""" Unit tests for pyNN/random.py. """ import unittest import numpy as np from numpy.testing import assert_allclose try: from neuron import h except ImportError: have_nrn = False else: have_nrn = True from pyNN.neuron.random import NativeRNG import pyNN.random as random # ============================================================================== class SimpleTests(unittest.TestCase): """Simple tests on a single RNG function.""" def setUp(self): self.rnglist = [random.NumpyRNG(seed=987)] for rng in self.rnglist: rng.mpi_rank = 0 rng.num_processes = 1 if random.have_gsl: self.rnglist.append(random.GSLRNG(seed=654)) if have_nrn: self.rnglist.append(NativeRNG(seed=321)) def testNextNone(self): """Calling next() with no number argument should return a float.""" for rng in self.rnglist: self.assertIsInstance(rng.next(distribution='uniform', parameters={'low': 0, 'high': 1}), float) def testNextOne(self): """Calling next() with n=1 should return an array.""" for rng in self.rnglist: self.assertIsInstance(rng.next(1, 'uniform', {'low': 0, 'high': 1}), np.ndarray) self.assertIsInstance(rng.next(n=1, distribution='uniform', parameters={'low': 0, 'high': 1}), np.ndarray) self.assertEqual(rng.next(1, distribution='uniform', parameters={'low': 0, 'high': 1}).shape, (1,)) def testNextTwoPlus(self): """Calling next(n=m) where m > 1 should return an array.""" for rng in self.rnglist: self.assertEqual(len(rng.next(5, 'uniform', {'low': 0, 'high': 1})), 5) def testNonPositiveN(self): """Calling next(m) where m < 0 should raise a ValueError.""" for rng in self.rnglist: self.assertRaises(ValueError, rng.next, -1, 'uniform', {'low': 0, 'high': 1}) def testNZero(self): """Calling next(0) should return an empty array.""" for rng in self.rnglist: self.assertEqual(len(rng.next(0)), 0) def test_invalid_seed(self): self.assertRaises(AssertionError, random.NumpyRNG, seed=2.3) class ParallelTests(unittest.TestCase): def setUp(self): self.rng_types = [random.NumpyRNG] if random.have_gsl: self.rng_types.append(random.GSLRNG) if have_nrn: self.rng_types.append(NativeRNG) self.orig_mpi_config = random.get_mpi_config def tearDown(self): random.get_mpi_config = self.orig_mpi_config def test_parallel_unsafe_without_mask(self): for rng_type in self.rng_types: random.get_mpi_config = lambda: (0, 2) rng0 = rng_type(seed=1000, parallel_safe=False) random.get_mpi_config = lambda: (1, 2) rng1 = rng_type(seed=1000, parallel_safe=False) self.assertEqual(rng0.seed, 1000) self.assertEqual(rng1.seed, 1001) draw0 = rng0.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) draw1 = rng1.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) self.assertEqual(len(draw0), 5) self.assertEqual(len(draw1), 5) self.assertNotEqual(draw0.tolist(), draw1.tolist()) def test_parallel_unsafe_with_mask(self): for rng_type in self.rng_types: random.get_mpi_config = lambda: (0, 2) rng0 = rng_type(seed=1000, parallel_safe=False) random.get_mpi_config = lambda: (1, 2) rng1 = rng_type(seed=1000, parallel_safe=False) rng_check = rng_type(seed=1000, parallel_safe=False) self.assertEqual(rng0.seed, 1000) self.assertEqual(rng1.seed, 1001) self.assertEqual(rng_check.seed, 1001) mask1 = np.array((1, 0, 1, 0, 1), bool) mask2 = np.array((0, 1, 0, 1, 0), bool) draw0 = rng0.next(5, 'uniform', {'low': 0, 'high': 1}, mask=mask1) draw1 = rng1.next(5, 'uniform', {'low': 0, 'high': 1}, mask=mask2) draw_check = rng_check.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) self.assertEqual(len(draw0), 3) self.assertEqual(len(draw1), 2) self.assertNotEqual(draw0.tolist(), draw1.tolist()) self.assertNotEqual(draw1.tolist(), draw_check[mask2].tolist()) def test_parallel_safe_with_mask(self): for rng_type in self.rng_types: random.get_mpi_config = lambda: (0, 2) rng0 = rng_type(seed=1000, parallel_safe=True) random.get_mpi_config = lambda: (1, 2) rng1 = rng_type(seed=1000, parallel_safe=True) rng_check = rng_type(seed=1000, parallel_safe=True) self.assertEqual(rng0.seed, 1000) self.assertEqual(rng1.seed, 1000) self.assertEqual(rng_check.seed, 1000) mask1 = np.array((1, 0, 1, 0, 1), bool) mask2 = np.array((0, 1, 0, 1, 0), bool) draw0 = rng0.next(5, 'uniform', {'low': 0, 'high': 1}, mask=mask1) draw1 = rng1.next(5, 'uniform', {'low': 0, 'high': 1}, mask=mask2) draw_check = rng_check.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) self.assertEqual(len(draw0), 3) self.assertEqual(len(draw1), 2) self.assertNotEqual(draw0.tolist(), draw1.tolist()) self.assertEqual(draw1.tolist(), draw_check[mask2].tolist()) def test_parallel_safe_without_mask(self): for rng_type in self.rng_types: random.get_mpi_config = lambda: (0, 2) rng0 = rng_type(seed=1000, parallel_safe=True) random.get_mpi_config = lambda: (1, 2) rng1 = rng_type(seed=1000, parallel_safe=True) draw0 = rng0.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) draw1 = rng1.next(5, 'uniform', {'low': 0, 'high': 1}, mask=None) self.assertEqual(len(draw0), 5) self.assertEqual(len(draw1), 5) self.assertEqual(draw0.tolist(), draw1.tolist()) def test_permutation(self): # only works for NumpyRNG at the moment. pygsl has a permutation module, but I can't find documentation for it. random.get_mpi_config = lambda: (0, 2) rng0 = random.NumpyRNG(seed=1000, parallel_safe=True) random.get_mpi_config = lambda: (1, 2) rng1 = random.NumpyRNG(seed=1000, parallel_safe=True) A = range(10) perm0 = rng0.permutation(A) perm1 = rng1.permutation(A) assert_allclose(perm0, perm1, 1e-99) class NativeRNGTests(unittest.TestCase): def test_create(self): rng = random.NativeRNG(seed=8274528) str(rng) class RandomDistributionTests(unittest.TestCase): def setUp(self): random.get_mpi_config = lambda: (0, 1) self.rnglist = [random.NumpyRNG(seed=987)] if random.have_gsl: self.rnglist.append(random.GSLRNG(seed=654)) if have_nrn: self.rnglist.append(NativeRNG(seed=321)) def test_uniform(self): rd = random.RandomDistribution(distribution='uniform', low=- 1.0, high=3.0, rng=self.rnglist[0]) vals = rd.next(100) assert vals.min() >= -1.0 assert vals.max() < 3.0 assert abs(vals.mean() - 1.0) < 0.2 def test_gaussian(self): mean = 1.0 std = 1.0 rd1 = random.RandomDistribution('normal', mu=mean, sigma=std, rng=self.rnglist[0]) vals_list = [rd1.next(100)] for vals in vals_list: assert vals.min() > mean - 4 * std assert vals.min() < mean + 4 * std assert abs(vals.mean() - mean) < 0.2, abs(vals.mean() - mean) def test_gamma(self): a = 2.0 b = 0.5 for rng in self.rnglist: rd = random.RandomDistribution('gamma', k=a, theta=1 / b, rng=rng) vals = rd.next(100) # need to check vals are as expected str(rd) # should be in a separate test def test_boundaries(self): rd = random.RandomDistribution('normal_clipped_to_boundary', mu=0, sigma=1, low=-0.5, high=0.5, rng=self.rnglist[0]) vals = rd.next(1000) assert vals.min() == -0.5 assert vals.max() == 0.5 assert abs(vals.mean()) < 0.05, vals.mean() rd = random.RandomDistribution(distribution='normal_clipped', mu=0, sigma=1, low=0, high=1, rng=self.rnglist[0]) vals = rd.next(1000) assert vals.min() >= 0 assert vals.max() < 1.0 def test_positional_args(self): for rng in self.rnglist: rd1 = random.RandomDistribution('normal', (0.5, 0.2), rng) self.assertEqual(rd1.parameters, {'mu': 0.5, 'sigma': 0.2}) self.assertEqual(rd1.rng, rng) self.assertRaises(ValueError, random.RandomDistribution, 'normal', (0.5,)) self.assertRaises(ValueError, random.RandomDistribution, 'normal', (0.5, 0.2), mu=0.5, sigma=0.2) def test_max_redraws(self): # for certain parameterizations, clipped distributions can require a very large, possibly infinite # number of redraws. This should be caught. for rng in self.rnglist: rd1 = random.RandomDistribution( 'normal_clipped', mu=0, sigma=1, low=5, high=np.inf, rng=rng) self.assertRaises(Exception, rd1.next, 1000) # ============================================================================== if __name__ == "__main__": unittest.main() PyNN-0.10.0/test/unittests/test_recording.py000066400000000000000000000123161415343567000210350ustar00rootroot00000000000000 from datetime import datetime from collections import defaultdict from unittest.mock import Mock from nose.tools import assert_equal, assert_raises from pyNN import recording, errors # def test_rename_existing(): # def test_gather(): #import time # for x in range(7): # N = pow(10, x) # local_data = np.empty((N,2)) # local_data[:,0] = np.ones(N, dtype=float)*comm.rank # local_data[:,1] = np.random.rand(N) # # start_time = time.time() # all_data = gather(local_data) # #print(comm.rank, "local", local_data) # if comm.rank == 0: # # print("all", all_data) # print(N, time.time()-start_time) # def test_gather_no_MPI(): # def test_gather_dict(): # def test_mpi_sum(): class MockState(object): def __init__(self, mpi_rank): self.mpi_rank = mpi_rank self.num_processes = 9 self.dt = 0.123 self.running = True self.recorders = set([]) self.t = 0.0 class MockSimulator(object): name = "MockSimulator" def __init__(self, mpi_rank): self.state = MockState(mpi_rank) class MockNeoIO(object): filename = "fake_file" write = Mock() class MockRecorder(recording.Recorder): _simulator = MockSimulator(mpi_rank=0) def _get_current_segment(self, filter_ids=None, variables='all', clear=False): segment = Mock() segment.analogsignals = [Mock(), Mock()] return segment class MockPopulation(object): size = 11 first_id = 2454 last_id = first_id + size label = "mock population" celltype = Mock(always_local=False) annotations = {'knights_say': 'Ni!'} def __len__(self): return self.size def can_record(self, variable): if variable in ["spikes", "v", "gsyn_exc", "gsyn_inh", "spam"]: return True else: return False def id_to_index(self, id): return id def describe(self): return "mock population" class MockNeoBlock(object): def __init__(self): self.name = None self.description = None self.segments = [Mock()] self.rec_datetime = datetime.now() def annotate(self, **annotations): pass def test_Recorder_create(): p = MockPopulation() r = MockRecorder(p) assert_equal(r.population, p) assert_equal(r.file, None) assert_equal(r.recorded, defaultdict(set)) def test_Recorder_invalid_variable(): p = MockPopulation() r = MockRecorder(p) all_ids = (MockID(0, True), MockID(1, False), MockID( 2, True), MockID(3, True), MockID(4, False)) assert_raises(errors.RecordingError, r.record, 'foo', all_ids) class MockID(object): def __init__(self, id, local): self.id = id self.local = local def test_record(): p = MockPopulation() r = MockRecorder(p) r._record = Mock() assert_equal(r.recorded, defaultdict(set)) all_ids = (MockID(0, True), MockID(1, False), MockID( 2, True), MockID(3, True), MockID(4, False)) first_ids = all_ids[0:3] r.record('spam', first_ids) assert_equal(r.recorded['spam'], set(id for id in first_ids if id.local)) assert_equal(len(r.recorded['spam']), 2) r._record.assert_called_with('spam', r.recorded['spam'], None) more_ids = all_ids[2:5] r.record('spam', more_ids) assert_equal(r.recorded['spam'], set(id for id in all_ids if id.local)) assert_equal(len(r.recorded['spam']), 3) r._record.assert_called_with('spam', set(all_ids[3:4]), None) def test_filter_recorded(): p = MockPopulation() r = MockRecorder(p) r._record = Mock() all_ids = (MockID(0, True), MockID(1, False), MockID( 2, True), MockID(3, True), MockID(4, False)) r.record(['spikes', 'spam'], all_ids) assert_equal(r.recorded['spikes'], set(id for id in all_ids if id.local)) assert_equal(r.recorded['spam'], set(id for id in all_ids if id.local)) filter = all_ids[::2] filtered_ids = r.filter_recorded('spam', filter) assert_equal(filtered_ids, set(id for id in filter if id.local)) assert_equal(r.filter_recorded('spikes', None), r.recorded['spikes']) def test_get(): p = MockPopulation() r = MockRecorder(p) data = r.get('spikes') assert_equal(data.name, p.label) assert_equal(data.description, p.describe()) # def test_write__with_filename__compatible_output__gather__onroot(): # orig_metadata = recording.Recorder.metadata # #recording.Recorder.metadata = {'a': 2, 'b':3} # p = MockPopulation() # r = MockRecorder(p) # #fake_data = MockNeoBlock() # r._get_current_segment = Mock() #return_value=fake_data) # output_io = MockNeoIO() # r.write("spikes", file=output_io, gather=True) # #recording.Recorder.metadata = orig_metadata # output_io.write.assert_called_with(fake_data) def test_metadata_property(): p = MockPopulation() r = MockRecorder(p) assert_equal(r.metadata, {'first_id': 2454, 'label': 'mock population', 'dt': 0.123, 'last_id': 2465, 'size': 11, 'first_index': 0, 'last_index': 11, 'knights_say': 'Ni!', 'simulator': 'MockSimulator', 'mpi_processes': 9}) # def test_count__spikes_gather(): # def test_count__spikes_nogather(): # def test_count__other(): PyNN-0.10.0/test/unittests/test_simulation_control.py000066400000000000000000000075351415343567000230140ustar00rootroot00000000000000""" Tests of the common implementation of the simulation control functions, using the pyNN.mock backend. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ import unittest import pyNN.mock as sim try: from mpi4py import MPI except ImportError: MPI = None if MPI: mpi_comm = MPI.COMM_WORLD class TestSimulationControl(unittest.TestCase): def setUp(self, sim=sim, **extra): self.extra = {} self.extra.update(extra) pass def tearDown(self, sim=sim): pass def test_setup(self, sim=sim): self.assertRaises(Exception, sim.setup, min_delay=1.0, max_delay=0.9, **self.extra) sim.end() self.assertRaises(Exception, sim.setup, mindelay=1.0, **self.extra) # } common sim.end() self.assertRaises(Exception, sim.setup, maxdelay=10.0, **self.extra) # } misspellings sim.end() self.assertRaises(Exception, sim.setup, dt=0.1, **self.extra) # } sim.end() self.assertRaises(Exception, sim.setup, timestep=0.1, min_delay=0.09, **self.extra) sim.end() def test_end(self, sim=sim): sim.setup(**self.extra) sim.end() # need a better test def test_run(self, sim=sim): sim.setup(**self.extra) self.assertAlmostEqual(sim.run(100.0), 100.0) sim.end() def test_run_twice(self, sim=sim): sim.setup(**self.extra) self.assertAlmostEqual(sim.run(100.0), 100.0) self.assertAlmostEqual(sim.run(100.0), 200.0) sim.end() def test_reset(self, sim=sim): sim.setup(**self.extra) sim.run(100.0) sim.reset() self.assertEqual(sim.get_current_time(), 0.0) sim.end() def test_current_time(self, sim=sim): sim.setup(timestep=0.1, **self.extra) sim.run(10.1) self.assertAlmostEqual(sim.get_current_time(), 10.1) sim.end() def test_current_time_two_runs(self, sim=sim): sim.setup(timestep=0.1, **self.extra) sim.run(10.1) self.assertAlmostEqual(sim.get_current_time(), 10.1) sim.run(23.4) self.assertAlmostEqual(sim.get_current_time(), 33.5) sim.end() def test_time_step(self, sim=sim): sim.setup(0.123, min_delay=0.246, **self.extra) self.assertAlmostEqual(sim.get_time_step(), 0.123) sim.end() def test_min_delay(self, sim=sim): sim.setup(0.123, min_delay=0.246, **self.extra) self.assertEqual(sim.get_min_delay(), 0.246) sim.end() def test_max_delay(self, sim=sim): sim.setup(max_delay=9.87, **self.extra) self.assertAlmostEqual(sim.get_max_delay(), 9.87) sim.end() def test_callbacks(self, sim=sim): total_time = 100. callback_steps = [10., 10., 20., 25.] # callbacks are called at 0. and after every step expected_callcount = [11, 11, 6, 5] num_callbacks = len(callback_steps) callback_callcount = [0] * num_callbacks def make_callback(idx): def callback(time): callback_callcount[idx] += 1 return time + callback_steps[idx] return callback callbacks = [make_callback(i) for i in range(num_callbacks)] sim.setup(timestep=0.1, min_delay=0.1, **self.extra) sim.run_until(total_time, callbacks=callbacks) self.assertTrue(all(callback_callcount[i] == expected_callcount[i] for i in range(num_callbacks))) sim.end() @unittest.skipUnless(MPI, "test requires mpi4py") def test_num_processes(self, sim=sim): self.assertEqual(sim.num_processes(), mpi_comm.size) @unittest.skipUnless(MPI, "test requires mpi4py") def test_rank(self, sim=sim): self.assertEqual(sim.rank(), mpi_comm.rank) if __name__ == '__main__': unittest.main() PyNN-0.10.0/test/unittests/test_space.py000066400000000000000000000246131415343567000201570ustar00rootroot00000000000000""" Tests of the `space` module. :copyright: Copyright 2006-2021 by the PyNN team, see AUTHORS. :license: CeCILL, see LICENSE for details. """ from math import sqrt import unittest from unittest.mock import Mock import numpy as np from nose.tools import assert_equal, assert_raises from numpy.testing import assert_array_equal, assert_allclose from pyNN import space def test_distance(): cell1 = Mock() cell2 = Mock() A = lambda *x: np.array(x) cell1.position = A(2.3, 4.5, 6.7) cell2.position = A(2.3, 4.5, 6.7) assert_equal(space.distance(cell1, cell2), 0.0) cell2.position = A(5.3, 4.5, 6.7) assert_equal(space.distance(cell1, cell2), 3.0) cell2.position = A(5.3, 8.5, 6.7) assert_equal(space.distance(cell1, cell2), 5.0) cell2.position = A(5.3, 8.5, -5.3) assert_equal(space.distance(cell1, cell2), 13.0) assert_equal(space.distance(cell1, cell2, mask=A(0, 1)), 5.0) assert_equal(space.distance(cell1, cell2, mask=A(2)), 12.0) assert_equal(space.distance(cell1, cell2, offset=A(-3.0, -4.0, 12.0)), 0.0) cell2.position = A(10.6, 17.0, -10.6) assert_equal(space.distance(cell1, cell2, scale_factor=0.5), 13.0) cell2.position = A(-1.7, 8.5, -5.3) assert_equal(space.distance(cell1, cell2, periodic_boundaries=A(7.0, 1e12, 1e12)), 13.0) class SpaceTest(unittest.TestCase): def setUp(self): N = np.array self.A = N([0.0, 0.0, 0.0]) self.B = N([1.0, 1.0, 1.0]) self.C = N([-1.0, -1.0, -1.0]) self.D = N([2.0, 3.0, 4.0]) self.ABCD = N([[0.0, 0.0, 0.0], [1.0, 1.0, 1.0], [-1.0, -1.0, -1.0], [2.0, 3.0, 4.0]]) def assertArraysEqual(self, A, B): self.assert_((A == B).all(), "%s != %s" % (A, B)) def test_infinite_space_with_3D_distances(self): s = space.Space() self.assertEqual(s.distances(self.A, self.B), sqrt(3)) self.assertEqual(s.distances(self.C, self.B), sqrt(12)) self.assertArraysEqual(s.distances(self.A, self.ABCD), np.array([0.0, sqrt(3), sqrt(3), sqrt(29)])) self.assertArraysEqual(s.distances(self.A, self.ABCD), s.distances(self.ABCD, self.A).T) assert_array_equal(s.distances(self.ABCD, self.ABCD), np.array([0.0, sqrt(3), sqrt(3), sqrt(29), sqrt(3), 0.0, sqrt(12), sqrt(14), sqrt(3), sqrt(12), 0.0, sqrt(50.0), sqrt(29), sqrt(14), sqrt(50.0), 0.0])) self.assertArraysEqual(s.distances(self.ABCD, self.A), np.array([0.0, sqrt(3), sqrt(3), sqrt(29)])) def test_generator_for_infinite_space_with_3D_distances(self): s = space.Space() def f(i): return self.ABCD[i] def g(j): return self.ABCD[j] self.assertArraysEqual(s.distance_generator(f, g)(0, np.arange(4)), np.array([0.0, sqrt(3), sqrt(3), sqrt(29)])) assert_array_equal(np.fromfunction(s.distance_generator(f, g), shape=(4, 4), dtype=int), np.array([(0.0, sqrt(3), sqrt(3), sqrt(29)), (sqrt(3), 0.0, sqrt(12), sqrt(14)), (sqrt(3), sqrt(12), 0.0, sqrt(50.0)), (sqrt(29), sqrt(14), sqrt(50.0), 0.0)])) def test_infinite_space_with_collapsed_axes(self): s_x = space.Space(axes='x') s_xy = space.Space(axes='xy') s_yz = space.Space(axes='yz') self.assertEqual(s_x.distances(self.A, self.B), 1.0) self.assertEqual(s_xy.distances(self.A, self.B), sqrt(2)) self.assertEqual(s_x.distances(self.A, self.D), 2.0) self.assertEqual(s_xy.distances(self.A, self.D), sqrt(13)) self.assertEqual(s_yz.distances(self.A, self.D), sqrt(25)) self.assertArraysEqual(s_yz.distances(self.D, self.ABCD), np.array([sqrt(25), sqrt(13), sqrt(41), sqrt(0)])) def test_infinite_space_with_scale_and_offset(self): s = space.Space(scale_factor=2.0, offset=1.0) self.assertEqual(s.distances(self.A, self.B), sqrt(48)) self.assertEqual(s.distances(self.B, self.A), sqrt(3)) self.assertEqual(s.distances(self.C, self.B), sqrt(75)) self.assertEqual(s.distances(self.B, self.C), sqrt(3)) self.assertArraysEqual(s.distances(self.A, self.ABCD), np.array([sqrt(12), sqrt(48), sqrt(0), sqrt(200)])) def test_cylindrical_space(self): s = space.Space(periodic_boundaries=((-1.0, 4.0), (-1.0, 4.0), (-1.0, 4.0))) self.assertEqual(s.distances(self.A, self.B), sqrt(3)) self.assertEqual(s.distances(self.A, self.D), sqrt(4 + 4 + 1)) self.assertEqual(s.distances(self.C, self.D), sqrt(4 + 1 + 0)) self.assertArraysEqual(s.distances(self.A, self.ABCD), np.array([0.0, sqrt(3), sqrt(3), sqrt(4 + 4 + 1)])) self.assertArraysEqual(s.distances(self.A, self.ABCD), s.distances(self.ABCD, self.A).T) self.assertArraysEqual(s.distances(self.C, self.ABCD), np.array([sqrt(3), sqrt(4 + 4 + 4), 0.0, sqrt(4 + 1 + 0)])) class LineTest(unittest.TestCase): def test_generate_positions_default_parameters(self): line = space.Line() n = 4 positions = line.generate_positions(n) assert_equal(positions.shape, (3, n)) assert_allclose( positions, np.array([[0, 0, 0], [1, 0, 0], [2, 0, 0], [3, 0, 0]], float).T, rtol=1e-15 ) def test_generate_positions(self): line = space.Line(dx=100.0, x0=-100.0, y=444.0, z=987.0) n = 2 positions = line.generate_positions(n) assert_equal(positions.shape, (3, n)) assert_allclose( positions, np.array([[-100, 444, 987], [0, 444, 987]], float).T, rtol=1e-15 ) def test__eq__(self): line1 = space.Line() line2 = space.Line(1.0, 0.0, 0.0, 0.0) line3 = space.Line(dx=2.0) assert_equal(line1, line2) assert line1 != line3 def test_get_parameters(self): params = dict(dx=100.0, x0=-100.0, y=444.0, z=987.0) line = space.Line(**params) assert_equal(line.get_parameters(), params) class Grid2D_Test(object): def setup(self): self.grid1 = space.Grid2D() self.grid2 = space.Grid2D(aspect_ratio=3.0, dx=11.1, dy=9.9, x0=123, y0=456, z=789) def test_calculate_size(self): assert_equal(self.grid1.calculate_size(n=1), (1, 1)) assert_equal(self.grid1.calculate_size(n=4), (2, 2)) assert_equal(self.grid1.calculate_size(n=9), (3, 3)) assert_raises(Exception, self.grid1.calculate_size, n=10) assert_equal(self.grid2.calculate_size(n=3), (3, 1)) assert_equal(self.grid2.calculate_size(n=12), (6, 2)) assert_equal(self.grid2.calculate_size(n=27), (9, 3)) assert_raises(Exception, self.grid2.calculate_size, n=4) def test_generate_positions(self): n = 4 positions = self.grid1.generate_positions(n) assert_equal(positions.shape, (3, n)) assert_allclose( positions, np.array([ [0, 0, 0], [0, 1, 0], [1, 0, 0], [1, 1, 0] ]).T, 1e-15) assert_allclose( self.grid2.generate_positions(12), np.array([ [123, 456, 789], [123, 465.9, 789], [123 + 11.1, 456, 789], [123 + 11.1, 465.9, 789], [123 + 22.2, 456, 789], [123 + 22.2, 465.9, 789], [123 + 33.3, 456, 789], [123 + 33.3, 465.9, 789], [123 + 44.4, 456, 789], [123 + 44.4, 465.9, 789], [123 + 55.5, 456, 789], [123 + 55.5, 465.9, 789], ]).T, 1e-15) class Grid3D_Test(object): def setup(self): self.grid1 = space.Grid3D() self.grid2 = space.Grid3D(aspect_ratioXY=3.0, aspect_ratioXZ=2.0, dx=11, dy=9, dz=7, x0=123, y0=456, z0=789) def test_calculate_size(self): assert_equal(self.grid1.calculate_size(n=1), (1, 1, 1)) assert_equal(self.grid1.calculate_size(n=8), (2, 2, 2)) assert_equal(self.grid1.calculate_size(n=27), (3, 3, 3)) assert_raises(Exception, self.grid1.calculate_size, n=10) assert_equal(self.grid2.calculate_size(n=36), (6, 2, 3)) assert_equal(self.grid2.calculate_size(n=288), (12, 4, 6)) assert_raises(Exception, self.grid2.calculate_size, n=100) def test_generate_positions(self): n = 8 positions = self.grid1.generate_positions(n) assert_equal(positions.shape, (3, n)) assert_allclose( positions, np.array([ [0, 0, 0], [0, 0, 1], [0, 1, 0], [0, 1, 1], [1, 0, 0], [1, 0, 1], [1, 1, 0], [1, 1, 1] ]).T, 1e-15) class TestSphere(object): def test__create(self): s = space.Sphere(2.5) assert_equal(s.radius, 2.5) def test_sample(self): n = 1000 s = space.Sphere(2.5) positions = s.sample(n, np.random) assert_equal(positions.shape, (n, 3)) for axis in range(2): assert 1 < max(positions[:, axis]) < 2.5 assert -1 > min(positions[:, axis]) > -2.5 s2 = np.sum(positions**2, axis=1) assert max(s2) < 6.25 class TestCuboid(object): def test_sample(self): n = 1000 c = space.Cuboid(3, 4, 5) positions = c.sample(n, np.random) assert_equal(positions.shape, (n, 3)) assert 1 < max(positions[:, 0]) < 1.5, max(positions[:, 0]) assert -1 > min(positions[:, 0]) > -1.5 assert -1.5 > min(positions[:, 1]) > -2.0 assert -2 > min(positions[:, 2]) > -2.5 class TestRandomStructure(object): def test_generate_positions(self): n = 1000 s = space.Sphere(2.5) rs = space.RandomStructure(boundary=s, origin=(1.0, 1.0, 1.0)) positions = rs.generate_positions(n) assert_equal(positions.shape, (3, n)) for axis in range(2): assert 3 < max(positions[axis, :]) < 3.5 assert -1 > min(positions[axis, :]) > -1.5 PyNN-0.10.0/test/unittests/test_standardmodels.py000066400000000000000000000161721415343567000220710ustar00rootroot00000000000000from pyNN.standardmodels import build_translations, StandardModelType, \ STDPWeightDependence, STDPTimingDependence from pyNN.standardmodels.synapses import StaticSynapse, STDPMechanism from pyNN import errors from pyNN.parameters import ParameterSpace from nose.tools import assert_equal, assert_raises try: from unittest.mock import Mock except ImportError: from mock import Mock import numpy as np def test_build_translations(): t = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A') ) assert_equal(set(t.keys()), set(['a', 'b', 'c'])) assert_equal(set(t['a'].keys()), set(['translated_name', 'forward_transform', 'reverse_transform'])) assert_equal(t['a']['translated_name'], 'A') assert_equal(t['a']['forward_transform'], 'a') assert_equal(t['a']['reverse_transform'], 'A') assert_equal(t['b']['translated_name'], 'B') assert_equal(t['b']['forward_transform'], 'float(1000)*b') assert_equal(t['b']['reverse_transform'], 'B/float(1000)') assert_equal(t['c']['translated_name'], 'C') assert_equal(t['c']['forward_transform'], 'c + a') assert_equal(t['c']['reverse_transform'], 'C - A') def test_has_parameter(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3} assert M.has_parameter('a') assert M.has_parameter('b') assert not M.has_parameter('z') def test_get_parameter_names(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3} assert_equal(set(M.get_parameter_names()), set(['a', 'b'])) def test_instantiate(): """ Instantiating a StandardModelType should set self.parameter_space to a ParameterSpace object containing the provided parameters. """ M = StandardModelType M.default_parameters = {'a': 0.0, 'b': 0.0} P1 = {'a': 22.2, 'b': 33.3} m = M(**P1) assert_equal(m.parameter_space._parameters, ParameterSpace(P1, None, None)._parameters) M.default_parameters = {} def _parameter_space_to_dict(parameter_space, size): parameter_space.shape = (size,) parameter_space.evaluate(simplify=True) return parameter_space.as_dict() def test_translate(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) m = M() native_parameters = m.translate(ParameterSpace( {'a': 23.4, 'b': 34.5, 'c': 45.6}, m.get_schema(), None)) assert_equal(_parameter_space_to_dict(native_parameters, 77), {'A': 23.4, 'B': 34500.0, 'C': 69.0}) def test_translate_with_invalid_transformation(): M = StandardModelType M.translations = build_translations( ('a', 'A'), ('b', 'B', 'b + z', 'B-Z'), ) M.default_parameters = {'a': 22.2, 'b': 33.3} # really we should trap such errors in build_translations(), not in translate() m = M() assert_raises(NameError, m.translate, ParameterSpace({'a': 23.4, 'b': 34.5}, m.get_schema(), None)) def test_translate_with_divide_by_zero_error(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3} M.translations = build_translations( ('a', 'A'), ('b', 'B', 'b/0', 'B*0'), ) m = M() native_parameters = m.translate(ParameterSpace({'a': 23.4, 'b': 34.5}, m.get_schema(), 77)) assert_raises(ZeroDivisionError, native_parameters.evaluate, simplify=True) def test_reverse_translate(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) assert_equal(_parameter_space_to_dict(M().reverse_translate(ParameterSpace({'A': 23.4, 'B': 34500.0, 'C': 69.0})), 88), {'a': 23.4, 'b': 34.5, 'c': 45.6}) def test_reverse_translate_with_invalid_transformation(): M = StandardModelType M.translations = build_translations( ('a', 'A'), ('b', 'B', 'b + z', 'B-Z'), ) M.default_parameters = {'a': 22.2, 'b': 33.3} # really we should trap such errors in build_translations(), not in reverse_translate() assert_raises(NameError, M().reverse_translate, {'A': 23.4, 'B': 34.5}) def test_simple_parameters(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) assert_equal(M().simple_parameters(), ['a']) def test_scaled_parameters(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) assert_equal(M().scaled_parameters(), ['b']) def test_computed_parameters(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) assert_equal(M().computed_parameters(), ['c']) def test_describe(): M = StandardModelType M.default_parameters = {'a': 22.2, 'b': 33.3, 'c': 44.4} M.translations = build_translations( ('a', 'A'), ('b', 'B', 1000.0), ('c', 'C', 'c + a', 'C - A'), ) assert isinstance(M().describe(), str) # test StandardCellType # test ComposedSynapseType # test create def test_describe_synapse_type(): StaticSynapse._get_minimum_delay = lambda self: 0.1 sd = StaticSynapse() assert isinstance(sd.describe(), str) assert isinstance(sd.describe(template=None), dict) del StaticSynapse._get_minimum_delay def test_STDPMechanism_create(): STDPMechanism._get_minimum_delay = lambda self: 0.1 STDPMechanism.base_translations = {} STDPTimingDependence.__init__ = Mock(return_value=None) STDPWeightDependence.__init__ = Mock(return_value=None) td = STDPTimingDependence() wd = STDPWeightDependence() stdp = STDPMechanism(td, wd, None, 0.5) assert_equal(stdp.timing_dependence, td) assert_equal(stdp.weight_dependence, wd) assert_equal(stdp.voltage_dependence, None) assert_equal(stdp.dendritic_delay_fraction, 0.5) del STDPMechanism._get_minimum_delay del STDPMechanism.base_translations def test_STDPMechanism_create_invalid_types(): assert_raises(AssertionError, # probably want a more informative error STDPMechanism, timing_dependence="abc") assert_raises(AssertionError, # probably want a more informative error STDPMechanism, weight_dependence="abc") assert_raises(AssertionError, # probably want a more informative error STDPMechanism, dendritic_delay_fraction="abc") assert_raises(AssertionError, # probably want a more informative error STDPMechanism, dendritic_delay_fraction="1.1") # test STDPWeightDependence # test STDPTimingDependence PyNN-0.10.0/test/unittests/test_utility_functions.py000066400000000000000000000043261415343567000226560ustar00rootroot00000000000000from pyNN import utility import unittest import os import time import sys try: from io import StringIO except ImportError: from StringIO import StringIO class NotifyTests(unittest.TestCase): def test_notify(self): utility.notify() class GetArgTests(unittest.TestCase): def test_get_script_args(self): utility.get_script_args(0) # fails with nose, passes with python # def test_get_script_args1(self): # self.assertRaises(Exception, utility.get_script_args, 1) # fails with nose, passes with python # class InitLoggingTests(unittest.TestCase): # # def test_initlogging_debug(self): # utility.init_logging("test.log", debug=True, num_processes=2, rank=99) # assert os.path.exists("test.log.99") # os.remove("test.log.99") class TimerTest(unittest.TestCase): def test_timer(self): timer = utility.Timer() time.sleep(0.1) assert timer.elapsed_time() > 0 assert isinstance(timer.elapsedTime(format='long'), str) timer.reset() def test_diff(self): timer = utility.Timer() time.sleep(0.1) self.assertAlmostEqual(timer.diff(), 0.1, places=2) time.sleep(0.2) self.assertAlmostEqual(timer.diff(), 0.2, places=1) self.assertAlmostEqual(timer.elapsed_time(), 0.3, places=2) class ProgressBarTest(unittest.TestCase): def test_fixed(self): bar = utility.ProgressBar(width=12, mode='fixed') sys.stdout = StringIO() bar(0) bar(0.5) bar(1) sys.stdout.seek(0) self.assertEqual(sys.stdout.read(), "[ ] 0% \r" "[ ##### ] 50% \r" "[ ########## ] 100% \r") sys.stdout = sys.__stdout__ def test_dynamic(self): bar = utility.ProgressBar(width=12, mode='dynamic') sys.stdout = StringIO() bar(0) bar(0.5) bar(1) sys.stdout.seek(0) self.assertEqual(sys.stdout.read(), "[ ] 0% \r" "[ ##### ] 50% \r" "[ ########## ] 100% \r") sys.stdout = sys.__stdout__ if __name__ == "__main__": unittest.main()