pax_global_header00006660000000000000000000000064136113122210014502gustar00rootroot0000000000000052 comment=28674b1605d2cc0bd66e5e608bf15c7b407b2b95 snakemake-5.10.0/000077500000000000000000000000001361131222100135245ustar00rootroot00000000000000snakemake-5.10.0/.dockerignore000066400000000000000000000000571361131222100162020ustar00rootroot00000000000000.eggs images misc docs tutorial tests examples snakemake-5.10.0/.gitattributes000066400000000000000000000004021361131222100164130ustar00rootroot00000000000000snakemake/_version.py export-subst snakemake/report/report.html linguist-vendored=true snakemake/gui.html linguist-vendored=true snakemake/report/table.html linguist-vendored=true tests/test_issue1046/expected-results/test_report.html linguist-vendored=true snakemake-5.10.0/.github/000077500000000000000000000000001361131222100150645ustar00rootroot00000000000000snakemake-5.10.0/.github/CODEOWNERS000066400000000000000000000000231361131222100164520ustar00rootroot00000000000000* @johanneskoester snakemake-5.10.0/.github/ISSUE_TEMPLATE/000077500000000000000000000000001361131222100172475ustar00rootroot00000000000000snakemake-5.10.0/.github/ISSUE_TEMPLATE/bug_report.md000066400000000000000000000012601361131222100217400ustar00rootroot00000000000000--- name: Bug report about: Create a report to help us improve title: '' labels: bug assignees: '' --- **Snakemake version** Note the Snakemake version for which you experience the bug. Always make sure to try out the latest version before creating a new bug report. **Describe the bug** A clear and concise description of what the bug is. **Logs** If applicable, any terminal output to help explain your problem. **Minimal example** Add a minimal example for reproducing the bug. **Additional context** Add any other context about the problem here. snakemake-5.10.0/.github/ISSUE_TEMPLATE/feature_request.md000066400000000000000000000011341361131222100227730ustar00rootroot00000000000000--- name: Feature request about: Suggest an idea for this project title: '' labels: enhancement assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. snakemake-5.10.0/.github/workflows/000077500000000000000000000000001361131222100171215ustar00rootroot00000000000000snakemake-5.10.0/.github/workflows/main.yml000066400000000000000000000046331361131222100205760ustar00rootroot00000000000000name: CI on: push: branches: - master pull_request: branches_ignore: [] jobs: formatting: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Setup black environment run: conda create --quiet --name black black - name: Check formatting run: | export PATH="/usr/share/miniconda/bin:$PATH" source activate black black --check snakemake tests/*.py - name: Comment PR if: github.event_name == 'pull_request' && failure() uses: marocchino/sticky-pull-request-comment@v1.1.0 with: message: 'Please format your code with [black](https://black.readthedocs.io): `black snakemake tests/*.py`.' GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} testing: runs-on: ubuntu-latest needs: formatting steps: - uses: actions/checkout@v1 - name: Setup Snakemake environment run: conda env create --quiet --name snakemake --file test-environment.yml - name: Setup apt dependencies run: sudo apt install -y singularity-container stress - name: Setup iRODS run: | docker build -t irods-server tests/test_remote_irods docker run -d -p 1247:1247 --name provider irods-server -i run_irods sleep 10 docker exec -u irods provider iput /incoming/infile cp -r tests/test_remote_irods/setup-data ~/.irods - name: Run tests env: CI: true run: | # enable coverage recording for subprocesses echo -e "try:\n import coverage\n coverage.process_startup()\nexcept:\n pass" > sitecustomize.py export COVERAGE_PROCESS_START=.coveragerc # activate conda env export PATH="/usr/share/miniconda/bin:$PATH" source activate snakemake # run tests export AWS_DEFAULT_REGION=us-east-1 export AWS_ACCESS_KEY_ID=${{ secrets.AWS_ACCESS_KEY_ID }} export AWS_SECRET_ACCESS_KEY=${{ secrets.AWS_SECRET_ACCESS_KEY }} coverage run -m pytest tests/test*.py -v -x # collect coverage report #coverage combine #coverage xml #- name: Upload coverage report #uses: codecov/codecov-action@v1.0.3 #with: #token: ${{secrets.CODECOV_TOKEN}} - name: Build container image run: docker build . snakemake-5.10.0/.gitignore000066400000000000000000000002661361131222100155200ustar00rootroot00000000000000*~ *.pyc *.swp docs/_build build/ dist/ *.egg-info/ *.egg .eggs/ .snakemake* .venv .venv/* .idea .pytest* .cache .ipynb* .ropeproject .test* tests/test*/* playground/* tutorial/*snakemake-5.10.0/.readthedocs.yml000066400000000000000000000001771361131222100166170ustar00rootroot00000000000000version: 2 sphinx: configuration: docs/conf.py python: version: 3.6 install: - requirements: docs/requirements.txt snakemake-5.10.0/.sonarcloud.properties000066400000000000000000000000301361131222100200620ustar00rootroot00000000000000sonar.sources=snakemake snakemake-5.10.0/AUTHORS.rst000066400000000000000000000000001361131222100153710ustar00rootroot00000000000000snakemake-5.10.0/CHANGELOG.rst000066400000000000000000000712211361131222100155500ustar00rootroot00000000000000[5.10.0] - 2019-01-20 ===================== Added ----- - Jupyter notebook integration, see docs. This enables interactive development of certain data analysis parts (e.g. for plotting). - Ability to overwrite thread definitions at the command line (``--threads rulename=3``), thereby improving scalability. - Requester pays configuration for google storage remote files. - Add keyword ``allow_missing`` to expand function, thereby allowing partical expansion by skipping wildcards for which no keywords are defined. Changed ------- - Various bug fixes, e.g. for between workflow caching and script execution. [5.9.1] - 2019-12-20 ==================== Changed ------- - Added a missing module. [5.9.0] - 2019-12-20 ==================== Added ----- - Support for per-rule environment module definitions to enable HPC specific software deployment (see docs). - Allow custom log handler defitions via --log-handler-script (e.g. post errors and progress to a slack channel or send emails). - Allow setting threads as a function of the given cores (see docs). Changed ------- - Various minor fixes. [5.8.2] - 2019-12-16 ==================== Added ----- - Implemented a ``multiext`` helper, allowing to define a set of output files that just differ by extension. Changed ------- - Fixed a failure when caching jobs with conda environments. - Fixed various minor bugs. - Caching now allows to cache the output of rules using ``multiext``. [5.8.1] - 2019-11-15 ==================== Changed ------- - Fixed a bug by adding a missing module. [5.8.0] - 2019-11-15 ==================== Added ----- - Blockchain based caching between workflows (in collaboration with Sven Nahnsen from QBiC), see `the docs `_. - New flag --skip-cleanup-scripts, that leads to temporary scripts (coming from script or wrapper directive) are not deleted (by Vanessa Sochat). Changed ------- - Various bug fixes. [5.7.4] - 2019-10-23 ==================== Changed ------- - Various fixes and adaptations in the docker container image and the test suite. [5.7.1] - 2019-10-16 ==================== Added ----- - Ability to print log files of failed jobs with --show-failed-logs. Changed ------- - Fixed bugs in tibanna executor. - Fixed handling of symbolic links. - Fixed typos in help texts. - Fixed handling of default resources. - Fixed bugs in azure storage backend. [5.7.0] - 2019-10-07 ==================== Changed ------- - Fixed various corner case bugs. Many thanks to the community for pull requests and reporting! - Container execution adapted to latest singularity. Added ----- - First class support for Amazon cloud execution via a new `Tibanna backend `. Thanks to Soo Lee from Harvard Biomedical Informatics! - Allow multiple config files to be passed via the command line. - A new, more detailed way to visualize the DAG (--filegraph). Thanks to Henning Timm! - Pathlib compatibility added. Input and output files can now also be Path objects. Thanks to Frederik Boulund! - New azure storage remote provider. Transparently access input and output files on Microsoft Azure. Thanks to Sebastian Kurscheid! [5.6.0] - 2019-09-06 ==================== Changed ------- - Fix compatibility with latest singularity versions. - Various bug fixes (e.g. in cluster error handling, remote providers, kubernetes backend). Added ----- - Add --default-resources flag, that allows to define default resources for jobs (e.g. mem_mb, disk_mb), see `docs `_. - Accept ``--dry-run`` as a synonym of ``--dryrun``. Other Snakemake options are similarly hyphenated, so other documentation now refers to ``--dry-run`` but both (and also ``-n``) will always be accepted equivalently. [5.5.4] - 2019-07-21 ==================== Changed ------- - Reports now automatically include workflow code and configuration for improved transparency. [5.5.3] - 2019-07-11 ==================== Changed ------- - Various bug fixes. - Polished reports. [5.5.2] - 2019-06-25 ==================== Changed ------- - Various minor bug fixes in reports. - Speed improvements when using checkpoints. [5.5.1] - 2019-06-18 ==================== Changed ------- - Improved report interface. In particular for large files. - Small TSV tables are automatically rendered as HTML with datatables. - Be more permissive with Snakefile choices: allow "Snakefile", "snakefile", "workflow/Snakefile", "workflow/snakefile". [5.5.0] - 2019-05-31 ==================== Added ----- - Script directives now also support Julia. Changed ------- - Various small bug fixes. [5.4.5] - 2019-04-12 ==================== Changed ------- - Fixed a bug with pipe output. - Cleaned up error output. [5.4.4] - 2019-03-22 ==================== Changed ------- - Vastly improved performance of HTML reports generated with --report, via a more efficient encoding of dara-uri based download links. - Tighter layout, plus thumbnails and a lightbox for graphical results in HTML reports. - Bug fix for pipe groups. - Updated docs. - Better error handling in DRMAA executor. [5.4.3] - 2019-03-11 ==================== Changed ------- - More robust handling of conda environment activation that should work with all setups where the conda is available when starting snakemake. - Fixed bugs on windows. [5.4.2] - 2019-02-15 ==================== Changed ------- - Fixed a bug where git module cannot be imported from wrapper. [5.4.1] - 2019-02-14 ==================== Added ----- - Warning when R script is used in combination with conda and R_LIBS environment variable is set. This can cause unexpected results and should be avoided. Changed ------- - Improved quoting of paths in conda commands. - Fixed various issues with checkpoints. - Improved error messages when combining groups with cluster config. - Fixed bugs in group implementation. - Fixed singularity in combination with shadow. [5.4.0] - 2018-12-18 ==================== Added ----- - Snakemake now allows for data-dependent conditional re-evaluation of the job DAG via checkpoints. This feature also deprecates the ``dynamic`` flag. See `the docs `_. [5.3.1] - 2018-12-06 ==================== Changed ------- - Various fixed bugs and papercuts, e.g., in group handling, kubernetes execution, singularity support, wrapper and script usage, benchmarking, schema validation. [5.3.0] - 2018-09-18 ==================== Added ----- - Snakemake workflows can now be exported to CWL via the flag --export-cwl, see `the docs `_. Changed ------- - Fixed bug in script and wrapper execution when using ``--use-singularity --use-conda``. - Add host argument to S3RemoteProvider. - Various minor bug fixes. [5.2.4] - 2018-09-10 ==================== Added ----- - New command line flag --shadow-prefix Changed ------- - Fixed permission issue when using the script directive. This is a breaking change for scripts referring to files relative to the script directory (see the `docs `__). - Fixed various minor bugs and papercuts. - Allow URL to local git repo with wrapper directive (``git+file:///path/to/your/repo/path_to_file@@version``) [5.2.2] - 2018-08-01 ==================== Changed ------- - Always print timestamps, removed the --timestamps CLI option. - more robust detection of conda command - Fixed bug in RMarkdown script execution. - Fixed a bug in detection of group jobs. [5.2.0] - 2018-06-28 ==================== Changed ------- - Directory outputs have to marked with ``directory``. This ensures proper handling of timestamps and cleanup. This is a breaking change. Implemented by Rasmus Ågren. - Fixed kubernetes tests, fixed kubernetes volume handling. Implemented by Andrew Schriefer. - jinja2 and networkx are not optional dependencies when installing via pip. - When conda or singularity directives are used and the corresponding CLI flags are not specified, the user is notified at the beginning of the log output. - Fixed numerous small bugs and papercuts and extended documentation. [5.1.5] - 2018-06-24 ==================== Changed ------- - fixed missing version info in docker image. - several minor fixes to EGA support. [5.1.4] - 2018-05-28 ==================== Added ----- - Allow ``category`` to be set. Changed ------- - Various cosmetic changes to reports. - Fixed encoding issues in reports. [5.1.3] - 2018-05-22 ==================== Changed ------- - Fixed various bugs in job groups, shadow directive, singularity directive, and more. [5.1.2] - 2018-05-18 ==================== Changed ------- - Fixed a bug in the report stylesheet. [5.1.0] - 2018-05-17 ==================== Added ----- - A new framework for self-contained HTML reports, including results, statistics and topology information. In future releases this will be further extended. - A new utility snakemake.utils.validate() which allows to validate config and pandas data frames using JSON schemas. - Two new flags --cleanup-shadow and --cleanup-conda to clean up old unused conda and shadow data. Changed ------- - Benchmark repeats are now specified inside the workflow via a new flag repeat(). - Command line interface help has been refactored into groups for better readability. [5.0.0] - 2018-05-11 ==================== Added ----- - Group jobs for reduced queuing and network overhead, in particular with short running jobs. - Output files can be marked as pipes, such that producing and consuming job are executed simultaneously and interfomation is transferred directly without using disk. - Command line flags to clean output files. - Command line flag to list files in working directory that are not tracked by Snakemake. Changed ------- - Fix of --default-remote-prefix in case of input functions returning lists or dicts. - Scheduler no longer prefers jobs with many downstream jobs. [4.8.1] - 2018-04-25 ==================== Added ----- - Allow URLs for the conda directive. # Changed - Various minor updates in the docs. - Several bug fixes with remote file handling. - Fix ImportError occuring with script directive. - Use latest singularity. - Improved caching for file existence checks. We first check existence of parent directories and cache these results. By this, large parts of the generated FS tree can be pruned if files are not yet present. If files are present, the overhead is minimal, since the checks for the parents are cached. - Various minor bug fixes. [4.8.0] - 2018-03-13 ==================== Added ----- - Integration with CWL: the ``cwl`` directive allows to use CWL tool definitions in addition to shell commands or Snakemake wrappers. - A global ``singularity`` directive allows to define a global singularity container to be used for all rules that don't specify their own. - Singularity and Conda can now be combined. This can be used to specify the operating system (via singularity), and the software stack (via conda), without the overhead of creating specialized container images for workflows or tasks. [4.7.0] - 2018-02-19 ==================== Changed ------- - Speedups when calculating dry-runs. - Speedups for workflows with many rules when calculating the DAG. - Accept SIGTERM to gracefully finish all running jobs and exit. - Various minor bug fixes. [4.6.0] - 2018-02-06 ==================== Changed ------- - Log files can now be used as input files for other rules. - Adapted to changes in Kubernetes client API. - Fixed minor issues in --archive option. - Search path order in scripts was changed to fix a bug with leaked packages from root env when using script directive together with conda. [4.5.1] - 2018-02-01 ==================== Added ----- - Input and output files can now tag pathlib objects. # ## Changed - Various minor bug fixes. [4.5.0] - 2018-01-18 ==================== Added ----- - iRODS remote provider # ## Changed - Bug fix in shell usage of scripts and wrappers. - Bug fixes for cluster execution, --immediate-submit and subworkflows. [4.4.0] - 2017-12-21 -------------------- Added ----- - A new shadow mode (minimal) that only symlinks input files has been added. Changed ------- - The default shell is now bash on linux and macOS. If bash is not installed, we fall back to sh. Previously, Snakemake used the default shell of the user, which defeats the purpose of portability. If the developer decides so, the shell can be always overwritten using shell.executable(). - Snakemake now requires Singularity 2.4.1 at least (only when running with --use-singularity). - HTTP remote provider no longer automatically unpacks gzipped files. - Fixed various smaller bugs. [4.3.1] - 2017-11-16 -------------------- Added ----- - List all conda environments with their location on disk via --list-conda-envs. Changed ------- - Do not clean up shadow on dry-run. - Allow R wrappers. [4.3.0] - 2017-10-27 -------------------- Added ----- - GridFTP remote provider. This is a specialization of the GFAL remote provider that uses globus-url-copy to download or upload files. # ## Changed - Scheduling and execution mechanisms have undergone a major revision that removes several potential (but rare) deadlocks. - Several bugs and corner cases of the singularity support have been fixed. - Snakemake now requires singularity 2.4 at least. [4.2.0] - 2017-10-10 -------------------- Added ----- - Support for executing jobs in per-rule singularity images. This is meant as an alternative to the conda directive (see docs), providing even more guarantees for reproducibility. Changed ------- - In cluster mode, jobs that are still running after Snakemake has been killed are automatically resumed. - Various fixes to GFAL remote provider. - Fixed --summary and --list-code-changes. - Many other small bug fixes. [4.1.0] - 2017-09-26 -------------------- Added ----- - Support for configuration profiles. Profiles allow to specify default options, e.g., a cluster submission command. They can be used via 'snakemake --profile myprofile'. See the docs for details. - GFAL remote provider. This allows to use GridFTP, SRM and any other protocol supported by GFAL for remote input and output files. - Added --cluster-status flag that allows to specify a command that returns jobs status. # ## Changed - The scheduler now tries to get rid of the largest temp files first. - The Docker image used for kubernetes support can now be configured at the command line. - Rate-limiting for cluster interaction has been unified. - S3 remote provider uses boto3. - Resource functions can now use an additional ``attempt`` parameter, that contains the number of times this job has already been tried. - Various minor fixes. [4.0.0] - 2017-07-24 -------------------- Added ----- - Cloud computing support via Kubernetes. Snakemake workflows can be executed transparently in the cloud, while storing input and output files within the cloud storage (e.g. S3 or Google Storage). I.e., this feature does not need a shared filesystem between the cloud notes, and thereby makes the setup really simple. - WebDAV remote file support: Snakemake can now read and write from WebDAV. Hence, it can now, e.g., interact with Nextcloud or Owncloud. - Support for default remote providers: define a remote provider to implicitly use for all input and output files. - Added an option to only create conda environments instead of executing the workflow. # ## Changed - The number of files used for the metadata tracking of Snakemake (e.g., code, params, input changes) in the .snakemake directory has been reduced by a factor of 10, which should help with NFS and IO bottlenecks. This is a breaking change in the sense that Snakemake 4.x won't see the metadata of workflows executed with Snakemake 3.x. However, old metadata won't be overwritten, so that you can always go back and check things by installing an older version of Snakemake again. - The google storage (GS) remote provider has been changed to use the google SDK. This is a breaking change, since the remote provider invocation has been simplified (see docs). - Due to WebDAV support (which uses asyncio), Snakemake now requires Python 3.5 at least. - Various minor bug fixes (e.g. for dynamic output files). [3.13.3] - 2017-06-23 --------------------- Changed ------- - Fix a followup bug in Namedlist where a single item was not returned as string. [3.13.2] - 2017-06-20 --------------------- Changed ------- - The --wrapper-prefix flag now also affects where the corresponding environment definition is fetched from. - Fix bug where empty output file list was recognized as containing duplicates (issue #574). [3.13.1] - 2017-06-20 --------------------- Changed ------- - Fix --conda-prefix to be passed to all jobs. - Fix cleanup issue with scripts that fail to download. [3.13.0] - 2017-06-12 --------------------- Added ----- - An NCBI remote provider. By this, you can seamlessly integrate any NCBI resouce (reference genome, gene/protein sequences, ...) as input file. # ## Changed - Snakemake now detects if automatically generated conda environments have to be recreated because the workflow has been moved to a new path. - Remote functionality has been made more robust, in particular to avoid race conditions. - ``--config`` parameter evaluation has been fixed for non-string types. - The Snakemake docker container is now based on the official debian image. [3.12.0] - 2017-05-09 --------------------- Added ----- - Support for RMarkdown (.Rmd) in script directives. - New option --debug-dag that prints all decisions while building the DAG of jobs. This helps to debug problems like cycles or unexpected MissingInputExceptions. - New option --conda-prefix to specify the place where conda environments are stored. Changed ------- - Benchmark files now also include the maximal RSS and VMS size of the Snakemake process and all sub processes. - Speedup conda environment creation. - Allow specification of DRMAA log dir. - Pass cluster config to subworkflow. [3.11.2] - 2017-03-15 --------------------- Changed ------- - Fixed fix handling of local URIs with the wrapper directive. [3.11.1] - 2017-03-14 --------------------- Changed ------- - --touch ignores missing files - Fixed handling of local URIs with the wrapper directive. [3.11.0] - 2017-03-08 --------------------- Added ----- - Param functions can now also refer to threads. # ## Changed - Improved tutorial and docs. - Made conda integration more robust. - None is converted to NULL in R scripts. [3.10.2] - 2017-02-28 --------------------- Changed ------- - Improved config file handling and merging. - Output files can be referred in params functions (i.e. lambda wildcards, output: ...) - Improved conda-environment creation. - Jobs are cached, leading to reduced memory footprint. - Fixed subworkflow handling in input functions. [3.10.0] - 2017-01-18 --------------------- Added ----- - Workflows can now be archived to a tarball with ``snakemake --archive my-workflow.tar.gz``. The archive contains all input files, source code versioned with git and all software packages that are defined via conda environments. Hence, the archive allows to fully reproduce a workflow on a different machine. Such an archive can be uploaded to Zenodo, such that your workflow is secured in a self-contained, executable way for the future. # ## Changed - Improved logging. - Reduced memory footprint. - Added a flag to automatically unpack the output of input functions. - Improved handling of HTTP redirects with remote files. - Improved exception handling with DRMAA. - Scripts referred by the script directive can now use locally defined external python modules. [3.9.1] - 2016-12-23 -------------------- Added ----- - Jobs can be restarted upon failure (--restart-times). # ## Changed - The docs have been restructured and improved. Now available under snakemake.readthedocs.org. - Changes in scripts show up with --list-code-changes. - Duplicate output files now cause an error. - Various bug fixes. [3.9.0] - 2016-11-15 -------------------- Added ----- - Ability to define isolated conda software environments (YAML) per rule. Environments will be deployed by Snakemake upon workflow execution. - Command line argument --wrapper-prefix in order to overwrite the default URL for looking up wrapper scripts. # ## Changed - --summary now displays the log files correspoding to each output file. - Fixed hangups when using run directive and a large number of jobs - Fixed pickling errors with anonymous rules and run directive. - Various small bug fixes [3.8.2] - 2016-09-23 -------------------- Changed ------- - Add missing import in rules.py. - Use threading only in cluster jobs. [3.8.1] - 2016-09-14 -------------------- Changed ------- - Snakemake now warns when using relative paths starting with "./". - The option -R now also accepts an empty list of arguments. - Bug fix when handling benchmark directive. - Jobscripts exit with code 1 in case of failure. This should improve the error messages of cluster system. - Fixed a bug in SFTP remote provider. [3.8.0] - 2016-08-26 -------------------- Added ----- - Wildcards can now be constrained by rule and globally via the new ``wildcard_constraints`` directive (see the `docs `__). - Subworkflows now allow to overwrite their config file via the configfile directive in the calling Snakefile. - A method ``log_fmt_shell`` in the snakemake proxy object that is available in scripts and wrappers allows to obtain a formatted string to redirect logging output from STDOUT or STDERR. - Functions given to resources can now optionally contain an additional argument ``input`` that refers to the input files. - Functions given to params can now optionally contain additional arguments ``input`` (see above) and ``resources``. The latter refers to the resources. - It is now possible to let items in shell commands be automatically quoted (see the `docs `__). This is usefull when dealing with filenames that contain whitespaces. Changed ------- - Snakemake now deletes output files before job exection. Further, it touches output files after job execution. This solves various problems with slow NFS filesystems. - A bug was fixed that caused dynamic output rules to be executed multiple times when forcing their execution with -R. - A bug causing double uploads with remote files was fixed. Various additional bug fixes related to remote files. - Various minor bug fixes. [3.7.1] - 2016-05-16 -------------------- Changed ------- - Fixed a missing import of the multiprocessing module. [3.7.0] - 2016-05-05 -------------------- Added ----- - The entries in ``resources`` and the ``threads`` job attribute can now be callables that must return ``int`` values. - Multiple ``--cluster-config`` arguments can be given to the Snakemake command line. Later one override earlier ones. - In the API, multiple ``cluster_config`` paths can be given as a list, alternatively to the previous behaviour of expecting one string for this parameter. - When submitting cluster jobs (either through ``--cluster`` or ``--drmaa``), you can now use ``--max-jobs-per-second`` to limit the number of jobs being submitted (also available through Snakemake API). Some cluster installations have problems with too many jobs per second. - Wildcard values are now printed upon job execution in addition to input and output files. # ## Changed - Fixed a bug with HTTP remote providers. [3.6.1] - 2016-04-08 -------------------- Changed ------- - Work around missing RecursionError in Python < 3.5 - Improved conversion of numpy and pandas data structures to R scripts. - Fixed locking of working directory. [3.6.0] - 2016-03-10 -------------------- Added ----- - onstart handler, that allows to add code that shall be only executed before the actual workflow execution (not on dryrun). - Parameters defined in the cluster config file are now accessible in the job properties under the key "cluster". - The wrapper directive can be considered stable. # ## Changed - Allow to use rule/job parameters with braces notation in cluster config. - Show a proper error message in case of recursion errors. - Remove non-empty temp dirs. - Don't set the process group of Snakemake in order to allow kill signals from parent processes to be propagated. - Fixed various corner case bugs. - The params directive no longer converts a list ``l`` implicitly to ``" ".join(l)``. [3.5.5] - 2016-01-23 -------------------- Added ----- - New experimental wrapper directive, which allows to refer to re-usable `wrapper scripts `__. Wrappers are provided in the `Snakemake Wrapper Repository `__. - David Koppstein implemented two new command line options to constrain the execution of the DAG of job to sub-DAGs (--until and --omit-from). # ## Changed - Fixed various bugs, e.g. with shadow jobs and --latency-wait. [3.5.4] - 2015-12-04 -------------------- Changed ------- - The params directive now fully supports non-string parameters. Several bugs in the remote support were fixed. [3.5.3] - 2015-11-24 -------------------- Changed ------- - The missing remote module was added to the package. [3.5.2] - 2015-11-24 -------------------- Added ----- - Support for easy integration of external R and Python scripts via the new `script directive `__. - Chris Tomkins-Tinch has implemented support for remote files: Snakemake can now handle input and output files from Amazon S3, Google Storage, FTP, SFTP, HTTP and Dropbox. - Simon Ye has implemented support for sandboxing jobs with `shadow rules `__. Changed ------- - Manuel Holtgrewe has fixed dynamic output files in combination with multiple wildcards. - It is now possible to add suffixes to all shell commands with shell.suffix("mysuffix"). - Job execution has been refactored to spawn processes only when necessary, resolving several problems in combination with huge workflows consisting of thousands of jobs and reducing the memory footprint. - In order to reflect the new collaborative development model, Snakemake has moved from my personal bitbucket account to http://snakemake.bitbucket.org. [3.4.2] - 2015-09-12 -------------------- Changed ------- - Willem Ligtenberg has reduced the memory usage of Snakemake. - Per Unneberg has improved config file handling to provide a more intuitive overwrite behavior. - Simon Ye has improved the test suite of Snakemake and helped with setting up continuous integration via Codeship. - The cluster implementation has been rewritten to use only a single thread to wait for jobs. This avoids failures with large numbers of jobs. - Benchmarks are now writing tab-delimited text files instead of JSON. - Snakemake now always requires to set the number of jobs with -j when in cluster mode. Set this to a high value if your cluster does not have restrictions. - The Snakemake Conda package has been moved to the bioconda channel. - The handling of Symlinks was improved, which made a switch to Python 3.3 as the minimum required Python version necessary. [3.4.1] - 2015-08-05 -------------------- Changed ------- - This release fixes a bug that caused named input or output files to always be returned as lists instead of single files. [3.4] - 2015-07-18 ------------------ Added ----- - This release adds support for executing jobs on clusters in synchronous mode (e.g. qsub -sync). Thanks to David Alexander for implementing this. - There is now vim syntax highlighting support (thanks to Jay Hesselberth). - Snakemake is now available as Conda package. Changed ------- - Lots of bugs have been fixed. Thanks go to e.g. David Koppstein, Marcel Martin, John Huddleston and Tao Wen for helping with useful reports and debugging. See `here `__ for older changes. snakemake-5.10.0/Dockerfile000066400000000000000000000017601361131222100155220ustar00rootroot00000000000000FROM bitnami/minideb:stretch MAINTAINER Johannes Köster ADD . /tmp/repo WORKDIR /tmp/repo ENV PATH /opt/conda/bin:${PATH} ENV LANG C.UTF-8 ENV SHELL /bin/bash RUN /bin/bash -c "install_packages wget bzip2 ca-certificates gnupg2 squashfs-tools git && \ wget -O- http://neuro.debian.net/lists/xenial.us-ca.full > /etc/apt/sources.list.d/neurodebian.sources.list && \ wget -O- http://neuro.debian.net/_static/neuro.debian.net.asc | apt-key add - && \ install_packages singularity-container && \ wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh && \ bash Miniconda3-latest-Linux-x86_64.sh -b -p /opt/conda && \ rm Miniconda3-latest-Linux-x86_64.sh && \ conda env create -n snakemake --file /tmp/repo/environment.yml && \ conda clean --all -y && \ source activate snakemake && \ which python && \ pip install ." RUN echo "source activate snakemake" > ~/.bashrc ENV PATH /opt/conda/envs/snakemake/bin:${PATH} snakemake-5.10.0/LICENSE.md000066400000000000000000000021131361131222100151250ustar00rootroot00000000000000Copyright (c) 2012-2019 Johannes Köster Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. snakemake-5.10.0/MANIFEST.in000066400000000000000000000001071361131222100152600ustar00rootroot00000000000000include versioneer.py include snakemake/_version.py include LICENSE.md snakemake-5.10.0/README.md000066400000000000000000000036071361131222100150110ustar00rootroot00000000000000[![GitHub actions status](https://github.com/snakemake/snakemake/workflows/CI/badge.svg?branch=master)](https://github.com/snakemake/snakemake/actions?query=branch%3Amaster+workflow%3ACI) [![Sonarcloud Status](https://sonarcloud.io/api/project_badges/measure?project=snakemake_snakemake&metric=alert_status)](https://sonarcloud.io/dashboard?id=snakemake_snakemake) [![Bioconda](https://img.shields.io/conda/dn/bioconda/snakemake.svg?label=Bioconda)](https://bioconda.github.io/recipes/snakemake/README.html) [![Pypi](https://img.shields.io/pypi/pyversions/snakemake.svg)](https://pypi.org/project/snakemake) [![Docker Cloud Build Status](https://img.shields.io/docker/cloud/build/snakemake/snakemake)](https://hub.docker.com/r/snakemake/snakemake) [![Stack Overflow](https://img.shields.io/badge/stack-overflow-orange.svg)](https://stackoverflow.com/questions/tagged/snakemake) [![Twitter](https://img.shields.io/twitter/follow/johanneskoester.svg?style=social&label=Follow)](https://twitter.com/search?l=&q=%23snakemake%20from%3Ajohanneskoester) [![Github stars](https://img.shields.io/github/stars/snakemake/snakemake?style=social)](https://github.com/snakemake/snakemake/stargazers) # Snakemake The Snakemake workflow management system is a tool to create **reproducible and scalable** data analyses. Workflows are described via a human readable, Python based language. They can be seamlessly scaled to server, cluster, grid and cloud environments without the need to modify the workflow definition. Finally, Snakemake workflows can entail a description of required software, which will be automatically deployed to any execution environment. **Homepage: https://snakemake.readthedocs.io** **Note: Snakemake has just (Oct 4 2019) moved from bitbucket.org/snakemake/snakemake to Github. Forks and stars are not yet insightful here!** Copyright (c) 2012-2019 Johannes Köster (see LICENSE) snakemake-5.10.0/doc-environment.yml000066400000000000000000000005141361131222100173560ustar00rootroot00000000000000channels: - conda-forge dependencies: - python >=3.5 - datrie - wrapt - pyyaml - requests - appdirs - docutils - ratelimiter - configargparse - jsonschema - gitpython - sphinx - pip: - sphinxcontrib-napoleon - sphinx-argparse - sphinx_rtd_theme - docutils==0.12 - recommonmark - commonmark snakemake-5.10.0/docs/000077500000000000000000000000001361131222100144545ustar00rootroot00000000000000snakemake-5.10.0/docs/Makefile000066400000000000000000000151661361131222100161250ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 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 " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @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/Snakemake.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Snakemake.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/Snakemake" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Snakemake" @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." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @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." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 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." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." snakemake-5.10.0/docs/_static/000077500000000000000000000000001361131222100161025ustar00rootroot00000000000000snakemake-5.10.0/docs/_static/sphinx-argparse.css000066400000000000000000000002011361131222100217200ustar00rootroot00000000000000.wy-table-responsive table td { white-space: normal !important; } .wy-table-responsive { overflow: visible !important; } snakemake-5.10.0/docs/api_reference/000077500000000000000000000000001361131222100172435ustar00rootroot00000000000000snakemake-5.10.0/docs/api_reference/internal/000077500000000000000000000000001361131222100210575ustar00rootroot00000000000000snakemake-5.10.0/docs/api_reference/internal/modules.rst000066400000000000000000000002021361131222100232530ustar00rootroot00000000000000Internal API ============ These pages document the entire internal API of Snakemake. .. toctree:: :maxdepth: 4 snakemake snakemake-5.10.0/docs/api_reference/internal/snakemake.remote.rst000066400000000000000000000045331361131222100250470ustar00rootroot00000000000000snakemake.remote package ======================== Submodules ---------- snakemake.remote.EGA module --------------------------- .. automodule:: snakemake.remote.EGA :members: :undoc-members: :show-inheritance: snakemake.remote.FTP module --------------------------- .. automodule:: snakemake.remote.FTP :members: :undoc-members: :show-inheritance: snakemake.remote.GS module -------------------------- .. automodule:: snakemake.remote.GS :members: :undoc-members: :show-inheritance: snakemake.remote.HTTP module ---------------------------- .. automodule:: snakemake.remote.HTTP :members: :undoc-members: :show-inheritance: snakemake.remote.NCBI module ---------------------------- .. automodule:: snakemake.remote.NCBI :members: :undoc-members: :show-inheritance: snakemake.remote.S3 module -------------------------- .. automodule:: snakemake.remote.S3 :members: :undoc-members: :show-inheritance: snakemake.remote.S3Mocked module -------------------------------- .. automodule:: snakemake.remote.S3Mocked :members: :undoc-members: :show-inheritance: snakemake.remote.SFTP module ---------------------------- .. automodule:: snakemake.remote.SFTP :members: :undoc-members: :show-inheritance: snakemake.remote.XRootD module ------------------------------ .. automodule:: snakemake.remote.XRootD :members: :undoc-members: :show-inheritance: snakemake.remote.dropbox module ------------------------------- .. automodule:: snakemake.remote.dropbox :members: :undoc-members: :show-inheritance: snakemake.remote.gfal module ---------------------------- .. automodule:: snakemake.remote.gfal :members: :undoc-members: :show-inheritance: snakemake.remote.gridftp module ------------------------------- .. automodule:: snakemake.remote.gridftp :members: :undoc-members: :show-inheritance: snakemake.remote.iRODS module ----------------------------- .. automodule:: snakemake.remote.iRODS :members: :undoc-members: :show-inheritance: snakemake.remote.webdav module ------------------------------ .. automodule:: snakemake.remote.webdav :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: snakemake.remote :members: :undoc-members: :show-inheritance: snakemake-5.10.0/docs/api_reference/internal/snakemake.report.rst000066400000000000000000000002561361131222100250650ustar00rootroot00000000000000snakemake.report package ======================== Module contents --------------- .. automodule:: snakemake.report :members: :undoc-members: :show-inheritance: snakemake-5.10.0/docs/api_reference/internal/snakemake.rst000066400000000000000000000073631361131222100235610ustar00rootroot00000000000000snakemake package ================= Subpackages ----------- .. toctree:: snakemake.remote snakemake.report Submodules ---------- snakemake.benchmark module -------------------------- .. automodule:: snakemake.benchmark :members: :undoc-members: :show-inheritance: snakemake.checkpoints module ---------------------------- .. automodule:: snakemake.checkpoints :members: :undoc-members: :show-inheritance: snakemake.common module ----------------------- .. automodule:: snakemake.common :members: :undoc-members: :show-inheritance: snakemake.conda module ---------------------- .. automodule:: snakemake.conda :members: :undoc-members: :show-inheritance: snakemake.cwl module -------------------- .. automodule:: snakemake.cwl :members: :undoc-members: :show-inheritance: snakemake.dag module -------------------- .. automodule:: snakemake.dag :members: :undoc-members: :show-inheritance: snakemake.decorators module --------------------------- .. automodule:: snakemake.decorators :members: :undoc-members: :show-inheritance: snakemake.exceptions module --------------------------- .. automodule:: snakemake.exceptions :members: :undoc-members: :show-inheritance: snakemake.executors module -------------------------- .. automodule:: snakemake.executors :members: :undoc-members: :show-inheritance: snakemake.gui module -------------------- .. automodule:: snakemake.gui :members: :undoc-members: :show-inheritance: snakemake.io module ------------------- .. automodule:: snakemake.io :members: :undoc-members: :show-inheritance: snakemake.jobs module --------------------- .. automodule:: snakemake.jobs :members: :undoc-members: :show-inheritance: snakemake.logging module ------------------------ .. automodule:: snakemake.logging :members: :undoc-members: :show-inheritance: snakemake.output\_index module ------------------------------ .. automodule:: snakemake.output_index :members: :undoc-members: :show-inheritance: snakemake.parser module ----------------------- .. automodule:: snakemake.parser :members: :undoc-members: :show-inheritance: snakemake.persistence module ---------------------------- .. automodule:: snakemake.persistence :members: :undoc-members: :show-inheritance: snakemake.rules module ---------------------- .. automodule:: snakemake.rules :members: :undoc-members: :show-inheritance: snakemake.scheduler module -------------------------- .. automodule:: snakemake.scheduler :members: :undoc-members: :show-inheritance: snakemake.script module ----------------------- .. automodule:: snakemake.script :members: :undoc-members: :show-inheritance: snakemake.shell module ---------------------- .. automodule:: snakemake.shell :members: :undoc-members: :show-inheritance: snakemake.singularity module ---------------------------- .. automodule:: snakemake.singularity :members: :undoc-members: :show-inheritance: snakemake.stats module ---------------------- .. automodule:: snakemake.stats :members: :undoc-members: :show-inheritance: snakemake.utils module ---------------------- .. automodule:: snakemake.utils :members: :undoc-members: :show-inheritance: snakemake.workflow module ------------------------- .. automodule:: snakemake.workflow :members: :undoc-members: :show-inheritance: snakemake.wrapper module ------------------------ .. automodule:: snakemake.wrapper :members: :undoc-members: :show-inheritance: Module contents --------------- .. automodule:: snakemake :members: :undoc-members: :show-inheritance: snakemake-5.10.0/docs/api_reference/snakemake.rst000066400000000000000000000001511361131222100217310ustar00rootroot00000000000000.. _api_reference_snakemake: The Snakemake API ================= .. autofunction:: snakemake.snakemake snakemake-5.10.0/docs/api_reference/snakemake_utils.rst000066400000000000000000000001411361131222100231500ustar00rootroot00000000000000.. _utils-api: Additional utils ================ .. automodule:: snakemake.utils :members: snakemake-5.10.0/docs/conf.py000066400000000000000000000206201361131222100157530ustar00rootroot00000000000000#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Snakemake documentation build configuration file, created by # sphinx-quickstart on Sat Feb 1 16:01:02 2014. # # 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 from recommonmark.parser import CommonMarkParser source_parsers = {'.md': CommonMarkParser} # 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.mathjax', 'sphinx.ext.viewcode', 'sphinxcontrib.napoleon', 'sphinxarg.ext' ] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = ['.rst', '.md'] # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = 'Snakemake' copyright = '2014-2016, Johannes Koester' import snakemake # 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 = snakemake.__version__ # The full version, including alpha/beta/rc tags. release = snakemake.__version__ # 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 = True # 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 = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'sphinx_rtd_theme' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = sphinx_bootstrap_theme.get_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 = None # 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 = None # 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'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # 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 = 'Snakemakedoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'Snakemake.tex', 'Snakemake Documentation', 'Johannes Koester', '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 # 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', 'snakemake', 'Snakemake Documentation', ['Johannes Koester'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Snakemake', 'Snakemake Documentation', 'Johannes Koester', 'Snakemake', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False def setup(app): app.add_stylesheet('sphinx-argparse.css') snakemake-5.10.0/docs/executing/000077500000000000000000000000001361131222100164475ustar00rootroot00000000000000snakemake-5.10.0/docs/executing/caching.rst000066400000000000000000000064571361131222100206110ustar00rootroot00000000000000.. _caching: ======================== Between workflow caching ======================== Within certain data analysis fields, there are certain intermediate results that reoccur in exactly the same way in many analysis. For example, in bioinformatics, reference genomes or annotations are downloaded, and read mapping indexes are built. Since such steps are independent of the actual data or measurements that are analyzed, but still computationally or timely expensive to conduct, it has been common practice to externalize their computation and assume the presence of the resulting files before execution of a workflow. From version 5.8.0 on, Snakemake offers a way to keep those steps inside the actual analysis without requiring from redundant computations. By hashing all steps, parameters, software stacks (in terms of conda environments or containers), and raw input required up to a certain step in a `blockchain `_, Snakemake is able to recognize **before** the computation whether a certain result is already available in a central cache on the same system. **Note that this is explicitly intended for caching results between workflows! There is no need to use this feature to avoid redundant computations within a workflow. Snakemake does this already out of the box.** Such caching has to be explitly activated per rule, which can be done via the command line interface. For example, .. code-block:: console $ export SNAKEMAKE_OUTPUT_CACHE=/mnt/snakemake-cache/ $ snakemake --cache download_reference create_index would instruct Snakemake to cache and reuse the results of the rules ``download_reference``and ``create_index``. The environment variable definition that happens in the first line (defining the location of the cache) should of course be done only once and system wide in reality. When Snakemake is executed without a shared filesystem (e.g., in the cloud, see :ref:`cloud`), the environment variable has to point to a location compatible with the given remote provider (e.g. an S3 or Google Storage bucket). In any case, the provided location should be shared between all workflows of your group, institute or computing environment, in order to benefit from the reuse of previously obtained intermediate results. Note that only rules with just a single output file (or directory) or with :ref:`multiext output files ` are eligible for caching. The reason is that for other rules it would be impossible to unambiguously assign the output files to cache entrys while being agnostic of the actual file names. Also note that the rules need to retrieve all their parameters via the ``params`` directive (except input files). It is not allowed to directly use ``wildcards``, ``config`` or any global variable in the shell command or script, because these are not captured in the hash (otherwise, reuse would be unnecessarily limited). Also note that Snakemake will store everything in the cache as readable and writeable for **all users** on the system (except in the remote case, where permissions are not enforced and depend on your storage configuration). Hence, caching is not intended for private data, just for steps that deal with publicly available resources. Finally, be aware that the implementation has to be considered **experimental** until this note is removed.snakemake-5.10.0/docs/executing/cli.rst000066400000000000000000000117431361131222100177560ustar00rootroot00000000000000.. _executable: ====================== Command line interface ====================== This part of the documentation describes the ``snakemake`` executable. Snakemake is primarily a command-line tool, so the ``snakemake`` executable is the primary way to execute, debug, and visualize workflows. .. user_manual-snakemake_options: ----------------------------- Useful Command Line Arguments ----------------------------- If called without parameters, i.e. .. code-block:: console $ snakemake Snakemake tries to execute the workflow specified in a file called ``Snakefile`` in the same directory (instead, the Snakefile can be given via the parameter ``-s``). By issuing .. code-block:: console $ snakemake -n a dry-run can be performed. This is useful to test if the workflow is defined properly and to estimate the amount of needed computation. Further, the reason for each rule execution can be printed via .. code-block:: console $ snakemake -n -r Importantly, Snakemake can automatically determine which parts of the workflow can be run in parallel. By specifying the number of available cores, i.e. .. code-block:: console $ snakemake --cores 4 one can tell Snakemake to use up to 4 cores and solve a binary knapsack problem to optimize the scheduling of jobs. If the number is omitted (i.e., only ``--cores`` is given), the number of used cores is determined as the number of available CPU cores in the machine. Snakemake workflows usually define the number of used threads of certain rules. Sometimes, it makes sense to overwrite the defaults given in the workflow definition. This can be done by using the ``--set-threads`` argument, e.g., .. code-block:: console $ snakemake --cores 4 --set-threads myrule=2 would overwrite whatever number of threads has been defined for the rule ``myrule`` and use ``2`` instead. This can be particularly handy when used in combination with :ref:`cluster execution `. Dealing with very large workflows --------------------------------- If your workflow has a lot of jobs, Snakemake might need some time to infer the dependencies (the job DAG) and which jobs are actually required to run. The major bottleneck involved is the filesystem, which has to be queried for existence and modification dates of files. To overcome this issue, Snakemake allows to run large workflows in batches. This way, fewer files have to be evaluated at once, and therefore the job DAG can be inferred faster. By running .. code-block:: console $ snakemake --cores 4 --batch myrule=1/3 you instruct to only compute the first of three batches of the inputs of the rule `myrule`. To generate the second batch, run .. code-block:: console $ snakemake --cores 4 --batch myrule=2/3 Finally, when running .. code-block:: console $ snakemake --cores 4 --batch myrule=3/3 Snakemake will process beyond the rule `myrule`, because all of its input files have been generated, and complete the workflow. Obviously, a good choice of the rule to perform the batching is a rule that has a lot of input files and upstream jobs, for example a central aggregation step within your workflow. We advice all workflow developers to inform potential users of the best suited batching rule. .. _profiles: -------- Profiles -------- Adapting Snakemake to a particular environment can entail many flags and options. Therefore, since Snakemake 4.1, it is possible to specify a configuration profile to be used to obtain default options: .. code-block:: console $ snakemake --profile myprofile Here, a folder ``myprofile`` is searched in per-user and global configuration directories (on Linux, this will be ``$HOME/.config/snakemake`` and ``/etc/xdg/snakemake``, you can find the answer for your system via ``snakemake --help``). Alternatively, an absolute or relative path to the folder can be given. The profile folder is expected to contain a file ``config.yaml`` that defines default values for the Snakemake command line arguments. For example, the file .. code-block:: yaml cluster: qsub jobs: 100 would setup Snakemake to always submit to the cluster via the ``qsub`` command, and never use more than 100 parallel jobs in total. Under https://github.com/snakemake-profiles/doc, you can find publicly available profiles. Feel free to contribute your own. The profile folder can additionally contain auxilliary files, e.g., jobscripts, or any kind of wrappers. See https://github.com/snakemake-profiles/doc for examples. .. _all_options: ----------- All Options ----------- .. argparse:: :module: snakemake :func: get_argument_parser :prog: snakemake All command line options can be printed by calling ``snakemake -h``. .. _getting_started-bash_completion: --------------- Bash Completion --------------- Snakemake supports bash completion for filenames, rulenames and arguments. To enable it globally, just append .. code-block:: bash `snakemake --bash-completion` including the accents to your ``.bashrc``. This only works if the ``snakemake`` command is in your path. snakemake-5.10.0/docs/executing/cluster-cloud.rst000066400000000000000000000261231361131222100217720ustar00rootroot00000000000000=========================== Cluster and cloud execution =========================== .. _cloud: ------------- Cloud Support ------------- Snakemake 4.0 and later supports execution in the cloud via Kubernetes. This is independent of the cloud provider, but we provide the setup steps for GCE below. Google cloud engine ~~~~~~~~~~~~~~~~~~~ First, install the `Google Cloud SDK `_. Then, run .. code-block:: console $ gcloud init to setup your access. Then, you can create a new kubernetes cluster via .. code-block:: console $ gcloud container clusters create $CLUSTER_NAME --num-nodes=$NODES --scopes storage-rw with ``$CLUSTER_NAME`` being the cluster name and ``$NODES`` being the number of cluster nodes. If you intent to use google storage, make sure that `--scopes storage-rw` is set. This enables Snakemake to write to the google storage from within the cloud nodes. Next, you configure Kubernetes to use the new cluster via .. code-block:: console $ gcloud container clusters get-credentials $CLUSTER_NAME If you are having issues with authentication, please refer to the help text: .. code-block:: console $ gcloud container clusters get-credentials --help You likely also want to use google storage for reading and writing files. For this, you will additionally need to authenticate with your google cloud account via .. code-block:: console $ gcloud auth application-default login This enables Snakemake to access google storage in order to check existence and modification dates of files. Now, Snakemake is ready to use your cluster. **Important:** After finishing your work, do not forget to delete the cluster with .. code-block:: console $ gcloud container clusters delete $CLUSTER_NAME in order to avoid unnecessary charges. .. _kubernetes: Executing a Snakemake workflow via kubernetes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Assuming that kubernetes has been properly configured (see above), you can execute a workflow via: .. code-block:: console snakemake --kubernetes --use-conda --default-remote-provider $REMOTE --default-remote-prefix $PREFIX In this mode, Snakemake will assume all input and output files to be stored in a given remote location, configured by setting ``$REMOTE`` to your provider of choice (e.g. ``GS`` for Google cloud storage or ``S3`` for Amazon S3) and ``$PREFIX`` to a bucket name or subfolder within that remote storage. After successful execution, you find your results in the specified remote storage. Of course, if any input or output already defines a different remote location, the latter will be used instead. Importantly, this means that Snakemake does **not** require a shared network filesystem to work in the cloud. .. sidebar:: Note Consider to :ref:`group jobs ` in order to minimize overhead, in particular for short-running jobs. Currently, this mode requires that the Snakemake workflow is stored in a git repository. Snakemake uses git to query necessary source files (the Snakefile, scripts, config, ...) for workflow execution and encodes them into the kubernetes job. It is further possible to forward arbitrary environment variables to the kubernetes jobs via the flag ``--kubernetes-env`` (see ``snakemake --help``). When executing, Snakemake will make use of the defined resources and threads to schedule jobs to the correct nodes. In particular, it will forward memory requirements defined as `mem_mb` to kubernetes. Further, it will propagate the number of threads a job intends to use, such that kubernetes can allocate it to the correct cloud computing node. Executing a Snakemake workflow via Tibanna on Amazon Web Services ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ First, install `Tibanna `_. .. code-block:: console $ pip install -U tibanna Set up aws configuration either by creating files ``~/.aws/credentials`` and ``~/.aws/config`` or by setting up environment variables as below (see Tibanna or AWS documentation for more details): .. code-block:: console $ export AWS_ACCESS_KEY_ID= $ export AWS_SECRET_ACCESS_KEY= $ export AWS_DEFAULT_REGION= As an AWS admin, deploy Tibanna Unicorn to Cloud with permissions to a specific S3 bucket. Name the Unicorn / Unicorn usergroup with the ``--usergroup`` option. Unicorn is a serverless scheduler, and keeping unicorn on the cloud does not incur extra cost. One may have many different unicorns with different names and different bucket permissions. Then, add other (IAM) users to the user group that has permission to use this unicorn / buckets. .. code-block:: console $ tibanna deploy_unicorn -g -b $ tibanna add_user -u -g As a user that has been added to the group (or as an admin), set up the default unicorn. .. code-block:: console $ export TIBANNA_DEFAULT_STEP_FUNCTION_NAME=tibanna_unicorn_ Then, you can run as many snakemake runs as you wish as below, inside a directory that contains Snakefile and other necessary components (e.g. ``env.yml``, ``config.json``, ...). .. code-block:: console $ snakemake --tibanna --default-remote-prefix=/ [] In this mode, Snakemake will assume all input and output files to be stored in the specified remote location (a subdirectory inside a given S3 bucket.) After successful execution, you find your results in the specified remote storage. Of course, if any input or output already defines a different remote location, the latter will be used instead. In that case, Tibanna Unicorn must be deployed with all the relevant buckets (``-b bucket1,bucket2,bucket3,...``) to allow access to the Unicorn serverless components. Snakemake will assign 3x of the total input size as the allocated space for each execution. The execution may fail if the total input + output + temp file sizes exceed this estimate. In addition to regular snakemake options, ``--precommand=`` option allows sending a command to execute before executing each step on an isolated environment. This kind of command could involve downloading or installing necessary files that cannot be handled using conda (e.g. the command may begin with ``wget``, ``git clone``, etc.) To check Tibanna execution logs, first use ``tibanna stat`` to see the list of all the individual runs. .. code-block:: console $ tibanna stat -n -l Then, check the detailed log for each job using the Tibanna job id that can be obtained from the first column of the output of ``tibanna stat``. .. code-block:: console $ tibanna log -j .. sidebar:: Note Consider to :ref:`group jobs ` in order to minimize overhead, in particular for short-running jobs. When executing, Snakemake will make use of the defined resources and threads to schedule jobs to the correct nodes. In particular, it will forward memory requirements defined as `mem_mb` to Tibanna. Further, it will propagate the number of threads a job intends to use, such that Tibanna can allocate it to the most cost-effective cloud compute instance available. .. _cluster: ----------------- Cluster Execution ----------------- Snakemake can make use of cluster engines that support shell scripts and have access to a common filesystem, (e.g. the Sun Grid Engine). In this case, Snakemake simply needs to be given a submit command that accepts a shell script as first positional argument: .. code-block:: console $ snakemake --cluster qsub -j 32 Here, ``-j`` denotes the number of jobs submitted being submitted to the cluster at the same time (here 32). The cluster command can be decorated with job specific information, e.g. .. sidebar:: Note Consider to :ref:`group jobs ` in order to minimize overhead, in particular for short-running jobs. .. code-block:: console $ snakemake --cluster "qsub {threads}" Thereby, all keywords of a rule are allowed (e.g. rulename, params, input, output, threads, priority, ...). For example, you could encode the expected running time into params: .. code-block:: python rule: input: ... output: ... params: runtime="4h" shell: ... and forward it to the cluster scheduler: .. code-block:: console $ snakemake --cluster "qsub --runtime {params.runtime}" If your cluster system supports `DRMAA `_, Snakemake can make use of that to increase the control over jobs. E.g. jobs can be cancelled upon pressing ``Ctrl+C``, which is not possible with the generic ``--cluster`` support. With DRMAA, no ``qsub`` command needs to be provided, but system specific arguments can still be given as a string, e.g. .. code-block:: console $ snakemake --drmaa " -q username" -j 32 Note that the string has to contain a leading whitespace. Else, the arguments will be interpreted as part of the normal Snakemake arguments, and execution will fail. Job Properties ~~~~~~~~~~~~~~ When executing a workflow on a cluster using the ``--cluster`` parameter (see below), Snakemake creates a job script for each job to execute. This script is then invoked using the provided cluster submission command (e.g. ``qsub``). Sometimes you want to provide a custom wrapper for the cluster submission command that decides about additional parameters. As this might be based on properties of the job, Snakemake stores the job properties (e.g. name, rulename, threads, input, output, params etc.) as JSON inside the job script (for group jobs, the rulename will be "GROUP", otherwise it will be the same as the job name). For convenience, there exists a parser function `snakemake.utils.read_job_properties` that can be used to access the properties. The following shows an example job submission wrapper: .. code-block:: python #!python #!/usr/bin/env python3 import os import sys from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) # do something useful with the threads threads = job_properties[threads] # access property defined in the cluster configuration file (Snakemake >=3.6.0) job_properties["cluster"]["time"] os.system("qsub -t {threads} {script}".format(threads=threads, script=jobscript)) .. _getting_started-visualization: ------------- Visualization ------------- To visualize the workflow, one can use the option ``--dag``. This creates a representation of the DAG in the graphviz dot language which has to be postprocessed by the graphviz tool ``dot``. E.g. to visualize the DAG that would be executed, you can issue: .. code-block:: console $ snakemake --dag | dot | display For saving this to a file, you can specify the desired format: .. code-block:: console $ snakemake --dag | dot -Tpdf > dag.pdf To visualize the whole DAG regardless of the eventual presence of files, the ``forceall`` option can be used: .. code-block:: console $ snakemake --forceall --dag | dot -Tpdf > dag.pdf Of course the visual appearance can be modified by providing further command line arguments to ``dot``. snakemake-5.10.0/docs/executing/interoperability.rst000066400000000000000000000032521361131222100225700ustar00rootroot00000000000000 ================ Interoperability ================ .. _cwl_export: ---------- CWL export ---------- Snakemake workflows can be exported to `CWL `_, such that they can be executed in any `CWL-enabled workflow engine `_. Since, CWL is less powerful for expressing workflows than Snakemake (most importantly Snakemake offers more flexible scatter-gather patterns, since full Python can be used), export works such that every Snakemake job is encoded into a single step in the CWL workflow. Moreover, every step of that workflow calls Snakemake again to execute the job. The latter enables advanced Snakemake features like scripts, benchmarks and remote files to work inside CWL. So, when exporting keep in mind that the resulting CWL file can become huge, depending on the number of jobs in your workflow. To export a Snakemake workflow to CWL, simply run .. code-block:: console $ snakemake --export-cwl workflow.cwl The resulting workflow will by default use the `Snakemake docker image `_ for every step, but this behavior can be overwritten via the CWL execution environment. Then, the workflow can be executed in the same working directory with, e.g., .. code-block:: console $ cwltool workflow.cwl Note that due to limitations in CWL, it seems currently impossible to avoid that all target files (output files of target jobs), are written directly to the workdir, regardless of their relative paths in the Snakefile. Note that export is impossible in case the workflow contains :ref:`dynamic output files ` or output files with absolute paths.snakemake-5.10.0/docs/getting_started/000077500000000000000000000000001361131222100176435ustar00rootroot00000000000000snakemake-5.10.0/docs/getting_started/img/000077500000000000000000000000001361131222100204175ustar00rootroot00000000000000snakemake-5.10.0/docs/getting_started/img/c-dag.png000066400000000000000000000177331361131222100221130ustar00rootroot00000000000000PNG  IHDREDFbKGDIDATxyt[Wd˫ɎE@v~d@)8!CJ8L !iPʤ9sSBӁ:!츶5,"[uʶ{%=s|tu>}}ޠDNk]UB4#ȟ㠜?H\w5~kS:_ֆY-҄**nqB3%OJv-tj3mn]f3y~ 4t,^ ^ir2d35%n Ξ8:;1R_kS߹Q"{2rbȟ9p ە+TWmKZnތ fCmhկ/_꒒/Dq1pl׮د^EO8[HRh/8y$?fix'6cxQSS3g̙33<si+ -[xG&s`!?oxT~ATE !lnٳV_B\=VZ+w^,\(//5\'x"VH>rC("oFD/h?x{7+VDB.kWDVee'(|?D]FT!lK?B87:KH?t"=Ab qN/\7BBbƍǛ!He˖Chp~cpNM6!c#vQ oъ֭Æ Ѐp X~-n7L&^}U`ҥhmm6Q__Rl߾=,7oS|"S7m۶zj}'{LcO>m388j߶e1*++YFF`j=pKOOg)))f6-É\ I'1<ȟ1O ǎ]w5nwߍcǎMh^ 666GSO!TM EIoъx7m7~iۿذaÔpB[x衇`e 777GDo@DSOa͚5HLL y>|m, %77k֬oxI]L#W__o۶- [!O> #Gرc(**;{JϟG^^^۱c~o ׯGii)/^B<BL Fp0rR]>KH(W\7(WD? o?ZYv-}Y DH}#&sM (NBVVz9'V߈I ţ>Pb! Ha 7n @+P*BAƊ._D^^j5V\ ._x~;RSSn ~_x@Tkg}_EEE|uuu|ۍ#??)))0S&TLes OT 3ND6O,HG,='~UY8qݸ馛sN;?0F#._lg?kѣŝwމ7G}n|~}Mi0K.?|cn[FԽ%"'b 30ZZZ@킔A~lU~>d))3:L  ?eO=/8w{/cmc顇ۧDLЅ߶?3\rss١Cftk;6:JJJRd/f}avڵmqي+Jbgwc?rrrXrr2{'}xUرTXjjs|ev㥗0'ݝw"[!čd¥={\.RR7* ȟ cw/MM'f|‰tz{PJ ?mrO1^˗O$|DIۼgи?\!2/E]:,% gϢsT"{&w YDo~ZB V c|n7 KMtII?B9>RS{dP\.t~{'Cg䙙a) =+O> hp- q"S&HT* KNe #!^\CCh?tlDzLpACp/_]Ss (8hiVNӟ+$.ݻ' 'OaۥKoGHR1 DEh4HAš5H.ZzZP05du:opl|->|gqR)ҶoGhWW_뭷.hʂ,9J㞟9)6!? ˔l  GG`NgqH.(f@Q8fX`:~$>qDVCjR#XJhOX,@7p4^ogQfF.^Ӱ67O5|x#qP57#ȑW@eN_Z NW) ?ZtqJ%H{mpcP@Ev64\sa ?n7Ν;Wi)..Ffb #77W`D -L$ JJJ FVR!Bϕ+Wx'%%d1tTr^`bٌVT*EqqE$j3vh0 Q]h8Cqq1$PVbv477h%@H!BѠ+ C F>9$0鐑s4Za!(>PjbÌ`r4㮽[nP[ِÌL&;_iiiRSS(! @rr2v [$nBAI;A/mkgg'H8N,X@Q|A/csh5;`-I ]%Hyĩ^Q~j _`U* lX7O* lUtApB> B_V_Z $~&JEQ|a/boF7C(**G[鯺:8@NN@IMM嗟_&⋏(> G cl\b$_n>KN{\}^pi i⤴/cg/)O ht%BHLLDvv6F#?P&`2x~cnGUUrrr H$laB Ӄ\. jlddx޻B8?`nY8Z,݉0ي$9! $l6ٳqh1ѭu"z{{q97~H Ο??m!n L0Ξ=0[G$@VCN;ͭ`@ӏ Jq ?Xl qܤ?D4v^c@#l,] b=sv8;E"@.c0 # G (//4fBa&HLLDyy9_"ɰh"]:x3 Xd  $&##eeeP~?BOsRR'2HN\\JKK1A3)>N2hZl"&cT6 vXXfİ 'scg@ )Cv2r'.8hFzJ 2N4ي,@iZ) !TY:zPc,)x|BF =dX>Mp$X.NFY}@;v38 qȐ#A9Ac=Np^'Ŧb0J 噅9߈7y9pX&#"+s 4d$NjW& h]LA#N+@ m&/^g iov} 9)vfc6WXbpy83)!$؞7$ u!/u!¤px ' kXCq'H(R lUha>;`^eqYB;Jo'coQ/|ܒRmi9|+\u kXC &w}'{ r)6%aSeǡ/$_$]ŏoO_ulWwgb,d}|E`t|U 'lpHe-U;J_<Qc3CɞU{#81*AQ _`fԍt|eMŐ^O kLB>akD\$H(Zm}uN]9$~ YqgjG'"xS:KuꇒG Dt>ɀKغ_`XJT`KćNJ}A/0P$uޱAMOy?A($~QH_|yyyPXr%.\v~#%%wfaDff&v $(ȑ#8qqM7aΝgĉ'pq455p`߾}OgΜ3gPUUgy&l.`v*VoގLObN⩙pB(.wttCkk+ `ѢE .[oEcccPB8F )rF㷀KPRR L&|vl0kpB1Lnn.ZZZ\̿ollu" bǎnGuu5l2m[FFغuk,& 0vD"m݆Ŵ݋G}͛7'D?'dPnUH "F! t4U[p|_`9`15RD'$~IyJU٩FXLv3=/0Vt:Ϩ%G31na8 ̒ fCCIcvPBqYPrUi^4[K2A/0q9V':I4-bO*В/nO/S 0b*+ųBҸL~n"E@LMɞ.;^<7[m%[E-)H] oiZD#n'm_D( '?W[. lUq; %jU?PJȨtNer;}>k;!^/CT!eQ/B2؊:9&^,V`˂!#^<~g! y<^ 񋕺n8 7-@/ԿO9{s_!~*$_s +W/\8rls_09BbdsR+ގ_NNC%D@X` ֖=я> C Xn_ӧDJ% IW^b0r8 ?#|V?/MOGcA^P `u@~4s:c_SXB=@&::K%_s 'qJ'@߂9M$- +Wx[{;$))!zh}lBƍ$" I6G?f3읝Hrp!=a.{ P.[""$ۏ9Ei)Dqq;{` cg"K NHYԿ|i{V^9VchK/{%K1d6nCd~Q!cwByA}ꦛ&hZ&߅1}ڀ6/x{(+ pd:2d}7 W_ vG?։ dl@P Zkt4ub7_֭PVT01lԿ g.=VSß4x16e/nٖBuy.g`:u c55Aݞf/9sWo vPnwZn9kA^{ RoMCM6H7ɟ+Aߧ=_p4As}Ϟo ׿n.#|`/eg x읃=r$ۣ= ٿ_v@ʕ/Z*L!~|8Kc#צxIq1zX ƍ<'o~qжe5k@P/_n7vލE!55۶m;^@QQr9/_G⥗^BII  6l؀=aϞ=h4HKK? 9ફ_W:;; ~ ~2@&~ꀮ}8r-kK @lY@ڦ6o1wiw6I;kܴ}-Ȋ+Xee%cfw<++ocVutto}[~-{ص^Z[[ ;ٮ]؍7Țdb?Xbb"YKK W_뮻uwwnvuױ~g9clҥr˾oٵ}q&ֺcG@;WYMq&f 躩|X V~7躩ɷIlި6gl>ݬq&"&a㼼<~yδ544z=+((`Zl6|S(>;w,~z+0kjjbl6gXMtt/w .5(ۘ@JMLglL}Ưȑ\cc|ۀܿ_Ѝ7Mvހq)/}T*`b_^UUnVƖ,Yy) ~}D"х7x `lXX\\ܔ{AVZZʜN'۲e ۿ\~,~ݼ9f1}|}~Fdĉ1FOnjyr6u=ڜܿ_A)..fӟs:l```fokYii)kap vOXnUTT{26m-ASA `|Կ]l& ٌ$ǣ<-[bD^d̴lQ__ݎl޼yܹ===Ν;qwL}'/~ <䓐>cy6ɉOXo6+--eR|RyWYii)cWf~-s\lrVQQMHtv='ڜz& 1=PǞph(ۅBmcO,yT4!(qo2CR!55?Ȼヒk ٸ000<,' 'Hv$<-y\}ՈCvv6^6 h?0όkS.~>ըᅬ>(qH$s=*B|~#l߾=إZhz/#@ףEEEغukj H@tnC6ۅq€oW6ӟįѢEؽ{7^z%~ᅬ믿 P/~?x ӷ9Ν;罝pD>sG#jۼvRRR]vĉd O<G=ى<>q! b֭p0 ظq#|A:t8{,3LY۾}pTVVO=T 6ׯ[~ee%V\ |6Gqqqn8vo=ZPfq5lsۅM66df]\Lb"vnj>9m76}v%0ZluOjw挅T泷9ﴥVݠ,իQRRӧOkEjj*8Crr2t:볲J% 339󚢢"bLY[GG23357;IBBFFFHLL9o VZ |6w{.*Ovo=P1`ضm[Q@sbg.+Yf 8~ݻק͛7{Ass3N'úuNNΔߗ6DX@?aٲe8uXlk;ꪫ t65KNm>} _ϙskz>t~ yRRvڅ9mcBpO:>n6mۅs}x'ֆ6<䓸{?<>%\2mLhgy=6kͧos΂:\^hۚ P}8XԿlm~w]]]x'/~qNۘ<MX˷L 6dSۅv7yn7{XJJ KIIacӽ33n[˷7mb?kb7c c~|6ͦ;˯u3LX}=&o-_ub &X^^fd3nR C!UB̵>EdN:1gΠ?o݊ԛo6*:6 ,@f ۶m ]R& yqjlEC+V_x=6n@R,] N*텭|8A|9G__At$J(+ 6/UrHeN !\.O"nmm%(!`|"Ku3>vͰjD}pQ>/>I,>IW\p>|8e] > ^Hʠ$)_*/ v+Vi0`d$B$^z)IIރkp+7m;Կ)O ih bd*.se7)+*Zgi 7o='"}Wf嗃-2=cUVTu{Կ }ʀBuM!7/QtAlw2A&x8L\ |AfyAt\2Ĝ6}ˉw͡jԛo3?xړ3+UqcH+G qm:KS87hUωhA-N! ɶ\wO?T 8e`rw?c!b$[SpLpt$VAZbO/pNWiSP5: kK ͆я>LѣA-EE[ɕ|Br$]k $$Ggg'L&b1.RHԠ}f級7Km-==sl,CŲeut:4M599HII Er[;w.8ppI$h)([,Da{;H B\Q,8|1󰶶®d-" G޺4#\(+*fXxxxAYYYSwӿE@dBgg'}OHH@~~>222,?ZVtR+nVvv6,Y"pEѩ0O ، #N KanGAAu\.TUU2~]ryy9usP+ ]0:i*P0G6 a6AA###8s @*b>v>}\WZ6'DF^tuuM22f˃Z8Hss3zzzx:r+.uuupNN/^,pE1> (Bf3<u`n>}`u Pm`h4ˣ`Pn"|ntNAp8s cJXnd2eE4mj*$%% ]VD.9Z`QbV*ϧtwwӱl2+lΝ 77Wy&cccч:ʌ:@n7pҥPG#᦯<#J@Ru\\󑙙IA'^8ڞ? EV+:;;)]]]4,_\"v^^(̙G\N:FPP/Fuu52 u hhdt?Ʉj0 Hn:dpepi8Np5kUpt`&(cLA,˙B[[:;;P?h"+ ?d:1j:n;7QPO1*~^^Fk茗`fDBnn.s&j\< j/4=;ڞ34aEA=vtttT*VX!pEٳP؂lG h2% 1gh4JKK)pU{h;11Wm f2_dFT*E^^rrrb6b1֯_féSrq֮]Nn&@LGMbݍ j/hoo@C݀vaa! (f 朜Lt/dN(M.))AVVU Cӡ @lm8 IDATyyyJVH" 4 PUU XuYUUUp\DXvmrVKL,LAәOzf`KE:6^s`a ǮYP,HTћO/9߹27S8Gecè|DBMcz[n,UU`DJ=k4H 4tu ("CHm@F hXqW9(IT9Y.$"\ƵZ xcr6gh'!$X(ITW(-onXBvLHd'pPʼn#4?7$*iA*$vTt|/zwHGM"6$ZX$cmv}Ə(Iī7_ӵk~$u\HS0/wרMJ h331+==G.+!$0(IijyBRF Yq^moKHތD9vQۓhCM!! hB!$ Q@B!a$Ӊz PTxe6 <233Gy6ۅv8 /"r,_GK/( lذ >ٳg4 nÇc͚5P((,,ċ//sؽ{7-ZTl۶ fٯ|l8{E^^D"oߎ4dffbϞ=8u(ILzgg?Cmm-N>ӧOٳ׿8tuVp 8p}] lܸ>k:j={xg]w݅'x裏p ~پ}pTVVO=?ɓp=gS޽Auu5> }D%FHw\66k/^jjj\VTTjkk555 iZlfNyNP{ΝYlcg[VVzL޽̶=g;EEE\[[˼okgfBD_}^P(044gjBRbLq&qX,ӮsmVWW?)>cWzRoH$5dy/m8n!lBar) o46%beYsz-!ᆆILCss˲?nnnFNN΂Vuζ5k޽{m6~Y~~>5Ÿy3ymmmc*{ $P@t;Z[[144;w˶lق;vرclٲmܹ===Ν;qw-[`Q__ݎl޼yNg>۲e vV V]v-xDPh9mCT*{e};ai4|Y,~T6==LV3JŶoάV|WYii)cWf2ʘ\.g~^fTX,`*jIҀot D:M"|ATucٳgq뭷^4ԽPt D&Ν;с]v[o$Bݛ(6d-oޣ/,,aq-`!>!jx;w؁;v]!Iy$(Hhy=I4xJ_1[J^m&/C)1gNʹ^mOGH䣀&/?Ed$5z}A|$$2P@4]A&!B99R1tp$%"̌Y2h"j̓Vy}Uf<Hb8/5+!B mAMBFESwˈ1 R h5XY/ޏASH Zb+&Q#?Y//NIEG+Ӎ=G{b_^<:{D hUX>lsǴPHGӍi>l+pNJ "$v$.]ЍO\ @Л=ЛD~!шю?~ևޱٿ \F6 i@MbJM*FP5_}ȅ4S;zFbkp͢d hN7kØCr,iDˁQ(pE "7Y &1(ߏ:@yy922`B"},%B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB Cc ]!dnf3jkkrnZ,C$_/[ JB8X,)9΋yhT*0k&$$@T*BH Q@4 f:Bq4M+"4!JVϺVCT !$( P2 IIIӆtRRd2Y" 4!l!l&$Q@222浌( `*g8B"( dЄDixGMHKOOh:--M!@MHD|Hst~OBH䢿bB@FFc`ѵτD YZ -M8i:Vk+vp2 ]Z!{| .-(@,Y X˔!BFHPP@`~GWrBfi->KIQ]|RN*t94*L/\J,$*$,ĈkC!:|il͸ T!GMm*i9HIW(趋рڂOFя|z]z!N'`4xz{?ޮH8 W݀[RoR/pu$f9&9s\Y.2tl$9a9ȐH#XW$pe$ڬاۋ~G?@%QGy?BD0 hF]xI9r< +#B0Lث}:ZODq2?t4H.ot e^(cX8ʕxFWSkX]rFb4H cg<(d88~Y;WEP@3śo"veq"Up7wAF&uks{f56:[\$Cm5+"d(ID18 82/@,7n"nP݈,Y6#t \!sCM"'ƣp بH0i9166Txtc+"dn(ID2U"9.OBjHېt9B"4vfGPXFgmY99ʕk3!pED{?]D3?pCkBGM"ƈs:C!`%$I~ w$b6㊄VVUJ hAhpq3s{Q""!šHBMHQ@B!aD5 /r˗/ѣGK/ 6l@CCۍݻwcѢEHMMŶm`6}ֹw^A$ l6l߾iiiĞ={|pg[L} iiiHNN/?|0֬YBB~-}g{FAZZ~aiZfzD+ h~m:tغu+n8p. 6n܈|}ȑ#DKK z)u~8y$nuٻwFGGjjjP]]Ç|?׿uZqb8ҸmiN5Vft: 544z=+((Yg{{vX}}=yyͶιa㼼<~yδ̟9ןV[[?>w+..Y>׳2|~o M^VVRdffTήVK3&#CFkՐHdiI&D *69^G/m pyB 4($\ ``8<Րpwx0xΒ4R!dn(ID$R$ pE$\? Y \!sCM"bS& 6f,pE$\gؙ)}3T hqN" SOqxBH9a<ӦSyJZ; hqDn=op&,MxA00pp^#~kIDZ(ǖ;㹞_&pUDhmV<%s-wLT$h s|qEWEN[>ܫ&pU4hNo<SoƭA. \ ۆhߐ3EM" 4IdlecrÍx :x&z7pK4) x$jT5JRqyX@,*"91ήŹ3X%!CMmŁCɜBCB@Ipܖz;DqBCHP@Qptȗ`8tBYd):z_ڛB3TI*L|ϝ$104[ϣ ]v{ՉfIg'̞,p`N4AE!$$Ȓe(qf( rqVZ$Akr`2-B~~5VBvQWW3]XX(x8H$Byy9b1CCCWEHlb IDAT&D0KU((--0.pU hBB: 0 'v׃ZЄΟ?8K.TA,..Fbb"`xxWDHl&$D&w.((@JJUMO$aҥH<ttthBBimm;T*\ lVDH저&$ zzzx;{^KKKCnn.pGЄjEcc# ǝe2UMQQhoo Bb4!AC]]N fyyya}y:Oh`*B4!AHNNFaa-\.ollsu( .T*ҥKq=wtjj*NGDЄqg(++\.Yhs4557y|Ӈ"$&& YbGl6. hnnìc8s z{{C\Ytř3g`06ۍWFHx&1oԉ477Ϻwq C*"$'u@*ۻ6;_WWKd˲eN|K|ɵI(P.=lڞ9iS2&Д´2i=h3gЅRv-v6mn)Rh@./qݲeٺߞV[,镬gAk}<&uU0V*]hRwPv/V \?4ޙ6Й+QT)+oƘ{m@ 9W LOPRlӽ(ϮДw!AcÆݨ2c[WG^˜5o5Exһ:C ev\|ڀ#ȩsmo2$}Д?メ|^|#VmR8:ΎQ!ߖzP7BLC'rn}Pk_PcO!r.7$44]!8,a}.4܎6SOmÖH-;YB](i\m@@ }vʷ*ӷi"5qpr= e_@C#&*y&]xA \ (ȉ6((iʏG_\ou1}1iD04Xo*,j|#/\B`66kC~RZ#^Bp*}`@ӢM n܈ueH6C36}|{V[d߻Om`oDlJe-a8Z2׷i.4-J$9ǿT)zj?kjFgҾh8Y $ y(k?ܷIߦo7-JQT"eI/C|޷mWi_dϛ69ۀDP߷д(jI5([L(ס$8k'4zHj- lJ}qLvo۷Д/<WPQQe "O\~$0>}cLt W|j=ۀR;ӷ݃4?4%Ό4Taڢ`%nbc;k'Sۀd۾m +XIѨ (ʓ L.m=ə6((fPhrLoД?<{lԔ)XIv2j(Wϴ6oДPDb*d'Ӷim@};N_ߦ1n8II{ۨ-y+Sgvd%ۊ})}fc@SVG /y\|Nϲ*={,m6s|/MYrA(0)# ͆ |;߉[>}n(o%%%x{O~~gϞ|.\@(c=^z //2~ӟu8y$N>v|NVғO>/8|07s/??~ǏӧOĽW_믿q}ؾ};:?p8ƽfm{]wa޽Coo/#Ľ3=hTzN$vލo|o{oQSS|ղ x9qP~իW3gΈoW{,bχDMMM:qQWW'kvgϞWNs D>/uYF9s@ Ğ{<@ `{͵l[%V\IZJ,E-[JM I_&eAُMqwcG?~kϞ=WVA9s;wx<޽?qw>9|gױ~s=K|K_ۇ.8N߿?l׮]x衇ׇ>ev)>Znz*CܑHDb$!x;-?uP$)=db =LhܑmmKʅ~xQ(]Q2{0)wdz۲-i!nJHMX$MTNJ$efoӼДVe=E VBQy)TѐܷUm2jb V)մN (3Sr׷i~ hJȢ=u(XIFO,?<}{~!~o׷i~ hJ԰*I ws6fݴ)m1BRmgPMgmR(7}0ӟ8ˉtFӶ.FS~gN-mb[`%٣|˷WY>>3}b@ӢYը/xGB8W;x9v[oFQa8WN@7*RO$ƥA T|"ɞ}0ڕ4MIqpIxB+K=wpF~Qo @%i+ܦh]I!1iIutYJ ,%&]h0&0>A1DD(̸[Q[`s ع_yLl([&;cp!0Kp{DdoPAhշRb*8#?ҥAcÆݨ2LRȩ;ڀCclno+4Ԩ ]1>*]΂T LO_x+J()h@umXej^9WaO@D mOPG&.gPPҀ`^u+TMi1=%B˙CDp_E4\ IVmYWR;}a?gt]Bȗ}mpTD)HҼZt*Pv`@S޺p7o$I WEB|TWWN᪈2PުbLNN[قi:V+jkk-H! h[$aݺuh4^8Not:477-45^>[[[ Uiڵ8$/4=͆*@(Bkk+xiFfEQ;w|!XMM HY h" U V+jj8 JºuVs-www|t cpPCcڵ WDD 455Ş|ty^?E{ZV᪈h2X c-Q]y޹6v1XfM|8.]pES{{{sii)(0p/r)\244@AAAܩ"1`0`͚5'[[[ v9<=X `@ͣ@ )Dp9DQmfY᪈h 0v|>y+WT"ŀ&ZZƺuRɿ*]]](Bcce74Q&ip8pUejj .\ _~zw&JM+Vn~ttt(\QqW^ ɤpUDُMHѣP(//Gee4"j477G_pn[᪲[__F WD;Dl6ܡ*)BWWQb hkTUURWq 6C8睉Ā&JlF]]|tkkkV|t: ` /DtmCVfx<$( -v޹ A᪈rh(+\QM (++S*ƀ&Z"FkƝz WY=== _z>4Q );Ӊ3_Vxޙh[D"555ZӓC,g@ :Xq7G?>"Od h%zفa@_:IpUz-NyDU>4LFx+V C^lPi>oZ1 (\Qnc@-ˎ\莒y}]k%kWz8pEDM$Gȋ7]:3>mmP"}ڈJE_(IL bۭMPsK%n6/d.E!J @Kd-_HĬԈ0\D.vGHD(71ޮ/(QiQ(71 BT5*XIvif+Wŀ&JB :3W^ʿ=?zyha h$d@$]q?k:lVDيMDDDDDYMDDD9wy'l6c8"b@堻 {E__z{{QUUGyD鲈(xQ:uT`?+XrO~%%%$ EEET,"J!4Qڹs'^tvv"cll Ǽ&ZND9bd2<%Q1rЏc8pf۶mmݦtIDbH(m߾۷o} !r( 1( YS8F9Dc LwI0`Pic=d8k[1`ךbS VfmYۊM2|bH]7@>P5*\ Qnb@%A rE#!e'` <M 4Qn,\ 亠l1YO]n4T\ƀ&Jҍ数tv*\a/sP. hd1TYlTW,6!BUkgh%!J{ojQ/{S} W#Sp㶢Ze "q h%PA½͐ ASះA@KP0%a@-:C.uEOb]~'xˇ)ۈ2"}\tD)IqB[+`\pUT/~8t !!kp}U- h (~0>L] fQ jyMqhoE~yV / #J 4Q [gc4F]7Wy(L]GOc< s\z|dm:€&Jxd,Dcjl4Q3ê1dfXD 0:;3}:l5PJM&hokE4 rFRa;J֢`' h4 y'WޙE%>n'f"c@ey8 sfZ#BgF} 6g&4Q+T( iDDDYG~ъIENDB`snakemake-5.10.0/docs/getting_started/img/latex-dag.png000066400000000000000000000224671361131222100230060ustar00rootroot00000000000000PNG  IHDRs{bKGD IDATxyt[ս/`YȲَؙ<$2)-ah1d( p)$+#ҮPhz&Ȣ-By-7*!yBI ! d۱!YdI5c˖%ٖu$Ykiw~:gc B™V$tBf9!DJ$3m@7h)݃hduls校H"qIDPHEȈBQJEf|СMvε0C7h`92 2Pt3jA؋A!Թka,V$%FP| ڰ'Z MBTx*$Lk[;dnཊnk7=.qP+H"^&DM' lF+L )`i2BoOls@]>w\X)Tr)ddm =CQϠ(1;"%d)O-ЏTt>A8,UrP3Յ0e9P9#zw_fbɸ`qߌ_B7C vFt ݠ ߴym xR\y2/ o‘f=XIJ ZFzo;])7.WC,𯳯Z xL#{ ,M5q}pJWK6|++iw2w0`׉6i3*K恣OYc?XùsPYY:{Q__J9s?}~|V͛7`pM/Яm~)= Nn 6l{P]]3g̙3?5X޶L^;\*BZћ}zzW2AP-)o̐+OaK}ul1&pBvE~GGq˶nrssYGGǶϝ;233ݖW]]߿p=_UUx=9p{cm߾%''7|1<8=Om`}r9?//oBcg6>c_mok/}u.M}zv::6l2O?vqzBL G^D 1r9'^m6ܹcGa/!GGGOx~:|c>]1ƲhО1O].kD{`_[nJ'N%xG08''g̈́q 4Vfc vo6>޽ n߻w/֭[w;& _1bcc}A.ofÁbt>2mWx# Ĥ2k9>{JNNf555nlʕfVYYd38x [ld1K/ m`z+jLղ[om۶7+W7YFF۳gcW^yeff;vxlӶ𴼱=3lʕL0FnfϯXeF5552o+o=0Bx3H2饗ؼy6ng{a .d2?1OS?ѭ]v~1&xgߏ/Nrk[yWJzۆޖ=m1YN\_DL1Eg>`YffFc2L>U_D},((`UUU'F~O1B(|cb{a---V՟Ҵ۲-}|LT(b?eHUJWO%X,3B5{{,#pٳgُcR؂ ءCڛj)ݩlq|;=Ӎi_S*ėܟګW͆ /J*ݙ51rH_R14D?˱~twwcxGﭼ틷e{㓭?1zjײRBx:1M>_mOHr!5c9~0LB+Wc͸͍7g?jkkaXPUUիWF]v-|I[lq{~͚5x'jjO`͚5=6oތ&'䟻ꪫsNL&477z1K19D3AK`m(鴹fjncO>uֹ[۲W=kپ>?LcvEo2{`_{`~LâYQQJ,;;ٳgʐzkW)Q_;- 233RdvUs6ZJ_ldcOv%~vp쏞ϊXtt4[l;|[{ʻ߾۲yꟾbײRBx:1MU={?=;UP+3>!sYɊ}pujQacIkabnx9pW9> ]ib ܒf;>NiFPIˌ¢$9#YY!BzoK&'J/SD/_k.;VRbzC?ֿduCu #3[$"(qB%bceqb]ebt'5b'/9'%ș7#!vߌ*qaL_]_~?nЊOw}8bWXd%D!E!8?́Ngی8f>}c$ńN ;cwU-TXP+AmC~؎.u=C8jDnN"p{/UMԒI~I ,MƵqB26>߅A/w b+~EՐgM5XƍpCv\f=ζaGnJaD܄*$BC9\rf2a`|,ݠ]ChdEAM8ejI%jyH Lհ́ ]- Z'^$6c)2P P`9]]]x"_C#++ yyyGEBYSS4 gE!9d&%CVcѢEg4Fi!.99"555p8h4X`Сr%p^mPTGEÀJ’%Kk(^HwKK,D>Q2(..Xގ:"BC{{;@,('G<̛7-wtt.201P[[|<. yIHH@II л(1D~(HPZZ##Ba`0 VsJŋqujjj㼊T*EII bcC2t451\šRĝTWWݑp9yEYYuz{{)G(ݎ*:/2JG<Ġ2koo/*++aSYHJLR$tQ2111(++Ctt4@ףzp%r^FYY%r2%R΋BzTTTf2fOr6!cQ2 `@EE?ㅄՊ  ȉo451Y,TVVd2Ww(%b i( xCS#QTTJKKP(b82⋧DN_#T*EYY✗rDyy9fɘf<..eeexC>044r  oxxr^7>>%%%HJ5%'HP\\'Iol||<))snDž O\4_YX& yaҥ|!5BD@ WTR `@k \T8&d*(!"K.BJFQ6HRaҥTL9uY$ON!o``d$2{ xb6 8700*̂ZŋqpG|8-BJJ ф:@J|"OII… )d>q"'pdϭejj*(d>ǹzFFl.\@ =zzzp!IQ2'^UUN'pTOӹ])33GE"%s+((@vv6kjj(@ww7jjj://;;GE"%s&77999FzggQN<''GE"%s2󑗗!pTᣣuuu|" D233 mڴZ-?C8pdeeQ'9 ,V˟`#v8}8Z-`n2 WbH D 7.A$!##:DB86["f_' eIxdN1XY ͡\Tҟz  () H.G'Dslᇰ () ȉ5w=Ij$}7޽GD2JoV#GQP~ OyJO = 3XIDAT'dNf8~)NpaZ 18;<0ksrvP}=:^{9VqP? >2eŋv-Y,hݹf & f׭|""sǨ^(F9aV8e+tz+ԏ<"pT@KɜL?pw݅ĻH&6sݿi⮻6e(b Cs%*׮Euׅ8ɓ_au>qH~$t I`ΞE[o&<D]=bR{f6 VUpĄuJyQ(/0JB&dN1<>@ߡC`6'@jh!dՀB@gBHD !9p8IENDB`snakemake-5.10.0/docs/getting_started/installation.rst000066400000000000000000000061301361131222100230760ustar00rootroot00000000000000.. _getting_started-installation: ============ Installation ============ Snakemake is available on PyPi as well as through Bioconda and also from source code. You can use one of the following ways for installing Snakemake. .. _conda-install: Installation via Conda ====================== This is the **recommended** way to install Snakemake, because it also enables Snakemake to :ref:`handle software dependencies of your workflow `. First, you have to install the Miniconda Python3 distribution. See `here `_ for installation instructions. Make sure to ... * Install the **Python 3** version of Miniconda. * Answer yes to the question whether conda shall be put into your PATH. Then, you can install Snakemake with .. code-block:: console $ conda install -c bioconda -c conda-forge snakemake from the `Bioconda `_ channel. Alternatively, Snakemake can be installed into an isolated software environment with .. code-block:: console $ conda create -c bioconda -c conda-forge -n snakemake snakemake The software environment has to be activated before using Snakemake: .. code-block:: console $ conda activate snakemake $ snakemake --help A minimal version of Snakemake which only depends on the bare necessities can be installed with .. code-block:: console $ conda install -c bioconda -c conda-forge snakemake-minimal Note that Snakemake is available via Bioconda for historical, reproducibility, and continuity reasons. However, it is easy to combine Snakemake installation with other channels, e.g., by prefixing the package name with ``::bioconda``, i.e., .. code-block:: console $ conda install -c conda-forge bioconda::snakemake bioconda::snakemake-minimal Global Installation =================== With a working Python ``>=3.5`` setup, installation of Snakemake can be performed by issuing .. code-block:: console $ easy_install3 snakemake or .. code-block:: console $ pip3 install snakemake in your terminal. Installing in Virtualenv ======================== To create an installation in a virtual environment, use the following commands: .. code-block:: console $ virtualenv -p python3 .venv $ source .venv/bin/activate $ pip install snakemake Installing from Source ====================== We recommend installing Snakemake into a virtualenv or a conda environment instead of globally. Use the following commands to create a virtualenv and install Snakemake. Note that this will install the development version and as you are installing from the source code, we trust that you know what you are doing and how to checkout individual versions/tags. .. code-block:: console $ git clone https://github.com/snakemake/snakemake.git $ cd snakemake $ virtualenv -p python3 .venv $ source .venv/bin/activate $ python setup.py install You can also use ``python setup.py develop`` to create a "development installation" in which no files are copied but a link is created and changes in the source code are immediately visible in your ``snakemake`` commands. snakemake-5.10.0/docs/index.rst000066400000000000000000000375361361131222100163330ustar00rootroot00000000000000.. _manual-main: ========= Snakemake ========= .. image:: https://img.shields.io/conda/dn/bioconda/snakemake.svg?label=Bioconda :target: https://bioconda.github.io/recipes/snakemake/README.html .. image:: https://img.shields.io/pypi/pyversions/snakemake.svg :target: https://www.python.org .. image:: https://img.shields.io/pypi/v/snakemake.svg :target: https://pypi.python.org/pypi/snakemake .. image:: https://img.shields.io/docker/cloud/build/snakemake/snakemake :target: https://hub.docker.com/r/snakemake/snakemake .. image:: https://github.com/snakemake/snakemake/workflows/CI/badge.svg?branch=master :target: https://github.com/snakemake/snakemake/actions?query=branch%3Amaster+workflow%3ACI .. image:: https://img.shields.io/badge/stack-overflow-orange.svg :target: https://stackoverflow.com/questions/tagged/snakemake .. image:: https://img.shields.io/twitter/follow/johanneskoester.svg?style=social&label=Follow :target: https://twitter.com/search?l=&q=%23snakemake%20from%3Ajohanneskoester .. image:: https://img.shields.io/github/stars/snakemake/snakemake?style=social :alt: GitHub stars :target: https://github.com/snakemake/snakemake/stargazers .. .. raw:: html The Snakemake workflow management system is a tool to create **reproducible and scalable** data analyses. Workflows are described via a human readable, Python based language. They can be seamlessly scaled to server, cluster, grid and cloud environments, without the need to modify the workflow definition. Finally, Snakemake workflows can entail a description of required software, which will be automatically deployed to any execution environment. Snakemake is **highly popular** with, `~3 new citations per week `_. .. _manual-quick_example: ------------- Quick Example ------------- Snakemake workflows are essentially Python scripts extended by declarative code to define **rules**. Rules describe how to create **output files** from **input files**. .. code-block:: python rule targets: input: "plots/myplot.pdf" rule transform: input: "raw/{dataset}.csv" output: "transformed/{dataset}.csv" singularity: "docker://somecontainer:v1.0" shell: "somecommand {input} {output}" rule aggregate_and_plot: input: expand("transformed/{dataset}.csv", dataset=[1, 2]) output: "plots/myplot.pdf" conda: "envs/matplotlib.yaml" script: "scripts/plot.py" * Similar to GNU Make, you specify targets in terms of a pseudo-rule at the top. * For each target and intermediate file, you create rules that define how they are created from input files. * Snakemake determines the rule dependencies by matching file names. * Input and output files can contain multiple named wildcards. * Rules can either use shell commands, plain Python code or external Python or R scripts to create output files from input files. * Snakemake workflows can be easily executed on **workstations**, **clusters**, **the grid**, and **in the cloud** without modification. The job scheduling can be constrained by arbitrary resources like e.g. available CPU cores, memory or GPUs. * Snakemake can automatically deploy required software dependencies of a workflow using `Conda `_ or `Singularity `_. * Snakemake can use Amazon S3, Google Storage, Dropbox, FTP, WebDAV, SFTP and iRODS to access input or output files and further access input files via HTTP and HTTPS. .. _main-getting-started: --------------- Getting started --------------- To get a first impression, see our `introductory slides `_ or watch the `live demo video `_. News about Snakemake are published via `Twitter `_. To learn Snakemake, please do the :ref:`tutorial`, and see the :ref:`FAQ `. .. _main-support: ------- Support ------- * For releases, see :ref:`Changelog `. * Check :ref:`frequently asked questions (FAQ) `. * In case of questions, please post on `stack overflow `_. * To discuss with other Snakemake users, you can use the `mailing list `_. **Please do not post questions there. Use stack overflow for questions.** * For bugs and feature requests, please use the `issue tracker `_. * For contributions, visit Snakemake on `Github `_ and read the :ref:`guidelines `. -------- Citation -------- `Köster, Johannes and Rahmann, Sven. "Snakemake - A scalable bioinformatics workflow engine". Bioinformatics 2012. `_ See :doc:`Citations ` for more information. --------- Resources --------- `Snakemake Wrappers Repository `_ The Snakemake Wrapper Repository is a collection of reusable wrappers that allow to quickly use popular tools from Snakemake rules and workflows. `Snakemake Workflows Project `_ This project provides a collection of high quality modularized and re-usable workflows. The provided code should also serve as a best-practices of how to build production ready workflows with Snakemake. Everybody is invited to contribute. `Snakemake Profiles Project `_ This project provides Snakemake configuration profiles for various execution environments. Please consider contributing your own if it is still missing. `Bioconda `_ Bioconda can be used from Snakemake for creating completely reproducible workflows by defining the used software versions and providing binaries. .. project_info-publications_using: ---------------------------- Publications using Snakemake ---------------------------- In the following you find an **incomplete list** of publications making use of Snakemake for their analyses. Please consider to add your own. * Kuzniar et al. 2020. `sv-callers: a highly portable parallel workflow for structural variant detection in whole-genome sequence data `_. PeerJ. * Doris et al. 2018. `Spt6 is required for the fidelity of promoter selection `_. Molecular Cell. * Karlsson et al. 2018. `Four evolutionary trajectories underlie genetic intratumoral variation in childhood cancer `_. Nature Genetics. * Planchard et al. 2018. `The translational landscape of Arabidopsis mitochondria `_. Nucleic acids research. * Schult et al. 2018. `Effect of UV irradiation on Sulfolobus acidocaldarius and involvement of the general transcription factor TFB3 in the early UV response `_. Nucleic acids research. * Goormaghtigh et al. 2018. `Reassessing the Role of Type II Toxin-Antitoxin Systems in Formation of Escherichia coli Type II Persister Cells `_. mBio. * Ramirez et al. 2018. `Detecting macroecological patterns in bacterial communities across independent studies of global soils `_. Nature microbiology. * Amato et al. 2018. `Evolutionary trends in host physiology outweigh dietary niche in structuring primate gut microbiomes `_. The ISME journal. * Uhlitz et al. 2017. `An immediate–late gene expression module decodes ERK signal duration `_. Molecular Systems Biology. * Akkouche et al. 2017. `Piwi Is Required during Drosophila Embryogenesis to License Dual-Strand piRNA Clusters for Transposon Repression in Adult Ovaries `_. Molecular Cell. * Beatty et al. 2017. `Giardia duodenalis induces pathogenic dysbiosis of human intestinal microbiota biofilms `_. International Journal for Parasitology. * Meyer et al. 2017. `Differential Gene Expression in the Human Brain Is Associated with Conserved, but Not Accelerated, Noncoding Sequences `_. Molecular Biology and Evolution. * Lonardo et al. 2017. `Priming of soil organic matter: Chemical structure of added compounds is more important than the energy content `_. Soil Biology and Biochemistry. * Beisser et al. 2017. `Comprehensive transcriptome analysis provides new insights into nutritional strategies and phylogenetic relationships of chrysophytes `_. PeerJ. * Piro et al 2017. `MetaMeta: integrating metagenome analysis tools to improve taxonomic profiling `_. Microbiome. * Dimitrov et al 2017. `Successive DNA extractions improve characterization of soil microbial communities `_. PeerJ. * de Bourcy et al. 2016. `Phylogenetic analysis of the human antibody repertoire reveals quantitative signatures of immune senescence and aging `_. PNAS. * Bray et al. 2016. `Near-optimal probabilistic RNA-seq quantification `_. Nature Biotechnology. * Etournay et al. 2016. `TissueMiner: a multiscale analysis toolkit to quantify how cellular processes create tissue dynamics `_. eLife Sciences. * Townsend et al. 2016. `The Public Repository of Xenografts Enables Discovery and Randomized Phase II-like Trials in Mice `_. Cancer Cell. * Burrows et al. 2016. `Genetic Variation, Not Cell Type of Origin, Underlies the Majority of Identifiable Regulatory Differences in iPSCs `_. PLOS Genetics. * Ziller et al. 2015. `Coverage recommendations for methylation analysis by whole-genome bisulfite sequencing `_. Nature Methods. * Li et al. 2015. `Quality control, modeling, and visualization of CRISPR screens with MAGeCK-VISPR `_. Genome Biology. * Schmied et al. 2015. `An automated workflow for parallel processing of large multiview SPIM recordings `_. Bioinformatics. * Chung et al. 2015. `Whole-Genome Sequencing and Integrative Genomic Analysis Approach on Two 22q11.2 Deletion Syndrome Family Trios for Genotype to Phenotype Correlations `_. Human Mutation. * Kim et al. 2015. `TUT7 controls the fate of precursor microRNAs by using three different uridylation mechanisms `_. The EMBO Journal. * Park et al. 2015. `Ebola Virus Epidemiology, Transmission, and Evolution during Seven Months in Sierra Leone `_. Cell. * Břinda et al. 2015. `RNF: a general framework to evaluate NGS read mappers `_. Bioinformatics. * Břinda et al. 2015. `Spaced seeds improve k-mer-based metagenomic classification `_. Bioinformatics. * Spjuth et al. 2015. `Experiences with workflows for automating data-intensive bioinformatics `_. Biology Direct. * Schramm et al. 2015. `Mutational dynamics between primary and relapse neuroblastomas `_. Nature Genetics. * Berulava et al. 2015. `N6-Adenosine Methylation in MiRNAs `_. PLOS ONE. * The Genome of the Netherlands Consortium 2014. `Whole-genome sequence variation, population structure and demographic history of the Dutch population `_. Nature Genetics. * Patterson et al. 2014. `WhatsHap: Haplotype Assembly for Future-Generation Sequencing Reads `_. Journal of Computational Biology. * Fernández et al. 2014. `H3K4me1 marks DNA regions hypomethylated during aging in human stem and differentiated cells `_. Genome Research. * Köster et al. 2014. `Massively parallel read mapping on GPUs with the q-group index and PEANUT `_. PeerJ. * Chang et al. 2014. `TAIL-seq: Genome-wide Determination of Poly(A) Tail Length and 3′ End Modifications `_. Molecular Cell. * Althoff et al. 2013. `MiR-137 functions as a tumor suppressor in neuroblastoma by downregulating KDM1A `_. International Journal of Cancer. * Marschall et al. 2013. `MATE-CLEVER: Mendelian-Inheritance-Aware Discovery and Genotyping of Midsize and Long Indels `_. Bioinformatics. * Rahmann et al. 2013. `Identifying transcriptional miRNA biomarkers by integrating high-throughput sequencing and real-time PCR data `_. Methods. * Martin et al. 2013. `Exome sequencing identifies recurrent somatic mutations in EIF1AX and SF3B1 in uveal melanoma with disomy 3 `_. Nature Genetics. * Czeschik et al. 2013. `Clinical and mutation data in 12 patients with the clinical diagnosis of Nager syndrome `_. Human Genetics. * Marschall et al. 2012. `CLEVER: Clique-Enumerating Variant Finder `_. Bioinformatics. .. toctree:: :caption: Getting started :name: getting_started :hidden: :maxdepth: 1 getting_started/installation tutorial/tutorial tutorial/short .. toctree:: :caption: Executing workflows :name: execution :hidden: :maxdepth: 1 executing/cli executing/cluster-cloud executing/caching executing/interoperability .. toctree:: :caption: Defining workflows :name: snakefiles :hidden: :maxdepth: 1 snakefiles/writing_snakefiles snakefiles/rules snakefiles/configuration snakefiles/modularization snakefiles/remote_files snakefiles/utils snakefiles/deployment snakefiles/reporting .. toctree:: :caption: API Reference :name: api-reference :hidden: :maxdepth: 1 api_reference/snakemake api_reference/snakemake_utils api_reference/internal/modules .. toctree:: :caption: Project Info :name: project-info :hidden: :maxdepth: 1 project_info/citations project_info/more_resources project_info/faq project_info/contributing project_info/authors project_info/history project_info/license snakemake-5.10.0/docs/project_info/000077500000000000000000000000001361131222100171355ustar00rootroot00000000000000snakemake-5.10.0/docs/project_info/authors.rst000066400000000000000000000015201361131222100213520ustar00rootroot00000000000000.. project_info-authors: ======= Credits ======= Development Lead ---------------- - Johannes Köster Development Team ---------------- - Christopher Tomkins-Tinch - David Koppstein - Tim Booth - Manuel Holtgrewe - Christian Arnold - Wibowo Arindrarto - Rasmus Ågren - Soohyun Lee Contributors ------------ In alphabetical order - Andreas Wilm - Anthony Underwood - Ryan Dale - David Alexander - Elias Kuthe - Elmar Pruesse - Hyeshik Chang - Jay Hesselberth - Jesper Foldager - John Huddleston - Joona Lehtomäki - Karel Brinda - Karl Gutwin - Kemal Eren - Kostis Anagnostopoulos - Kyle A. Beauchamp - Kyle Meyer - Lance Parsons - Manuel Holtgrewe - Marcel Martin - Matthew Shirley - Mattias Franberg - Matt Shirley - Paul Moore - Per Unneberg - Ryan C. Thompson - Ryan Dale - Sean Davis - Simon Ye - Tobias Marschall - Willem Ligtenberg snakemake-5.10.0/docs/project_info/citations.rst000066400000000000000000000032171361131222100216670ustar00rootroot00000000000000.. _project_info-citations: ==================== Citing and Citations ==================== This section gives instructions on how to cite Snakemake and lists citing articles. .. project_info-citing_snakemake: ---------------- Citing Snakemake ---------------- When using Snakemake for a publication, **please cite the following article** in you paper: Cite This ========= `Köster, Johannes and Rahmann, Sven. "Snakemake - A scalable bioinformatics workflow engine". Bioinformatics 2012. `_ More References =============== Another publication describing more of Snakemake internals: `Köster, Johannes and Rahmann, Sven. "Building and Documenting Bioinformatics Workflows with Python-based Snakemake". Proceedings of the GCB 2012. `_ And my PhD thesis which describes all algorithmic details: `Johannes Köster, "Parallelization, Scalability, and Reproducibility in Next-Generation Sequencing Analysis", TU Dortmund 2014 `_ Project Pages ============= If you publish a Snakemake workflow, consider to add this badge to your project page: .. image:: https://img.shields.io/badge/snakemake-≥5.6.0-brightgreen.svg?style=flat :target: https://snakemake.readthedocs.io The markdown syntax is .. sourcecode:: text [![Snakemake](https://img.shields.io/badge/snakemake-≥5.6.0-brightgreen.svg?style=flat)](https://snakemake.readthedocs.io) Replace the ``3.5.2`` with the minimum required Snakemake version. You can also `change the style `_. snakemake-5.10.0/docs/project_info/contributing.rst000066400000000000000000000245471361131222100224120ustar00rootroot00000000000000.. _project_info-contributing: ============ Contributing ============ Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given. You can contribute in many ways: ---------------------- Types of Contributions ---------------------- Report Bugs =========== Report bugs at https://github.com/snakemake/snakemake/issues If you are reporting a bug, please include: * Your operating system name and version. * Any details about your local setup that might be helpful in troubleshooting. * Detailed steps to reproduce the bug. Fix Bugs ======== Look through the Github issues for bugs. If you want to start working on a bug then please write short message on the issue tracker to prevent duplicate work. Implement Features ================== Look through the Github issues for features. If you want to start working on an issue then please write short message on the issue tracker to prevent duplicate work. Contributing a new cluster or cloud execution backend ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Execution backends are added by implementing a so-called ``Executor``. All executors are located in `snakemake/executors.py `_. In order to implement a new executor, you have to inherit from the class ``ClusterExecutor``. Below you find a skeleton .. code-block:: python class SkeletonExecutor(ClusterExecutor): def __init__(self, workflow, dag, cores, jobname="snakejob.{name}.{jobid}.sh", printreason=False, quiet=False, printshellcmds=False, latency_wait=3, cluster_config=None, local_input=None, restart_times=None, exec_job=None, assume_shared_fs=True, max_status_checks_per_second=1): # overwrite the command to execute a single snakemake job if necessary # exec_job = "..." super().__init__(workflow, dag, None, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, local_input=local_input, restart_times=restart_times, exec_job=exec_job, assume_shared_fs=False, max_status_checks_per_second=10) # add additional attributes def shutdown(self): # perform additional steps on shutdown if necessary super().shutdown() def cancel(self): for job in self.active_jobs: # cancel active jobs here self.shutdown() def run(self, job, callback=None, submit_callback=None, error_callback=None): import kubernetes.client super()._run(job) # obtain job execution command exec_job = self.format_job( self.exec_job, job, _quote_all=True, use_threads="--force-use-threads" if not job.is_group() else "") # submit job here, and obtain job ids from the backend # register job as active, using your own namedtuple. # The namedtuple must at least contain the attributes # job, jobid, callback, error_callback. self.active_jobs.append(MyJob( job, jobid, callback, error_callback)) def _wait_for_jobs(self): # busy wait on job completion # This is only needed if your backend does not allow to use callbacks # for obtaining job status. while True: # always use self.lock to avoid race conditions with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() for j in active_jobs: # use self.status_rate_limiter to avoid too many API calls. with self.status_rate_limiter: # Retrieve status of job j from your backend via j.jobid # Handle completion and errors, calling either j.callback(j.job) # or j.error_callback(j.job) # In case of error, add job j to still_running. with self.lock: self.active_jobs.extend(still_running) sleep() Write Documentation =================== Snakemake could always use more documentation, whether as part of the official vcfpy docs, in docstrings, or even on the web in blog posts, articles, and such. Snakemake uses `Sphinx `_ for the user manual (that you are currently reading). See `project_info-doc_guidelines` on how the documentation reStructuredText is used. Submit Feedback =============== The best way to send feedback is to file an issue at https://github.com/snakemake/snakemake/issues If you are proposing a feature: * Explain in detail how it would work. * Keep the scope as narrow as possible, to make it easier to implement. * Remember that this is a volunteer-driven project, and that contributions are welcome :) ----------------------- Pull Request Guidelines ----------------------- To update the documentation, fix bugs or add new features you need to create a Pull Request . A PR is a change you make to your local copy of the code for us to review and potentially integrate into the code base. To create a Pull Request you need to do these steps: 1. Create a Github account. 2. Fork the repository. 3. Clone your fork locally. 4. Go to the created snakemake folder with :code:`cd snakemake`. 5. Create a new branch with :code:`git checkout -b `. 6. Make your changes to the code or documentation. 7. Run :code:`git add .` to add all the changed files to the commit (to see what files will be added you can run :code:`git add . --dry-run`). 8. To commit the added files use :code:`git commit`. (This will open a command line editor to write a commit message. These should have a descriptive 80 line header, followed by an empty line, and then a description of what you did and why. To use your command line text editor of choice use (for example) :code:`export GIT_EDITOR=vim` before running :code:`git commit`). 9. Now you can push your changes to your Github copy of Snakemake by running :code:`git push origin `. 10. If you now go to the webpage for your Github copy of Snakemake you should see a link in the sidebar called "Create Pull Request". 11. Now you need to choose your PR from the menu and click the "Create pull request" button. Be sure to change the pull request target branch to ! If you want to create more pull requests, first run :code:`git checkout master` and then start at step 5. with a new branch name. Feel free to ask questions about this if you want to contribute to Snakemake :) ------------------ Testing Guidelines ------------------ To ensure that you do not introduce bugs into Snakemake, you should test your code thouroughly. To have integration tests run automatically when commiting code changes to Github, you need to sign up on wercker.com and register a user. The easiest way to run your development version of Snakemake is perhaps to go to the folder containing your local copy of Snakemake and call: .. code-block:: console $ conda env create -f environment.yml -n snakemake-testing $ conda activate snakemake-testing $ pip install -e . This will make your development version of Snakemake the one called when running snakemake. You do not need to run this command after each time you make code changes. From the base snakemake folder you call :code:`nosetests` to run all the tests, or choose one specific test. For this to work, Nose (the testing framework we use) can be installed to the conda environment using pip: .. code-block:: console $ pip install nose $ nosetests $ nosetests tests.tests:test_log_input If you introduce a new feature you should add a new test to the tests directory. See the folder for examples. .. project_info-doc_guidelines: ------------------------ Documentation Guidelines ------------------------ For the documentation, please adhere to the following guidelines: - Put each sentence on its own line, this makes tracking changes through Git SCM easier. - Provide hyperlink targets, at least for the first two section levels. For this, use the format ``-``, e.g., ``project_info-doc_guidelines``. - Use the section structure from below. :: .. document_part-heading_1: ========= Heading 1 ========= .. document_part-heading_2: --------- Heading 2 --------- .. document_part-heading_3: Heading 3 ========= .. document_part-heading_4: Heading 4 --------- .. document_part-heading_5: Heading 5 ~~~~~~~~~ .. document_part-heading_6: Heading 6 ::::::::: .. _doc_setup: ------------------- Documentation Setup ------------------- For building the documentation, you have to install the Sphinx. If you have already installed Conda, all you need to do is to create a Snakemake development environment via .. code-block:: console $ git clone git@github.com:snakemake/snakemake.git $ cd snakemake $ conda env create -f environment.yml -n snakemake Then, the docs can be built with .. code-block:: console $ source activate snakemake $ cd docs $ make html $ make clean && make html # force rebuild Alternatively, you can use virtualenv. The following assumes you have a working Python 3 setup. .. code-block:: console $ git clone git@github.org:snakemake/snakemake.git $ cd snakemake/docs $ virtualenv -p python3 .venv $ source .venv/bin/activate $ pip install --upgrade -r requirements.txt Afterwards, the docs can be built with .. code-block:: console $ source .venv/bin/activate $ make html # rebuild for changed files only $ make clean && make html # force rebuild snakemake-5.10.0/docs/project_info/faq.rst000066400000000000000000000704711361131222100204470ustar00rootroot00000000000000.. _project_info-faq: ========================== Frequently Asked Questions ========================== .. contents:: What is the key idea of Snakemake workflows? -------------------------------------------- The key idea is very similar to GNU Make. The workflow is determined automatically from top (the files you want) to bottom (the files you have), by applying very general rules with wildcards you give to Snakemake: .. image:: img/idea.png :alt: Snakemake idea When you start using Snakemake, please make sure to walk through the :ref:`official tutorial `. It is crucial to understand how to properly use the system. My shell command fails with with errors about an "unbound variable", what's wrong? ---------------------------------------------------------------------------------- This happens often when calling virtual environments from within Snakemake. Snakemake is using `bash strict mode `_, to ensure e.g. proper error behavior of shell scripts. Unfortunately, virtualenv and some other tools violate bash strict mode. The quick fix for virtualenv is to temporarily deactivate the check for unbound variables .. code-block:: bash set +u; source /path/to/venv/bin/activate; set -u For more details on bash strict mode, see the `here `_. My shell command fails with exit code != 0 from within a pipe, what's wrong? ---------------------------------------------------------------------------- Snakemake is using `bash strict mode `_ to ensure best practice error reporting in shell commands. This entails the pipefail option, which reports errors from within a pipe to outside. If you don't want this, e.g., to handle empty output in the pipe, you can disable pipefail via prepending .. code-block:: bash set +o pipefail; to your shell command in the problematic rule. I don't want Snakemake to detect an error if my shell command exits with an exitcode > 1. What can I do? --------------------------------------------------------------------------------------------------------- Sometimes, tools encode information in exit codes bigger than 1. Snakemake by default treats anything > 0 as an error. Special cases have to be added by yourself. For example, you can write .. code-block:: python shell: """ set +e somecommand ... exitcode=$? if [ $exitcode -eq 1 ] then exit 1 else exit 0 fi """ This way, Snakemake only treats exit code 1 as an error, and thinks that everything else is fine. Note that such tools are an excellent use case for contributing a `wrapper `_. .. _glob-wildcards: How do I run my rule on all files of a certain directory? --------------------------------------------------------- In Snakemake, similar to GNU Make, the workflow is determined from the top, i.e. from the target files. Imagine you have a directory with files ``1.fastq, 2.fastq, 3.fastq, ...``, and you want to produce files ``1.bam, 2.bam, 3.bam, ...`` you should specify these as target files, using the ids ``1,2,3,...``. You could end up with at least two rules like this (or any number of intermediate steps): .. code-block:: python IDS = "1 2 3 ...".split() # the list of desired ids # a pseudo-rule that collects the target files rule all: input: expand("otherdir/{id}.bam", id=IDS) # a general rule using wildcards that does the work rule: input: "thedir/{id}.fastq" output: "otherdir/{id}.bam" shell: "..." Snakemake will then go down the line and determine which files it needs from your initial directory. In order to infer the IDs from present files, Snakemake provides the ``glob_wildcards`` function, e.g. .. code-block:: python IDS, = glob_wildcards("thedir/{id}.fastq") The function matches the given pattern against the files present in the filesystem and thereby infers the values for all wildcards in the pattern. A named tuple that contains a list of values for each wildcard is returned. Here, this named tuple has only one item, that is the list of values for the wildcard ``{id}``. I don't want expand to use the product of every wildcard, what can I do? ------------------------------------------------------------------------ By default the expand function uses ``itertools.product`` to create every combination of the supplied wildcards. Expand takes an optional, second positional argument which can customize how wildcards are combined. To create the list ``["a_1.txt", "b_2.txt", "c_3.txt"]``, invoke expand as: ``expand("{sample}_{id}.txt", zip, sample=["a", "b", "c"], id=["1", "2", "3"])`` I don't want expand to use every wildcard, what can I do? --------------------------------------------------------- Sometimes partially expanding wildcards is useful to define inputs which still depend on some wildcards. Expand takes an optional keyword argument, allow_missing=True, that will format only wildcards which are supplied, leaving others as is. To create the list ``["{sample}_1.txt", "{sample}_2.txt"]``, invoke expand as: ``expand("{sample}_{id}.txt", id=["1", "2"], allow_missing=True)`` If the filename contains the wildcard ``allow_missing``, it will be formatted normally: ``expand("{allow_missing}.txt", allow_missing=True)`` returns ``["True.txt"]``. Snakemake complains about a cyclic dependency or a PeriodicWildcardError. What can I do? ---------------------------------------------------------------------------------------- One limitation of Snakemake is that graphs of jobs have to be acyclic (similar to GNU Make). This means, that no path in the graph may be a cycle. Although you might have considered this when designing your workflow, Snakemake sometimes runs into situations where a cyclic dependency cannot be avoided without further information, although the solution seems obvious for the developer. Consider the following example: .. code-block:: text rule all: input: "a" rule unzip: input: "{sample}.tar.gz" output: "{sample}" shell: "tar -xf {input}" If this workflow is executed with .. code-block:: console snakemake -n two things may happen. 1. If the file ``a.tar.gz`` is present in the filesystem, Snakemake will propose the following (expected and correct) plan: .. code-block:: text rule a: input: a.tar.gz output: a wildcards: sample=a localrule all: input: a Job counts: count jobs 1 a 1 all 2 2. If the file ``a.tar.gz`` is not present and cannot be created by any other rule than rule ``a``, Snakemake will try to run rule ``a`` again, with ``{sample}=a.tar.gz``. This would infinitely go on recursively. Snakemake detects this case and produces a ``PeriodicWildcardError``. In summary, ``PeriodicWildcardErrors`` hint to a problem where a rule or a set of rules can be applied to create its own input. If you are lucky, Snakemake can be smart and avoid the error by stopping the recursion if a file exists in the filesystem. Importantly, however, bugs upstream of that rule can manifest as ``PeriodicWildcardError``, although in reality just a file is missing or named differently. In such cases, it is best to restrict the wildcard of the output file(s), or follow the general rule of putting output files of different rules into unique subfolders of your working directory. This way, you can discover the true source of your error. Is it possible to pass variable values to the workflow via the command line? ---------------------------------------------------------------------------- Yes, this is possible. Have a look at :ref:`snakefiles_configuration`. Previously it was necessary to use environment variables like so: E.g. write .. code-block:: bash $ SAMPLES="1 2 3 4 5" snakemake and have in the Snakefile some Python code that reads this environment variable, i.e. .. code-block:: python SAMPLES = os.environ.get("SAMPLES", "10 20").split() I get a NameError with my shell command. Are braces unsupported? ---------------------------------------------------------------- You can use the entire Python `format minilanguage `_ in shell commands. Braces in shell commands that are not intended to insert variable values thus have to be escaped by doubling them: This: .. code-block:: python ... shell: "awk '{print $1}' {input}" becomes: .. code-block:: python ... shell: "awk '{{print $1}}' {input}" Here the double braces are escapes, i.e. there will remain single braces in the final command. In contrast, ``{input}`` is replaced with an input filename. In addition, if your shell command has literal backslashes, ``\\``, you must escape them with a backslash, ``\\\\``. For example: This: .. code-block:: python shell: """printf \">%s\"" {{input}}""" becomes: .. code-block:: python shell: """printf \\">%s\\"" {{input}}""" How do I incorporate files that do not follow a consistent naming scheme? ------------------------------------------------------------------------- The best solution is to have a dictionary that translates a sample id to the inconsistently named files and use a function (see :ref:`snakefiles-input_functions`) to provide an input file like this: .. code-block:: python FILENAME = dict(...) # map sample ids to the irregular filenames here rule: # use a function as input to delegate to the correct filename input: lambda wildcards: FILENAME[wildcards.sample] output: "somefolder/{sample}.csv" shell: ... How do I force Snakemake to rerun all jobs from the rule I just edited? ----------------------------------------------------------------------- This can be done by invoking Snakemake with the ``--forcerules`` or ``-R`` flag, followed by the rules that should be re-executed: .. code-block:: console $ snakemake -R somerule This will cause Snakemake to re-run all jobs of that rule and everything downstream (i.e. directly or indirectly depending on the rules output). How do I enable syntax highlighting in Vim for Snakefiles? ---------------------------------------------------------- A vim syntax highlighting definition for Snakemake is available `here `_. You can copy that file to ``$HOME/.vim/syntax`` directory and add .. code-block:: vim au BufNewFile,BufRead Snakefile set syntax=snakemake au BufNewFile,BufRead *.smk set syntax=snakemake to your ``$HOME/.vimrc`` file. Highlighting can be forced in a vim session with ``:set syntax=snakemake``. I want to import some helper functions from another python file. Is that possible? ---------------------------------------------------------------------------------- Yes, from version 2.4.8 on, Snakemake allows to import python modules (and also simple python files) from the same directory where the Snakefile resides. How can I run Snakemake on a cluster where its main process is not allowed to run on the head node? --------------------------------------------------------------------------------------------------- This can be achived by submitting the main Snakemake invocation as a job to the cluster. If it is not allowed to submit a job from a non-head cluster node, you can provide a submit command that goes back to the head node before submitting: .. code-block:: bash qsub -N PIPE -cwd -j yes python snakemake --cluster "ssh user@headnode_address 'qsub -N pipe_task -j yes -cwd -S /bin/sh ' " -j This hint was provided by Inti Pedroso. Can the output of a rule be a symlink? -------------------------------------- Yes. As of Snakemake 3.8, output files are removed before running a rule and then touched after the rule completes to ensure they are newer than the input. Symlinks are treated just the same as normal files in this regard, and Snakemake ensures that it only modifies the link and not the target when doing this. Here is an example where you want to merge N files together, but if N == 1 a symlink will do. This is easier than attempting to implement workflow logic that skips the step entirely. Note the **-r** flag, supported by modern versions of ln, is useful to achieve correct linking between files in subdirectories. .. code-block:: python rule merge_files: output: "{foo}/all_merged.txt" input: my_input_func # some function that yields 1 or more files to merge run: if len(input) > 1: shell("cat {input} | sort > {output}") else: shell("ln -sr {input} {output}") Do be careful with symlinks in combination with :ref:`tutorial_temp-and-protected-files`. When the original file is deleted, this can cause various errors once the symlink does not point to a valid file any more. If you get a message like ``Unable to set utime on symlink .... Your Python build does not support it.`` this means that Snakemake is unable to properly adjust the modification time of the symlink. In this case, a workaround is to add the shell command `touch -h {output}` to the end of the rule. Can the input of a rule be a symlink? ------------------------------------- Yes. In this case, since Snakemake 3.8, one extra consideration is applied. If *either* the link itself or the target of the link is newer than the output files for the rule then it will trigger the rule to be re-run. I would like to receive a mail upon snakemake exit. How can this be achieved? ----------------------------------------------------------------------------- On unix, you can make use of the commonly pre-installed `mail` command: .. code-block:: bash snakemake 2> snakemake.log mail -s "snakemake finished" youremail@provider.com < snakemake.log In case your administrator does not provide you with a proper configuration of the sendmail framework, you can configure `mail` to work e.g. via Gmail (see `here `_). I want to pass variables between rules. Is that possible? --------------------------------------------------------- Because of the cluster support and the ability to resume a workflow where you stopped last time, Snakemake in general should be used in a way that information is stored in the output files of your jobs. Sometimes it might though be handy to have a kind of persistent storage for simple values between jobs and rules. Using plain python objects like a global dict for this will not work as each job is run in a separate process by snakemake. What helps here is the `PersistentDict` from the `pytools `_ package. Here is an example of a Snakemake workflow using this facility: .. code-block:: python from pytools.persistent_dict import PersistentDict storage = PersistentDict("mystorage") rule a: input: "test.in" output: "test.out" run: myvar = storage.fetch("myvar") # do stuff rule b: output: temp("test.in") run: storage.store("myvar", 3.14) Here, the output rule b has to be temp in order to ensure that ``myvar`` is stored in each run of the workflow as rule a relies on it. In other words, the PersistentDict is persistent between the job processes, but not between different runs of this workflow. If you need to conserve information between different runs, use output files for them. Why do my global variables behave strangely when I run my job on a cluster? --------------------------------------------------------------------------- This is closely related to the question above. Any Python code you put outside of a rule definition is normally run once before Snakemake starts to process rules, but on a cluster it is re-run again for each submitted job, because Snakemake implements jobs by re-running itself. Consider the following... .. code-block:: python from mydatabase import get_connection dbh = get_connection() latest_parameters = dbh.get_params().latest() rule a: input: "{foo}.in" output: "{foo}.out" shell: "do_op -params {latest_parameters} {input} {output}" When run a single machine, you will see a single connection to your database and get a single value for *latest_parameters* for the duration of the run. On a cluster you will see a connection attempt from the cluster node for each job submitted, regardless of whether it happens to involve rule a or not, and the parameters will be recalculated for each job. I want to configure the behavior of my shell for all rules. How can that be achieved with Snakemake? ---------------------------------------------------------------------------------------------------- You can set a prefix that will prepended to all shell commands by adding e.g. .. code-block:: python shell.prefix("set -o pipefail; ") to the top of your Snakefile. Make sure that the prefix ends with a semicolon, such that it will not interfere with the subsequent commands. To simulate a bash login shell, you can do the following: .. code-block:: python shell.executable("/bin/bash") shell.prefix("source ~/.bashrc; ") Some command line arguments like --config cannot be followed by rule or file targets. Is that intended behavior? ---------------------------------------------------------------------------------------------------------------- This is a limitation of the argparse module, which cannot distinguish between the perhaps next arg of ``--config`` and a target. As a solution, you can put the `--config` at the end of your invocation, or prepend the target with a single ``--``, i.e. .. code-block:: console $ snakemake --config foo=bar -- mytarget $ snakemake mytarget --config foo=bar How do I enforce config values given at the command line to be interpreted as strings? -------------------------------------------------------------------------------------- When passing config values like this .. code-block:: console $ snakemake --config version=2018_1 Snakemake will first try to interpret the given value as number. Only if that fails, it will interpret the value as string. Here, it does not fail, because the underscore `_` is interpreted as thousand separator. In order to ensure that the value is interpreted as string, you have to pass it in quotes. Since bash otherwise automatically removes quotes, you have to also wrap the entire entry into quotes, e.g.: .. code-block:: console $ snakemake --config 'version="2018_1"' How do I make my rule fail if an output file is empty? ------------------------------------------------------ Snakemake expects shell commands to behave properly, meaning that failures should cause an exit status other than zero. If a command does not exit with a status other than zero, Snakemake assumes everything worked fine, even if output files are empty. This is because empty output files are also a reasonable tool to indicate progress where no real output was produced. However, sometimes you will have to deal with tools that do not properly report their failure with an exit status. Here, the recommended way is to use bash to check for non-empty output files, e.g.: .. code-block:: python rule: input: ... output: "my/output/file.txt" shell: "somecommand {input} {output} && [[ -s {output} ]]" How does Snakemake lock the working directory? ---------------------------------------------- Per default, Snakemake will lock a working directory by output and input files. Two Snakemake instances that want to create the same output file are not possible. Two instances creating disjoint sets of output files are possible. With the command line option ``--nolock``, you can disable this mechanism on your own risk. With ``--unlock``, you can be remove a stale lock. Stale locks can appear if your machine is powered off with a running Snakemake instance. Snakemake does not trigger re-runs if I add additional input files. What can I do? ---------------------------------------------------------------------------------- Snakemake has a kind of "lazy" policy about added input files if their modification date is older than that of the output files. One reason is that information what to do cannot be inferred just from the input and output files. You need additional information about the last run to be stored. Since behaviour would be inconsistent between cases where that information is available and where it is not, this functionality has been encoded as an extra switch. To trigger updates for jobs with changed input files, you can use the command line argument --list-input-changes in the following way: .. code-block:: console $ snakemake -n -R `snakemake --list-input-changes` Here, ``snakemake --list-input-changes`` returns the list of output files with changed input files, which is fed into ``-R`` to trigger a re-run. How do I trigger re-runs for rules with updated code or parameters? ------------------------------------------------------------------- Similar to the solution above, you can use .. code-block:: console $ snakemake -n -R `snakemake --list-params-changes` and .. code-block:: console $ snakemake -n -R `snakemake --list-code-changes` Again, the list commands in backticks return the list of output files with changes, which are fed into ``-R`` to trigger a re-run. How do I remove all files created by snakemake, i.e. like ``make clean`` ------------------------------------------------------------------------ To remove all files created by snakemake as output files to start from scratch, you can use .. code-block:: console $ snakemake some_target --delete-all-output Only files that are output of snakemake rules will be removed, not those that serve as primary inputs to the workflow. Note that this will only affect the files involved in reaching the specified target(s). It is strongly advised to first run together with ``--dry-run`` to list the files that would be removed without actually deleting anything. The flag ``--delete-temp-output`` can be used in a similar manner to only delete files flagged as temporary. Why can't I use the conda directive with a run block? ----------------------------------------------------- The run block of a rule (see :ref:`snakefiles-rules`) has access to anything defined in the Snakefile, outside of the rule. Hence, it has to share the conda environment with the main Snakemake process. To avoid confusion we therefore disallow the conda directive together with the run block. It is recommended to use the script directive instead (see :ref:`snakefiles-external_scripts`). My workflow is very large, how do I stop Snakemake from printing all this rule/job information in a dry-run? ------------------------------------------------------------------------------------------------------------ Indeed, the information for each individual job can slow down a dry-run if there are tens of thousands of jobs. If you are just interested in the final summary, you can use the ``--quiet`` flag to suppress this. .. code-block:: console $ snakemake -n --quiet Git is messing up the modification times of my input files, what can I do? -------------------------------------------------------------------------- When you checkout a git repository, the modification times of updated files are set to the time of the checkout. If you rely on these files as input **and** output files in your workflow, this can cause trouble. For example, Snakemake could think that a certain (git-tracked) output has to be re-executed, just because its input has been checked out a bit later. In such cases, it is advisable to set the file modification dates to the last commit date after an update has been pulled. See `here `_ for a solution to achieve this. How do I exit a running Snakemake workflow? ------------------------------------------- There are two ways to exit a currently running workflow. 1. If you want to kill all running jobs, hit Ctrl+C. Note that when using ``--cluster``, this will only cancel the main Snakemake process. 2. If you want to stop the scheduling of new jobs and wait for all running jobs to be finished, you can send a TERM signal, e.g., via .. code-block:: bash killall -TERM snakemake How can I make use of node-local storage when running cluster jobs? ------------------------------------------------------------------- When running jobs on a cluster you might want to make use of a node-local scratch directory in order to reduce cluster network traffic and/or get more efficient disk storage for temporary files. There is currently no way of doing this in Snakemake, but a possible workaround involves the ``shadow`` directive and setting the ``--shadow-prefix`` flag to e.g. ``/scratch``. .. code-block:: python rule: output: "some_summary_statistics.txt" shadow: "minimal" shell: """ generate huge_file.csv summarize huge_file.csv > {output} """ The following would then lead to the job being executed in ``/scratch/shadow/some_unique_hash/``, and the temporary file ``huge_file.csv`` could be kept at the compute node. .. code-block:: console $ snakemake --shadow-prefix /scratch some_summary_statistics.txt --cluster ... How do I access elements of input or output by a variable index? ---------------------------------------------------------------- Assuming you have something like the following rule .. code-block:: python rule a: output: expand("test.{i}.out", i=range(20)) run: for i in range(20): shell("echo test > {output[i]}") Snakemake will fail upon execution with the error ``'OutputFiles' object has no attribute 'i'``. The reason is that the shell command is using the `Python format mini language `_, which does only allow indexing via constants, e.g., ``output[1]``, but not via variables. Variables are treated as attribute names instead. The solution is to write .. code-block:: python rule a: output: expand("test.{i}.out", i=range(20)) run: for i in range(20): f = output[i] shell("echo test > {f}") or, more concise in this special case: .. code-block:: python rule a: output: expand("test.{i}.out", i=range(20)) run: for f in output: shell("echo test > {f}") There is a compiler error when installing Snakemake with pip or easy_install, what shall I do? ---------------------------------------------------------------------------------------------- Snakemake itself is plain Python, hence the compiler error must come from one of the dependencies, like e.g., datrie. You should have a look if maybe you are missing some library or a certain compiler package. If everything seems fine, please report to the upstream developers of the failing dependency. Note that in general it is recommended to install Snakemake via `Conda `_ which gives you precompiled packages and the additional benefit of having :ref:`automatic software deployment ` integrated into your workflow execution. How to enable autocompletion for the zsh shell? ----------------------------------------------- For users of the `Z shell `_ (zsh), just run the following (assuming an activated zsh) to activate autocompletion for snakemake: .. code-block:: console compdef _gnu_generic snakemake Example: Say you have forgotten how to use the various options starting ``force``, just type the partial match i.e. ``--force`` which results in a list of all potential hits along with a description: .. code-block:: console $snakemake --force**pressing tab** --force -- Force the execution of the selected target or the --force-use-threads -- Force threads rather than processes. Helpful if shared --forceall -- Force the execution of the selected (or the first) --forcerun -- (TARGET (TARGET ...)), -R (TARGET (TARGET ...)) To activate this autocompletion permanently, put this line in ``~/.zshrc``. `Here `_ is some further reading. snakemake-5.10.0/docs/project_info/history.rst000066400000000000000000000001551361131222100213710ustar00rootroot00000000000000.. project_info-history: .. _changelog: ========== Change Log ========== .. include:: ../../CHANGELOG.rst snakemake-5.10.0/docs/project_info/img/000077500000000000000000000000001361131222100177115ustar00rootroot00000000000000snakemake-5.10.0/docs/project_info/img/idea.png000066400000000000000000001247001361131222100213250ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs@VtEXtSoftwarewww.inkscape.org< IDATxwUՇ_B{7ޛ4. | Rt^ H")"I B}=s'sKM~<޳יs^{5٦+40ش=3ϻt$Q+l [m$ l \oF˓d2L& D7AHe'O | S:6'G'iLqW7jםe2L&L,t2 0C+ۙ4kv > &YϠ!I+Ip9$&itVt>pk%T:v[L&d2D i$"igI;J'}A u%h9K]]p>:WLg氽-l/Jkv`ұ].i&d2Lf2I% W%遗v3OGӁɣUyj02Dl$鷶4Xٻ_ K%.i+l+iJ$:wKHZV>%Ej'釒*X x;x&i>I֓y,5ARP*}t$MOSgFW] DV<t)l?<\D(WKS#`=d2LfRIkHx@*vP@R:ie:b⋤c`q)%]K0&tD-b$Bӄ)69_gJZ:$ߗڗu% lG(?WH*,02I:$|YKwu{UAie j4P( i% \"iRk K I9$V[TvZA `#۫[(\\Ѫ݇H$HIDZE둭Wlyۛ>>>&)_}ֶ);*W&d2HKQC4pҾY81]۰vV6Mn.Ap`%I+Du.z=$IyqJ>8߾m+MAbB~,1q.=pZpgxغHp#bB!GґD.iR'SƤy9v\؆4# K.>0UH 'v>[^:\7m)\$-F(sy]X-hЯK|۷IZgi)+k]V@RY/#͹k&#^L&d&a$V$JjdwÀm߫hIBLLOXUY/Z :?n!ͭH;(~ `vTodde!4YҊkrI?'u$\%i֨R`:=:DIڒMne{c*пJmVڞXx&#D=l6O1[#~z@$mYڷ(X ؖe]Bwh %eK}Iijbb5d&)RE]\_ 8KQș6h)%f`p) *DXc FqC\ҹ &4f[`I/'II:XR7o+=6#i`OfR>$^cSGB+yz!4Y.N"Cש2_-8p6V'dkN }KWX|KIZYKJdr'`L j8}tmL m `$Tf-~. YERg,tҾloE$8ٗ"V6K24R I3HZ0MvIOHǜP˲_`{}Ԓ}yGI&IZH<ް.ԮH=l&LHژ=iP%8eiM3};٘1AؾUz++Gş'$=Mڼ Uir9)m)Mش]$]KX>~R$ǫ&VH+$ɣ KJ| ;JmKċy$㬒'SҹB\#z Mc.5f;dzXyr,1fH?siZONj_܈GwU˓;$̶%%f&,f3%/WdW( 9OJGDi^$)nq֠ KR.zYLeI n."^k^oO]4d I ֵFb;ۧ'֘L=خ"2 v|֎w Xݯfj` ,[ڞ0yWBL>䵚ug hc?r=Tz1*QdSbBRڷpyzdxt*c AN!2mF] K!ON{8*`p[xp, Ͳ/Ri*vHo\7=+'E-K NiӫE󼛪Pt#]*pEjͨV=;XulK/Sn#cCϚƭw Zѽk0|B C/i ][X kf_q-1QXbeeݿ0)^te! >=[52 Eȃmoϳ6C=~1C^OgCg2O؁$ޖ[DPk!I껴%۶7 "J{_ {%Pj[k2,&ʞv&"ViIՐ7AGS#BtVSIKZ]Q.x;pj=٨tν$XC AQ8t$1LnqLW3a ˤb?3؊HHeb!+ I4q^m 1I/ z8pH"D(yXEfC_Aӷޫ]4L&5DdFJ$쁴8"1DKԔ Cb1dNRD7lDAͳH$8>X\:|DYZ=/g(>ǎD;K}&&G Ktu V7 gRL#?֓CqK)M4%(Nh5+{cMJcS͉DxDE_J cH:(ZҸDNvN ,I淈bd:D] R?t`ZERya+M3HZzV\cVe+]ұ\ Qb࿶7}{{e$nr" =k{e( ٪Ovqif#nc$Mw7Ubjۏ7K<6N'\ʾ0+#ir)}Rߣ?W'%m k~ؾ \!D:)[$x ?uI- $ӈ{όADlNXj_QKK}|8a>}.{K E/g͕i1"]f<<9,PnfNcnaRgR҃D2kL IaY14jo(̞ a'WWDJ%$ӷib74ݔ˱:_5LLM(f'W-AXKN\^X9rd2NVZ1XHoD&[si%k]%=C8ɱqc5/n2"VA-VStWčq K&}Rj%-T2kmHڄP&o&DsoD͊R5l()Eu}/[Jb ȵ Ӯ- "t1$HHy]L&3 iPtD!KSJ LGX k =[GSۯ.|S=a 34Pub׸6SU`"8Y#zyظԯ,YuFt'ʗ"\@0M3R5aYYl{G(<=.NVL&r*o Q:6mD-qn2|1^2>F*p*RA0T[KIlN4y-JeHdI4x8Q `Up<xAÄdTͻ ?*Y%>t;[֓"S>9DG@񝝬K+bK?dVdfCZc{(/VU$ݒ^"\w܏>jП뒞#迴h{_s?ll&JtgSmQj?մm碒%@(-|k>x_hM;^5!~ ->$MW>ȆjL&d2)fF/*B}uګK؟n{n*d2 n!qF#PD_-K{0ǯt i?#c]"e{iL 73Пྉ%(d2zi~@ =-i68Rl_ ,*iFRs~ʂ Sid2LL)i IZ?ZԍLUh+i"X=d. Q7 ”ui[f2L&31g,sr#I O LAP9S8[&d2$2Q'$d*L?i-KfDZޭ%/iu -;}u2%傕d2̤Fʪw( RzsKүNhZMI[E@GW*C] H')ِ2O&M$}@ԧ_YO~ƚ H&dz%JwSU&$j\}(i`;Bg.'|N:p헨1Omv$*>+%K`vdδ}nDqa{kd:z&i;`g`Ex*ojc`#˕ݷR\RH$¹t폐tJ!ǯSf5C*%?ul iv`>_7 78%+]CV@2L&koInOeFK x8 Ms*1 V~zSm&WwJ: w7nkMEY+鼤lmJLJ^R_חD;Y`=޴r6Ò#r ȟ d'IWKڬTk`I?nNnJDAğS.?H=wt=$ 3 Vοj0>wbHAu7٫ H&d20 o$1=#V䑴*#v?Vu3’ђN^(.{qZxp0]XJ iJ,)'edTj r$$]5mt3 0 Ia;uPJNx-v,f y"&/hR^/C~>պW5RܜFj[d+Zzuw\^o~u<&YfR2-KL>()$U !o5$o?`DzQLp?B9?."d2L&&Ud$|b&dyb2' soץ9fn!&jWYD<2%MMK& ?Sdn޶B{ 搴K"`_l&i+I}RP]3@ PD^H)_­dIHAif%+ 3HMvJp6aAx)=I.`^L`ł>bLI;7xY#O(6&k [Lr$E@ҼZD<Ik9%`Òc^'8-^r*8 7XҁDZv-4dj-d2ԯzTTNȲ(0s=pwX:1gu[ IYlTE:Dăbwz#8S#"fJ ?POv\ofPۇ828ozB9 m%\d$d2IE=jM2H fD`vDm\G AJp Q+T>u\f{:DX$%)L&d&p[8iV(#'Ms \;MW( sKXD",":?d2LC}K3LY2&V&jfLFT8̖t4 5`M a]x] Y:LJ<+ϛ\' ^J!+ L&8$#iaOš&R=_\}F>sKN5}j"WDZuù!,"{w#Yd2L!onL{I)} 2R͗vT0,K^0u$}^fxc8ƒ;YGtXYRlj,L&H$ f3FN p M!Z>j  ,2jlHFakY3c{twhzn4e娒̃m)p؂6lpu iWIwHکѲd2IJ:IҡiBWm$} =Ia$-u:*CTfuIJ0$ch;*Sf!H(e&VjI$\o{ \C` 1R_ $MV@&,k]9)=n%F  VBond&R7}Do+.!2,ڮ!VIs!ʹkYMET_(ع9wz$7"iv`o`|IĀ$,"suF-~bHM0l?m{gQIS‰^d`BVW>L:WzJHehJ_4π HLNH]mZ8RdAc~uUmd2&?֔45p{Bo~\J9\ӄ21K ߩCJYJŽ *4e,s`"73(py egotێf">"AۏwIOR@RDPDn_K:Y,{2BM(dw/]14XIa{b#=P {V d2e jpy\mH{~3_H"]]*rl#`*bı `K%Hx:< &ڶMaaIWIz)Wwѫ``p|r@9pq[<1J+e$"?ml_QF3gXX8%m3-> d24H+]#S;gtލ+?^/7ӮǏj$-DXIgjc~w=х qwbI 2*@zΕnâ`JzUR`I6 a419ɒ`@"j{z2_Uc`7KG_ɃŁG}\L&3dcºQV (M:87)Pt>R/?"$o%T3=I"F3`nnI[7Hj]Fo B҄6K0mϟ|\UrI+}:x[Ң6 !W'R+SKZ=?H_Y ")utci{"ߕՈ$i%Iח%M%4$ޱSKL |M QQ@$#$W=znGE0HWRxǁ1La9)&.ML&Zbupp(O.IqNn P PE*Q$-Az[*@35 ez tpQ||Uqؾ ؎p˼QviyvzW6 o~`i`Ҿ дox i%5dX,x3mO S ZO^dw`ʾ?\V:'齈)9̄˥}/J}JۃJVӹ+6w__U ±7{2sw gg!pOiS7`o5F_V=P:WhG"2uի\{[.n̛V.$ˆt0k_V%Lk?$Rf7U?!,+Z;U 〩$Xq?ULJ;2cb%ljb5 I>Ϥmjt}c +[~MYҚDƖ!:h{Rg+S&l_ʱL|-_6_ҌDr&Tπwl=!)gIꝁ- }H=&(Βn}WE5>7]u Nh;GbG":aj>P> nƄN٧w9~|H= aḦ و,S4`D,K?> lKϴa? _oJJ:$NLJH:P(v&ɔY?7pɭ 壴ﳖwgi6!܌ؾ"u ^ ֑LB F p82\?H%WfʎDC4ͳ=}-#b|$ag/>53aix5W^/,lV>_-02 xGI*8.coRWG< P%$뮶Wƞ)xr'bg)O&dՄr$=?,.iV IVf&攱p&YC/t= )}{41ؿqY[wզ G?wiʞt<&S0EUiNM|~I>!Q[\ ?۽[%m.hӮ,NJI;HwuaKWʆf|?&IڝPu}v H]5Ot*_)s #\j4Q-VO yKw&v$l:JAD_NNv۔|!~rՅyX$mL?PlD,?H&IoI^+Mﱽ gS_ޱ >&~쟑R>?mI>FPM,K$^^#a{<!3+Bi|ŅR݈.Kz< 3IQDIy:,nAM|ωU ,%H%y dEÉ'y_eZ#iatISH8QgZIE%ewF"2Y,ޠ*.?PLfMHLq0_eـ;(߼D^rz _t+a59XxPoOͥ߀ߤk09pN}9R^zDiD0d="u ்W~W_o weRzD-W6Zn\S ͞ɍ|F,lԠq{9c/rPXhIS "o[':(*Sv#$AX!&텅b0ʑѦX-bK۟s2S[zE0"[k$gޓ4B.M:jGLfb!eY2VTT=5[Ik*V芶U5XOjiZ|fNy#`t݊* 8}eDLײi y %>Q1~5`OB|!NOkWe2<^Y(_~Ѳdl۶[f:KG, '&<˖J:x-a{ ۫~e6:L*kpE$,wq _e|ȢYw"@H qNej*=3ݓ3tWݪ{J{ h##B=%MD5hm/73 ?#RJ:wk8 ء>'?}$A^5Yl)3# Q.?3H+{w8Ad8#4y6$ҿkv\ gf;\`9`3|t]ܡX+Ӆ s5,':{gk@މ"m$=W+b} qXO|ƮL9> W!ާ^q|~+_$jA!shnyOrZI _7<I=p;pJnx^F.kJyV*3Sbgˎձxpyf-O]/ȃ{3m>5'37pwMJx}!ޓ4?AHt"@WH$Āk+cJ\g|>Fx*5|%W0*ޑs~p׳naY~Lı,is`  n+وXwJa4af:'+Ƶ[ au3Tf60jJp3)Y4).#Z=;H9>  wXW<&i/`#?Y ?Yf6V0&΀WRl3]/S>#8a1ht|1{>m|I _qf8>0 ,ʇϔ.2xE A;ugn^3|=p|d|ߏAn~,\AuG2Q"-.+p}}Á$-+ ;{K/?ygWpw.4}uxO~>o+񉉭7yl=)=Qw/ZDNhKk0홹u ǟr~@*ď%`?IWI_3^My߾! w3* hp˳=\H|f3MRޅf]\I|g<_pיr>B\y p3+6F'問ṔLc5c  wfG(HolnQq<~cC\P KƧa.\Xwv^pJgKyBVobZ,"y|V6 ߬![۫]oJH^9Y ̆e $gfǦ7q >!pz8Y"%ni²41.od |g]%5>KǹWΛIv']W3 QWCϙa07~'rۊ/s;Mxu-ᕱ:93=yTzx8hӱ>PY]RyOX~~<Wf6"\ q}cc`>/ Sf/q7".da/tfZ8ժwQ~ާp`p[TI'Ⳝ'ֽ_\-A~fַnUq>< A?WnmԬ+N)q9.A  [7pH{w}lPm1Hpĭ 2g ^Q^qEq/\Y<ثѐYx<} 8iX Z65}>9\h@mgɨ2\+@bZϗ+Z=Zp8|bZӂgʰVь=KZ05rqW4x< _C 4Z7;2x;^Gޱ;RI+M[Bc{5@{<ך:qv.,Rk~gmV}^ W֌3 oϦO0PăOg6țp&/vk~7Y&WoݞZdzЩrv3~_A W طֲ4#ZP2ZRBXk-Kȳk N 4 5ey0~ XQ1>g3JRǗhE,$u@dT;0w<W"ѮHZW?ƽ*^)R?lROI77C%Cҭfdeɐ+y@>fvL y/S ĵ+wI;O+I(I,FgfP"Vn3H:P"#"#t$KUQ6f-cIԖxjRM$G8u^6ē $ɃЭ]OFaFà75KK|,}|e0 Ou?iXY( W?n)-ZLn#-1 $~*y=x!@<@|p=zfx3 #HEK4bf?y<=Oޒ:5u"Q­ʻk+PǪ0L^;gEdy~ L Ha-_U`tkLHUK`IUnf:^GaiTx-yLd&>k,f}kf[gQ,\hf3scU|pGM|4dօ)/5v]NJ PSžl|?M{޳g蘑eOPpj渇qoTٯW94j˳p5cfV|PL5MDK7\ƶMm[oM4fqtG?1iafwcj tD>jfX w5% 뜮"$F+6^E2TW69j>>pf$ 4if+>;>p;X]zVZ!*%|F{hP<~-Y>/t'0] P0k~74 ,@n]XdWZ+^ snYkntqM-xWPP5*q=- tƭzmZ܀+ @ s+}d*`` o]ce"?s;^qᖶq7?oq—tɐeH Hy2td.>0[p\gX]Қ*7 s[= O+KR~_0 j d\QR7p3<7qBA+Bij Wp1 d'/lv \+7űffoϸNܼ%Qb2^3#rV^:wlVKjfGܲ}miQ̎5ܲ:'MX8Zv oEDbb1Oq$nL$m$)Nfyv[bx .v>j$Zo,xj:3t]P@^A4+q_bPP| ?moXK[/W`8 8<88c|6z<.`udMFx,8-3RR܄L#q+%)>f~ƒDGE_ZCF}ᖖ+yWrot$KX*S X

uiDD 95HT},i_F OHtx&93jf),׋Q;7fqoiH:+6nPkP1a^x_)To+ĸv" #p&7> eU:,WgI.6eޯVR 3Z '΁fv/FCF }Ɔ> IDAT,q yr"{Jw; B&X7 z7&*2) O_ b6bxURWjehPH}%pY%M#<&ckt*Zv_PGq lZ"}C|O#3q(v7r;q"Es?0/F_m}x!fv ݧnގ-;70 neCt5~-ǽLIDb~2tS ϗ4e &%1p[ؤ} '/dZϬMXZ5$YcZ^QH;nUăom"Ρ{s٫m!_2RNLISot% Nxyz5Έ Q꒖VIITMzma?E HZw4{qvƷD=fI'| $^pĤJXh7Ÿ[gHک"%:f6w X53gd5p/ ӵι/pqyUo|+ ;lA3ިVIITSB3$2at(p2p9%uM++%W]:|FH*H$Jbfo[k%YcHM#p#k+ffC#!50/|r[W̆mRh1) jZ+wEs8 ǥ4yWV67U[3ŕ" #D"13{ $DF"$/0WB櫩PrErӮ)a*Ht6~)< Gf`aݢfcnY3}NfD"hI>ٖ-O$dו_ρujQRҕf2`i, `f?V2KG{?|IJfhD"Qo[H:$:w7nş ,\[-<$綨D"HL֘`{=XL(Gw7sD"Htt/sX I1x+iI8I\jHR@D"$m\, ̘AҔel׍* ۠aUg5Xl8-sM0qfv:^3`L Z#&URCHT I3I7D"cJ:VRvi38g`26׊P֝̎5OR@u`\aY`gIs@IR>V&Ɣ9k7!iITa?KD_{2_SK :d^`˟j_Q*f) maTI9JzLRh5ANoE\N1GEg5]-<RCRC$0{@4 e插:IZ4˼#i 3{xSHZ=YFu\%,_\ϒHZVR9 Aq&gţYfέF "i~IS7tE{Ie??u)g9FWK-iIWRkBҔ]CI>7y.$)/Cf=Shy47p2><,V,Y`BIyRz$ HU 5J8,eP-Wjx. ܎8fƳLij\֛ɭ{K)["Q-SydfCQ!K{zs*p+9f1"iaIK7FI$]gPҙiIKU) 1h^'DrW%-KyV$=(irNcfRbpﶋ+U`Y8gG?S;%ff_˛yy:`cDI)WxF-]oۥ, sa2eP2QaԱnD7|`54}/ Kl 灭ixSj0Α?3>}[t2ex XT7>>14#-u1^gUCh 8ގe;;@OTN9$|wW<*{ϵf?kMR!ff%`Y㘍IS0~h%ik"`|s\9UE8,ėm춖TT^P0UAOVpTK eU@$ vKͷ$=ڹzZղ} 4Y~Ti̞\DU0%|[e|^x^m\_,ϢXJBNZ/Z+|?ΌLlwL)8lIcdfv|nw@PgB1S!䔯`7|Lz2Ttf6p- 4pKf}d곂= } ֑ nY*MIU+'O|]N.Xa X\/'i >[6?x=+B Z) l.i+_6X xmφߏD%Lʳ tP2̋zcl32Z$mď|Q83D9of#w[wL8&o ^`/ Ag~IYj̈?N((0ʎs8>Y4 &⵼=[3IR>#$d+̬ٙqIk~%I7HJҺ%w򙑾Ngx0 ʏ<o`lIVǴvߊ]O)i]IO)ix9PlSK:^N-C(v'l };K7I C?>`/q<9ڻ&S4 Dza$/j%[>tanۯ;j3{2}$b~4yk򘐣sg/{@>z$nʧΘ k6tZW;&~-W&ߨ`k -V-/ڍJJU//f/N γ{ BWrDd1* Mi%brpx0gWnqt}gfew_` `IC8X_Ҧ;p4pp6G "5b`ApKN8 ?{fE(qIQ }ef 5S3q3;~330r3˻%$u<؛lQx+64~?p\;Y%qXkQ?]"v#Jׄ~~P.}ù^ɵYhx0qM`lfDqgcxW}?nA"Ncki4IX9՟v),; J+[),o  \ľ*>[#dI%#2ƀLO 1+hngN4ZɃ_ƒ_gm.gf_/4Iپfv/ H ź}_8X{fƕultp>ϖf} efo ŧ@oI3ٰhs>_`AÁPF/eWQu]9 cN$&g^t>$>[8NKj) ] |.xP2WZV'smkh,#6nɘ7܅O܄?kz{bB6gNo9+:4W@^->tpE$ oOVrAoyE%;`[fz#hڵx2~4e`f$= M\Q[ԲxdG!) d)0U쯗˸1r%ϻ>}0fއPN)O)]$:}O ڇ͑^;^f́웘\ijr~0Cqw3#fNVέ$<Jvn>'9Y l vV2ѱbaRnD,w? 7Ucufm+dx]Ҏfi|ors=^ +%HzX ڌ4гc1)/gyg🰈W^ Q4'Ogw_sQZ3י,fV*DY)`Bw+ Fqo-nfU.dKR@mITѸrj xW$d>Lgfw,ipܴQ| [ dof؄+T:W|B~08(&p5^/a&OQd;3_pWX>=r-Wkn 3w><(kk>ܢN/T"nj|V[$Dͬ?+ rnjܚmF^%n/Eo)tԶ,fvhg{gT-9ﺓ/ȷ#nyx O4`ˣ̷g<0t-bQnjgxdȥJ6ئ=/KU<'~Xo\ {x5& IDAT6RN1/"p k\MU*OZCs8WmHZO007^#ak\fIX7zDE]nҐQ>Ir+y<, lo ֐XDw ^o HN$ZXd84U=WRM)Un34EMߏUi?ܪhH#?+N\ؾTbfKz)&^|1I<5=-+i-7gKvSҐrz Mn9畏Nd?D{3k;7%U-ibRDۓDJD,"@Cy /E{NbBq,s'WpyGBp%~(<\8y^$ I[]~]>lu/y5n(\PJj$mM']($1q5N(@R@ZܖܭkAR)"Y+gGpUvd獠|g}. x 7YNcfF&_ugfIl_y efo=/0yUf$TE uc8Oeik#fc1<.3nϼ~̅x='gxWO\zּ>4T~~ Kcxиܶ?FU$- ̐Kxh3*i<$팤6G _>h5 ^ D;2Y) 1v,A^aȓޚwewtC*1r`3{Ղi:r=Isn~[.Y}_h.H׾ZP?[ۂ"p{kXnxqh[rȷ[d [xߤ4?O(6 i$A^8|k!|v|D3N@rHPg=9D^A|{xmǁKoZ;bt5YKC-Wv>;soi6bZJ:-n\vYۥi2Y;p7Nt؎ !gڂxQ'J()O#i-g A8Ue-U4\v Oc|k\<S> mwe- ȥfV_Œ$j&xx1ߣ% N=\d1tS+-3vg1gx54;Ëh:l p}grk~gdr*BIt@uS}f ۮ*g{Ig II8εYya_MIK:$L䐋^fZI{H(iܪ#l)l%])iŦsv gsq}hGRI疭\`Qe//cfvg"B}$nH_-iEIJZ 8XW)a@Ҟv yvoG`~ k4Z|yF҆q;K:THZ(~3'J-^H+i IGEX=>*q+\=[ffƨ? |[Z?7 h,)@I;HzHNΒΕOb$ڗaxK$[?a1^"36qMk,=gf>^7!3IJZ&ڌSl7Jvr\X- #pkqwZ\&3{)S>$m ^6'ΒtH&ȯ;$Ogfg{e0M3;иSҟڣJf^mωw3▗c%vdd)?t):kXoǠ{ &?O5֜ "^쁿4]{6/0q<_d/ +2km'zWA6];,33_K/<O n5㾪<5Bm*wOY 'i!Ws|<l[;8O>Ynd^쾜lN ر&.jF\-iwIw⿥hǟvgg~_d } VclWPxƽ>W*n--Y&')<)>;hjIP0gԻBY\5zR*퉙]Oܨ\z@>nɮw?R+&nےȖfvg-e EzcsT#+ iP=/"Cps<0b`0=w#uk|k r%p!k|'~C`f_ t <43u43+?l.|g(ٳiy`3;3,pƭK搤䙹GEp_SB2P){٬Tءs;>pymh"/I>oZ3bwp h2{Qp|wx|&qerA<_\Yp+…K3/񸊾*>;0*_?HH$2O A "Fi@ND@^rۡqHӀ̂`$B3 H0H,2Bcgש!IUszoΩs[xgfy:LfxxXfvivZl'y(8^?gCvj1@ $w'߉;pg깻`!>[ef67GE~ұoKl8ߟrQw^7 ^=$ץ-f{8?^!?+}mqs?P1'X]0^hyXV[YZp:ܗYxu]N=8,3̺+c]Y~ gԂH3(i0EeED`wM2p<> \ZD|zh/wklTU^HE-̞vo&>_lϭlPa?3e'`Dۄ}{Iq8pq5#'a u@"r=e`fI:^:%!M=#[G ,jsXо.FUٮ70fV;f\sִ_$\4c-O3|E,/ga%vlifIݗI&A<"%{e&r8jgR[ x"Q3qUJtAcܙ(Im򁌙]+],C|Nucd<_*> Wi)p!u=>r'yRH >?'6ϢՒxru`RxצG*Oqx.af7ږdy̅fvDf@.Ia3ƚپ °U]"iB+`;G[AiGG{_=UlEꝣWֲ*OQRS#$qo #ذ=^П&It$Id'Vzt@$i$-cXE](k愄x-'#z0 }3w٨R\o 0 cAsu–x ܛRP[g 3®qxm?ľ!w?QsL#g[q< ա[lI9E?+GIER\}؟E $YeIepa2>h> [׶x+~DA)Zjy!Λ$uT(J^Zο |ԟT:w8IW I\mS:p}f+S2Gnŝ1`wmf$/ bk 쇯U#'.7u&v||Eexpj3{dBIt3pfD%=ƻt)}LoՍdp;LƜ x 8Ij#b3{>BךYGLHϛ8 oY2 fv<0 ҵèf0OבS=\QՒiDݒRH/*\;R3p>$.3<%aqI0w4X&idJ_1`:%&]6N$IՎ(lY}`<'EGϢ2?6Qx|%}(p= '[]?Zc+Q̞43{ ;O/F ï MNt,P-6j-xr$ XHS{xn?N:!懢Y3qP e4VxO$`|x9`r%Hr%#gx> 䕼 1~mCmX_ n!i]IߏჃSN~'W BwRi_5wfjغ+ppP!XٵAIJ _LIb@9amJB%nʬ.00 x6<43{W${ICl^iHA3a=m 3jfzfvM7?\'^| >W&jW![$ Ǖ s$wSuV]o (3$m`fsKkꌬhI?Àsl|i&㑄^9fvji{g3{,>҉-Eۮxf6X\a]WfvIw_w5>\|Կi4qlfg5؎~vMk-p\+oj-U~~7pH: ID҄r¼l|e368^Rߟ1{.I&6|̞^ג$G! z)9OH IDAT:H6F7ږ21 02Tmh{/_xPf@E#fvu`%It1nu*~$C$ t$vt%P I*G4 xt[$}5añѶ%]#JIGJam$Lv %]$Wmk$}7 }Gt> /XyLMerm҄t -zϢIyɦ ՗5`f!Nl-.k3N.ɦ@pY﫡5du;p|&-_=;\i\Js8^5Yfv$x\BH\sx=#%!b3{ۼf6j+C]}pi#$mv|Wz^a./pv ^4h;`h} ,YǢ@Z);zc˘ًfZW:k≺U9 MZ-K]tu$mK? ę..'aH,i ty@~+iFN׋t@1\W<> /i-`͊0"l^1^aP}]0 jK-~G[qgi+j mgf `mId"/e])pg<<1Z%B|`G`<8I6i xa1X$ie߿j渢PZ,3%~*;Xrf•>zH=@xtpv=JZiy$iZm$I7o.ڛWZ̮#I[τW{[h[ci1G%} 8]d =q]|rG\ͬs: I䕅1Mx-urz? _^XU JjI*aXe!3'Ѷ:Q<|`6};^UwbΔm̝A.o\י-II[Tbk?(f6^]x|&FIf$-h ђvHi+I 3#VG᪛@?6VAҶ#]/^y`؋q ! lJwO2 ?љZ``%Ieh,ħ\v+ ⲲJ}g]duEUw,qMrGfvʼ"IqS$~&t|T,wCw(^`3%I$@: IZ i8l~^mppSYƶՐ"=^ӺUǭѿS43/3vpe$I$%$INUp ﭙ[x"a\]m058^jmI$I{I$6y\Vxh~kMXxuG҆xb\TJ$i^I$YA$OF xfF {ף,“5-I$t@$IVIEI`{P . p( p~_%I$N: I$U++҄bx}k>^z$I&$IB@Yx{Uqݕ4~/#i|0`h~sKYn:I$i-I$:Q(`h^w}vi+.p7t4ƺ$I'I$I }UAk)0=~{D#pǣ`  *s;$IV?I$#"Dk`?`_ * `Invited talk by Johannes Köster at the Broad Institute, Boston 2015. `_ * `Introduction to Snakemake. Tutorial Slides presented by Johannes Köster at the GCB 2015, Dortmund, Germany. `_ * `Invited talk by Johannes Köster at the DTL Focus Meeting: "NGS Production Pipelines", Dutch Techcentre for Life Sciences, Utrecht 2014. `_ * `Taming Snakemake by Jeremy Leipzig, Bioinformatics software developer at Children's Hospital of Philadelphia, 2014. `_ * `"Snakemake makes ... snakes?" - An Introduction by Marcel Martin from SciLifeLab, Stockholm 2015 `_ * `"Workflow Management with Snakemake" by Johannes Köster, 2015. Held at the Department of Biostatistics and Computational Biology, Dana-Farber Cancer Institute `_ .. _project_info-external_resources: ------------------ External Resources ------------------ These resources are not part of the official documentation. * `A number of tutorials on the subject "Tools for reproducible research" `_ * `Snakemake workflow used for the Kallisto paper `_ * `An alternative tutorial for Snakemake `_ * `An Emacs mode for Snakemake `_ * `Flexible bioinformatics pipelines with Snakemake `_ * `Sandwiches with Snakemake `_ * `A visualization of the past years of Snakemake development `_ * `Japanese version of the Snakemake tutorial `_ * `Basic `_ and `advanced `_ french Snakemake tutorial. * `Mini tutorial on Snakemake and Bioconda `_ * `Snakeparse: a utility to expose Snakemake workflow configuation via a command line interface `_ snakemake-5.10.0/docs/requirements.txt000066400000000000000000000001621361131222100177370ustar00rootroot00000000000000sphinx sphinxcontrib-napoleon sphinx-argparse sphinx_rtd_theme docutils==0.12 recommonmark configargparse appdirs snakemake-5.10.0/docs/snakefiles/000077500000000000000000000000001361131222100166005ustar00rootroot00000000000000snakemake-5.10.0/docs/snakefiles/configuration.rst000066400000000000000000000214651361131222100222110ustar00rootroot00000000000000.. _snakefiles_configuration: ============= Configuration ============= Snakemake allows you to use configuration files for making your workflows more flexible and also for abstracting away direct dependencies to a fixed HPC cluster scheduler. .. _snakefiles_standard_configuration: ---------------------- Standard Configuration ---------------------- Snakemake directly supports the configuration of your workflow. A configuration is provided as a JSON or YAML file and can be loaded with: .. code-block:: python configfile: "path/to/config.json" The config file can be used to define a dictionary of configuration parameters and their values. In the workflow, the configuration is accessible via the global variable `config`, e.g. .. code-block:: python rule all: input: expand("{sample}.{param}.output.pdf", sample=config["samples"], param=config["yourparam"]) If the `configfile` statement is not used, the config variable provides an empty array. In addition to the `configfile` statement, config values can be overwritten via the command line or the :ref:`api_reference_snakemake`, e.g.: .. code-block:: console $ snakemake --config yourparam=1.5 Further, you can manually alter the config dictionary using any Python code **outside** of your rules. Changes made from within a rule won't be seen from other rules. Finally, you can use the `--configfile` command line argument to overwrite values from the `configfile` statement. Note that any values parsed into the `config` dictionary with any of above mechanisms are merged, i.e., all keys defined via a `configfile` statement, or the `--configfile` and `--config` command line arguments will end up in the final `config` dictionary, but if two methods define the same key, command line overwrites the `configfile` statement. For adding config placeholders into a shell command, Python string formatting syntax requires you to leave out the quotes around the key name, like so: .. code-block:: python shell: "mycommand {config[foo]} ..." --------------------- Tabular configuration --------------------- It is usually advisable to complement YAML based configuration (see above) by a sheet based approach for meta-data that is of tabular form. For example, such a sheet can contain per-sample information. With the `Pandas library `_ such data can be read and used with minimal overhead, e.g., .. code-block:: python import pandas as pd samples = pd.read_table("samples.tsv").set_index("samples", drop=False) reads in a table ``samples.tsv`` in TSV format and makes every record accessible by the sample name. For details, see the `Pandas documentation `_. A fully working real-world example containing both types of configuration can be found `here `_. ---------- Validation ---------- With Snakemake 5.1, it is possible to validate both types of configuration via `JSON schemas `_. The function ``snakemake.utils.validate`` takes a loaded configuration (a config dictionary or a Pandas data frame) and validates it with a given JSON schema. Thereby, the schema can be provided in JSON or YAML format. Also, by using the defaults property it is possible to populate entries with default values. See `jsonschema FAQ on setting default values `_ for details. In case of the data frame, the schema should model the record that is expected in each row of the data frame. In the following example, .. code-block:: python import pandas as pd from snakemake.utils import validate configfile: "config.yaml" validate(config, "config.schema.yaml") samples = pd.read_table(config["samples"]).set_index("sample", drop=False) validate(samples, "samples.schema.yaml") rule all: input: expand("test.{sample}.txt", sample=samples.index) rule a: output: "test.{sample}.txt" shell: "touch {output}" the schema for validating the samples data frame looks like this: .. code-block:: yaml $schema: "http://json-schema.org/draft-06/schema#" description: an entry in the sample sheet properties: sample: type: string description: sample name/identifier condition: type: string description: sample condition that will be compared during differential expression analysis (e.g. a treatment, a tissue time, a disease) case: type: boolean default: true description: boolean that indicates if sample is case or control required: - sample - condition Here, in case the case column is missing, the validate function will populate it with True for all entries. .. _snakefiles-cluster_configuration: ---------------------------------- Cluster Configuration (deprecated) ---------------------------------- While still being possible, **cluster configuration has been deprecated** by the introduction of :ref:`profiles`. Snakemake supports a separate configuration file for execution on a cluster. A cluster config file allows you to specify cluster submission parameters outside the Snakefile. The cluster config is a JSON- or YAML-formatted file that contains objects that match names of rules in the Snakefile. The parameters in the cluster config are then accessed by the ``cluster.*`` wildcard when you are submitting jobs. Note that a workflow shall never depend on a cluster configuration, because this would limit its portability. Therefore, it is also not intended to access the cluster configuration from **within** the workflow. For example, say that you have the following Snakefile: .. code-block:: python rule all: input: "input1.txt", "input2.txt" rule compute1: output: "input1.txt" shell: "touch input1.txt" rule compute2: output: "input2.txt" shell: "touch input2.txt" This Snakefile can then be configured by a corresponding cluster config, say "cluster.json": .. code-block:: json { "__default__" : { "account" : "my account", "time" : "00:15:00", "n" : 1, "partition" : "core" }, "compute1" : { "time" : "00:20:00" } } Any string in the cluster configuration can be formatted in the same way as shell commands, e.g. ``{rule}.{wildcards.sample}`` is formatted to ``a.xy`` if the rulename is ``a`` and the wildcard value is ``xy``. Here ``__default__`` is a special object that specifies default parameters, these will be inherited by the other configuration objects. The ``compute1`` object here changes the ``time`` parameter, but keeps the other parameters from ``__default__``. The rule ``compute2`` does not have any configuration, and will therefore use the default configuration. You can then run the Snakefile with the following command on a SLURM system. .. code-block:: console $ snakemake -j 999 --cluster-config cluster.json --cluster "sbatch -A {cluster.account} -p {cluster.partition} -n {cluster.n} -t {cluster.time}" For cluster systems using LSF/BSUB, a cluster config may look like this: .. code-block:: json { "__default__" : { "queue" : "medium_priority", "nCPUs" : "16", "memory" : 20000, "resources" : "\"select[mem>20000] rusage[mem=20000] span[hosts=1]\"", "name" : "JOBNAME.{rule}.{wildcards}", "output" : "logs/cluster/{rule}.{wildcards}.out", "error" : "logs/cluster/{rule}.{wildcards}.err" }, "trimming_PE" : { "memory" : 30000, "resources" : "\"select[mem>30000] rusage[mem=30000] span[hosts=1]\"", } } The advantage of this setup is that it is already pretty general by exploiting the wildcard possibilities that Snakemake provides via ``{rule}`` and ``{wildcards}``. So job names, output and error files all have reasonable and trackable default names, only the directies (``logs/cluster``) and job names (``JOBNAME``) have to adjusted accordingly. If a rule named ``bamCoverage`` is executed with the wildcard ``basename = sample1``, for example, the output and error files will be ``bamCoverage.basename=sample1.out`` and ``bamCoverage.basename=sample1.err``, respectively. --------------------------- Configure Working Directory --------------------------- All paths in the snakefile are interpreted relative to the directory snakemake is executed in. This behaviour can be overridden by specifying a workdir in the snakefile: .. code-block:: python workdir: "path/to/workdir" Usually, it is preferred to only set the working directory via the command line, because above directive limits the portability of Snakemake workflows. snakemake-5.10.0/docs/snakefiles/deployment.rst000066400000000000000000000247631361131222100215260ustar00rootroot00000000000000 ================================ Distribution and Reproducibility ================================ It is recommended to store each workflow in a dedicated git repository of the following structure: .. code-block:: none ├── .gitignore ├── README.md ├── LICENSE.md ├── workflow │ ├── scripts | │ ├── script1.py | │ └── script2.R │ ├── rules | │ ├── module1.smk | │ └── module2.smk │ ├── report | │ ├── plot1.smk | │ └── plot2.smk │ └── envs | │ ├── tool1.smk | │ └── tool2.smk ├── config │ ├── config.yaml │ └── some-sheet.tsv └── Snakefile Then, a workflow can be deployed to a new system via the following steps .. code-block:: python # clone workflow into working directory git clone https://github.com/user/myworkflow.git path/to/workdir cd path/to/workdir # edit config and workflow as needed vim config/config.yaml # execute workflow, deploy software dependencies via conda snakemake -n --use-conda Importantly, git branching and pull requests can be used to modify and possibly re-integrate workflows. A `cookiecutter `_ template for creating this structure can be found `here `_. Given that cookiecutter is installed, you can use it via: .. code-block:: bash cookiecutter gh:snakemake-workflows/cookiecutter-snakemake-workflow Visit the `Snakemake Workflows Project `_ for best-practice workflows. .. _integrated_package_management: ----------------------------- Integrated Package Management ----------------------------- With Snakemake 3.9.0 it is possible to define isolated software environments per rule. Upon execution of a workflow, the `Conda package manager `_ is used to obtain and deploy the defined software packages in the specified versions. Packages will be installed into your working directory, without requiring any admin/root priviledges. Given that conda is available on your system (see `Miniconda `_), to use the Conda integration, add the ``--use-conda`` flag to your workflow execution command, e.g. ``snakemake --cores 8 --use-conda``. When ``--use-conda`` is activated, Snakemake will automatically create software environments for any used wrapper (see :ref:`snakefiles-wrappers`). Further, you can manually define environments via the ``conda`` directive, e.g.: .. code-block:: python rule NAME: input: "table.txt" output: "plots/myplot.pdf" conda: "envs/ggplot.yaml" script: "scripts/plot-stuff.R" with the following `environment definition `_: .. code-block:: yaml channels: - r dependencies: - r=3.3.1 - r-ggplot2=2.1.0 The path to the environment definition is interpreted as **relative to the Snakefile that contains the rule** (unless it is an absolute path, which is discouraged). .. sidebar:: Note Note that conda environments are only used with ``shell``, ``script`` and the ``wrapper`` directive, not the ``run`` directive. The reason is that the ``run`` directive has access to the rest of the Snakefile (e.g. globally defined variables) and therefore must be executed in the same process as Snakemake itself. Snakemake will store the environment persistently in ``.snakemake/conda/$hash`` with ``$hash`` being the MD5 hash of the environment definition file content. This way, updates to the environment definition are automatically detected. Note that you need to clean up environments manually for now. However, in many cases they are lightweight and consist of symlinks to your central conda installation. Conda deployment also works well for offline or air-gapped environments. Running ``snakemake -n --use-conda --create-envs-only`` will only install the required conda environments without running the full workflow. Subsequent runs with ``--use-conda`` will make use of the local environments without requiring internet access. .. _singularity: -------------------------- Running jobs in containers -------------------------- As an alternative to using Conda (see above), it is possible to define, for each rule, a docker or singularity container to use, e.g., .. code-block:: python rule NAME: input: "table.txt" output: "plots/myplot.pdf" singularity: "docker://joseespinosa/docker-r-ggplot2" script: "scripts/plot-stuff.R" When executing Snakemake with .. code-block:: bash snakemake --use-singularity it will execute the job within a singularity container that is spawned from the given image. Allowed image urls entail everything supported by singularity (e.g., ``shub://`` and ``docker://``). .. sidebar:: Note Note that singularity integration is only used with ``shell``, ``script`` and the ``wrapper`` directive, not the ``run`` directive. The reason is that the ``run`` directive has access to the rest of the Snakefile (e.g. globally defined variables) and therefore must be executed in the same process as Snakemake itself. When ``--use-singularity`` is combined with ``--kubernetes`` (see :ref:`kubernetes`), cloud jobs will be automatically configured to run in priviledged mode, because this is a current requirement of the singularity executable. Importantly, those privileges won't be shared by the actual code that is executed in the singularity container though. -------------------------------------------------- Combining Conda package management with containers -------------------------------------------------- While :ref:`integrated_package_management` provides control over the used software in exactly the desired versions, it does not control the underlying operating system. Here, it becomes handy that Snakemake >=4.8.0 allows to combine Conda-based package management with :ref:`singularity`. For example, you can write .. code-block:: python singularity: "docker://continuumio/miniconda3:4.4.10" rule NAME: input: "table.txt" output: "plots/myplot.pdf" conda: "envs/ggplot.yaml" script: "scripts/plot-stuff.R" in other words, a global definition of a container image can be combined with a per-rule conda directive. Then, upon invocation with .. code-block:: bash snakemake --use-conda --use-singularity Snakemake will first pull the defined container image, and then create the requested conda environment from within the container. The conda environments will still be stored in your working environment, such that they don't have to be recreated unless they have changed. The hash under which the environments are stored includes the used container image url, such that changes to the container image also lead to new environments to be created. When a job is executed, Snakemake will first enter the container and then activate the conda environment. By this, both packages and OS can be easily controlled without the overhead of creating and distributing specialized container images. Of course, it is also possible (though less common) to define a container image per rule in this scenario. The user can, upon execution, freely choose the desired level of reproducibility: * no package management (use whatever is on the system) * Conda based package management (use versions defined by the workflow developer) * Conda based package management in containerized OS (use versions and OS defined by the workflow developer) ------------------------- Using environment modules ------------------------- In high performace cluster systems (HPC), it can be preferable to use environment modules for deployment of optimized versions of certain standard tools. Snakemake allows to define environment modules per rule: .. code-block:: python rule bwa: input: "genome.fa" "reads.fq" output: "mapped.bam" conda: "envs/bwa.yaml" envmodules: "bio/bwa/0.7.9", "bio/samtools/1.9" shell: "bwa mem {input} | samtools view -Sbh - > {output}" Here, when Snakemake is executed with `snakemake --use-envmodules`, it will load the defined modules in the given order, instead of using the also defined conda environment. Note that although not mandatory, one should always provide either a conda environment or a container (see above), along with environment module definitions. The reason is that environment modules are often highly platform specific, and cannot be assumed to be available somewhere else, thereby limiting reproducibility. By definition an equivalent conda environment or container as a fallback, people outside of the HPC system where the workflow has been designed can still execute it, e.g. by running `snakemake --use-conda` instead of `snakemake --use-envmodules`. -------------------------------------- Sustainable and reproducible archiving -------------------------------------- With Snakemake 3.10.0 it is possible to archive a workflow into a `tarball `_ (`.tar`, `.tar.gz`, `.tar.bz2`, `.tar.xz`), via .. code-block:: bash snakemake --archive my-workflow.tar.gz If above layout is followed, this will archive any code and config files that is under git version control. Further, all input files will be included into the archive. Finally, the software packages of each defined conda environment are included. This results in a self-contained workflow archive that can be re-executed on a vanilla machine that only has Conda and Snakemake installed via .. code-block:: bash tar -xf my-workflow.tar.gz snakemake -n Note that the archive is platform specific. For example, if created on Linux, it will run on any Linux newer than the minimum version that has been supported by the used Conda packages at the time of archiving (e.g. CentOS 6). A useful pattern when publishing data analyses is to create such an archive, upload it to `Zenodo `_ and thereby obtain a `DOI `_. Then, the DOI can be cited in manuscripts, and readers are able to download and reproduce the data analysis at any time in the future. snakemake-5.10.0/docs/snakefiles/modularization.rst000066400000000000000000000156511361131222100224030ustar00rootroot00000000000000.. snakefiles-modularization: .. _Snakemake Wrapper Repository: https://snakemake-wrappers.readthedocs.io ============== Modularization ============== Modularization in Snakemake comes at different levels. 1. The most fine-grained level are wrappers. They are available and can be published at the `Snakemake Wrapper Repository`_. These wrappers can then be composed and customized according to your needs, by copying skeleton rules into your workflow. In combination with conda integration, wrappers also automatically deploy the needed software dependencies into isolated environments. 2. For larger, reusable parts that shall be integrated into a common workflow, it is recommended to write small Snakefiles and include them into a master Snakefile via the include statement. In such a setup, all rules share a common config file. 3. The third level of separation are subworkflows. Importantly, these are rather meant as links between otherwise separate data analyses. .. _snakefiles-wrappers: -------- Wrappers -------- The wrapper directive allows to have re-usable wrapper scripts around e.g. command line tools. In contrast to modularization strategies like ``include`` or subworkflows, the wrapper directive allows to re-wire the DAG of jobs. For example .. code-block:: python rule samtools_sort: input: "mapped/{sample}.bam" output: "mapped/{sample}.sorted.bam" params: "-m 4G" threads: 8 wrapper: "0.0.8/bio/samtools_sort" Refers to the wrapper ``"0.0.8/bio/samtools_sort"`` to create the output from the input. Snakemake will automatically download the wrapper from the `Snakemake Wrapper Repository`_. Thereby, 0.0.8 can be replaced with the git `version tag `_ you want to use, or a `commit id `_. This ensures reproducibility since changes in the wrapper implementation won't be propagated automatically to your workflow. Alternatively, e.g., for development, the wrapper directive can also point to full URLs, including URLs to local files with absolute paths ``file://`` or relative paths ``file:``. Examples for each wrapper can be found in the READMEs located in the wrapper subdirectories at the `Snakemake Wrapper Repository`_. The `Snakemake Wrapper Repository`_ is meant as a collaborative project and pull requests are very welcome. .. _cwl: -------------------------------------- Common-Workflow-Language (CWL) support -------------------------------------- With Snakemake 4.8.0, it is possible to refer to `CWL `_ tool definitions in rules instead of specifying a wrapper or a plain shell command. A CWL tool definition can be used as follows. .. code-block:: python rule samtools_sort: input: input="mapped/{sample}.bam" output: output_name="mapped/{sample}.sorted.bam" params: threads=lambda wildcards, threads: threads, memory="4G" threads: 8 cwl: "https://github.com/common-workflow-language/workflows/blob/" "fb406c95/tools/samtools-sort.cwl" It is advisable to use a github URL that includes the commit as above instead of a branch name, in order to ensure reproducible results. Snakemake will execute the rule by invoking `cwltool`, which has to be available via your `$PATH` variable, and can be, e.g., installed via `conda` or `pip`. When using in combination with :ref:`--use-singularity `, Snakemake will instruct `cwltool` to execute the command via Singularity in user space. Otherwise, `cwltool` will in most cases use a Docker container, which requires Docker to be set up properly. The advantage is that predefined tools available via any `repository of CWL tool definitions `_ can be used in any supporting workflow management system. In contrast to a :ref:`Snakemake wrapper `, CWL tool definitions are in general not suited to alter the behavior of a tool, e.g., by normalizing output names or special input handling. As you can see in comparison to the analog :ref:`wrapper declaration ` above, the rule becomes slightly more verbose, because input, output, and params have to be dispatched to the specific expectations of the CWL tool definition. .. _snakefiles-includes: -------- Includes -------- Another Snakefile with all its rules can be included into the current: .. code-block:: python include: "path/to/other/snakefile" The default target rule (often called the ``all``-rule), won't be affected by the include. I.e. it will always be the first rule in your Snakefile, no matter how many includes you have above your first rule. Includes are relative to the directory of the Snakefile in which they occur. For example, if above Snakefile resides in the directory ``my/dir``, then Snakemake will search for the include at ``my/dir/path/to/other/snakefile``, regardless of the working directory. .. _snakefiles-sub_workflows: ------------- Sub-Workflows ------------- In addition to including rules of another workflow, Snakemake allows to depend on the output of other workflows as sub-workflows. A sub-workflow is executed independently before the current workflow is executed. Thereby, Snakemake ensures that all files the current workflow depends on are created or updated if necessary. This allows to create links between otherwise separate data analyses. .. code-block:: python subworkflow otherworkflow: workdir: "../path/to/otherworkflow" snakefile: "../path/to/otherworkflow/Snakefile" configfile: "path/to/custom_configfile.yaml" rule a: input: otherworkflow("test.txt") output: ... shell: ... Here, the subworkflow is named "otherworkflow" and it is located in the working directory ``../path/to/otherworkflow``. The snakefile is in the same directory and called ``Snakefile``. If ``snakefile`` is not defined for the subworkflow, it is assumed be located in the workdir location and called ``Snakefile``, hence, above we could have left the ``snakefile`` keyword out as well. If ``workdir`` is not specified, it is assumed to be the same as the current one. The (optional) definition of a ``configfile`` allows to parameterize the subworkflow as needed. Files that are output from the subworkflow that we depend on are marked with the ``otherworkflow`` function (see the input of rule a). This function automatically determines the absolute path to the file (here ``../path/to/otherworkflow/test.txt``). When executing, snakemake first tries to create (or update, if necessary) ``test.txt`` (and all other possibly mentioned dependencies) by executing the subworkflow. Then the current workflow is executed. This can also happen recursively, since the subworkflow may have its own subworkflows as well. snakemake-5.10.0/docs/snakefiles/remote_files.rst000066400000000000000000000770211361131222100220160ustar00rootroot00000000000000.. _snakefiles-remote_files: ============ Remote files ============ In versions ``snakemake>=3.5``. The ``Snakefile`` supports a wrapper function, ``remote()``, indicating a file is on a remote storage provider (this is similar to ``temp()`` or ``protected()``). In order to use all types of remote files, the Python packages ``boto``, ``moto``, ``filechunkio``, ``pysftp``, ``dropbox``, ``requests``, ``ftputil``, ``XRootD``, and ``biopython`` must be installed. During rule execution, a remote file (or object) specified is downloaded to the local ``cwd``, within a sub-directory bearing the same name as the remote provider. This sub-directory naming lets you have multiple remote origins with reduced likelihood of name collisions, and allows Snakemake to easily translate remote objects to local file paths. You can think of each local remote sub-directory as a local mirror of the remote system. The ``remote()`` wrapper is mutually-exclusive with the ``temp()`` and ``protected()`` wrappers. Snakemake includes the following remote providers, supported by the corresponding classes: * Amazon Simple Storage Service (AWS S3): ``snakemake.remote.S3`` * Google Cloud Storage (GS): ``snakemake.remote.GS`` * Microsoft Azure Storage: ``snakemake.remote.AzureStorage`` * File transfer over SSH (SFTP): ``snakemake.remote.SFTP`` * Read-only web (HTTP[S]): ``snakemake.remote.HTTP`` * File transfer protocol (FTP): ``snakemake.remote.FTP`` * Dropbox: ``snakemake.remote.dropbox`` * XRootD: ``snakemake.remote.XRootD`` * GenBank / NCBI Entrez: ``snakemake.remote.NCBI`` * WebDAV: ``snakemake.remote.webdav`` * GFAL: ``snakemake.remote.gfal`` * GridFTP: ``snakemake.remote.gridftp`` * iRODS: ``snakemake.remote.iRODS`` * EGA: ``snakemake.remote.EGA`` Amazon Simple Storage Service (S3) ================================== This section describes usage of the S3 RemoteProvider, and also provides an intro to remote files and their usage. It is important to note that you must have credentials (``access_key_id`` and ``secret_access_key``) which permit read/write access. If a file only serves as input to a Snakemake rule, read access is sufficient. You may specify credentials as environment variables or in the file ``=/.aws/credentials``, prefixed with ``AWS_*``, as with a standard `boto config `_. Credentials may also be explicitly listed in the ``Snakefile``, as shown below: For the Amazon S3 and Google Cloud Storage providers, the sub-directory used must be the bucket name. Using remote files is easy (AWS S3 shown): .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider(access_key_id="MYACCESSKEY", secret_access_key="MYSECRET") rule all: input: S3.remote("bucket-name/file.txt") Expand still works as expected, just wrap the expansion: .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider() rule all: input: S3.remote(expand("bucket-name/{letter}-2.txt", letter=["A", "B", "C"])) Only remote files needed to satisfy the DAG build are downloaded for the workflow. By default, remote files are downloaded prior to rule execution and are removed locally as soon as no rules depend on them. Remote files can be explicitly kept by setting the ``keep_local=True`` keyword argument: .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider(access_key_id="MYACCESSKEY", secret_access_key="MYSECRET") rule all: input: S3.remote('bucket-name/prefix{split_id}.txt', keep_local=True) If you wish to have a rule to simply download a file to a local copy, you can do so by declaring the same file path locally as is used by the remote file: .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider(access_key_id="MYACCESSKEY", secret_access_key="MYSECRET") rule all: input: S3.remote("bucket-name/out.txt") output: "bucket-name/out.txt" run: shell("cp {output[0]} ./") In some cases the rule can use the data directly on the remote provider, in these cases ``stay_on_remote=True`` can be set to avoid downloading/uploading data unnecessarily. Additionally, if the backend supports it, any potentially corrupt output files will be removed from the remote. The default for ``stay_on_remote`` and ``keep_local`` can be configured by setting these properties on the remote provider object: .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider(access_key_id="MYACCESSKEY", secret_access_key="MYSECRET", keep_local=True, stay_on_remote=True) The remote provider also supports a new ``glob_wildcards()`` (see :ref:`glob-wildcards`) which acts the same as the local version of ``glob_wildcards()``, but for remote files: .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider(access_key_id="MYACCESSKEY", secret_access_key="MYSECRET") S3.glob_wildcards("bucket-name/{file_prefix}.txt") # (result looks just like as if the local glob_wildcards() function were used on a locally with a folder called "bucket-name") If the AWS CLI is installed it is possible to configure your keys globally. This removes the necessity of hardcoding the keys in the Snakefile. The interactive AWS credentials setup can be done using the following command: .. code-block:: python aws configure S3 then can be used without the keys. .. code-block:: python from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider() Finally, it is also possible to overwrite the S3 host via adding a ``host`` argument (taking a URL string) to ``S3RemoteProvider``. Google Cloud Storage (GS) ========================= Usage of the GS provider is the same as the S3 provider. For authentication, one simply needs to login via the ``gcloud`` tool before executing Snakemake, i.e.: .. code-block:: console $ gcloud auth application-default login In the Snakefile, no additional authentication information has to be provided: .. code-block:: python from snakemake.remote.GS import RemoteProvider as GSRemoteProvider GS = GSRemoteProvider() rule all: input: GS.remote("bucket-name/file.txt") Microsoft Azure Storage ======================= Usage of the Azure Storage provider is similar to the S3 provider. For authentication, one needs to provide an account name and a key, which can e.g. be taken from environment variables. .. code-block:: python from snakemake.remote.AzureStorage import RemoteProvider as AzureRemoteProvider account_key=os.environ['AZURE_KEY'] account_name=os.environ['AZURE_ACCOUNT'] AS = AzureRemoteProvider(account_name=account_name, account_key=account_key) rule a: input: AS.remote("path/to/file.txt") File transfer over SSH (SFTP) ============================= Snakemake can use files on remove servers accessible via SFTP (i.e. most \*nix servers). It uses `pysftp `_ for the underlying support of SFTP, so the same connection options exist. Assuming you have SSH keys already set up for the server you are using in the ``Snakefile``, usage is simple: .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider() rule all: input: SFTP.remote("example.com/path/to/file.bam") The remote file addresses used must be specified with the host (domain or IP address) and the absolute path to the file on the remote server. A port may be specified if the SSH daemon on the server is listening on a port other than 22, in either the ``RemoteProvider`` or in each instance of ``remote()``: .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider(port=4040) rule all: input: SFTP.remote("example.com/path/to/file.bam") .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider() rule all: input: SFTP.remote("example.com:4040/path/to/file.bam") The standard keyword arguments used by `pysftp `_ may be provided to the RemoteProvider to specify credentials (either password or private key): .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider(username="myusername", private_key="/Users/myusername/.ssh/particular_id_rsa") rule all: input: SFTP.remote("example.com/path/to/file.bam") .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider(username="myusername", password="mypassword") rule all: input: SFTP.remote("example.com/path/to/file.bam") If you share credentials between servers but connect to one on a different port, the alternate port may be specified in the ``remote()`` wrapper: .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider(username="myusername", password="mypassword") rule all: input: SFTP.remote("some-example-server-1.com/path/to/file.bam"), SFTP.remote("some-example-server-2.com:2222/path/to/file.bam") There is a ``glob_wildcards()`` function: .. code-block:: python from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider() SFTP.glob_wildcards("example.com/path/to/{sample}.bam") Read-only web (HTTP[s]) ======================= Snakemake can access web resources via a read-only HTTP(S) provider. This provider can be helpful for including public web data in a workflow. Web addresses must be specified without protocol, so if your URI looks like this: .. code-block:: text http://server3.example.com/path/to/myfile.tar.gz The URI used in the ``Snakefile`` must look like this: .. code-block:: text server3.example.com/path/to/myfile.tar.gz It is straightforward to use the HTTP provider to download a file to the `cwd`: .. code-block:: python import os from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("www.example.com/path/to/document.pdf", keep_local=True) run: outputName = os.path.basename(input[0]) shell("mv {input} {outputName}") To connect on a different port, specify the port as part of the URI string: .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("www.example.com:8080/path/to/document.pdf", keep_local=True) By default, the HTTP provider always uses HTTPS (TLS). If you need to connect to a resource with regular HTTP (no TLS), you must explicitly include ``insecure`` as a ``kwarg`` to ``remote()``: .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("www.example.com/path/to/document.pdf", insecure=True, keep_local=True) If the URI used includes characters not permitted in a local file path, you may include them as part of the ``additional_request_string`` in the ``kwargs`` for ``remote()``. This may also be useful for including additional parameters you don not want to be part of the local filename (since the URI string becomes the local file name). .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("example.com/query.php", additional_request_string="?range=2;3") If the file requires authentication, you can specify a username and password for HTTP Basic Auth with the Remote Provider, or with each instance of `remote()`. For different types of authentication, you can pass in a Python ```requests.auth`` object (see `here `_) the `auth` ``kwarg``. .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider(username="myusername", password="mypassword") rule all: input: HTTP.remote("example.com/interactive.php", keep_local=True) .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("example.com/interactive.php", username="myusername", password="mypassword", keep_local=True) .. code-block:: python from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() rule all: input: HTTP.remote("example.com/interactive.php", auth=requests.auth.HTTPDigestAuth("myusername", "mypassword"), keep_local=True) Since remote servers do not present directory contents uniformly, ``glob_wildcards()`` is __not__ supported by the HTTP provider. File Transfer Protocol (FTP) ============================ Snakemake can work with files stored on regular FTP. Currently supported are authenticated FTP and anonymous FTP, excluding FTP via TLS. Usage is similar to the SFTP provider, however the paths specified are relative to the FTP home directory (since this is typically a chroot): .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider(username="myusername", password="mypassword") rule all: input: FTP.remote("example.com/rel/path/to/file.tar.gz") The port may be specified in either the provider, or in each instance of `remote()`: .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider(username="myusername", password="mypassword", port=2121) rule all: input: FTP.remote("example.com/rel/path/to/file.tar.gz") .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider(username="myusername", password="mypassword") rule all: input: FTP.remote("example.com:2121/rel/path/to/file.tar.gz") Anonymous download of FTP resources is possible: .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider() rule all: input: # only keeping the file so we can move it out to the cwd FTP.remote("example.com/rel/path/to/file.tar.gz", keep_local=True) run: shell("mv {input} ./") ``glob_wildcards()``: .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider(username="myusername", password="mypassword") print(FTP.glob_wildcards("example.com/somedir/{file}.txt")) Setting `immediate_close=True` allows the use of a large number of remote FTP input files in a job where the endpoint server limits the number of concurrent connections. When `immediate_close=True`, Snakemake will terminate FTP connections after each remote file action (`exists()`, `size()`, `download()`, `mtime()`, etc.). This is in contrast to the default behavior which caches FTP details and leaves the connection open across actions to improve performance (closing the connection upon job termination). : .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider() rule all: input: # only keep the file so we can move it out to the cwd # This server limits the number of concurrent connections so we need to have Snakemake close each after each FTP action. FTP.remote(expand("ftp.example.com/rel/path/to/{file}", file=large_list), keep_local=True, immediate_close=True) run: shell("mv {input} ./") ``glob_wildcards()``: .. code-block:: python from snakemake.remote.FTP import RemoteProvider as FTPRemoteProvider FTP = FTPRemoteProvider(username="myusername", password="mypassword") print(FTP.glob_wildcards("example.com/somedir/{file}.txt")) Dropbox ======= The Dropbox remote provider allows you to upload and download from your `Dropbox `_ account without having the client installed on your machine. In order to use the provider you first need to register an "app" on the `Dropbox developer website `_, with access to the Full Dropbox. After registering, generate an OAuth2 access token. You will need the token to use the Snakemake Dropbox remote provider. Using the Dropbox provider is straightforward: .. code-block:: python from snakemake.remote.dropbox import RemoteProvider as DropboxRemoteProvider DBox = DropboxRemoteProvider(oauth2_access_token="mytoken") rule all: input: DBox.remote("path/to/input.txt") ``glob_wildcards()`` is supported: .. code-block:: python from snakemake.remote.dropbox import RemoteProvider as DropboxRemoteProvider DBox = DropboxRemoteProvider(oauth2_access_token="mytoken") DBox.glob_wildcards("path/to/{title}.txt") Note that Dropbox paths are case-insensitive. XRootD ======= Snakemake can be used with `XRootD `_ backed storage provided the python bindings are installed. This is typically most useful when combined with the ``stay_on_remote`` flag to minimise local storage requirements. This flag can be overridden on a file by file basis as described in the S3 remote. Additionally ``glob_wildcards()`` is supported: .. code-block:: python from snakemake.remote.XRootD import RemoteProvider as XRootDRemoteProvider XRootD = XRootDRemoteProvider(stay_on_remote=True) file_numbers = XRootD.glob_wildcards("root://eospublic.cern.ch//eos/opendata/lhcb/MasterclassDatasets/D0lifetime/2014/mclasseventv2_D0_{n}.root") rule all: input: XRootD.remote(expand("local_data/mclasseventv2_D0_{n}.root", n=file_numbers)) rule make_data: input: XRootD.remote("root://eospublic.cern.ch//eos/opendata/lhcb/MasterclassDatasets/D0lifetime/2014/mclasseventv2_D0_{n}.root") output: 'local_data/mclasseventv2_D0_{n}.root' shell: 'xrdcp {input[0]} {output[0]}' GenBank / NCBI Entrez ===================== Snakemake can directly source input files from `GenBank `_ and other `NCBI Entrez databases `_ if the Biopython library is installed. .. code-block:: python from snakemake.remote.NCBI import RemoteProvider as NCBIRemoteProvider NCBI = NCBIRemoteProvider(email="someone@example.com") # email required by NCBI to prevent abuse rule all: input: "size.txt" rule download_and_count: input: NCBI.remote("KY785484.1.fasta", db="nuccore") output: "size.txt" run: shell("wc -c {input} > {output}") The output format and source database of a record retrieved from GenBank is inferred from the file extension specified. For example, ``NCBI.RemoteProvider().remote("KY785484.1.fasta", db="nuccore")`` will download a FASTA file while ``NCBI.RemoteProvider().remote("KY785484.1.gb", db="nuccore")`` will download a GenBank-format file. If the options are ambiguous, Snakemake will raise an exception and inform the user of possible format choices. To see available formats, consult the `Entrez EFetch documentation `_. To view the valid file extensions for these formats, access ``NCBI.RemoteProvider()._gb.valid_extensions``, or instantiate an ``NCBI.NCBIHelper()`` and access ``NCBI.NCBIHelper().valid_extensions`` (this is a property). When used in conjunction with ``NCBI.RemoteProvider().search()``, Snakemake and ``NCBI.RemoteProvider().remote()`` can be used to find accessions by query and download them: .. code-block:: python from snakemake.remote.NCBI import RemoteProvider as NCBIRemoteProvider NCBI = NCBIRemoteProvider(email="someone@example.com") # email required by NCBI to prevent abuse # get accessions for the first 3 results in a search for full-length Zika virus genomes # the query parameter accepts standard GenBank search syntax query = '"Zika virus"[Organism] AND (("9000"[SLEN] : "20000"[SLEN]) AND ("2017/03/20"[PDAT] : "2017/03/24"[PDAT])) ' accessions = NCBI.search(query, retmax=3) # give the accessions a file extension to help the RemoteProvider determine the # proper output type. input_files = expand("{acc}.fasta", acc=accessions) rule all: input: "sizes.txt" rule download_and_count: input: # Since *.fasta files could come from several different databases, specify the database here. # if the input files are ambiguous, the provider will alert the user with possible options # standard options like "seq_start" are supported NCBI.remote(input_files, db="nuccore", seq_start=5000) output: "sizes.txt" run: shell("wc -c {input} > sizes.txt") Normally, all accessions for a query are returned from ``NCBI.RemoteProvider.search()``. To truncate the results, specify ``retmax=``. Standard Entrez `fetch query options `_ are supported as kwargs, and may be passed in to ``NCBI.RemoteProvider.remote()`` and ``NCBI.RemoteProvider.search()``. WebDAV ====== WebDAV support is currently ``experimental`` and available in Snakemake 4.0 and later. Snakemake supports reading and writing WebDAV remote files. The protocol defaults to ``https://``, but insecure connections can be used by specifying ``protocol=="http://"``. Similarly, the port defaults to 443, and can be overridden by specifying ``port=##`` or by including the port as part of the file address. .. code-block:: python from snakemake.remote import webdav webdav = webdav.RemoteProvider(username="test", password="test", protocol="http://") rule a: input: webdav.remote("example.com:8888/path/to/input_file.csv"), shell: # do something GFAL ==== GFAL support is available in Snakemake 4.1 and later. Snakemake supports reading and writing remote files via the `GFAL `_ command line client (gfal-* commands). By this, it supports various grid storage protocols like `GridFTP `_. In general, if you are able to use the `gfal-*` commands directly, Snakemake support for GFAL will work as well. .. code-block:: python from snakemake.remote import gfal gfal = gfal.RemoteProvider(retry=5) rule a: input: gfal.remote("gridftp.grid.sara.nl:2811/path/to/infile.txt") output: gfal.remote("gridftp.grid.sara.nl:2811/path/to/outfile.txt") shell: # do something Authentication has to be setup in the system, e.g. via certificates in the ``.globus`` directory. Usually, this is already the case and no action has to be taken. The keyword argument to the remote provider allows to set the number of retries (10 per default) in case of failed commands (the GRID is usually relatively unreliable). The latter may be unsupported depending on the system configuration. Note that GFAL support used together with the flags ``--no-shared-fs`` and ``--default-remote-provider`` enables you to transparently use Snakemake in a grid computing environment without a shared network filesystem. For an example see the `surfsara-grid configuration profile `_. GridFTP ======= GridFTP support is available in Snakemake 4.3.0 and later. As a more specialized alternative to the GFAL remote provider, Snakemake provides a `GridFTP `_ remote provider. This provider only supports the GridFTP protocol. Internally, it uses the `globus-url-copy `_ command for downloads and uploads, while all other tasks are delegated to the GFAL remote provider. .. code-block:: python from snakemake.remote import gridftp gridftp = gridftp.RemoteProvider(retry=5) rule a: input: gridftp.remote("gridftp.grid.sara.nl:2811/path/to/infile.txt") output: gridftp.remote("gridftp.grid.sara.nl:2811/path/to/outfile.txt") shell: # do something Authentication has to be setup in the system, e.g. via certificates in the ``.globus`` directory. Usually, this is already the case and no action has to be taken. The keyword argument to the remote provider allows to set the number of retries (10 per default) in case of failed commands (the GRID is usually relatively unreliable). The latter may be unsupported depending on the system configuration. Note that GridFTP support used together with the flags ``--no-shared-fs`` and ``--default-remote-provider`` enables you to transparently use Snakemake in a grid computing environment without a shared network filesystem. For an example see the `surfsara-grid configuration profile `_. Remote cross-provider transfers =============================== It is possible to use Snakemake to transfer files between remote providers (using the local machine as an intermediary), as long as the sub-directory (bucket) names differ: .. code-block:: python from snakemake.remote.GS import RemoteProvider as GSRemoteProvider from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider GS = GSRemoteProvider(access_key_id="MYACCESSKEYID", secret_access_key="MYSECRETACCESSKEY") S3 = S3RemoteProvider(access_key_id="MYACCESSKEYID", secret_access_key="MYSECRETACCESSKEY") fileList, = S3.glob_wildcards("source-bucket/{file}.bam") rule all: input: GS.remote( expand("destination-bucket/{file}.bam", file=fileList) ) rule transfer_S3_to_GS: input: S3.remote( expand("source-bucket/{file}.bam", file=fileList) ) output: GS.remote( expand("destination-bucket/{file}.bam", file=fileList) ) run: shell("cp {input} {output}") iRODS ===== You can access an iRODS server to retrieve data from and upload data to it. If your iRODS server is not set to a certain timezone, it is using UTC. It is advised to shift the modification time provided by iRODS (``modify_time``) then to your timezone by providing the ``timezone`` parameter such that timestamps coming from iRODS are converted to the correct time. iRODS actually does not save the timestamp from your original file but creates its own timestamp of the upload time. When iRODS downloads the file for processing, it does not take the timestamp from the remote file. Instead, the file will have the timestamp when it was downloaded. To get around this, we create a metadata entry to store the original file stamp from your system and alter the timestamp of the downloaded file accordingly. While uploading, the metadata entries ``atime``, ``ctime`` and ``mtime`` are added. When this entry does not exist (because this module didn't upload the file), we fall back to the timestamp provided by iRODS with the above mentioned strategy. To access the iRODS server you need to have an iRODS environment configuration file available and in this file the authentication needs to be configured. The iRODS configuration file can be created by following the `official instructions `_). The default location for the configuration file is ``~/.irods/irods_environment.json``. The ``RemoteProvider()`` class accepts the parameter ``irods_env_file`` where an alternative path to the ``irods_environment.json`` file can be specified. Another way is to export the environment variable ``IRODS_ENVIRONMENT_FILE`` in your shell to specify the location. There are several ways to configure the authentication against the iRODS server, depending on what your iRODS server offers. If you are using the authentication via password, the default location of the authentication file is ``~/.irods/.irodsA``. Usually this file is generated with the ``iinit`` command from the ``iCommands`` program suite. Inside the ``irods_environment.json`` file, the parameter ``"irods_authentication_file"`` can be set to specifiy an alternative location for the ``.irodsA`` file. Another possibility to change the location is to export the environment variable ``IRODS_AUTHENTICATION_FILE``. The ``glob_wildcards()`` function is supported. .. code-block:: python from snakemake.remote.iRODS import RemoteProvider irods = RemoteProvider(irods_env_file='setup-data/irods_environment.json', timezone="Europe/Berlin") # all parameters are optional # please note the comma after the variable name! # access: irods.remote(expand('home/rods/{f}), f=files)) files, = irods.glob_wildcards('home/rods/{files}) rule all: input: irods.remote('home/rods/testfile.out'), rule gen: input: irods.remote('home/rods/testfile.in') output: irods.remote('home/rods/testfile.out') shell: r""" touch {output} """ An example for the iRODS configuration file (``irods_environment.json``): .. code-block:: json { "irods_host": "localhost", "irods_port": 1247, "irods_user_name": "rods", "irods_zone_name": "tempZone", "irods_authentication_file": "setup-data/.irodsA" } Please note that the ``zone`` folder is not included in the path as it will be taken from the configuration file. The path also must not start with a ``/``. By default, temporarily stored local files are removed. You can specify anyway the parameter ``overwrite`` to tell iRODS to overwrite existing files that are downloaded, because iRODS complains if a local file already exists when a download attempt is issued (uploading is not a problem, though). In the Snakemake source directory in ``snakemake/tests/test_remote_irods`` you can find a working example. EGA === The European Genome-phenome Archive (EGA) is a service for permanent archiving and sharing of all types of personally identifiable genetic and phenotypic data resulting from biomedical research projects. From version 5.2 on, Snakemake provides experimental support to use EGA as a remote provider, such that EGA hosted files can be transparently used as input. For this to work, you need to define your username and password as environment variables ``EGA_USERNAME`` and ``EGA_PASSWORD``. Files in a dataset are addressed via the pattern ``ega//``. Note that the filename should not include the ``.cip`` ending that is sometimes displayed in EGA listings: .. code-block:: python import snakemake.remote.EGA as EGA ega = EGA.RemoteProvider() rule a: input: ega.remote("ega/EGAD00001002142/COLO_829_EPleasance_TGENPipe.bam.bai") output: "data/COLO_829BL_BCGSC_IlluminaPipe.bam.bai" shell: "cp {input} {output}" Upon download, Snakemake will automatically decrypt the file and check the MD5 hash. snakemake-5.10.0/docs/snakefiles/reporting.rst000066400000000000000000000102411361131222100213410ustar00rootroot00000000000000.. _snakefiles-reports: ------- Reports ------- From Snakemake 5.1 on, it is possible to automatically generate detailed self-contained HTML reports that encompass runtime statistics, provenance information, workflow topology and results. **A realistic example report from a real workflow can be found** `here `_. For including results into the report, the Snakefile has to be annotated with additional information. Each output file that shall be part of the report has to be marked with the ``report`` flag, which optionally points to a caption in `restructured text format `_ and allows to define a ``category`` for grouping purposes. Moreover, a global workflow description can be defined via the ``report`` directive. Consider the following example: .. code-block:: python report: "report/workflow.rst" rule all: input: ["fig1.svg", "fig2.png"] rule c: output: "test.{i}.out" singularity: "docker://continuumio/miniconda3:4.4.10" conda: "envs/test.yaml" shell: "sleep `shuf -i 1-3 -n 1`; touch {output}" rule a: input: expand("test.{i}.out", i=range(10)) output: report("fig1.svg", caption="report/fig1.rst", category="Step 1") shell: "sleep `shuf -i 1-3 -n 1`; cp data/fig1.svg {output}" rule b: input: expand("test.{i}.out", i=range(10)) output: report("fig2.png", caption="report/fig2.rst", category="Step 2") shell: "sleep `shuf -i 1-3 -n 1`; cp data/fig2.png {output}" As can be seen, we define a global description which is contained in the file ``report/workflow.rst``. In addition, we mark ``fig1.svg`` and ``fig2.png`` for inclusion into the report, while in both cases specifying a caption text via again referring to a restructured text file. Note the paths to the ``.rst``-files are interpreted relative to the current Snakefile. Inside the ``.rst``-files you can use `Jinja2 `_ templating to access context information. In case of the global description, you can access the config dictionary via ``{{ snakemake.config }}``, (e.g., use ``{{ snakemake.config["mykey"] }}`` to access the key ``mykey``). In case of output files, you can access the same values as available with the :ref:`script directive `. Moreover, in every ``.rst`` document, you can link to * the **Rules** section (with ``Rules_``), * the **Statistics** section (with ``Statistics_``), * the **Results** section (with ``Results_``), * any **category** section (with ``Mycategory_``, while ``Mycategory`` is the name given for the category argument of the report flag). E.g., with above example, you could write ``see `Step 2`_`` in order to link to the section with the results that have been assigned to the category ``Step 2``. * any **file** marked with the report flag (with ``myfile.txt_``, while ``myfile.txt`` is the basename of the file, without any leading directories). E.g., with above example, you could write ``see fig2.png_`` in order to link to the result in the report document. For details about the hyperlink mechanism of restructured text see `here `_. To create the report simply run .. code-block:: bash snakemake --report report.html after your workflow has finished. All other information contained in the report (e.g. runtime statistics) is automatically collected during creation. These statistics are obtained from the metadata that is stored in the ``.snakemake`` directory inside your working directory. The report for above example can be found :download:`here <../../tests/test_report/report.html>`. The full example source code can be found `here `_. Note that the report can be restricted to particular jobs and results by specifying targets at the command line, analog to normal Snakemake execution. For example, with .. code-block:: bash snakemake fig1.svg --report report-short.html the report contains only ``fig1.svg``. snakemake-5.10.0/docs/snakefiles/rules.rst000066400000000000000000001576101361131222100204760ustar00rootroot00000000000000.. _snakefiles-rules: ===== Rules ===== Most importantly, a rule can consist of a name (the name is optional and can be left out, creating an anonymous rule), input files, output files, and a shell command to generate the output from the input, i.e. .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" shell: "somecommand {input} {output}" Inside the shell command, all local and global variables, especially input and output files can be accessed via their names in the `python format minilanguage `_. Here input and output (and in general any list or tuple) automatically evaluate to a space-separated list of files (i.e. ``path/to/inputfile path/to/other/inputfile``). From Snakemake 3.8.0 on, adding the special formatting instruction ``:q`` (e.g. ``"somecommand {input:q} {output:q}")``) will let Snakemake quote each of the list or tuple elements that contains whitespace. Instead of a shell command, a rule can run some python code to generate the output: .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", somename = "path/to/another/outputfile" run: for f in input: ... with open(output[0], "w") as out: out.write(...) with open(output.somename, "w") as out: out.write(...) As can be seen, instead of accessing input and output as a whole, we can also access by index (``output[0]``) or by keyword (``output.somename``). Note that, when adding keywords or names for input or output files, their order won't be preserved when accessing them as a whole via e.g. ``{output}`` in a shell command. Shell commands like above can also be invoked inside a python based rule, via the function ``shell`` that takes a string with the command and allows the same formatting like in the rule above, e.g.: .. code-block:: python shell("somecommand {output.somename}") Further, this combination of python and shell commands, allows to iterate over the output of the shell command, e.g.: .. code-block:: python for line in shell("somecommand {output.somename}", iterable=True): ... # do something in python Note that shell commands in Snakemake use the bash shell in `strict mode `_ by default. .. _snakefiles-wildcards: Wildcards --------- Usually, it is useful to generalize a rule to be applicable to a number of e.g. datasets. For this purpose, wildcards can be used. Automatically resolved multiple named wildcards are a key feature and strength of Snakemake in comparison to other systems. Consider the following example. .. code-block:: python rule complex_conversion: input: "{dataset}/inputfile" output: "{dataset}/file.{group}.txt" shell: "somecommand --group {wildcards.group} < {input} > {output}" Here, we define two wildcards, ``dataset`` and ``group``. By this, the rule can produce all files that follow the regular expression pattern ``.+/file\..+\.txt``, i.e. the wildcards are replaced by the regular expression ``.+``. If the rule's output matches a requested file, the substrings matched by the wildcards are propagated to the input files and to the variable wildcards, that is here also used in the shell command. The wildcards object can be accessed in the same way as input and output, which is described above. For example, if another rule in the workflow requires the file ``101/file.A.txt``, Snakemake recognizes that this rule is able to produce it by setting ``dataset=101`` and ``group=A``. Thus, it requests file ``101/inputfile`` as input and executes the command ``somecommand --group A < 101/inputfile > 101/file.A.txt``. Of course, the input file might have to be generated by another rule with different wildcards. Importantly, the wildcard names in input and output must be named identically. Most typically, the same wildcard is present in both input and output, but it is of course also possible to have wildcards only in the output but not the input section. Multiple wildcards in one filename can cause ambiguity. Consider the pattern ``{dataset}.{group}.txt`` and assume that a file ``101.B.normal.txt`` is available. It is not clear whether ``dataset=101.B`` and ``group=normal`` or ``dataset=101`` and ``group=B.normal`` in this case. Hence wildcards can be constrained to given regular expressions. Here we could restrict the wildcard ``dataset`` to consist of digits only using ``\d+`` as the corresponding regular expression. With Snakemake 3.8.0, there are three ways to constrain wildcards. First, a wildcard can be constrained within the file pattern, by appending a regular expression separated by a comma: .. code-block:: python output: "{dataset,\d+}.{group}.txt" Second, a wildcard can be constrained within the rule via the keyword ``wildcard_constraints``: .. code-block:: python rule complex_conversion: input: "{dataset}/inputfile" output: "{dataset}/file.{group}.txt" wildcard_constraints: dataset="\d+" shell: "somecommand --group {wildcards.group} < {input} > {output}" Finally, you can also define global wildcard constraints that apply for all rules: .. code-block:: python wildcard_constraints: dataset="\d+" rule a: ... rule b: ... See the `Python documentation on regular expressions `_ for detailed information on regular expression syntax. Aggregation ----------- Input files can be Python lists, allowing to easily aggregate over parameters or samples: .. code-block:: python rule aggregate: input: ["{dataset}/a.txt".format(dataset=dataset) for dataset in DATASETS] output: "aggregated.txt" shell: ... Above expression can be simplified in two ways. The expand function ~~~~~~~~~~~~~~~~~~~ .. code-block:: python rule aggregate: input: expand("{dataset}/a.txt", dataset=DATASETS) output: "aggregated.txt" shell: ... Note that *dataset* is NOT a wildcard here because it is resolved by Snakemake due to the ``expand`` statement. The ``expand`` function thereby allows also to combine different variables, e.g. .. code-block:: python rule aggregate: input: expand("{dataset}/a.{ext}", dataset=DATASETS, ext=FORMATS) output: "aggregated.txt" shell: ... If now ``FORMATS=["txt", "csv"]`` contains a list of desired output formats then expand will automatically combine any dataset with any of these extensions. Further, the first argument can also be a list of strings. In that case, the transformation is applied to all elements of the list. E.g. .. code-block:: python expand(["{dataset}/a.{ext}", "{dataset}/b.{ext}"], dataset=DATASETS, ext=FORMATS) leads to .. code-block:: python ["ds1/a.txt", "ds1/b.txt", "ds2/a.txt", "ds2/b.txt", "ds1/a.csv", "ds1/b.csv", "ds2/a.csv", "ds2/b.csv"] Per default, ``expand`` uses the python itertools function ``product`` that yields all combinations of the provided wildcard values. However by inserting a second positional argument this can be replaced by any combinatoric function, e.g. ``zip``: .. code-block:: python expand(["{dataset}/a.{ext}", "{dataset}/b.{ext}"], zip, dataset=DATASETS, ext=FORMATS) leads to .. code-block:: python ["ds1/a.txt", "ds1/b.txt", "ds2/a.csv", "ds2/b.csv"] You can also mask a wildcard expression in expand such that it will be kept, e.g. .. code-block:: python expand("{{dataset}}/a.{ext}", ext=FORMATS) will create strings with all values for ext but starting with the wildcard ``"{dataset}"``. .. _snakefiles-multiext: The multiext function ~~~~~~~~~~~~~~~~~~~~~ ``multiext`` provides a simplified variant of ``expand`` that allows to define a set of output or input files that just differ by their extension: .. code-block:: python rule plot: input: ... output: multiext("some/plot", ".pdf", ".svg", ".png") shell: ... The effect is the same as if you would write ``expand("some/plot.{ext}", ext=[".pdf", ".svg", ".png"])``, however, using a simpler syntax. Moreover, defining output with ``multiext`` is the only way to use :ref:`between workflow caching ` for rules with multiple output files. .. _snakefiles-targets: Targets and aggregation ----------------------- By default snakemake executes the first rule in the snakefile. This gives rise to pseudo-rules at the beginning of the file that can be used to define build-targets similar to GNU Make: .. code-block:: python rule all: input: ["{dataset}/file.A.txt".format(dataset=dataset) for dataset in DATASETS] Here, for each dataset in a python list ``DATASETS`` defined before, the file ``{dataset}/file.A.txt`` is requested. In this example, Snakemake recognizes automatically that these can be created by multiple applications of the rule ``complex_conversion`` shown above. .. _snakefiles-threads: Threads ------- Further, a rule can be given a number of threads to use, i.e. .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" threads: 8 shell: "somecommand --threads {threads} {input} {output}" .. sidebar:: Note On a cluster node, Snakemake uses as many cores as available on that node. Hence, the number of threads used by a rule never exceeds the number of physically available cores on the node. Note: This behavior is not affected by ``--local-cores``, which only applies to jobs running on the master node. Snakemake can alter the number of cores available based on command line options. Therefore it is useful to propagate it via the built in variable ``threads`` rather than hardcoding it into the shell command. In particular, it should be noted that the specified threads have to be seen as a maximum. When Snakemake is executed with fewer cores, the number of threads will be adjusted, i.e. ``threads = min(threads, cores)`` with ``cores`` being the number of cores specified at the command line (option ``--cores``). Hardcoding a particular maximum number of threads like above is useful when a certain tool has a natural maximum beyond it parallelization won't help to further speed it up. This is often the case, and should be evaluated carefully for production workflows. If it is certain that no such maximum exists for a tool, one can instead define threads as a function of the number of cores given to Snakemake: .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" threads: workflow.cores * 0.75 shell: "somecommand --threads {threads} {input} {output}" The number of given cores is globally available in the Snakefile as an attribute of the workflow object: ``workflow.cores``. Any arithmetic operation can be performed to derive a number of threads from this. E.g., in the above example, we reserve 75% of the given cores for the rule. Snakemake will always round the calculated value down (while enforcing a minimum of 1 thread). Starting from version 3.7, threads can also be a callable that returns an ``int`` value. The signature of the callable should be ``callable(wildcards[, input])`` (input is an optional parameter). It is also possible to refer to a predefined variable (e.g, ``threads: threads_max``) so that the number of cores for a set of rules can be changed with one change only by altering the value of the variable ``threads_max``. .. _snakefiles-resources: Resources --------- In addition to threads, a rule can use arbitrary user-defined resources by specifying them with the resources-keyword: .. code-block:: python rule: input: ... output: ... resources: mem_mb=100 shell: "..." If limits for the resources are given via the command line, e.g. .. code-block:: console $ snakemake --resources mem_mb=100 the scheduler will ensure that the given resources are not exceeded by running jobs. If no limits are given, the resources are ignored in local execution. In cluster or cloud execution, resources are always passed to the backend, even if ``--resources`` is not specified. Apart from making Snakemake aware of hybrid-computing architectures (e.g. with a limited number of additional devices like GPUs) this allows to control scheduling in various ways, e.g. to limit IO-heavy jobs by assigning an artificial IO-resource to them and limiting it via the ``--resources`` flag. Resources must be ``int`` values. Note that you are free to choose any names for the given resources. There are two **standard resources** for memory and disk usage though: ``mem_mb`` and ``disk_mb``. When defining memory constraints, it is advised to use ``mem_mb``, because there are Some execution modes make direct use of this information (e.g., when using :ref:`Kubernetes `). Since it would be cumbersome to define them for every rule, you can set default values at the terminal or in a :ref:`profile `. This works via the command line flag ``--default-resources``, see ``snakemake --help`` for more information. If those resource definitions are mandatory for a certain execution mode, Snakemake will fail with a hint if they are missing. Any resource definitions inside a rule override what has been defined with ``--default-resources``. Resources can also be callables that return ``int`` values. The signature of the callable has to be ``callable(wildcards [, input] [, threads] [, attempt])`` (``input``, ``threads``, and ``attempt`` are optional parameters). The parameter ``attempt`` allows to adjust resources based on how often the job has been restarted (see :ref:`all_options`, option ``--restart-times``). This is handy when executing a Snakemake workflow in a cluster environment, where jobs can e.g. fail because of too limited resources. When Snakemake is executed with ``--restart-times 3``, it will try to restart a failed job 3 times before it gives up. Thereby, the parameter ``attempt`` will contain the current attempt number (starting from ``1``). This can be used to adjust the required memory as follows .. code-block:: python rule: input: ... output: ... resources: mem_mb=lambda wildcards, attempt: attempt * 100 shell: "..." Here, the first attempt will require 100 MB memory, the second attempt will require 200 MB memory and so on. When passing memory requirements to the cluster engine, you can by this automatically try out larger nodes if it turns out to be necessary. Messages -------- When executing snakemake, a short summary for each running rule is given to the console. This can be overridden by specifying a message for a rule: .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" threads: 8 message: "Executing somecommand with {threads} threads on the following files {input}." shell: "somecommand --threads {threads} {input} {output}" Note that access to wildcards is also possible via the variable ``wildcards`` (e.g, ``{wildcards.sample}``), which is the same as with shell commands. It is important to have a namespace around wildcards in order to avoid clashes with other variable names. Priorities ---------- Snakemake allows rules to specify numeric priorities: .. code-block:: python rule: input: ... output: ... priority: 50 shell: ... Per default, each rule has a priority of 0. Any rule that specifies a higher priority, will be preferred by the scheduler over all rules that are ready to execute at the same time without having at least the same priority. Furthermore, the ``--prioritize`` or ``-P`` command line flag allows to specify files (or rules) that shall be created with highest priority during the workflow execution. This means that the scheduler will assign the specified target and all its dependencies highest priority, such that the target is finished as soon as possible. The ``--dry-run`` (equivalently ``--dryrun``) or ``-n`` option allows you to see the scheduling plan including the assigned priorities. Log-Files --------- Each rule can specify a log file where information about the execution is written to: .. code-block:: python rule abc: input: "input.txt" output: "output.txt" log: "logs/abc.log" shell: "somecommand --log {log} {input} {output}" Log files can be used as input for other rules, just like any other output file. However, unlike output files, log files are not deleted upon error. This is obviously necessary in order to discover causes of errors which might become visible in the log file. The variable ``log`` can be used inside a shell command to tell the used tool to which file to write the logging information. The log file has to use the same wildcards as output files, e.g. .. code-block:: python log: "logs/abc.{dataset}.log" For programs that do not have an explicit ``log`` parameter, you may always use ``2> {log}`` to redirect standard output to a file (here, the ``log`` file) in Linux-based systems. Note that it is also supported to have multiple (named) log files being specified: .. code-block:: python rule abc: input: "input.txt" output: "output.txt" log: log1="logs/abc.log", log2="logs/xyz.log" shell: "somecommand --log {log.log1} METRICS_FILE={log.log2} {input} {output}" Non-file parameters for rules ----------------------------- Sometimes you may want to define certain parameters separately from the rule body. Snakemake provides the ``params`` keyword for this purpose: .. code-block:: python rule: input: ... params: prefix="somedir/{sample}" output: "somedir/{sample}.csv" shell: "somecommand -o {params.prefix}" The ``params`` keyword allows you to specify additional parameters depending on the wildcards values. This allows you to circumvent the need to use ``run:`` and python code for non-standard commands like in the above case. Here, the command ``somecommand`` expects the prefix of the output file instead of the actual one. The ``params`` keyword helps here since you cannot simply add the prefix as an output file (as the file won't be created, Snakemake would throw an error after execution of the rule). Furthermore, for enhanced readability and clarity, the ``params`` section is also an excellent place to name and assign parameters and variables for your subsequent command. Similar to ``input``, ``params`` can take functions as well (see :ref:`snakefiles-input_functions`), e.g. you can write .. code-block:: python rule: input: ... params: prefix=lambda wildcards, output: output[0][:-4] output: "somedir/{sample}.csv" shell: "somecommand -o {params.prefix}" to get the same effect as above. Note that in contrast to the ``input`` directive, the ``params`` directive can optionally take more arguments than only ``wildcards``, namely ``input``, ``output``, ``threads``, and ``resources``. From the Python perspective, they can be seen as optional keyword arguments without a default value. Their order does not matter, apart from the fact that ``wildcards`` has to be the first argument. In the example above, this allows you to derive the prefix name from the output file. .. _snakefiles-external_scripts: External scripts ---------------- A rule can also point to an external script instead of a shell command or inline Python code, e.g. .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" script: "scripts/script.py" The script path is always relative to the Snakefile (in contrast to the input and output file paths, which are relative to the working directory). It is recommended to put all scripts into a subfolder ``scripts`` as above. Inside the script, you have access to an object ``snakemake`` that provides access to the same objects that are available in the ``run`` and ``shell`` directives (input, output, params, wildcards, log, threads, resources, config), e.g. you can use ``snakemake.input[0]`` to access the first input file of above rule. Apart from Python scripts, this mechanism also allows you to integrate R_ and R Markdown_ scripts with Snakemake, e.g. .. _R: https://www.r-project.org .. _Markdown: http://rmarkdown.rstudio.com .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" script: "scripts/script.R" In the R script, an S4 object named ``snakemake`` analog to the Python case above is available and allows access to input and output files and other parameters. Here the syntax follows that of S4 classes with attributes that are R lists, e.g. we can access the first input file with ``snakemake@input[[1]]`` (note that the first file does not have index ``0`` here, because R starts counting from ``1``). Named input and output files can be accessed in the same way, by just providing the name instead of an index, e.g. ``snakemake@input[["myfile"]]``. Alternatively, it is possible to integrate Julia_ scripts, e.g. .. _Julia: https://julialang.org .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" script: "path/to/script.jl" In the Julia_ script, a ``snakemake`` object is available, which can be accessed similar to the Python case (see above), with the only difference that you have to index from 1 instead of 0. For technical reasons, scripts are executed in ``.snakemake/scripts``. The original script directory is available as ``scriptdir`` in the ``snakemake`` object. A convenience method, ``snakemake@source()``, acts as a wrapper for the normal R ``source()`` function, and can be used to source files relative to the original script directory. An example external Python script could look like this: .. code-block:: python def do_something(data_path, out_path, threads, myparam): # python code do_something(snakemake.input[0], snakemake.output[0], snakemake.threads, snakemake.config["myparam"]) You can use the Python debugger from within the script if you invoke Snakemake with ``--debug``. An equivalent script written in R would look like this: .. code-block:: r do_something <- function(data_path, out_path, threads, myparam) { # R code } do_something(snakemake@input[[1]], snakemake@output[[1]], snakemake@threads, snakemake@config[["myparam"]]) To debug R scripts, you can save the workspace with ``save.image()``, and invoke R after Snakemake has terminated. Then you can use the usual R debugging facilities while having access to the ``snakemake`` variable. It is best practice to wrap the actual code into a separate function. This increases the portability if the code shall be invoked outside of Snakemake or from a different rule. An R Markdown file can be integrated in the same way as R and Python scripts, but only a single output (html) file can be used: .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/report.html", script: "path/to/report.Rmd" In the R Markdown file you can insert output from a R command, and access variables stored in the S4 object named ``snakemake`` .. code-block:: R --- title: "Test Report" author: - "Your Name" date: "`r format(Sys.time(), '%d %B, %Y')`" params: rmd: "report.Rmd" output: html_document: highlight: tango number_sections: no theme: default toc: yes toc_depth: 3 toc_float: collapsed: no smooth_scroll: yes --- ## R Markdown This is an R Markdown document. Test include from snakemake `r snakemake@input`. ## Source R Markdown source file (to produce this document) A link to the R Markdown document with the snakemake object can be inserted. Therefore a variable called ``rmd`` needs to be added to the ``params`` section in the header of the ``report.Rmd`` file. The generated R Markdown file with snakemake object will be saved in the file specified in this ``rmd`` variable. This file can be embedded into the HTML document using base64 encoding and a link can be inserted as shown in the example above. Also other input and output files can be embedded in this way to make a portable report. Note that the above method with a data URI only works for small files. An experimental technology to embed larger files is using Javascript Blob `object `_. .. _snakefiles_notebook-integration: Jupyter notebook integration ---------------------------- Instead of plain scripts (see above), one can integrate Jupyter_ Notebooks. This enables the interactive development of data analysis components (e.g. for plotting). Integration works as follows (note the use of `notebook:` instead of `script:`): .. _Jupyter: https://jupyter.org/ .. code-block:: python rule NAME: input: "path/to/inputfile", "path/to/other/inputfile" output: "path/to/outputfile", "path/to/another/outputfile" log: # optional path to the processed notebook notebook = "logs/notebooks/processed_notebook.ipynb" notebook: "notebooks/notebook.ipynb" .. note: Consider Jupyter notebook integration as a way to get the best of both worlds. A modular, readable workflow definition with Snakemake, and the ability to quickly explore and plot data with Jupyter. The benefit will be maximal when integrating many small notebooks that each do a particular job, hence allowing to get away from large monolithic, and therefore unreadable notebooks. In the notebook, a snakemake object is available, which can be accessed in the same way as the with :ref:`script integration `. In other words, you have access to input files via ``snakemake.input`` (in the Python case) and ``snakemake@input`` (in the R case) etc.. Hence, integrating a new notebook works by first writing it from scratch in the usual interactive way. Then, you replace all hardcoded variables with references to properties of the rule where it shall be integrated, e.g. replacing the path to an input file with ``snakemake.input[0]``. Once having moved the notebook to the right place in the pipeline (ideally a subfolder ``notebooks``) and referring to it from the rule, Snakemake will be able to re-execute it while inserting the desired variable values from the rule properties. Optionally it is possible to automatically store the processed notebook. This can be achieved by adding a named logfile ``notebook=...`` to the ``log`` directive. Protected and Temporary Files ----------------------------- A particular output file may require a huge amount of computation time. Hence one might want to protect it against accidental deletion or overwriting. Snakemake allows this by marking such a file as ``protected``: .. code-block:: python rule NAME: input: "path/to/inputfile" output: protected("path/to/outputfile") shell: "somecommand {input} {output}" A protected file will be write-protected after the rule that produces it is completed. Further, an output file marked as ``temp`` is deleted after all rules that use it as an input are completed: .. code-block:: python rule NAME: input: "path/to/inputfile" output: temp("path/to/outputfile") shell: "somecommand {input} {output}" Directories as outputs ---------------------- Sometimes it can be convenient to have directories, rather than files, as outputs of a rule. As of version 5.2.0, directories as outputs have to be explicitly marked with ``directory``. This is primarily for safety reasons; since all outputs are deleted before a job is executed, we don't want to risk deleting important directories if the user makes some mistake. Marking the output as ``directory`` makes the intent clear, and the output can be safely removed. Another reason comes down to how modification time for directories work. The modification time on a directory changes when a file or a subdirectory is added, removed or renamed. This can easily happen in not-quite-intended ways, such as when Apple macOS or MS Windows add ``.DS_Store`` or ``thumbs.db`` files to store parameters for how the directory contents should be displayed. When the ``directory`` flag is used a hidden file called ``.snakemake_timestamp`` is created in the output directory, and the modification time of that file is used when determining whether the rule output is up to date or if it needs to be rerun. Always consider if you can't formulate your workflow using normal files before resorting to using ``directory()``. .. code-block:: python rule NAME: input: "path/to/inputfile" output: directory("path/to/outputdir") shell: "somecommand {input} {output}" Ignoring timestamps ------------------- For determining whether output files have to be re-created, Snakemake checks whether the file modification date (i.e. the timestamp) of any input file of the same job is newer than the timestamp of the output file. This behavior can be overridden by marking an input file as ``ancient``. The timestamp of such files is ignored and always assumed to be older than any of the output files: .. code-block:: python rule NAME: input: ancient("path/to/inputfile") output: "path/to/outputfile" shell: "somecommand {input} {output}" Here, this means that the file ``path/to/outputfile`` will not be triggered for re-creation after it has been generated once, even when the input file is modified in the future. Note that any flag that forces re-creation of files still also applies to files marked as ``ancient``. Shadow rules ------------ Shadow rules result in each execution of the rule to be run in isolated temporary directories. This "shadow" directory contains symlinks to files and directories in the current workdir. This is useful for running programs that generate lots of unused files which you don't want to manually cleanup in your snakemake workflow. It can also be useful if you want to keep your workdir clean while the program executes, or simplify your workflow by not having to worry about unique filenames for all outputs of all rules. By setting ``shadow: "shallow"``, the top level files and directories are symlinked, so that any relative paths in a subdirectory will be real paths in the filesystem. The setting ``shadow: "full"`` fully shadows the entire subdirectory structure of the current workdir. The setting ``shadow: "minimal"`` only symlinks the inputs to the rule. Once the rule successfully executes, the output file will be moved if necessary to the real path as indicated by ``output``. Typically, you will not need to modify your rule for compatibility with ``shadow``, unless you reference parent directories relative to your workdir in a rule. .. code-block:: python rule NAME: input: "path/to/inputfile" output: "path/to/outputfile" shadow: "shallow" shell: "somecommand --other_outputs other.txt {input} {output}" Shadow directories are stored one per rule execution in ``.snakemake/shadow/``, and are cleared on successful execution. Consider running with the ``--cleanup-shadow`` argument every now and then to remove any remaining shadow directories from aborted jobs. The base shadow directory can be changed with the ``--shadow-prefix`` command line argument. Flag files ---------- Sometimes it is necessary to enforce some rule execution order without real file dependencies. This can be achieved by "touching" empty files that denote that a certain task was completed. Snakemake supports this via the `touch` flag: .. code-block:: python rule all: input: "mytask.done" rule mytask: output: touch("mytask.done") shell: "mycommand ..." With the ``touch`` flag, Snakemake touches (i.e. creates or updates) the file ``mytask.done`` after ``mycommand`` has finished successfully. .. _snakefiles-job_properties: Job Properties -------------- When executing a workflow on a cluster using the ``--cluster`` parameter (see below), Snakemake creates a job script for each job to execute. This script is then invoked using the provided cluster submission command (e.g. ``qsub``). Sometimes you want to provide a custom wrapper for the cluster submission command that decides about additional parameters. As this might be based on properties of the job, Snakemake stores the job properties (e.g. rule name, threads, input files, params etc.) as JSON inside the job script. For convenience, there exists a parser function ``snakemake.utils.read_job_properties`` that can be used to access the properties. The following shows an example job submission wrapper: .. code-block:: python #!/usr/bin/env python3 import os import sys from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) # do something useful with the threads threads = job_properties[threads] # access property defined in the cluster configuration file (Snakemake >=3.6.0) job_properties["cluster"]["time"] os.system("qsub -t {threads} {script}".format(threads=threads, script=jobscript)) .. _snakefiles-dynamic_files: Dynamic Files ------------- Snakemake provides experimental support for dynamic files. Dynamic files can be used whenever one has a rule, for which the number of output files is unknown before the rule was executed. This is useful for example with certain clustering algorithms: .. code-block:: python rule cluster: input: "afile.csv" output: dynamic("{clusterid}.cluster.csv") run: ... Now the results of the rule can be used in Snakemake although it does not know how many files will be present before executing the rule `cluster`, e.g. by: .. code-block:: python rule all: input: dynamic("{clusterid}.cluster.plot.pdf") rule plot: input: "{clusterid}.cluster.csv" output: "{clusterid}.cluster.plot.pdf" run: ... Here, Snakemake determines the input files for the rule `all` after the rule `cluster` was executed, and then dynamically inserts jobs of the rule `plot` into the DAG to create the desired plots. .. note: Note that dynamic file support is still experimental. Especially, using more than one wildcard within dynamic files can introduce various problems. Before using dynamic files, think about alternative, static solutions, where you know beforehand how many output files your rule will produce. In four years and hundreds of workflows, I needed dynamic files only once. .. _snakefiles-input_functions: Functions as Input Files ------------------------ Instead of specifying strings or lists of strings as input files, snakemake can also make use of functions that return single **or** lists of input files: .. code-block:: python def myfunc(wildcards): return [... a list of input files depending on given wildcards ...] rule: input: myfunc output: "someoutput.{somewildcard}.txt" shell: "..." The function has to accept a single argument that will be the wildcards object generated from the application of the rule to create some requested output files. Note that you can also use `lambda expressions `_ instead of full function definitions. By this, rules can have entirely different input files (both in form and number) depending on the inferred wildcards. E.g. you can assign input files that appear in entirely different parts of your filesystem based on some wildcard value and a dictionary that maps the wildcard value to file paths. Note that the function will be executed when the rule is evaluated and before the workflow actually starts to execute. Further note that using a function as input overrides the default mechanism of replacing wildcards with their values inferred from the output files. You have to take care of that yourself with the given wildcards object. Finally, when implementing the input function, it is best practice to make sure that it can properly handle all possible wildcard values your rule can have. In particular, input files should not be combined with very general rules that can be applied to create almost any file: Snakemake will try to apply the rule, and will report the exceptions of your input function as errors. For a practical example, see the :ref:`tutorial` (:ref:`tutorial-input_functions`). .. _snakefiles-unpack: Input Functions and ``unpack()`` -------------------------------- In some cases, you might want to have your input functions return named input files. This can be done by having them return ``dict()`` objects with the names as the dict keys and the file names as the dict values and using the ``unpack()`` keyword. .. code-block:: python def myfunc(wildcards): return { 'foo': '{wildcards.token}.txt'.format(wildcards=wildcards) rule: input: unpack(myfunc) output: "someoutput.{token}.txt" shell: "..." Note that ``unpack()`` only necessary for input functions returning ``dict``. While it also works for ``list``, remember that lists (and nested lists) of strings are automatically flattened. Also note that if you do not pass in a *function* into the input list but you directly *call a function* then you don't use ``unpack()`` either. Here, you can simply use Python's double-star (``**``) operator for unpacking the parameters. Note that as Snakefiles are translated into Python for execution, the same rules as for using the `star and double-star unpacking Python operators `_ apply. These restrictions do not apply when using ``unpack()``. .. code-block:: python def myfunc1(): return ['foo.txt'] def myfunc2(): return {'foo': 'nowildcards.txt'} rule: input: *myfunc1(), **myfunc2(), output: "..." shell: "..." .. _snakefiles-version_tracking: Version Tracking ---------------- Rules can specify a version that is tracked by Snakemake together with the output files. When the version changes snakemake informs you when using the flag ``--summary`` or ``--list-version-changes``. The version can be specified by the version directive, which takes a string: .. code-block:: python rule: input: ... output: ... version: "1.0" shell: ... The version can of course also be filled with the output of a shell command, e.g.: .. code-block:: python SOMECOMMAND_VERSION = subprocess.check_output("somecommand --version", shell=True) rule: version: SOMECOMMAND_VERSION Alternatively, you might want to use file modification times in case of local scripts: .. code-block:: python SOMECOMMAND_VERSION = str(os.path.getmtime("path/to/somescript")) rule: version: SOMECOMMAND_VERSION A re-run can be automated by invoking Snakemake as follows: .. code-block:: console $ snakemake -R `snakemake --list-version-changes` With the availability of the ``conda`` directive (see :ref:`integrated_package_management`) the ``version`` directive has become **obsolete** in favor of defining isolated software environments that can be automatically deployed via the conda package manager. .. _snakefiles-code_tracking: Code Tracking ------------- Snakemake tracks the code that was used to create your files. In combination with ``--summary`` or ``--list-code-changes`` this can be used to see what files may need a re-run because the implementation changed. Re-run can be automated by invoking Snakemake as follows: .. code-block:: console $ snakemake -R `snakemake --list-code-changes` .. _snakefiles-job_lifetime_handlers: Onstart, onsuccess and onerror handlers --------------------------------------- Sometimes, it is necessary to specify code that shall be executed when the workflow execution is finished (e.g. cleanup, or notification of the user). With Snakemake 3.2.1, this is possible via the ``onsuccess`` and ``onerror`` keywords: .. code-block:: python onsuccess: print("Workflow finished, no error") onerror: print("An error occurred") shell("mail -s "an error occurred" youremail@provider.com < {log}") The ``onsuccess`` handler is executed if the workflow finished without error. Else, the ``onerror`` handler is executed. In both handlers, you have access to the variable ``log``, which contains the path to a logfile with the complete Snakemake output. Snakemake 3.6.0 adds an ````onstart```` handler, that will be executed before the workflow starts. Note that dry-runs do not trigger any of the handlers. Rule dependencies ----------------- From version 2.4.8 on, rules can also refer to the output of other rules in the Snakefile, e.g.: .. code-block:: python rule a: input: "path/to/input" output: "path/to/output" shell: ... rule b: input: rules.a.output output: "path/to/output/of/b" shell: ... Importantly, be aware that referring to rule a here requires that rule a was defined above rule b in the file, since the object has to be known already. This feature also allows to resolve dependencies that are ambiguous when using filenames. Note that when the rule you refer to defines multiple output files but you want to require only a subset of those as input for another rule, you should name the output files and refer to them specifically: .. code-block:: python rule a: input: "path/to/input" output: a = "path/to/output", b = "path/to/output2" shell: ... rule b: input: rules.a.output.a output: "path/to/output/of/b" shell: ... .. _snakefiles-ambiguous-rules: Handling Ambiguous Rules ------------------------ When two rules can produce the same output file, snakemake cannot decide per default which one to use. Hence an ``AmbiguousRuleException`` is thrown. Note: ruleorder is not intended to bring rules in the correct execution order (this is solely guided by the names of input and output files you use), it only helps snakemake to decide which rule to use when multiple ones can create the same output file! The proposed strategy to deal with such ambiguity is to provide a ``ruleorder`` for the conflicting rules, e.g. .. code-block:: python ruleorder: rule1 > rule2 > rule3 Here, ``rule1`` is preferred over ``rule2`` and ``rule3``, and ``rule2`` is preferred over ``rule3``. Only if rule1 and rule2 cannot be applied (e.g. due to missing input files), rule3 is used to produce the desired output file. Alternatively, rule dependencies (see above) can also resolve ambiguities. Another (quick and dirty) possiblity is to tell snakemake to allow ambiguity via a command line option .. code-block:: console $ snakemake --allow-ambiguity such that similar to GNU Make always the first matching rule is used. Here, a warning that summarizes the decision of snakemake is provided at the terminal. Local Rules ----------- When working in a cluster environment, not all rules need to become a job that has to be submitted (e.g. downloading some file, or a target-rule like `all`, see :ref:`snakefiles-targets`). The keyword `localrules` allows to mark a rule as local, so that it is not submitted to the cluster and instead executed on the host node: .. code-block:: python localrules: all, foo rule all: input: ... rule foo: ... rule bar: ... Here, only jobs from the rule ``bar`` will be submitted to the cluster, whereas all and foo will be run locally. Note that you can use the localrules directive **multiple times**. The result will be the union of all declarations. Benchmark Rules --------------- Since version 3.1, Snakemake provides support for benchmarking the run times of rules. This can be used to create complex performance analysis pipelines. With the `benchmark` keyword, a rule can be declared to store a benchmark of its code into the specified location. E.g. the rule .. code-block:: python rule benchmark_command: input: "path/to/input.{sample}.txt" output: "path/to/output.{sample}.txt" benchmark: "benchmarks/somecommand/{sample}.tsv" shell: "somecommand {input} {output}" benchmarks the CPU and wall clock time of the command ``somecommand`` for the given output and input files. For this, the shell or run body of the rule is executed on that data, and all run times are stored into the given benchmark tsv file (which will contain a tab-separated table of run times and memory usage in MiB). Per default, Snakemake executes the job once, generating one run time. However, the benchmark file can be annotated with the desired number of repeats, e.g., .. code-block:: python rule benchmark_command: input: "path/to/input.{sample}.txt" output: "path/to/output.{sample}.txt" benchmark: repeat("benchmarks/somecommand/{sample}.tsv", 3) shell: "somecommand {input} {output}" will instruct Snakemake to run each job of this rule three times and store all measurements in the benchmark file. The resulting tsv file can be used as input for other rules, just like any other output file. .. note:: Note that benchmarking is only possible in a reliable fashion for subprocesses (thus for tasks run through the ``shell``, ``script``, and ``wrapper`` directive). In the ``run`` block, the variable ``bench_record`` is available that you can pass to ``shell()`` as ``bench_record=bench_record``. When using ``shell(..., bench_record=bench_record)``, the maximum of all measurements of all ``shell()`` calls will be used but the running time of the rule execution including any Python code. .. _snakefiles-grouping: Defining groups for execution ----------------------------- From Snakemake 5.0 on, it is possible to assign rules to groups. Such groups will be executed together in **cluster** or **cloud mode**, as a so-called **group job**, i.e., all jobs of a particular group will be submitted at once, to the same computing node. By this, queueing and execution time can be saved, in particular if one or several short-running rules are involved. When executing locally, group definitions are ignored. Groups can be defined via the ``group`` keyword, e.g., .. code-block:: python samples = [1,2,3,4,5] rule all: input: "test.out" rule a: output: "a/{sample}.out" group: "mygroup" shell: "touch {output}" rule b: input: "a/{sample}.out" output: "b/{sample}.out" group: "mygroup" shell: "touch {output}" rule c: input: expand("b/{sample}.out", sample=samples) output: "test.out" shell: "touch {output}" Here, jobs from rule ``a`` and ``b`` end up in one group ``mygroup``, whereas jobs from rule ``c`` are executed separately. Note that Snakemake always determines a **connected subgraph** with the same group id to be a **group job**. Here, this means that, e.g., the jobs creating ``a/1.out`` and ``b/1.out`` will be in one group, and the jobs creating ``a/2.out`` and ``b/2.out`` will be in a separate group. However, if we would add ``group: "mygroup"`` to rule ``c``, all jobs would end up in a single group, including the one spawned from rule ``c``, because ``c`` connects all the other jobs. Piped output ------------ From Snakemake 5.0 on, it is possible to mark output files as pipes, via the ``pipe`` flag, e.g.: .. code-block:: python rule all: input: expand("test.{i}.out", i=range(2)) rule a: output: pipe("test.{i}.txt") shell: "for i in {{0..2}}; do echo {wildcards.i} >> {output}; done" rule b: input: "test.{i}.txt" output: "test.{i}.out" shell: "grep {wildcards.i} < {input} > {output}" If an output file is marked to be a pipe, then Snakemake will first create a `named pipe `_ with the given name and then execute the creating job simultaneously with the consuming job, inside a **group job** (see above). This works in all execution modes, local, cluster, and cloud. Naturally, a pipe output may only have a single consumer. It is possible to combine explicit group definition as above with pipe outputs. Thereby, pipe jobs can live within, or (automatically) extend existing groups. However, the two jobs connected by a pipe may not exist in conflicting groups. .. _snakefiles-checkpoints: Data-dependent conditional execution ------------------------------------ From Snakemake 5.4 on, conditional reevaluation of the DAG of jobs based on the content outputs is possible. The key idea is that rules can be declared as checkpoints, e.g., .. code-block:: python checkpoint somestep: input: "samples/{sample}.txt" output: "somestep/{sample}.txt" shell: "somecommand {input} > {output}" Snakemake allows to re-evaluate the DAG after the successful execution of every job spawned from a checkpoint. For this, every checkpoint is registered by its name in a globally available ``checkpoints`` object. The ``checkpoints`` object can be accessed by :ref:`input functions `. Assuming that the checkpoint is named ``somestep`` as above, the output files for a particular job can be retrieved with .. code-block:: python checkpoints.somestep.get(sample="a").output Thereby, the ``get`` method throws ``snakemake.exceptions.IncompleteCheckpointException`` if the checkpoint has not yet been executed for these particular wildcard value(s). Inside an input function, the exception will be automatically handled by Snakemake, and leads to a re-evaluation after the checkpoint has been successfully passed. To illustrate the possibilities of this mechanism, consider the following complete example: .. code-block:: python # a target rule to define the desired final output rule all: input: "aggregated/a.txt", "aggregated/b.txt" # the checkpoint that shall trigger re-evaluation of the DAG checkpoint somestep: input: "samples/{sample}.txt" output: "somestep/{sample}.txt" shell: # simulate some output value "echo {wildcards.sample} > somestep/{wildcards.sample}.txt" # intermediate rule rule intermediate: input: "somestep/{sample}.txt" output: "post/{sample}.txt" shell: "touch {output}" # alternative intermediate rule rule alt_intermediate: input: "somestep/{sample}.txt" output: "alt/{sample}.txt" shell: "touch {output}" # input function for the rule aggregate def aggregate_input(wildcards): # decision based on content of output file # Important: use the method open() of the returned file! # This way, Snakemake is able to automatically download the file if it is generated in # a cloud environment without a shared filesystem. with checkpoints.somestep.get(sample=wildcards.sample).output[0].open() as f: if f.read().strip() == "a": return "post/{sample}.txt" else: return "alt/{sample}.txt" rule aggregate: input: aggregate_input output: "aggregated/{sample}.txt" shell: "touch {output}" As can be seen, the rule aggregate uses an input function. Inside the function, we first retrieve the output files of the checkpoint ``somestep`` with the wildcards, passing through the value of the wildcard sample. Upon execution, if the checkpoint is not yet complete, Snakemake will record ``somestep`` as a direct dependency of the rule ``aggregate``. Once ``somestep`` has finished for a given sample, the input function will automatically be re-evaluated and the method ``get`` will no longer return an exception. Instead, the output file will be opened, and depending on its contents either ``"post/{sample}.txt"`` or ``"alt/{sample}.txt"`` will be returned by the input function. This way, the DAG becomes conditional on some produced data. It is also possible to use checkpoints for cases where the output files are unknown before execution. A typical example is a clustering process with an unknown number of clusters, where each cluster shall be saved into a separate file. Consider the following example: .. code-block:: python # a target rule to define the desired final output rule all: input: "aggregated/a.txt", "aggregated/b.txt" # the checkpoint that shall trigger re-evaluation of the DAG checkpoint clustering: input: "samples/{sample}.txt" output: clusters=directory("clustering/{sample}") shell: "mkdir clustering/{wildcards.sample}; " "for i in 1 2 3; do echo $i > clustering/{wildcards.sample}/$i.txt; done" # an intermediate rule rule intermediate: input: "clustering/{sample}/{i}.txt" output: "post/{sample}/{i}.txt" shell: "cp {input} {output}" def aggregate_input(wildcards): checkpoint_output = checkpoints.clustering.get(**wildcards).output[0] return expand("post/{sample}/{i}.txt", sample=wildcards.sample, i=glob_wildcards(os.path.join(checkpoint_output, "{i}.txt")).i) # an aggregation over all produced clusters rule aggregate: input: aggregate_input output: "aggregated/{sample}.txt" shell: "cat {input} > {output}" Here, our checkpoint simulates a clustering. We pretend that the number of clusters is unknown beforehand. Hence, the checkpoint only defines an output ``directory``. The rule ``aggregate`` again uses the ``checkpoints`` object to retrieve the output of the checkpoint. This time, instead of explicitly writing .. code-block:: python checkpoints.clustering.get(sample=wildcards.sample).output[0] we use the shorthand .. code-block:: python checkpoints.clustering.get(**wildcards).output[0] which automatically unpacks the wildcards as keyword arguments (this is standard python argument unpacking). If the checkpoint has not yet been executed, accessing ``checkpoints.clustering.get(**wildcards)`` ensure that Snakemake records the checkpoint as a direct dependency of the rule ``aggregate``. Upon completion of the checkpoint, the input function is re-evaluated, and the code beyond its first line is executed. Here, we retrieve the values of the wildcard ``i`` based on all files named ``{i}.txt`` in the output directory of the checkpoint. These values are then used to expand the pattern ``"post/{sample}/{i}.txt"``, such that the rule ``intermediate`` is executed for each of the determined clusters. This mechanism can be used to replace the use of the :ref:`dynamic-flag ` which will be deprecated in Snakemake 6.0. snakemake-5.10.0/docs/snakefiles/utils.rst000066400000000000000000000003061361131222100204710ustar00rootroot00000000000000.. _snakefiles-utils: ===== Utils ===== The module ``snakemake.utils`` provides a collection of helper functions for common tasks in Snakemake workflows. Details can be found in :ref:`utils-api`. snakemake-5.10.0/docs/snakefiles/writing_snakefiles.rst000066400000000000000000000037111361131222100232230ustar00rootroot00000000000000.. _user_manual-writing_snakefiles: ================= Writing Workflows ================= In Snakemake, workflows are specified as Snakefiles. Inspired by GNU Make, a Snakefile contains rules that denote how to create output files from input files. Dependencies between rules are handled implicitly, by matching filenames of input files against output files. Thereby wildcards can be used to write general rules. .. _snakefiles-grammar: ------- Grammar ------- The Snakefile syntax obeys the following grammar, given in extended Backus-Naur form (EBNF) .. code-block:: text snakemake = statement | rule | include | workdir rule = "rule" (identifier | "") ":" ruleparams include = "include:" stringliteral workdir = "workdir:" stringliteral ni = NEWLINE INDENT ruleparams = [ni input] [ni output] [ni params] [ni message] [ni threads] [ni (run | shell)] NEWLINE snakemake input = "input" ":" parameter_list output = "output" ":" parameter_list params = "params" ":" parameter_list log = "log" ":" parameter_list benchmark = "benchmark" ":" statement message = "message" ":" stringliteral threads = "threads" ":" integer resources = "resources" ":" parameter_list version = "version" ":" statement run = "run" ":" ni statement shell = "shell" ":" stringliteral while all not defined non-terminals map to their Python equivalents. .. _snakefiles-depend_version: Depend on a Minimum Snakemake Version ------------------------------------- From Snakemake 3.2 on, if your workflow depends on a minimum Snakemake version, you can easily ensure that at least this version is installed via .. code-block:: python from snakemake.utils import min_version min_version("3.2") given that your minimum required version of Snakemake is 3.2. The statement will raise a WorkflowError (and therefore abort the workflow execution) if the version is not met. snakemake-5.10.0/docs/tutorial/000077500000000000000000000000001361131222100163175ustar00rootroot00000000000000snakemake-5.10.0/docs/tutorial/additional_features.rst000066400000000000000000000303651361131222100230660ustar00rootroot00000000000000.. tutorial-additional_features: Additional features ------------------- .. _Snakemake: https://snakemake.readthedocs.io .. _Snakemake homepage: https://snakemake.readthedocs.io .. _GNU Make: https://www.gnu.org/software/make .. _Python: http://www.python.org .. _BWA: http://bio-bwa.sourceforge.net .. _SAMtools: http://www.htslib.org .. _BCFtools: http://www.htslib.org .. _Pandas: http://pandas.pydata.org .. _Miniconda: http://conda.pydata.org/miniconda.html .. _Conda: http://conda.pydata.org .. _Bash: http://www.tldp.org/LDP/Bash-Beginners-Guide/html .. _Atom: https://atom.io .. _Anaconda: https://anaconda.org .. _Graphviz: http://www.graphviz.org .. _RestructuredText: http://docutils.sourceforge.net/rst.html .. _data URI: https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs .. _JSON: http://json.org .. _YAML: http://yaml.org .. _DRMAA: http://www.drmaa.org .. _rpy2: http://rpy.sourceforge.net .. _R: https://www.r-project.org .. _Rscript: https://stat.ethz.ch/R-manual/R-devel/library/utils/html/Rscript.html .. _PyYAML: http://pyyaml.org .. _Docutils: http://docutils.sourceforge.net .. _Bioconda: https://bioconda.github.io .. _Vagrant: https://www.vagrantup.com .. _Vagrant Documentation: https://docs.vagrantup.com .. _Blogpost: http://blog.osteel.me/posts/2015/01/25/how-to-use-vagrant-on-windows.html .. _slides: http://slides.com/johanneskoester/deck-1 In the following, we introduce some features that are beyond the scope of above example workflow. For details and even more features, see :ref:`user_manual-writing_snakefiles`, :ref:`project_info-faq` and the command line help (``snakemake --help``). Benchmarking :::::::::::: With the ``benchmark`` directive, Snakemake can be instructed to **measure the wall clock time of a job**. We activate benchmarking for the rule ``bwa_map``: .. code:: python rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: temp("mapped_reads/{sample}.bam") params: rg="@RG\tID:{sample}\tSM:{sample}" log: "logs/bwa_mem/{sample}.log" benchmark: "benchmarks/{sample}.bwa.benchmark.txt" threads: 8 shell: "(bwa mem -R '{params.rg}' -t {threads} {input} | " "samtools view -Sb - > {output}) 2> {log}" The ``benchmark`` directive takes a string that points to the file where benchmarking results shall be stored. Similar to output files, the path can contain wildcards (it must be the same wildcards as in the output files). When a job derived from the rule is executed, Snakemake will measure the wall clock time and memory usage (in MiB) and store it in the file in tab-delimited format. It is possible to repeat a benchmark multiple times in order to get a sense for the variability of the measurements. This can be done by annotating the benchmark file, e.g., with ``repeat("benchmarks/{sample}.bwa.benchmark.txt", 3)`` Snakemake can be told to run the job three times. The repeated measurements occur as subsequent lines in the tab-delimited benchmark file. Modularization :::::::::::::: In order to re-use building blocks or simply to structure large workflows, it is sometimes reasonable to **split a workflow into modules**. For this, Snakemake provides the ``include`` directive to include another Snakefile into the current one, e.g.: .. code:: python include: "path/to/other.snakefile" Alternatively, Snakemake allows to **define sub-workflows**. A sub-workflow refers to a working directory with a complete Snakemake workflow. Output files of that sub-workflow can be used in the current Snakefile. When executing, Snakemake ensures that the output files of the sub-workflow are up-to-date before executing the current workflow. This mechanism is particularly useful when you want to extend a previous analysis without modifying it. For details about sub-workflows, see the :ref:`documentation `. Exercise ........ * Put the read mapping related rules into a separate Snakefile and use the ``include`` directive to make them available in our example workflow again. .. _tutorial-conda: Automatic deployment of software dependencies ::::::::::::::::::::::::::::::::::::::::::::: In order to get a fully reproducible data analysis, it is not sufficient to be able to execute each step and document all used parameters. The used software tools and libraries have to be documented as well. In this tutorial, you have already seen how Conda_ can be used to specify an isolated software environment for a whole workflow. With Snakemake, you can go one step further and specify Conda environments per rule. This way, you can even make use of conflicting software versions (e.g. combine Python 2 with Python 3). In our example, instead of using an external environment we can specify environments per rule, e.g.: .. code:: python rule samtools_index: input: "sorted_reads/{sample}.bam" output: "sorted_reads/{sample}.bam.bai" conda: "envs/samtools.yaml" shell: "samtools index {input}" with ``envs/samtools.yaml`` defined as .. code:: yaml channels: - bioconda dependencies: - samtools =1.9 .. sidebar:: Note The conda directive does not work in combination with ``run`` blocks, because they have to share their Python environment with the surrounding snakefile. When Snakemake is executed with .. code:: console snakemake --use-conda it will automatically create required environments and activate them before a job is executed. It is best practice to specify at least the `major and minor version `_ of any packages in the environment definition. Specifying environments per rule in this way has two advantages. First, the workflow definition also documents all used software versions. Second, a workflow can be re-executed (without admin rights) on a vanilla system, without installing any prerequisites apart from Snakemake and Miniconda_. Tool wrappers ::::::::::::: In order to simplify the utilization of popular tools, Snakemake provides a repository of so-called wrappers (the `Snakemake wrapper repository `_). A wrapper is a short script that wraps (typically) a command line application and makes it directly addressable from within Snakemake. For this, Snakemake provides the ``wrapper`` directive that can be used instead of ``shell``, ``script``, or ``run``. For example, the rule ``bwa_map`` could alternatively look like this: .. code:: python rule bwa_mem: input: ref="data/genome.fa", sample=lambda wildcards: config["samples"][wildcards.sample] output: temp("mapped_reads/{sample}.bam") log: "logs/bwa_mem/{sample}.log" params: "-R '@RG\tID:{sample}\tSM:{sample}'" threads: 8 wrapper: "0.15.3/bio/bwa/mem" .. sidebar:: Note Updates to the Snakemake wrapper repository are automatically tested via `continuous integration `_. The wrapper directive expects a (partial) URL that points to a wrapper in the repository. These can be looked up in the corresponding `database `_. The first part of the URL is a Git version tag. Upon invocation, Snakemake will automatically download the requested version of the wrapper. Furthermore, in combination with ``--use-conda`` (see :ref:`tutorial-conda`), the required software will be automatically deployed before execution. Cluster execution ::::::::::::::::: By default, Snakemake executes jobs on the local machine it is invoked on. Alternatively, it can execute jobs in **distributed environments, e.g., compute clusters or batch systems**. If the nodes share a common file system, Snakemake supports three alternative execution modes. In cluster environments, compute jobs are usually submitted as shell scripts via commands like ``qsub``. Snakemake provides a **generic mode** to execute on such clusters. By invoking Snakemake with .. code:: console $ snakemake --cluster qsub --jobs 100 each job will be compiled into a shell script that is submitted with the given command (here ``qsub``). The ``--jobs`` flag limits the number of concurrently submitted jobs to 100. This basic mode assumes that the submission command returns immediately after submitting the job. Some clusters allow to run the submission command in **synchronous mode**, such that it waits until the job has been executed. In such cases, we can invoke e.g. .. code:: console $ snakemake --cluster-sync "qsub -sync yes" --jobs 100 The specified submission command can also be **decorated with additional parameters taken from the submitted job**. For example, the number of used threads can be accessed in braces similarly to the formatting of shell commands, e.g. .. code:: console $ snakemake --cluster "qsub -pe threaded {threads}" --jobs 100 Alternatively, Snakemake can use the Distributed Resource Management Application API (DRMAA_). This API provides a common interface to control various resource management systems. The **DRMAA support** can be activated by invoking Snakemake as follows: .. code:: console $ snakemake --drmaa --jobs 100 If available, **DRMAA is preferable over the generic cluster modes** because it provides better control and error handling. To support additional cluster specific parametrization, a Snakefile can be complemented by a :ref:`snakefiles-cluster_configuration` file. Using --cluster-status :::::::::::::::::::::: Sometimes you need specific detection to determine if a cluster job completed successfully, failed or is still running. Error detection with ``--cluster`` can be improved for edge cases such as timeouts and jobs exceeding memory that are silently terminated by the queueing system. This can be achieved with the ``--cluster-status`` option. This takes as input a script and passes a job id as first argument. The following (simplified) script detects the job status on a given SLURM cluster (>= 14.03.0rc1 is required for ``--parsable``). .. code:: python #!/usr/bin/env python import subprocess import sys jobid = sys.argv[1] output = str(subprocess.check_output("sacct -j %s --format State --noheader | head -1 | awk '{print $1}'" % jobid, shell=True).strip()) running_status=["PENDING", "CONFIGURING", "COMPLETING", "RUNNING", "SUSPENDED"] if "COMPLETED" in output: print("success") elif any(r in output for r in running_status): print("running") else: print("failed") To use this script call snakemake similar to below, where ``status.py`` is the script above. .. code:: console $ snakemake all --cluster "sbatch --cpus-per-task=1 --parsable" --cluster-status ./status.py Constraining wildcards :::::::::::::::::::::: Snakemake uses regular expressions to match output files to input files and determine dependencies between the jobs. Sometimes it is useful to constrain the values a wildcard can have. This can be achieved by adding a regular expression that describes the set of allowed wildcard values. For example, the wildcard ``sample`` in the output file ``"sorted_reads/{sample}.bam"`` can be constrained to only allow alphanumeric sample names as ``"sorted_reads/{sample,[A-Za-z0-9]+}.bam"``. Constraints may be defined per rule or globally using the ``wildcard_constraints`` keyword, as demonstrated in :ref:`snakefiles-wildcards`. This mechanism helps to solve two kinds of ambiguity. * It can help to avoid ambiguous rules, i.e. two or more rules that can be applied to generate the same output file. Other ways of handling ambiguous rules are described in the Section :ref:`snakefiles-ambiguous-rules`. * It can help to guide the regular expression based matching so that wildcards are assigned to the right parts of a file name. Consider the output file ``{sample}.{group}.txt`` and assume that the target file is ``A.1.normal.txt``. It is not clear whether ``dataset="A.1"`` and ``group="normal"`` or ``dataset="A"`` and ``group="1.normal"`` is the right assignment. Here, constraining the dataset wildcard by ``{sample,[A-Z]+}.{group}`` solves the problem. When dealing with ambiguous rules, it is best practice to first try to solve the ambiguity by using a proper file structure, for example, by separating the output files of different steps in different directories. snakemake-5.10.0/docs/tutorial/advanced.rst000066400000000000000000000373521361131222100206300ustar00rootroot00000000000000.. tutorial-advanced: Advanced: Decorating the example workflow ----------------------------------------- .. _Snakemake: https://snakemake.readthedocs.io .. _Snakemake homepage: https://snakemake.readthedocs.io .. _GNU Make: https://www.gnu.org/software/make .. _Python: http://www.python.org .. _BWA: http://bio-bwa.sourceforge.net .. _SAMtools: http://www.htslib.org .. _BCFtools: http://www.htslib.org .. _Pandas: http://pandas.pydata.org .. _Miniconda: http://conda.pydata.org/miniconda.html .. _Conda: http://conda.pydata.org .. _Bash: http://www.tldp.org/LDP/Bash-Beginners-Guide/html .. _Atom: https://atom.io .. _Anaconda: https://anaconda.org .. _Graphviz: http://www.graphviz.org .. _RestructuredText: http://docutils.sourceforge.net/rst.html .. _data URI: https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs .. _JSON: http://json.org .. _YAML: http://yaml.org .. _DRMAA: http://www.drmaa.org .. _rpy2: http://rpy.sourceforge.net .. _R: https://www.r-project.org .. _Rscript: https://stat.ethz.ch/R-manual/R-devel/library/utils/html/Rscript.html .. _PyYAML: http://pyyaml.org .. _Docutils: http://docutils.sourceforge.net .. _Bioconda: https://bioconda.github.io .. _Vagrant: https://www.vagrantup.com .. _Vagrant Documentation: https://docs.vagrantup.com .. _Blogpost: http://blog.osteel.me/posts/2015/01/25/how-to-use-vagrant-on-windows.html .. _slides: http://slides.com/johanneskoester/deck-1 Now that the basic concepts of Snakemake have been illustrated, we can introduce advanced topics. Step 1: Specifying the number of used threads ::::::::::::::::::::::::::::::::::::::::::::: For some tools, it is advisable to use more than one thread in order to speed up the computation. **Snakemake can be made aware of the threads a rule needs** with the ``threads`` directive. In our example workflow, it makes sense to use multiple threads for the rule ``bwa_map``: .. code:: python rule bwa_map: input: "data/genome.fa", "data/samples/{sample}.fastq" output: "mapped_reads/{sample}.bam" threads: 8 shell: "bwa mem -t {threads} {input} | samtools view -Sb - > {output}" The number of threads can be propagated to the shell command with the familiar braces notation (i.e. ``{threads}``). If no ``threads`` directive is given, a rule is assumed to need 1 thread. When a workflow is executed, **the number of threads the jobs need is considered by the Snakemake scheduler**. In particular, the scheduler ensures that the sum of the threads of all running jobs does not exceed a given number of available CPU cores. This number can be given with the ``--cores`` command line argument (per default, Snakemake uses only 1 CPU core). For example .. code:: console $ snakemake --cores 10 .. sidebar:: Note Apart from the very common thread resource, Snakemake provides a ``resources`` directive that can be used to **specify arbitrary resources**, e.g., memory usage or auxiliary computing devices like GPUs. Similar to threads, these can be considered by the scheduler when an available amount of that resource is given with the command line argument ``--resources`` (see :ref:`snakefiles-resources`). would execute the workflow with 10 cores. Since the rule ``bwa_map`` needs 8 threads, only one job of the rule can run at a time, and the Snakemake scheduler will try to saturate the remaining cores with other jobs like, e.g., ``samtools_sort``. The threads directive in a rule is interpreted as a maximum: when **less cores than threads** are provided, the number of threads a rule uses will be **reduced to the number of given cores**. Exercise ........ * With the flag ``--forceall`` you can enforce a complete re-execution of the workflow. Combine this flag with different values for ``--cores`` and examine how the scheduler selects jobs to run in parallel. Step 2: Config files :::::::::::::::::::: So far, we specified the samples to consider in a Python list within the Snakefile. However, often you want your workflow to be customizable, so that it can be easily adapted to new data. For this purpose, Snakemake provides a config file mechanism. Config files can be written in JSON_ or YAML_, and loaded with the ``configfile`` directive. In our example workflow, we add the line .. code:: python configfile: "config.yaml" to the top of the Snakefile. Snakemake will load the config file and store its contents into a globally available dictionary named ``config``. In our case, it makes sense to specify the samples in ``config.yaml`` as .. code:: yaml samples: A: data/samples/A.fastq B: data/samples/B.fastq Now, we can remove the statement defining ``SAMPLES`` from the Snakefile and change the rule ``bcftools_call`` to .. code:: python rule bcftools_call: input: fa="data/genome.fa", bam=expand("sorted_reads/{sample}.bam", sample=config["samples"]), bai=expand("sorted_reads/{sample}.bam.bai", sample=config["samples"]) output: "calls/all.vcf" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" .. _tutorial-input_functions: Step 3: Input functions ::::::::::::::::::::::: Since we have stored the path to the FASTQ files in the config file, we can also generalize the rule ``bwa_map`` to use these paths. This case is different to the rule ``bcftools_call`` we modified above. To understand this, it is important to know that Snakemake workflows are executed in three phases. * In the **initialization** phase, the workflow is parsed and all rules are instantiated. * In the **DAG** phase, the DAG of jobs is built by filling wildcards and matching input files to output files. * In the **scheduling** phase, the DAG of jobs is executed. The expand functions in the list of input files of the rule ``bcftools_call`` are executed during the initialization phase. In this phase, we don't know about jobs, wildcard values and rule dependencies. Hence, we cannot determine the FASTQ paths for rule ``bwa_map`` from the config file in this phase, because we don't even know which jobs will be generated from that rule. Instead, we need to defer the determination of input files to the DAG phase. This can be achieved by specifying an **input function** instead of a string as inside of the input directive. For the rule ``bwa_map`` this works as follows: .. code:: python rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: "mapped_reads/{sample}.bam" threads: 8 shell: "bwa mem -t {threads} {input} | samtools view -Sb - > {output}" .. sidebar:: Note Snakemake does not automatically rerun jobs when new input files are added as in the excercise below. However, you can get a list of output files that are affected by such changes with ``snakemake --list-input-changes``. To trigger a rerun, some bash magic helps: .. code:: console snakemake -n --forcerun $(snakemake --list-input-changes) Here, we use an anonymous function, also called `lambda expression `_. Any normal function would work as well. Input functions take as **single argument** a ``wildcards`` object, that allows to access the wildcards values via attributes (here ``wildcards.sample``). They have to **return a string or a list of strings**, that are interpreted as paths to input files (here, we return the path that is stored for the sample in the config file). Input functions are evaluated once the wildcard values of a job are determined. Exercise ........ * In the ``data/samples`` folder, there is an additional sample ``C.fastq``. Add that sample to the config file and see how Snakemake wants to recompute the part of the workflow belonging to the new sample, when invoking with ``snakemake -n --reason --forcerun bcftools_call``. Step 4: Rule parameters ::::::::::::::::::::::: Sometimes, shell commands are not only composed of input and output files and some static flags. In particular, it can happen that additional parameters need to be set depending on the wildcard values of the job. For this, Snakemake allows to **define arbitrary parameters** for rules with the ``params`` directive. In our workflow, it is reasonable to annotate aligned reads with so-called read groups, that contain metadata like the sample name. We modify the rule ``bwa_map`` accordingly: .. code:: python rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: "mapped_reads/{sample}.bam" params: rg=r"@RG\tID:{sample}\tSM:{sample}" threads: 8 shell: "bwa mem -R '{params.rg}' -t {threads} {input} | samtools view -Sb - > {output}" .. sidebar:: Note The ``params`` directive can also take functions like in Step 3 to defer initialization to the DAG phase. In contrast to input functions, these can optionally take additional arguments ``input``, ``output``, ``threads``, and ``resources``. Similar to input and output files, ``params`` can be accessed from the shell command the Python based ``run`` block, or the script directive (see :ref:`tutorial-script`). Exercise ........ * Variant calling can consider a lot of parameters. A particularly important one is the prior mutation rate (1e-3 per default). It is set via the flag ``-P`` of the ``bcftools call`` command. Consider making this flag configurable via adding a new key to the config file and using the ``params`` directive in the rule ``bcftools_call`` to propagate it to the shell command. Step 5: Logging ::::::::::::::: When executing a large workflow, it is usually desirable to store the output of each job persistently in files instead of just printing it to the terminal. For this purpose, Snakemake allows to **specify log files** for rules. Log files are defined via the ``log`` directive and handled similarly to output files, but they are not subject of rule matching and are not cleaned up when a job fails. We modify our rule ``bwa_map`` as follows: .. code:: python rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: "mapped_reads/{sample}.bam" params: rg=r"@RG\tID:{sample}\tSM:{sample}" log: "logs/bwa_mem/{sample}.log" threads: 8 shell: "(bwa mem -R '{params.rg}' -t {threads} {input} | " "samtools view -Sb - > {output}) 2> {log}" .. sidebar:: Note It is best practice to store all log files in a subdirectory ``logs/``, prefixed by the rule or tool name. The shell command is modified to collect STDERR output of both ``bwa`` and ``samtools`` and pipe it into the file referred by ``{log}``. Log files must contain exactly the same wildcards as the output files to avoid clashes. Exercise ........ * Add a log directive to the ``bcftools_call`` rule as well. * Time to re-run the whole workflow (remember the command line flags to force re-execution). See how log files are created for variant calling and read mapping. * The ability to track the provenance of each generated result is an important step towards reproducible analyses. Apart from the ``report`` functionality discussed before, Snakemake can summarize various provenance information for all output files of the workflow. The flag ``--summary`` prints a table associating each output file with the rule used to generate it, the creation date and optionally the version of the tool used for creation is provided. Further, the table informs about updated input files and changes to the source code of the rule after creation of the output file. Invoke Snakemake with ``--summary`` to examine the information for our example. .. _tutorial_temp-and-protected-files: Step 6: Temporary and protected files ::::::::::::::::::::::::::::::::::::: In our workflow, we create two BAM files for each sample, namely the output of the rules ``bwa_map`` and ``samtools_sort``. When not dealing with examples, the underlying data is usually huge. Hence, the resulting BAM files need a lot of disk space and their creation takes some time. Snakemake allows to **mark output files as temporary**, such that they are deleted once every consuming job has been executed, in order to save disk space. We use this mechanism for the output file of the rule ``bwa_map``: .. code:: python rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: temp("mapped_reads/{sample}.bam") params: rg=r"@RG\tID:{sample}\tSM:{sample}" log: "logs/bwa_mem/{sample}.log" threads: 8 shell: "(bwa mem -R '{params.rg}' -t {threads} {input} | " "samtools view -Sb - > {output}) 2> {log}" This results in the deletion of the BAM file once the corresponding ``samtools_sort`` job has been executed. Since the creation of BAM files via read mapping and sorting is computationally expensive, it is reasonable to **protect** the final BAM file **from accidental deletion or modification**. We modify the rule ``samtools_sort`` by marking its output file as ``protected``: .. code:: python rule samtools_sort: input: "mapped_reads/{sample}.bam" output: protected("sorted_reads/{sample}.bam") shell: "samtools sort -T sorted_reads/{wildcards.sample} " "-O bam {input} > {output}" After execution of the job, Snakemake will write-protect the output file in the filesystem, so that it can't be overwritten or deleted accidentally. Exercise ........ * Re-execute the whole workflow and observe how Snakemake handles the temporary and protected files. * Run Snakemake with the target ``mapped_reads/A.bam``. Although the file is marked as temporary, you will see that Snakemake does not delete it because it is specified as a target file. * Try to re-execute the whole workflow again with the dry-run option. You will see that it fails (as intended) because Snakemake cannot overwrite the protected output files. Summary ::::::: The final version of our workflow looks like this: .. code:: python configfile: "config.yaml" rule all: input: "plots/quals.svg" rule bwa_map: input: "data/genome.fa", lambda wildcards: config["samples"][wildcards.sample] output: temp("mapped_reads/{sample}.bam") params: rg=r"@RG\tID:{sample}\tSM:{sample}" log: "logs/bwa_mem/{sample}.log" threads: 8 shell: "(bwa mem -R '{params.rg}' -t {threads} {input} | " "samtools view -Sb - > {output}) 2> {log}" rule samtools_sort: input: "mapped_reads/{sample}.bam" output: protected("sorted_reads/{sample}.bam") shell: "samtools sort -T sorted_reads/{wildcards.sample} " "-O bam {input} > {output}" rule samtools_index: input: "sorted_reads/{sample}.bam" output: "sorted_reads/{sample}.bam.bai" shell: "samtools index {input}" rule bcftools_call: input: fa="data/genome.fa", bam=expand("sorted_reads/{sample}.bam", sample=config["samples"]), bai=expand("sorted_reads/{sample}.bam.bai", sample=config["samples"]) output: "calls/all.vcf" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" rule plot_quals: input: "calls/all.vcf" output: "plots/quals.svg" script: "scripts/plot-quals.py" snakemake-5.10.0/docs/tutorial/basics.rst000066400000000000000000000555631361131222100203330ustar00rootroot00000000000000.. _tutorial-basics: Basics: An example workflow --------------------------- .. _Snakemake: https://snakemake.readthedocs.io .. _Snakemake homepage: https://snakemake.readthedocs.io .. _GNU Make: https://www.gnu.org/software/make .. _Python: http://www.python.org .. _BWA: http://bio-bwa.sourceforge.net .. _SAMtools: http://www.htslib.org .. _BCFtools: http://www.htslib.org .. _Pandas: http://pandas.pydata.org .. _Miniconda: http://conda.pydata.org/miniconda.html .. _Conda: http://conda.pydata.org .. _Bash: http://www.tldp.org/LDP/Bash-Beginners-Guide/html .. _Atom: https://atom.io .. _Anaconda: https://anaconda.org .. _Graphviz: http://www.graphviz.org .. _RestructuredText: http://docutils.sourceforge.net/rst.html .. _data URI: https://developer.mozilla.org/en-US/docs/Web/HTTP/data_URIs .. _JSON: http://json.org .. _YAML: http://yaml.org .. _DRMAA: http://www.drmaa.org .. _rpy2: http://rpy.sourceforge.net .. _R: https://www.r-project.org .. _Rscript: https://stat.ethz.ch/R-manual/R-devel/library/utils/html/Rscript.html .. _PyYAML: http://pyyaml.org .. _Docutils: http://docutils.sourceforge.net .. _Bioconda: https://bioconda.github.io .. _Vagrant: https://www.vagrantup.com .. _Vagrant Documentation: https://docs.vagrantup.com .. _Blogpost: http://blog.osteel.me/posts/2015/01/25/how-to-use-vagrant-on-windows.html .. _slides: http://slides.com/johanneskoester/deck-1 Please make sure that you have **activated** the environment we created before, and that you have an open terminal in the working directory you have created. **A Snakemake workflow is defined by specifying rules in a Snakefile**. **Rules decompose the workflow into small steps** (e.g., the application of a single tool) by specifying how to create sets of **output files** from sets of **input files**. Snakemake automatically **determines the dependencies** between the rules by matching file names. The Snakemake language extends the Python language, adding syntactic structures for rule definition and additional controls. All added syntactic structures begin with a keyword followed by a code block that is either in the same line or indented and consisting of multiple lines. The resulting syntax resembles that of original Python constructs. In the following, we will introduce the Snakemake syntax by creating an example workflow. The workflow comes from the domain of genome analysis. It maps sequencing reads to a reference genome and call variants on the mapped reads. The tutorial does not require you to know what this is about. Nevertheless, we provide some background in the following paragraph. .. _tutorial-background: Background :::::::::: The genome of a living organism encodes its hereditary information. It serves as a blueprint for proteins, which form living cells, carry information and drive chemical reactions. Differences between populations, species, cancer cells and healthy tissue, as well as syndromes or diseases can be reflected and sometimes caused by changes in the genome. This makes the genome a major target of biological and medical research. Today, it is often analyzed with DNA sequencing, producing gigabytes of data from a single biological sample (e.g. a biopsy of some tissue). For technical reasons, DNA sequencing cuts the DNA of a sample into millions of small pieces, called **reads**. In order to recover the genome of the sample, one has to map these reads against a known **reference genome** (e.g., the human one obtained during the famous `human genome project `_). This task is called **read mapping**. Often, it is of interest where an individual genome is different from the species-wide consensus represented with the reference genome. Such differences are called **variants**. They are responsible for harmless individual differences (like eye color), but can also cause diseases like cancer. By investigating the differences between the all mapped reads and the reference sequence at one position, variants can be detected. This is a statistical challenge, because they have to be distinguished from artifacts generated by the sequencing process. Step 1: Mapping reads ::::::::::::::::::::: Our first Snakemake rule maps reads of a given sample to a given reference genome (see :ref:`tutorial-background`). For this, we will use the tool bwa_, specifically the subcommand ``bwa mem``. In the working directory, **create a new file** called ``Snakefile`` with an editor of your choice. We propose to use the Atom_ editor, since it provides out-of-the-box syntax highlighting for Snakemake. In the Snakefile, define the following rule: .. code:: python rule bwa_map: input: "data/genome.fa", "data/samples/A.fastq" output: "mapped_reads/A.bam" shell: "bwa mem {input} | samtools view -Sb - > {output}" .. sidebar:: Note A common error is to forget the comma between the input or output items. Since Python concatenates subsequent strings, this can lead to unexpected behavior. A Snakemake rule has a name (here ``bwa_map``) and a number of directives, here ``input``, ``output`` and ``shell``. The ``input`` and ``output`` directives are followed by lists of files that are expected to be used or created by the rule. In the simplest case, these are just explicit Python strings. The ``shell`` directive is followed by a Python string containing the shell command to execute. In the shell command string, we can refer to elements of the rule via braces notation (similar to the Python format function). Here, we refer to the output file by specifying ``{output}`` and to the input files by specifying ``{input}``. Since the rule has multiple input files, Snakemake will concatenate them separated by a whitespace. In other words, Snakemake will replace ``{input}`` with ``data/genome.fa data/samples/A.fastq`` before executing the command. The shell command invokes ``bwa mem`` with reference genome and reads, and pipes the output into ``samtools`` which creates a compressed `BAM `_ file containing the alignments. The output of ``samtools`` is piped into the output file defined by the rule. When a workflow is executed, Snakemake tries to generate given **target** files. Target files can be specified via the command line. By executing .. code:: console $ snakemake -np mapped_reads/A.bam in the working directory containing the Snakefile, we tell Snakemake to generate the target file ``mapped_reads/A.bam``. Since we used the ``-n`` (or ``--dry-run``) flag, Snakemake will only show the execution plan instead of actually perform the steps. The ``-p`` flag instructs Snakemake to also print the resulting shell command for illustration. To generate the target files, **Snakemake applies the rules given in the Snakefile in a top-down way**. The application of a rule to generate a set of output files is called **job**. For each input file of a job, Snakemake again (i.e. recursively) determines rules that can be applied to generate it. This yields a `directed acyclic graph (DAG) `_ of jobs where the edges represent dependencies. So far, we only have a single rule, and the DAG of jobs consists of a single node. Nevertheless, we can **execute our workflow** with .. code:: console $ snakemake mapped_reads/A.bam Note that, after completion of above command, Snakemake will not try to create ``mapped_reads/A.bam`` again, because it is already present in the file system. Snakemake **only re-runs jobs if one of the input files is newer than one of the output files or one of the input files will be updated by another job**. Step 2: Generalizing the read mapping rule :::::::::::::::::::::::::::::::::::::::::: Obviously, the rule will only work for a single sample with reads in the file ``data/samples/A.fastq``. However, Snakemake allows to **generalize rules by using named wildcards**. Simply replace the ``A`` in the second input file and in the output file with the wildcard ``{sample}``, leading to .. code:: python rule bwa_map: input: "data/genome.fa", "data/samples/{sample}.fastq" output: "mapped_reads/{sample}.bam" shell: "bwa mem {input} | samtools view -Sb - > {output}" .. sidebar:: Note Note that if a rule has multiple output files, Snakemake requires them to all have exactly the same wildcards. Otherwise, it could happen that two jobs from the same rule want to write the same file. When Snakemake determines that this rule can be applied to generate a target file by replacing the wildcard ``{sample}`` in the output file with an appropriate value, it will propagate that value to all occurrences of ``{sample}`` in the input files and thereby determine the necessary input for the resulting job. Note that you can have multiple wildcards in your file paths, however, to avoid conflicts with other jobs of the same rule, **all output files** of a rule have to **contain exactly the same wildcards**. When executing .. code:: console $ snakemake -np mapped_reads/B.bam Snakemake will determine that the rule ``bwa_map`` can be applied to generate the target file by replacing the wildcard ``{sample}`` with the value ``B``. In the output of the dry-run, you will see how the wildcard value is propagated to the input files and all filenames in the shell command. You can also **specify multiple targets**, e.g.: .. code:: console $ snakemake -np mapped_reads/A.bam mapped_reads/B.bam Some Bash_ magic can make this particularly handy. For example, you can alternatively compose our multiple targets in a single pass via .. code:: console $ snakemake -np mapped_reads/{A,B}.bam Note that this is not a special Snakemake syntax. Bash is just expanding the given path into two, one for each element of the set ``{A,B}``. In both cases, you will see that Snakemake only proposes to create the output file ``mapped_reads/B.bam``. This is because you already executed the workflow before (see the previous step) and no input file is newer than the output file ``mapped_reads/A.bam``. You can update the file modification date of the input file ``data/samples/A.fastq`` via .. code:: console $ touch data/samples/A.fastq and see how Snakemake wants to re-run the job to create the file ``mapped_reads/A.bam`` by executing .. code:: console $ snakemake -np mapped_reads/A.bam mapped_reads/B.bam Step 3: Sorting read alignments ::::::::::::::::::::::::::::::: For later steps, we need the read alignments in the BAM files to be sorted. This can be achieved with the samtools_ command. We add the following rule beneath the ``bwa_map`` rule: .. code:: python rule samtools_sort: input: "mapped_reads/{sample}.bam" output: "sorted_reads/{sample}.bam" shell: "samtools sort -T sorted_reads/{wildcards.sample} " "-O bam {input} > {output}" .. sidebar:: Note It is best practice to have subsequent steps of a workflow in separate, unique, output folders. This keeps the working directory structured. Further, such unique prefixes allow Snakemake to prune the search space for dependencies. This rule will take the input file from the ``mapped_reads`` directory and store a sorted version in the ``sorted_reads`` directory. Note that Snakemake **automatically creates missing directories** before jobs are executed. For sorting, ``samtools`` requires a prefix specified with the flag ``-T``. Here, we need the value of the wildcard ``sample``. Snakemake allows to access wildcards in the shell command via the ``wildcards`` object that has an attribute with the value for each wildcard. When issuing .. code:: console $ snakemake -np sorted_reads/B.bam you will see how Snakemake wants to run first the rule ``bwa_map`` and then the rule ``samtools_sort`` to create the desired target file: as mentioned before, the dependencies are resolved automatically by matching file names. Step 4: Indexing read alignments and visualizing the DAG of jobs :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Next, we need to use samtools_ again to index the sorted read alignments for random access. This can be done with the following rule: .. code:: python rule samtools_index: input: "sorted_reads/{sample}.bam" output: "sorted_reads/{sample}.bam.bai" shell: "samtools index {input}" .. sidebar:: Note Snakemake uses the Python format mini language to format shell commands. Sometimes you have to use braces for something else in a shell command. In that case, you have to escape them by doubling, e.g., ``ls {{A,B}}.txt``. Having three steps already, it is a good time to take a closer look at the resulting DAG of jobs. By executing .. code:: console $ snakemake --dag sorted_reads/{A,B}.bam.bai | dot -Tsvg > dag.svg we create a **visualization of the DAG** using the ``dot`` command provided by Graphviz_. For the given target files, Snakemake specifies the DAG in the dot language and pipes it into the ``dot`` command, which renders the definition into SVG format. The rendered DAG is piped into the file ``dag.svg`` and will look similar to this: .. image:: workflow/dag_index.png :align: center The DAG contains a node for each job and edges representing the dependencies. Jobs that don't need to be run because their output is up-to-date are dashed. For rules with wildcards, the value of the wildcard for the particular job is displayed in the job node. Exercise ........ * Run parts of the workflow using different targets. Recreate the DAG and see how different rules become dashed because their output is present and up-to-date. Step 5: Calling genomic variants :::::::::::::::::::::::::::::::: The next step in our workflow will aggregate the mapped reads from all samples and jointly call genomic variants on them (see :ref:`tutorial-background`). For the variant calling, we will combine the two utilities samtools_ and bcftools_. Snakemake provides a **helper function for collecting input files** that helps us to describe the aggregation in this step. With .. code:: python expand("sorted_reads/{sample}.bam", sample=SAMPLES) we obtain a list of files where the given pattern ``"sorted_reads/{sample}.bam"`` was formatted with the values in a given list of samples ``SAMPLES``, i.e. .. code:: python ["sorted_reads/A.bam", "sorted_reads/B.bam"] The function is particularly useful when the pattern contains multiple wildcards. For example, .. code:: python expand("sorted_reads/{sample}.{replicate}.bam", sample=SAMPLES, replicate=[0, 1]) would create the product of all elements of ``SAMPLES`` and the list ``[0, 1]``, yielding .. code:: python ["sorted_reads/A.0.bam", "sorted_reads/A.1.bam", "sorted_reads/B.0.bam", "sorted_reads/B.1.bam"] Here, we use only the simple case of ``expand``. We first let Snakemake know which samples we want to consider. Remember that Snakemake works top-down, it does not automatically infer this from, e.g., the fastq files in the data folder. Also remember that Snakefiles are in principle Python code enhanced by some declarative statements to define workflows. Hence, we can define the list of samples ad-hoc in plain Python at the top of the Snakefile: .. code:: python SAMPLES = ["A", "B"] Later, we will learn about more sophisticated ways like **config files**. Now, we can add the following rule to our Snakefile: .. code:: python rule bcftools_call: input: fa="data/genome.fa", bam=expand("sorted_reads/{sample}.bam", sample=SAMPLES), bai=expand("sorted_reads/{sample}.bam.bai", sample=SAMPLES) output: "calls/all.vcf" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" .. sidebar:: Note If you name input or output files like above, their order won't be preserved when referring them as ``{input}``. Further, note that named and not named (i.e., positional) input and output files can be combined, but the positional ones must come first, equivalent to Python functions with keyword arguments. With multiple input or output files, it is sometimes handy to refer them separately in the shell command. This can be done by **specifying names for input or output files** (here, e.g., ``fa=...``). The files can then be referred in the shell command via, e.g., ``{input.fa}``. For **long shell commands** like this one, it is advisable to **split the string over multiple indented lines**. Python will automatically merge it into one. Further, you will notice that the **input or output file lists can contain arbitrary Python statements**, as long as it returns a string, or a list of strings. Here, we invoke our ``expand`` function to aggregate over the aligned reads of all samples. Exercise ........ * obtain the updated DAG of jobs for the target file ``calls/all.vcf``, it should look like this: .. image:: workflow/dag_call.png :align: center .. _tutorial-script: Step 6: Using custom scripts :::::::::::::::::::::::::::: Usually, a workflow not only consists of invoking various tools, but also contains custom code to e.g. calculate summary statistics or create plots. While Snakemake also allows you to directly :ref:`write Python code inside a rule <.. _snakefiles-rules>`_, it is usually reasonable to move such logic into separate scripts. For this purpose, Snakemake offers the ``script`` directive. Add the following rule to your Snakefile: .. code:: python rule plot_quals: input: "calls/all.vcf" output: "plots/quals.svg" script: "scripts/plot-quals.py" This rule shall generate a histogram of the quality scores that have been assigned to the variant calls in the file ``calls/all.vcf``. The actual Python code to generate the plot is hidden in the script ``scripts/plot-quals.py``. Script paths are always relative to the referring Snakefile. In the script, all properties of the rule like ``input``, ``output``, ``wildcards``, etc. are available as attributes of a global ``snakemake`` object. Create the file ``scripts/plot-quals.py``, with the following content: .. code:: python import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from pysam import VariantFile quals = [record.qual for record in VariantFile(snakemake.input[0])] plt.hist(quals) plt.savefig(snakemake.output[0]) .. sidebar:: Note It is best practice to use the script directive whenever an inline code block would have more than a few lines of code. Although there are other strategies to invoke separate scripts from your workflow (e.g., invoking them via shell commands), the benefit of this is obvious: the script logic is separated from the workflow logic (and can be even shared between workflows), but **boilerplate code like the parsing of command line arguments is unnecessary**. Apart from Python scripts, it is also possible to use R scripts. In R scripts, an S4 object named ``snakemake`` analog to the Python case above is available and allows access to input and output files and other parameters. Here the syntax follows that of S4 classes with attributes that are R lists, e.g. we can access the first input file with ``snakemake@input[[1]]`` (note that the first file does not have index 0 here, because R starts counting from 1). Named input and output files can be accessed in the same way, by just providing the name instead of an index, e.g. ``snakemake@input[["myfile"]]``. For details and examples, see the :ref:`snakefiles-external_scripts` section in the Documentation. Step 7: Adding a target rule :::::::::::::::::::::::::::: So far, we always executed the workflow by specifying a target file at the command line. Apart from filenames, Snakemake **also accepts rule names as targets** if the referred rule does not have wildcards. Hence, it is possible to write target rules collecting particular subsets of the desired results or all results. Moreover, if no target is given at the command line, Snakemake will define the **first rule** of the Snakefile as the target. Hence, it is best practice to have a rule ``all`` at the top of the workflow which has all typically desired target files as input files. Here, this means that we add a rule .. code:: python rule all: input: "plots/quals.svg" to the top of our workflow. When executing Snakemake with .. code:: console $ snakemake -n .. sidebar:: Note In case you have mutliple reasonable sets of target files, you can add multiple target rules at the top of the Snakefile. While Snakemake will execute the first per default, you can target any of them via the command line (e.g., ``snakemake -n mytarget``). the execution plan for creating the file ``plots/quals.svg`` which contains and summarizes all our results will be shown. Note that, apart from Snakemake considering the first rule of the workflow as default target, **the appearance of rules in the Snakefile is arbitrary and does not influence the DAG of jobs**. Exercise ........ * Create the DAG of jobs for the complete workflow. * Execute the complete workflow and have a look at the resulting ``plots/quals.svg``. * Snakemake provides handy flags for forcing re-execution of parts of the workflow. Have a look at the command line help with ``snakemake --help`` and search for the flag ``--forcerun``. Then, use this flag to re-execute the rule ``samtools_sort`` and see what happens. * With ``--reason`` it is possible to display the execution reason for each job. Try this flag together with a dry-run and the ``--forcerun`` flag to understand the decisions of Snakemake. Summary ::::::: In total, the resulting workflow looks like this: .. code:: console SAMPLES = ["A", "B"] rule all: input: "plots/quals.svg" rule bwa_map: input: "data/genome.fa", "data/samples/{sample}.fastq" output: "mapped_reads/{sample}.bam" shell: "bwa mem {input} | samtools view -Sb - > {output}" rule samtools_sort: input: "mapped_reads/{sample}.bam" output: "sorted_reads/{sample}.bam" shell: "samtools sort -T sorted_reads/{wildcards.sample} " "-O bam {input} > {output}" rule samtools_index: input: "sorted_reads/{sample}.bam" output: "sorted_reads/{sample}.bam.bai" shell: "samtools index {input}" rule bcftools_call: input: fa="data/genome.fa", bam=expand("sorted_reads/{sample}.bam", sample=SAMPLES), bai=expand("sorted_reads/{sample}.bam.bai", sample=SAMPLES) output: "calls/all.vcf" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" rule plot_quals: input: "calls/all.vcf" output: "plots/quals.svg" script: "scripts/plot-quals.py" snakemake-5.10.0/docs/tutorial/setup.rst000066400000000000000000000134141361131222100202140ustar00rootroot00000000000000 .. _tutorial-setup: Setup ----- .. _Snakemake: http://snakemake.readthedocs.io .. _Snakemake homepage: http://snakemake.readthedocs.io .. _GNU Make: https://www.gnu.org/software/make .. _Python: http://www.python.org .. _BWA: http://bio-bwa.sourceforge.net .. _SAMtools: http://www.htslib.org .. _BCFtools: http://www.htslib.org .. _Pandas: http://pandas.pydata.org .. _Miniconda: http://conda.pydata.org/miniconda.html .. _Conda: http://conda.pydata.org .. _Bash: http://www.tldp.org/LDP/Bash-Beginners-Guide/html .. _Atom: https://atom.io .. _Graphviz: http://www.graphviz.org .. _PyYAML: http://pyyaml.org .. _Docutils: http://docutils.sourceforge.net .. _Jinja2: http://jinja.pocoo.org .. _NetworkX: https://networkx.github.io .. _Matplotlib: https://matplotlib.org .. _Pysam: https://pysam.readthedocs.io .. _Bioconda: https://bioconda.github.io .. _Vagrant: https://www.vagrantup.com .. _Vagrant Documentation: https://docs.vagrantup.com .. _Blogpost: http://blog.osteel.me/posts/2015/01/25/how-to-use-vagrant-on-windows.html Requirements :::::::::::: To go through this tutorial, you need the following software installed: * Python_ ≥3.5 * Snakemake_ 5.2.3 * BWA_ 0.7.12 * SAMtools_ 1.3.1 * Pysam_ 0.15.0 * BCFtools_ 1.3.1 * Graphviz_ 2.38.0 * Jinja2_ 2.10 * NetworkX_ 2.1 * Matplotlib_ 2.2.3 The easiest way to setup these prerequisites is to use the Miniconda_ Python 3 distribution. The tutorial assumes that you are using either Linux or MacOS X. Both Snakemake and Miniconda work also under Windows, but the Windows shell is too different to be able to provide generic examples. Setup a Linux VM with Vagrant under Windows ::::::::::::::::::::::::::::::::::::::::::: If you already use Linux or MacOS X, go on with **Step 1**. If you use Windows, you can setup a Linux virtual machine (VM) with Vagrant_. First, install Vagrant following the installation instructions in the `Vagrant Documentation`_. Then, create a reasonable new directory you want to share with your Linux VM, e.g., create a folder ``vagrant-linux`` somewhere. Open a command line prompt, and change into that directory. Here, you create a 64-bit Ubuntu Linux environment with .. code:: console > vagrant init hashicorp/precise64 > vagrant up If you decide to use a 32-bit image, you will need to download the 32-bit version of Miniconda in the next step. The contents of the ``vagrant-linux`` folder will be shared with the virtual machine that is set up by vagrant. You can log into the virtual machine via .. code:: console > vagrant ssh If this command tells you to install an SSH client, you can follow the instructions in this Blogpost_. Now, you can follow the steps of our tutorial from within your Linux VM. Step 1: Installing Miniconda 3 :::::::::::::::::::::::::::::: First, please **open a terminal** or make sure you are logged into your Vagrant Linux VM. Assuming that you have a 64-bit system, on Linux, download and install Miniconda 3 with .. code:: console $ wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh $ bash Miniconda3-latest-Linux-x86_64.sh On MacOS X, download and install with .. code:: console $ curl https://repo.continuum.io/miniconda/Miniconda3-latest-MacOSX-x86_64.sh -o Miniconda3-latest-MacOSX-x86_64.sh $ bash Miniconda3-latest-MacOSX-x86_64.sh For a 32-bit system, URLs and file names are analogous but without the ``_64``. When you are asked the question .. code:: Do you wish the installer to prepend the Miniconda3 install location to PATH ...? [yes|no] answer with **yes**. Along with a minimal Python 3 environment, Miniconda contains the package manager Conda_. After opening a **new terminal**, you can use the new ``conda`` command to install software packages and create isolated environments to, e.g., use different versions of the same package. We will later use Conda_ to create an isolated environment with all required software for this tutorial. Step 2: Preparing a working directory ::::::::::::::::::::::::::::::::::::: First, **create a new directory** ``snakemake-tutorial`` at a **reasonable place** and change into that directory in your terminal: .. code:: console $ mkdir snakemake-tutorial $ cd snakemake-tutorial If you use a Vagrant Linux VM from Windows as described above, create that directory under ``/vagrant/``, so that the contents are shared with your host system (you can then edit all files from within Windows with an editor that supports Unix line breaks). Then, **change to the newly created directory**. In this directory, we will later create an example workflow that illustrates the Snakemake syntax and execution environment. First, we download some example data on which the workflow shall be executed: .. code:: console $ wget https://github.com/snakemake/snakemake-tutorial-data/archive/v5.4.5.tar.gz $ tar -xf v5.4.5.tar.gz --strip 1 This will create a folder ``data`` and a file ``environment.yaml`` in the working directory. Step 3: Creating an environment with the required software :::::::::::::::::::::::::::::::::::::::::::::::::::::::::: The ``environment.yaml`` file can be used to install all required software into an isolated Conda environment with the name ``snakemake-tutorial`` via .. code:: console $ conda env create --name snakemake-tutorial --file environment.yaml Step 4: Activating the environment :::::::::::::::::::::::::::::::::: To activate the ``snakemake-tutorial`` environment, execute .. code:: console $ conda activate snakemake-tutorial Now you can use the installed tools. Execute .. code:: console $ snakemake --help to test this and get information about the command-line interface of Snakemake. To exit the environment, you can execute .. code:: console $ conda deactivate but **don't do that now**, since we finally want to start working with Snakemake :-). snakemake-5.10.0/docs/tutorial/short.rst000066400000000000000000000317131361131222100202150ustar00rootroot00000000000000Short tutorial ============== Here we provide a short tutorial that guides you through the main features of Snakemake. Note that this is not suited to learn Snakemake from scratch, rather to give a first impression. To really learn Snakemake (starting from something simple, and extending towards advanced features), use the main :ref:`tutorial`. This document shows all steps performed in the official `Snakemake live demo `_, such that it becomes possible to follow them at your own pace. Solutions to each step can be found at the bottom of this document. The examples presented in this tutorial come from Bioinformatics. However, Snakemake is a general-purpose workflow management system for any discipline. For an explanation of the steps you will perform here, have a look at :ref:`tutorial-background`. More thorough explanations are provided in the full :ref:`tutorial`. Prerequisites ------------- First, install Snakemake via Conda, as outlined in :ref:`conda-install`. The minimal version of Snakemake is sufficient for this demo. Second, download and unpack the test data needed for this example from `here `_, e.g., via :: mkdir snakemake-demo cd snakemake-demo wget https://github.com/snakemake/snakemake-tutorial-data/archive/v5.4.5.tar.gz tar --wildcards -xf v5.4.5.tar.gz --strip 1 "*/data" Step 1 ------ First, create an empty workflow in the current directory with: :: touch Snakefile Once a Snakefile is present, you can perform a dry run of Snakemake with: :: snakemake -n Since the Snakefile is empty, it will report that nothing has to be done. In the next steps, we will gradually fill the Snakefile with an example analysis workflow. Step 2 ------ The data folder in your working directory looks as follows: :: data ├── genome.fa ├── genome.fa.amb ├── genome.fa.ann ├── genome.fa.bwt ├── genome.fa.fai ├── genome.fa.pac ├── genome.fa.sa └── samples ├── A.fastq ├── B.fastq └── C.fastq You will create a workflow that maps the sequencing samples in the ``data/samples`` folder to the reference genome ``data/genome.fa``. Then, you will call genomic variants over the mapped samples, and create an example plot. First, create a rule called ``bwa``, with input files - ``data/genome.fa`` - ``data/samples/A.fastq`` and output file - ``mapped/A.bam`` To generate output from input, use the shell command .. code:: python "bwa mem {input} | samtools view -Sb - > {output}" Providing a shell command is not enough to run your workflow on an unprepared system. For reproducibility, you also have to provide the required software stack and define the desired version. This can be done with the `Conda package manager `__, which is directly integrated with Snakemake: add a directive ``conda: "envs/mapping.yaml"`` that points to a `Conda environment definition `__, with the following content .. code:: yaml channels: - bioconda - conda-forge dependencies: - bwa =0.7.17 - samtools =1.9 Upon execution, Snakemake will automatically create that environment, and execute the shell command within. Now, test your workflow by simulating the creation of the file ``mapped/A.bam`` via :: snakemake --use-conda -n mapped/A.bam to perform a dry-run and :: snakemake --use-conda mapped/A.bam to perform the actual execution. Step 3 ------ Now, generalize the rule ``bwa`` by replacing the concrete sample name ``A`` with a wildcard ``{sample}`` in input and output file the rule ``bwa``. This way, Snakemake can apply the rule to map any of the three available samples to the reference genome. Test this by creating the file ``mapped/B.bam``. Step 4 ------ Next, create a rule ``sort`` that sorts the obtained ``.bam`` file by genomic coordinate. The rule should have the input file - ``mapped/{sample}.bam`` and the output file - ``mapped/{sample}.sorted.bam`` and uses the shell command :: samtools sort -o {output} {input} to perform the sorting. Moreover, use the same ``conda:`` directive as for the previous rule. Test your workflow with :: snakemake --use-conda -n mapped/A.sorted.bam and :: snakemake --use-conda mapped/A.sorted.bam Step 5 ------ Now, we aggregate over all samples to perform a joint calling of genomic variants. First, we define a variable .. code:: python samples = ["A", "B", "C"] at the top of the ``Snakefile``. This serves as a definition of the samples over which we would want to aggregate. In real life, you would want to use an external sample sheet or a `config file `__ for things like this. For aggregation over many files, Snakemake provides the helper function ``expand`` (see `the docs `__). Create a rule ``call`` with input files - ``fa="data/genome.fa"`` - ``bam=expand("mapped/{sample}.sorted.bam", sample=samples)`` output file - ``"calls/all.vcf"`` and shell command :: samtools mpileup -g -f {input.fa} {input.bam} | bcftools call -mv - > {output} Further, define a new conda environment file with the following content: .. code:: yaml channels: - bioconda - conda-forge dependencies: - bcftools =1.9 - samtools =1.9 Step 6 ------ Finally, we strive to calculate some exemplary statistics. This time, we don’t use a shell command, but rather employ Snakemake’s ability to integrate with scripting languages like R and Python. First, we create a rule ``stats`` with input file - ``"calls/all.vcf"`` and output file - ``"plots/quals.svg"``. Instead of a shell command, we write .. code:: python script: "scripts/plot-quals.py" and create the corresponding script and its containing folder in our working directory with :: mkdir scripts touch scripts/plot-quals.py We open the script in the editor and add the following content .. code:: python import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from pysam import VariantFile quals = [record.qual for record in VariantFile(snakemake.input[0])] plt.hist(quals) plt.savefig(snakemake.output[0]) As you can see, instead of writing a command line parser for passing parameters like input and output files, you have direct access to the properties of the rule via a magic ``snakemake`` object, that Snakemake automatically inserts into the script before executing the rule. Finally, we have to define a conda environment for the rule, say ``envs/stats.yaml``, that provides the required Python packages to execute the script: .. code:: yaml channels: - bioconda - conda-forge dependencies: - pysam =0.15 - matplotlib =3.1 - python =3.7 Make sure to test your workflow with :: snakemake --use-conda plots/quals.svg Step 7 ------ So far, we have always specified a target file at the command line when invoking Snakemake. When no target file is specified, Snakemake tries to execute the first rule in the ``Snakefile``. We can use this property to define default target files. At the top of your ``Snakefile`` define a rule ``all``, with input files - ``"calls/all.vcf"`` - ``"plots/quals.svg"`` and neither a shell command nor output files. This rule simply serves as an indicator of what shall be collected as results. Step 8 ------ As a last step, we strive to annotate our workflow with some additional information. Automatic reports ~~~~~~~~~~~~~~~~~ Snakemake can automatically create HTML reports with :: snakemake --report report.html Such a report contains runtime statistics, a visualization of the workflow topology, used software and data provenance information. In addition, you can mark any output file generated in your workflow for inclusion into the report. It will be encoded directly into the report, such that it can be, e.g., emailed as a self-contained document. The reader (e.g., a collaborator of yours) can at any time download the enclosed results from the report for further use, e.g., in a manuscript you write together. In this example, please mark the output file ``"plots/quals.svg"`` for inclusion by replacing it with ``report("plots/quals.svg", caption="report/calling.rst")`` and adding a file ``report/calling.rst``, containing some description of the output file. This description will be presented as caption in the resulting report. Threads ~~~~~~~ The first rule ``bwa`` can in theory use multiple threads. You can make Snakemake aware of this, such that the information can be used for scheduling. Add a directive ``threads: 8`` to the rule and alter the shell command to :: bwa mem -t {threads} {input} | samtools view -Sb - > {output} This passes the threads defined in the rule as a command line argument to the ``bwa`` process. Temporary files ~~~~~~~~~~~~~~~ The output of the ``bwa`` rule becomes superfluous once the sorted version of the ``.bam`` file is generated by the rule ``sort``. Snakemake can automatically delete the superfluous output once it is not needed anymore. For this, mark the output as temporary by replacing ``"mapped/{sample}.bam"`` in the rule ``bwa`` with ``temp("mapped/{sample}.bam")``. Solutions --------- Only read this if you have a problem with one of the steps. .. _step-2-1: Step 2 ~~~~~~ The rule should look like this: .. code:: python rule bwa: input: "data/genome.fa", "data/samples/A.fastq" output: "mapped/A.bam" conda: "envs/mapping.yaml" shell: "bwa mem {input} | samtools view -Sb - > {output}" .. _step-3-1: Step 3 ~~~~~~ The rule should look like this: .. code:: python rule bwa: input: "data/genome.fa", "data/samples/{sample}.fastq" output: "mapped/{sample}.bam" conda: "envs/mapping.yaml" shell: "bwa mem {input} | samtools view -Sb - > {output}" .. _step-4-1: Step 4 ~~~~~~ The rule should look like this: .. code:: python rule sort: input: "mapped/{sample}.bam" output: "mapped/{sample}.sorted.bam" conda: "envs/mapping.yaml" shell: "samtools sort -o {output} {input}" .. _step-5-1: Step 5 ~~~~~~ The rule should look like this: .. code:: python samples = ["A", "B", "C"] rule call: input: fa="data/genome.fa", bam=expand("mapped/{sample}.sorted.bam", sample=samples) output: "calls/all.vcf" conda: "envs/calling.yaml" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" .. _step-6-1: Step 6 ~~~~~~ The rule should look like this: .. code:: python rule stats: input: "calls/all.vcf" output: "plots/quals.svg" conda: "envs/stats.yaml" script: "scripts/plot-quals.py" .. _step-7-1: Step 7 ~~~~~~ The rule should look like this: .. code:: python rule all: input: "calls/all.vcf", "plots/quals.svg" It has to appear as first rule in the ``Snakefile``. .. _step-8-1: Step 8 ~~~~~~ The complete workflow should look like this: .. code:: python samples = ["A", "B"] rule all: input: "calls/all.vcf", "plots/quals.svg" rule bwa: input: "data/genome.fa", "data/samples/{sample}.fastq" output: temp("mapped/{sample}.bam") conda: "envs/mapping.yaml" threads: 8 shell: "bwa mem -t {threads} {input} | samtools view -Sb - > {output}" rule sort: input: "mapped/{sample}.bam" output: "mapped/{sample}.sorted.bam" conda: "envs/mapping.yaml" shell: "samtools sort -o {output} {input}" rule call: input: fa="data/genome.fa", bam=expand("mapped/{sample}.sorted.bam", sample=samples) output: "calls/all.vcf" conda: "envs/calling.yaml" shell: "samtools mpileup -g -f {input.fa} {input.bam} | " "bcftools call -mv - > {output}" rule stats: input: "calls/all.vcf" output: report("plots/quals.svg", caption="report/calling.rst") conda: "envs/stats.yaml" script: "scripts/plot-quals.py" snakemake-5.10.0/docs/tutorial/tutorial.rst000066400000000000000000000041541361131222100207200ustar00rootroot00000000000000.. _tutorial: ================== Snakemake Tutorial ================== .. _Snakemake: http://snakemake.readthedocs.io .. _GNU Make: https://www.gnu.org/software/make .. _Python: http://www.python.org .. _slides: http://slides.com/johanneskoester/snakemake-tutorial .. _Conda: https://conda.io .. _Singularity: https://www.sylabs.io This tutorial introduces the text-based workflow system Snakemake_. Snakemake follows the `GNU Make`_ paradigm: workflows are defined in terms of rules that define how to create output files from input files. Dependencies between the rules are determined automatically, creating a DAG (directed acyclic graph) of jobs that can be automatically parallelized. Snakemake sets itself apart from other text-based workflow systems in the following way. Hooking into the Python interpreter, Snakemake offers a definition language that is an extension of Python_ with syntax to define rules and workflow specific properties. This allows to combine the flexibility of a plain scripting language with a pythonic workflow definition. The Python language is known to be concise yet readable and can appear almost like pseudo-code. The syntactic extensions provided by Snakemake maintain this property for the definition of the workflow. Further, Snakemake's scheduling algorithm can be constrained by priorities, provided cores and customizable resources and it provides a generic support for distributed computing (e.g., cluster or batch systems). Hence, a Snakemake workflow scales without modification from single core workstations and multi-core servers to cluster or batch systems. Finally, Snakemake integrates with the package manager Conda_ and the container engine Singularity_ such that defining the software stack becomes part of the workflow itself. The examples presented in this tutorial come from Bioinformatics. However, Snakemake is a general-purpose workflow management system for any discipline. We ensured that no bioinformatics knowledge is needed to understand the tutorial. Also have a look at the corresponding slides_. .. toctree:: :maxdepth: 2 setup basics advanced additional_features snakemake-5.10.0/environment.yml000066400000000000000000000010711361131222100166120ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - python >=3.5 - boto3 - datrie - moto >=1.0.1 - httpretty - wrapt - pyyaml - ftputil - pysftp - requests - dropbox - appdirs - pytools - docutils - psutil - pandas - nomkl - google-cloud-storage - azure-storage - azure-storage-common - ratelimiter - configargparse - appdirs - python-irodsclient - jsonschema - networkx - pygraphviz # needed by graphviz - xorg-libxrender - xorg-libxpm - gitpython - pygments - imagemagick - nbformat - toposort snakemake-5.10.0/examples/000077500000000000000000000000001361131222100153425ustar00rootroot00000000000000snakemake-5.10.0/examples/c/000077500000000000000000000000001361131222100155645ustar00rootroot00000000000000snakemake-5.10.0/examples/c/README.txt000066400000000000000000000000751361131222100172640ustar00rootroot00000000000000http://www.cs.colby.edu/maxwell/courses/tutorials/maketutor/ snakemake-5.10.0/examples/c/include/000077500000000000000000000000001361131222100172075ustar00rootroot00000000000000snakemake-5.10.0/examples/c/include/hello.h000066400000000000000000000000351361131222100204610ustar00rootroot00000000000000void myPrintHelloMake(void); snakemake-5.10.0/examples/c/src/000077500000000000000000000000001361131222100163535ustar00rootroot00000000000000snakemake-5.10.0/examples/c/src/Makefile000066400000000000000000000007451361131222100200210ustar00rootroot00000000000000 IDIR=../include ODIR=obj LDIR=../lib LIBS=-lm CC=gcc CFLAGS=-I$(IDIR) _HEADERS = hello.h HEADERS = $(patsubst %,$(IDIR)/%,$(_HEADERS)) _OBJS = hello.o hellofunc.o OBJS = $(patsubst %,$(ODIR)/%,$(_OBJS)) # build the executable from the object files hello: $(OBJS) $(CC) -o $@ $^ $(CFLAGS) # compile a single .c file to an .o file $(ODIR)/%.o: %.c $(HEADERS) $(CC) -c -o $@ $< $(CFLAGS) # clean up temporary files .PHONY: clean clean: rm -f $(ODIR)/*.o *~ core $(IDIR)/*~ snakemake-5.10.0/examples/c/src/Snakefile000066400000000000000000000013621361131222100202010ustar00rootroot00000000000000from os.path import join IDIR = '../include' ODIR = 'obj' LDIR = '../lib' LIBS = '-lm' CC = 'gcc' CFLAGS = '-I' + IDIR _HEADERS = ['hello.h'] HEADERS = [join(IDIR, hfile) for hfile in _HEADERS] _OBJS = ['hello.o', 'hellofunc.o'] OBJS = [join(ODIR, ofile) for ofile in _OBJS] rule hello: """build the executable from the object files""" output: 'hello' input: OBJS shell: "{CC} -o {output} {input} {CFLAGS} {LIBS}" rule c_to_o: """compile a single .c file to an .o file""" output: temp('{ODIR}/{name}.o') input: '{name}.c' shell: "{CC} -c -o {output} {input} {CFLAGS}" rule clean: """clean up temporary files""" shell: "rm -f *~ core {IDIR}/*~" snakemake-5.10.0/examples/c/src/dag.png000066400000000000000000000177331361131222100176270ustar00rootroot00000000000000PNG  IHDREDFbKGDIDATxyt[Wd˫ɎE@v~d@)8!CJ8L !iPʤ9sSBӁ:!츶5,"[uʶ{%=s|tu>}}ޠDNk]UB4#ȟ㠜?H\w5~kS:_ֆY-҄**nqB3%OJv-tj3mn]f3y~ 4t,^ ^ir2d35%n Ξ8:;1R_kS߹Q"{2rbȟ9p ە+TWmKZnތ fCmhկ/_꒒/Dq1pl׮د^EO8[HRh/8y$?fix'6cxQSS3g̙33<si+ -[xG&s`!?oxT~ATE !lnٳV_B\=VZ+w^,\(//5\'x"VH>rC("oFD/h?x{7+VDB.kWDVee'(|?D]FT!lK?B87:KH?t"=Ab qN/\7BBbƍǛ!He˖Chp~cpNM6!c#vQ oъ֭Æ Ѐp X~-n7L&^}U`ҥhmm6Q__Rl߾=,7oS|"S7m۶zj}'{LcO>m388j߶e1*++YFF`j=pKOOg)))f6-É\ I'1<ȟ1O ǎ]w5nwߍcǎMh^ 666GSO!TM EIoъx7m7~iۿذaÔpB[x衇`e 777GDo@DSOa͚5HLL y>|m, %77k֬oxI]L#W__o۶- [!O> #Gرc(**;{JϟG^^^۱c~o ׯGii)/^B<BL Fp0rR]>KH(W\7(WD? o?ZYv-}Y DH}#&sM (NBVVz9'V߈I ţ>Pb! Ha 7n @+P*BAƊ._D^^j5V\ ._x~;RSSn ~_x@Tkg}_EEE|uuu|ۍ#??)))0S&TLes OT 3ND6O,HG,='~UY8qݸ馛sN;?0F#._lg?kѣŝwމ7G}n|~}Mi0K.?|cn[FԽ%"'b 30ZZZ@킔A~lU~>d))3:L  ?eO=/8w{/cmc顇ۧDLЅ߶?3\rss١Cftk;6:JJJRd/f}avڵmqي+Jbgwc?rrrXrr2{'}xUرTXjjs|ev㥗0'ݝw"[!čd¥={\.RR7* ȟ cw/MM'f|‰tz{PJ ?mrO1^˗O$|DIۼgи?\!2/E]:,% gϢsT"{&w YDo~ZB V c|n7 KMtII?B9>RS{dP\.t~{'Cg䙙a) =+O> hp- q"S&HT* KNe #!^\CCh?tlDzLpACp/_]Ss (8hiVNӟ+$.ݻ' 'OaۥKoGHR1 DEh4HAš5H.ZzZP05du:opl|->|gqR)ҶoGhWW_뭷.hʂ,9J㞟9)6!? ˔l  GG`NgqH.(f@Q8fX`:~$>qDVCjR#XJhOX,@7p4^ogQfF.^Ӱ67O5|x#qP57#ȑW@eN_Z NW) ?ZtqJ%H{mpcP@Ev64\sa ?n7Ν;Wi)..Ffb #77W`D -L$ JJJ FVR!Bϕ+Wx'%%d1tTr^`bٌVT*EqqE$j3vh0 Q]h8Cqq1$PVbv477h%@H!BѠ+ C F>9$0鐑s4Za!(>PjbÌ`r4㮽[nP[ِÌL&;_iiiRSS(! @rr2v [$nBAI;A/mkgg'H8N,X@Q|A/csh5;`-I ]%Hyĩ^Q~j _`U* lX7O* lUtApB> B_V_Z $~&JEQ|a/boF7C(**G[鯺:8@NN@IMM嗟_&⋏(> G cl\b$_n>KN{\}^pi i⤴/cg/)O ht%BHLLDvv6F#?P&`2x~cnGUUrrr H$laB Ӄ\. jlddx޻B8?`nY8Z,݉0ي$9! $l6ٳqh1ѭu"z{{q97~H Ο??m!n L0Ξ=0[G$@VCN;ͭ`@ӏ Jq ?Xl qܤ?D4v^c@#l,] b=sv8;E"@.c0 # G (//4fBa&HLLDyy9_"ɰh"]:x3 Xd  $&##eeeP~?BOsRR'2HN\\JKK1A3)>N2hZl"&cT6 vXXfİ 'scg@ )Cv2r'.8hFzJ 2N4ي,@iZ) !TY:zPc,)x|BF =dX>Mp$X.NFY}@;v38 qȐ#A9Ac=Np^'Ŧb0J 噅9߈7y9pX&#"+s 4d$NjW& h]LA#N+@ m&/^g iov} 9)vfc6WXbpy83)!$؞7$ u!/u!¤px ' kXCq'H(R lUha>;`^eqYB;Jo'coQ/|ܒRmi9|+\u kXC &w}'{ r)6%aSeǡ/$_$]ŏoO_ulWwgb,d}|E`t|U 'lpHe-U;J_<Qc3CɞU{#81*AQ _`fԍt|eMŐ^O kLB>akD\$H(Zm}uN]9$~ YqgjG'"xS:KuꇒG Dt>ɀKغ_`XJT`KćNJ}A/0P$uޱAMOy?A($~QH_|yyyPXr%.\v~#%%wfaDff&v $(ȑ#8qqM7aΝgĉ'pq455p`߾}OgΜ3gPUUgy&l.`v*VoގLObN⩙pB(.wttCkk+ `ѢE .[oEcccPB8F )rF㷀KPRR L&|vl0kpB1Lnn.ZZZ\̿ollu" bǎnGuu5l2m[FFغuk,& 0vD"m݆Ŵ݋G}͛7'D?'dPnUH "F! t4U[p|_`9`15RD'$~IyJU٩FXLv3=/0Vt:Ϩ%G31na8 ̒ fCCIcvPBqYPrUi^4[K2A/0q9V':I4-bO*В/nO/S 0b*+ųBҸL~n"E@LMɞ.;^<7[m%[E-)H] oiZD#n'm_D( '?W[. lUq; %jU?PJȨtNer;}>k;!^/CT!eQ/B2؊:9&^,V`˂!#^<~g! y<^ 񋕺n int main() { // call a function in another file myPrintHello(); return(0); } snakemake-5.10.0/examples/c/src/hellofunc.c000066400000000000000000000001341361131222100204740ustar00rootroot00000000000000#include void myPrintHello(void) { printf("Hello makefiles!\n"); return; } snakemake-5.10.0/examples/cufflinks/000077500000000000000000000000001361131222100173265ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/Snakefile000066400000000000000000000033601361131222100211540ustar00rootroot00000000000000# path to track and reference TRACK = 'hg19.gtf' REF = 'hg19.fa' # sample names and classes CLASS1 = '101 102'.split() CLASS2 = '103 104'.split() SAMPLES = CLASS1 + CLASS2 # path to bam files CLASS1_BAM = expand('mapped/{sample}.bam', sample=CLASS1) CLASS2_BAM = expand('mapped/{sample}.bam', sample=CLASS2) rule all: input: 'diffexp/isoform_exp.diff', 'assembly/comparison' rule assembly: input: 'mapped/{sample}.bam' output: 'assembly/{sample}/transcripts.gtf', dir='assembly/{sample}' threads: 4 shell: 'cufflinks --num-threads {threads} -o {output.dir} ' '--frag-bias-correct {REF} {input}' rule compose_merge: input: expand('assembly/{sample}/transcripts.gtf', sample=SAMPLES) output: txt='assembly/assemblies.txt' run: with open(output.txt, 'w') as out: print(*input, sep="\n", file=out) rule merge_assemblies: input: 'assembly/assemblies.txt' output: 'assembly/merged/merged.gtf', dir='assembly/merged' shell: 'cuffmerge -o {output.dir} -s {REF} {input}' rule compare_assemblies: input: 'assembly/merged/merged.gtf' output: 'assembly/comparison/all.stats', dir='assembly/comparison' shell: 'cuffcompare -o {output.dir}all -s {REF} -r {TRACK} {input}' rule diffexp: input: class1=CLASS1_BAM, class2=CLASS2_BAM, gtf='assembly/merged/merged.gtf' output: 'diffexp/gene_exp.diff', 'diffexp/isoform_exp.diff' params: class1=",".join(CLASS1_BAM), class2=",".join(CLASS2_BAM) threads: 8 shell: 'cuffdiff --num-threads {threads} {input.gtf} {params.class1} {params.class2}' snakemake-5.10.0/examples/cufflinks/dag.png000066400000000000000000000642311361131222100205750ustar00rootroot00000000000000PNG  IHDRJUbKGD IDATxyxսhU^$vv8{Rh(y7$^JRB<\J&B! M5IUȞHٖ4Z~Kcc1@!"B!\B CЄBH,t'0V[ {Of36'@$CYNHX\K3sUհuui09plBvDYV䅅_B67ɓ®N'\fЕ\N"$#įX5k NL9?߹$6v,tټ)ʐvPX!t)r 8pcX.'bIҠz|R˙s80~}8z{.'bqR)io ]άshN_wE j5 II$ 9*kt^8.f 2 ,*fЛobveTH5HU\##p =6L ;~@啋YTBJqr@Յ/f10G_>8 7-@/ԿO9{s_!~*$_s +W/\8rls_09BbdsR+ގ_NNC%D@X` ֖=я> C Xn_ӧDJ% IW^b0r8 ?#|V?/MOGcA^P `u@~4s:c_SXB=@&::K%_s 'qJ'@߂9M$- +Wx[{;$))!zh}lBƍ$" I6G?f3읝Hrp!=a.{ P.[""$ۏ9Ei)Dqq;{` cg"K NHYԿ|i{V^9VchK/{%K1d6nCd~Q!cwByA}ꦛ&hZ&߅1}ڀ6/x{(+ pd:2d}7 W_ vG?։ dl@P Zkt4ub7_֭PVT01lԿ g.=VSß4x16e/nٖBuy.g`:u c55Aݞf/9sWo vPnwZn9kA^{ RoMCM6H7ɟ+Aߧ=_p4As}Ϟo ׿n.#|`/eg x읃=r$ۣ= ٿ_v@ʕ/Z*L!~|8Kc#צxIq1zX ƍ<'o~qжe5k@P/_n7vލE!55۶m;^@QQr9/_G⥗^BII  6l؀=aϞ=h4HKK? 9ફ_W:;; ~ ~2@&~ꀮ}8r-kK @lY@ڦ6o1wiw6I;kܴ}-Ȋ+Xee%cfw<++ocVutto}[~-{ص^Z[[ ;ٮ]؍7Țdb?Xbb"YKK W_뮻uwwnvuױ~g9clҥr˾oٵ}q&ֺcG@;WYMq&f 躩|X V~7躩ɷIlި6gl>ݬq&"&a㼼<~yδ544z=+((`Zl6|S(>;w,~z+0kjjbl6gXMtt/w .5(ۘ@JMLglL}Ưȑ\cc|ۀܿ_Ѝ7Mvހq)/}T*`b_^UUnVƖ,Yy) ~}D"х7x `lXX\\ܔ{AVZZʜN'۲e ۿ\~,~ݼ9f1}|}~Fdĉ1FOnjyr6u=ڜܿ_A)..fӟs:l```fokYii)kap vOXnUTT{26m-ASA `|Կ]l& ٌ$ǣ<-[bD^d̴lQ__ݎl޼yܹ===Ν;qwL}'/~ <䓐>cy6ɉOXo6+--eR|RyWYii)cWf~-s\lrVQQMHtv='ڜz& 1=PǞph(ۅBmcO,yT4!(qo2CR!55?Ȼヒk ٸ000<,' 'Hv$<-y\}ՈCvv6^6 h?0όkS.~>ըᅬ>(qH$s=*B|~#l߾=إZhz/#@ףEEEغukj H@tnC6ۅq€oW6ӟįѢEؽ{7^z%~ᅬ믿 P/~?x ӷ9Ν;罝pD>sG#jۼvRRR]vĉd O<G=ى<>q! b֭p0 ظq#|A:t8{,3LY۾}pTVVO=T 6ׯ[~ee%V\ |6Gqqqn8vo=ZPfq5lsۅM66df]\Lb"vnj>9m76}v%0ZluOjw挅T泷9ﴥVݠ,իQRRӧOkEjj*8Crr2t:볲J% 339󚢢"bLY[GG23357;IBBFFFHLL9o VZ |6w{.*Ovo=P1`ضm[Q@sbg.+Yf 8~ݻק͛7{Ass3N'úuNNΔߗ6DX@?aٲe8uXlk;ꪫ t65KNm>} _ϙskz>t~ yRRvڅ9mcBpO:>n6mۅs}x'ֆ6<䓸{?<>%\2mLhgy=6kͧos΂:\^hۚ P}8XԿlm~w]]]x'/~qNۘ<MX˷L 6dSۅv7yn7{XJJ KIIacӽ33n[˷7mb?kb7c c~|6ͦ;˯u3LX}=&o-_ub &X^^fd3nR C!UB̵>EdN:1gΠ?o݊ԛo6*:6 ,@f ۶m ]R& yqjlEC+V_x=6n@R,] N*텭|8A|9G__At$J(+ 6/UrHeN !\.O"nmm%(!`|"Ku3>vͰjD}pQ>/>I,>IW\p>|8e] > ^Hʠ$)_*/ v+Vi0`d$B$^z)IIރkp+7m;Կ)O ih bd*.se7)+*Zgi 7o='"}Wf嗃-2=cUVTu{Կ }ʀBuM!7/QtAlw2A&x8L\ |AfyAt\2Ĝ6}ˉw͡jԛo3?xړ3+UqcH+G qm:KS87hUωhA-N! ɶ\wO?T 8e`rw?c!b$[SpLpt$VAZbO/pNWiSP5: kK ͆я>LѣA-EE[ɕ|Br$]k $$Ggg'L&b1.RHԠ}f級7Km-==sl,CŲeut:4M599HII Er[;w.8ppI$h)([,Da{;H B\Q,8|1󰶶®d-" G޺4#\(+*fXxxxAYYYSwӿE@dBgg'}OHH@~~>222,?ZVtR+nVvv6,Y"pEѩ0O ، #N KanGAAu\.TUU2~]ryy9usP+ ]0:i*P0G6 a6AA###8s @*b>v>}\WZ6'DF^tuuM22f˃Z8Hss3zzzx:r+.uuupNN/^,pE1> (Bf3<u`n>}`u Pm`h4ˣ`Pn"|ntNAp8s cJXnd2eE4mj*$%% ]VD.9Z`QbV*ϧtwwӱl2+lΝ 77Wy&cccч:ʌ:@n7pҥPG#᦯<#J@Ru\\󑙙IA'^8ڞ? EV+:;;)]]]4,_\"v^^(̙G\N:FPP/Fuu52 u hhdt?Ʉj0 Hn:dpepi8Np5kUpt`&(cLA,˙B[[:;;P?h"+ ?d:1j:n;7QPO1*~^^Fk茗`fDBnn.s&j\< j/4=;ڞ34aEA=vtttT*VX!pEٳP؂lG h2% 1gh4JKK)pU{h;11Wm f2_dFT*E^^rrrb6b1֯_féSrq֮]Nn&@LGMbݍ j/hoo@C݀vaa! (f 朜Lt/dN(M.))AVVU Cӡ @lm8 IDATyyyJVH" 4 PUU XuYUUUp\DXvmrVKL,LAәOzf`KE:6^s`a ǮYP,HTћO/9߹27S8Gecè|DBMcz[n,UU`DJ=k4H 4tu ("CHm@F hXqW9(IT9Y.$"\ƵZ xcr6gh'!$X(ITW(-onXBvLHd'pPʼn#4?7$*iA*$vTt|/zwHGM"6$ZX$cmv}Ə(Iī7_ӵk~$u\HS0/wרMJ h331+==G.+!$0(IijyBRF Yq^moKHތD9vQۓhCM!! hB!$ Q@B!a$Ӊz PTxe6 <233Gy6ۅv8 /"r,_GK/( lذ >ٳg4 nÇc͚5P((,,ċ//sؽ{7-ZTl۶ fٯ|l8{E^^D"oߎ4dffbϞ=8u(ILzgg?Cmm-N>ӧOٳ׿8tuVp 8p}] lܸ>k:j={xg]w݅'x裏p ~پ}pTVVO=?ɓp=gS޽Auu5> }D%FHw\66k/^jjj\VTTjkk555 iZlfNyNP{ΝYlcg[VVzL޽̶=g;EEE\[[˼okgfBD_}^P(044gjBRbLq&qX,ӮsmVWW?)>cWzRoH$5dy/m8n!lBar) o46%beYsz-!ᆆILCss˲?nnnFNN΂Vuζ5k޽{m6~Y~~>5Ÿy3ymmmc*{ $P@t;Z[[144;w˶lق;vرclٲmܹ===Ν;qw-[`Q__ݎl޼yNg>۲e vV V]v-xDPh9mCT*{e};ai4|Y,~T6==LV3JŶoάV|WYii)cWf2ʘ\.g~^fTX,`*jIҀot D:M"|ATucٳgq뭷^4ԽPt D&Ν;с]v[o$Bݛ(6d-oޣ/,,aq-`!>!jx;w؁;v]!Iy$(Hhy=I4xJ_1[J^m&/C)1gNʹ^mOGH䣀&/?Ed$5z}A|$$2P@4]A&!B99R1tp$%"̌Y2h"j̓Vy}Uf<Hb8/5+!B mAMBFESwˈ1 R h5XY/ޏASH Zb+&Q#?Y//NIEG+Ӎ=G{b_^<:{D hUX>lsǴPHGӍi>l+pNJ "$v$.]ЍO\ @Л=ЛD~!шю?~ևޱٿ \F6 i@MbJM*FP5_}ȅ4S;zFbkp͢d hN7kØCr,iDˁQ(pE "7Y &1(ߏ:@yy922`B"},%B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB CЄBH&B4!( !0DM!! hB!$ Q@B!aB Cc ]!dnf3jkkrnZ,C$_/[ JB8X,)9΋yhT*0k&$$@T*BH Q@4 f:Bq4M+"4!JVϺVCT !$( P2 IIIӆtRRd2Y" 4!l!l&$Q@222浌( `*g8B"( dЄDixGMHKOOh:--M!@MHD|Hst~OBH䢿bB@FFc`ѵτD YZ -M8i:Vk+vp2 ]Z!{| .-(@,Y X˔!BFHPP@`~GWrBfi->KIQ]|RN*t94*L/\J,$*$,ĈkC!:|il͸ T!GMm*i9HIW(趋рڂOFя|z]z!N'`4xz{?ޮH8 W݀[RoR/pu$f9&9s\Y.2tl$9a9ȐH#XW$pe$ڬاۋ~G?@%QGy?BD0 hF]xI9r< +#B0Lث}:ZODq2?t4H.ot e^(cX8ʕxFWSkX]rFb4H cg<(d88~Y;WEP@3śo"veq"Up7wAF&uks{f56:[\$Cm5+"d(ID18 82/@,7n"nP݈,Y6#t \!sCM"'ƣp بH0i9166Txtc+"dn(ID2U"9.OBjHېt9B"4vfGPXFgmY99ʕk3!pED{?]D3?pCkBGM"ƈs:C!`%$I~ w$b6㊄VVUJ hAhpq3s{Q""!šHBMHQ@B!aD5 /r˗/ѣGK/ 6l@CCۍݻwcѢEHMMŶm`6}ֹw^A$ l6l߾iiiĞ={|pg[L} iiiHNN/?|0֬YBB~-}g{FAZZ~aiZfzD+ h~m:tغu+n8p. 6n܈|}ȑ#DKK z)u~8y$nuٻwFGGjjjP]]Ç|?׿uZqb8ҸmiN5Vft: 544z=+((Yg{{vX}}=yyͶιa㼼<~yδ̟9ןV[[?>w+..Y>׳2|~o M^VVRdffTήVK3&#CFkՐHdiI&D *69^G/m pyB 4($\ ``8<Րpwx0xΒ4R!dn(ID$R$ pE$\? Y \!sCM"bS& 6f,pE$\gؙ)}3T hqN" SOqxBH9a<ӦSyJZ; hqDn=op&,MxA00pp^#~kIDZ(ǖ;㹞_&pUDhmV<%s-wLT$h s|qEWEN[>ܫ&pU4hNo<SoƭA. \ ۆhߐ3EM" 4IdlecrÍx :x&z7pK4) x$jT5JRqyX@,*"91ήŹ3X%!CMmŁCɜBCB@Ipܖz;DqBCHP@Qptȗ`8tBYd):z_ڛB3TI*L|ϝ$104[ϣ ]v{ՉfIg'̞,p`N4AE!$$Ȓe(qf( rqVZ$Akr`2-B~~5VBvQWW3]XX(x8H$Byy9b1CCCWEHlb IDAT&D0KU((--0.pU hBB: 0 'v׃ZЄΟ?8K.TA,..Fbb"`xxWDHl&$D&w.((@JJUMO$aҥH<ttthBBimm;T*\ lVDH저&$ zzzx;{^KKKCnn.pGЄjEcc# ǝe2UMQQhoo Bb4!AC]]N fyyya}y:Oh`*B4!AHNNFaa-\.ollsu( .T*ҥKq=wtjj*NGDЄqg(++\.Yhs4557y|Ӈ"$&& YbGl6. hnnìc8s z{{C\Ytř3g`06ۍWFHx&1oԉ477Ϻwq C*"$'u@*ۻ6;_WWKd˲eN|K|ɵI(P.=lڞ9iS2&Д´2i=h3gЅRv-v6mn)Rh@./qݲeٺߞV[,镬gAk}<&uU0V*]hRwPv/V \?4ޙ6Й+QT)+oƘ{m@ 9W LOPRlӽ(ϮДw!AcÆݨ2c[WG^˜5o5Exһ:C ev\|ڀ#ȩsmo2$}Д?メ|^|#VmR8:ΎQ!ߖzP7BLC'rn}Pk_PcO!r.7$44]!8,a}.4܎6SOmÖH-;YB](i\m@@ }vʷ*ӷi"5qpr= e_@C#&*y&]xA \ (ȉ6((iʏG_\ou1}1iD04Xo*,j|#/\B`66kC~RZ#^Bp*}`@ӢM n܈ueH6C36}|{V[d߻Om`oDlJe-a8Z2׷i.4-J$9ǿT)zj?kjFgҾh8Y $ y(k?ܷIߦo7-JQT"eI/C|޷mWi_dϛ69ۀDP߷д(jI5([L(ס$8k'4zHj- lJ}qLvo۷Д/<WPQQe "O\~$0>}cLt W|j=ۀR;ӷ݃4?4%Ό4Taڢ`%nbc;k'Sۀd۾m +XIѨ (ʓ L.m=ə6((fPhrLoД?<{lԔ)XIv2j(Wϴ6oДPDb*d'Ӷim@};N_ߦ1n8II{ۨ-y+Sgvd%ۊ})}fc@SVG /y\|Nϲ*={,m6s|/MYrA(0)# ͆ |;߉[>}n(o%%%x{O~~gϞ|.\@(c=^z //2~ӟu8y$N>v|NVғO>/8|07s/??~ǏӧOĽW_믿q}ؾ};:?p8ƽfm{]wa޽Coo/#Ľ3=hTzN$vލo|o{oQSS|ղ x9qP~իW3gΈoW{,bχDMMM:qQWW'kvgϞWNs D>/uYF9s@ Ğ{<@ `{͵l[%V\IZJ,E-[JM I_&eAُMqwcG?~kϞ=WVA9s;wx<޽?qw>9|gױ~s=K|K_ۇ.8N߿?l׮]x衇ׇ>ev)>Znz*CܑHDb$!x;-?uP$)=db =LhܑmmKʅ~xQ(]Q2{0)wdz۲-i!nJHMX$MTNJ$efoӼДVe=E VBQy)TѐܷUm2jb V)մN (3Sr׷i~ hJȢ=u(XIFO,?<}{~!~o׷i~ hJ԰*I ws6fݴ)m1BRmgPMgmR(7}0ӟ8ˉtFӶ.FS~gN-mb[`%٣|˷WY>>3}b@ӢYը/xGB8W;x9v[oFQa8WN@7*RO$ƥA T|"ɞ}0ڕ4MIqpIxB+K=wpF~Qo @%i+ܦh]I!1iIutYJ ,%&]h0&0>A1DD(̸[Q[`s ع_yLl([&;cp!0Kp{DdoPAhշRb*8#?ҥAcÆݨ2LRȩ;ڀCclno+4Ԩ ]1>*]΂T LO_x+J()h@umXej^9WaO@D mOPG&.gPPҀ`^u+TMi1=%B˙CDp_E4\ IVmYWR;}a?gt]Bȗ}mpTD)HҼZt*Pv`@S޺p7o$I WEB|TWWN᪈2PުbLNN[قi:V+jkk-H! h[$aݺuh4^8Not:477-45^>[[[ Uiڵ8$/4=͆*@(Bkk+xiFfEQ;w|!XMM HY h" U V+jj8 JºuVs-www|t cpPCcڵ WDD 455Ş|ty^?E{ZV᪈h2X c-Q]y޹6v1XfM|8.]pES{{{sii)(0p/r)\244@AAAܩ"1`0`͚5'[[[ v9<=X `@ͣ@ )Dp9DQmfY᪈h 0v|>y+WT"ŀ&ZZƺuRɿ*]]](Bcce74Q&ip8pUejj .\ _~zw&JM+Vn~ttt(\QqW^ ɤpUDُMHѣP(//Gee4"j477G_pn[᪲[__F WD;Dl6ܡ*)BWWQb hkTUURWq 6C8睉Ā&JlF]]|tkkkV|t: ` /DtmCVfx<$( -v޹ A᪈rh(+\QM (++S*ƀ&Z"FkƝz WY=== _z>4Q );Ӊ3_Vxޙh[D"555ZӓC,g@ :Xq7G?>"Od h%zفa@_:IpUz-NyDU>4LFx+V C^lPi>oZ1 (\Qnc@-ˎ\莒y}]k%kWz8pEDM$Gȋ7]:3>mmP"}ڈJE_(IL bۭMPsK%n6/d.E!J @Kd-_HĬԈ0\D.vGHD(71ޮ/(QiQ(71 BT5*XIvif+Wŀ&JB :3W^ʿ=?zyha h$d@$]q?k:lVDيMDDDDDYMDDD9wy'l6c8"b@堻 {E__z{{QUUGyD鲈(xQ:uT`?+XrO~%%%$ EEET,"J!4Qڹs'^tvv"cll Ǽ&ZND9bd2<%Q1rЏc8pf۶mmݦtIDbH(m߾۷o} !r( 1( YS8F9Dc LwI0`Pic=d8k[1`ךbS VfmYۊM2|bH]7@>P5*\ Qnb@%A rE#!e'` <M 4Qn,\ 亠l1YO]n4T\ƀ&Jҍ数tv*\a/sP. hd1TYlTW,6!BUkgh%!J{ojQ/{S} W#Sp㶢Ze "q h%PA½͐ ASះA@KP0%a@-:C.uEOb]~'xˇ)ۈ2"}\tD)IqB[+`\pUT/~8t !!kp}U- h (~0>L] fQ jyMqhoE~yV / #J 4Q [gc4F]7Wy(L]GOc< s\z|dm:€&Jxd,Dcjl4Q3ê1dfXD 0:;3}:l5PJM&hokE4 rFRa;J֢`' h4 y'WޙE%>n'f"c@ey8 sfZ#BgF} 6g&4Q+T( iDDDYG~ъIENDB`snakemake-5.10.0/examples/cufflinks/dag.svg000066400000000000000000000172621361131222100206120ustar00rootroot00000000000000 snakemake_dag 0 assembly sample: 103 2 compose_merge 0->2 1 all 5 merge_assemblies 2->5 3 assembly sample: 102 3->2 4 assembly sample: 101 4->2 6 compare_assemblies 5->6 8 diffexp 5->8 6->1 7 assembly sample: 104 7->2 8->1 snakemake-5.10.0/examples/cufflinks/hg19.fa000066400000000000000000000000001361131222100203740ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/hg19.gtf000066400000000000000000000000001361131222100205660ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/mapped/000077500000000000000000000000001361131222100205745ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/mapped/101.bam000066400000000000000000000000001361131222100215440ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/mapped/102.bam000066400000000000000000000000001361131222100215450ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/mapped/103.bam000066400000000000000000000000001361131222100215460ustar00rootroot00000000000000snakemake-5.10.0/examples/cufflinks/mapped/104.bam000066400000000000000000000000001361131222100215470ustar00rootroot00000000000000snakemake-5.10.0/examples/hello-world/000077500000000000000000000000001361131222100175725ustar00rootroot00000000000000snakemake-5.10.0/examples/hello-world/Snakefile000066400000000000000000000015621361131222100214220ustar00rootroot00000000000000configfile: "config.yaml" rule all: input: expand("plots/{country}.hist.pdf", country=config["countries"]) rule select_by_country: input: "data/worldcitiespop.csv" output: "by-country/{country}.csv" conda: "envs/xsv.yaml" shell: "xsv search -s Country '{wildcards.country}' {input} > {output}" rule plot_histogram: input: "by-country/{country}.csv" output: "plots/{country}.hist.svg" singularity: "docker://faizanbashir/python-datascience:3.6" script: "scripts/plot-hist.py" rule convert_to_pdf: input: "{prefix}.svg" output: "{prefix}.pdf" wrapper: "0.47.0/utils/cairosvg" rule download_data: output: "data/worldcitiespop.csv" shell: "curl -L https://burntsushi.net/stuff/worldcitiespop.csv > {output}" snakemake-5.10.0/examples/hello-world/config.yaml000066400000000000000000000000401361131222100217150ustar00rootroot00000000000000countries: - fr - at - sp snakemake-5.10.0/examples/hello-world/envs/000077500000000000000000000000001361131222100205455ustar00rootroot00000000000000snakemake-5.10.0/examples/hello-world/envs/matplotlib.yaml000066400000000000000000000001321361131222100235740ustar00rootroot00000000000000channels: - conda-forge dependencies: - python 3.7 - matplotlib 3.1 - pandas 0.25 snakemake-5.10.0/examples/hello-world/envs/xsv.yaml000066400000000000000000000000651361131222100222520ustar00rootroot00000000000000channels: - conda-forge dependencies: - xsv 0.13 snakemake-5.10.0/examples/hello-world/scripts/000077500000000000000000000000001361131222100212615ustar00rootroot00000000000000snakemake-5.10.0/examples/hello-world/scripts/plot-hist.py000066400000000000000000000002511361131222100235540ustar00rootroot00000000000000import matplotlib.pyplot as plt import pandas as pd cities = pd.read_csv(snakemake.input[0]) plt.hist(cities["Population"], bins=50) plt.savefig(snakemake.output[0]) snakemake-5.10.0/examples/idea/000077500000000000000000000000001361131222100162445ustar00rootroot00000000000000snakemake-5.10.0/examples/idea/idea.pdf000066400000000000000000000505651361131222100176540ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xYێ#}W4X4#52G-%M&OɾPŐ%V*vו`}iv%=wIr'UpS̓(vۯkaW?x>ݽ7;wϷVaOTj4]kn\%Wn{v_>xNH78=ݱsV:3L+oiw~˂LvI1SmJ8 >.%W dB/$&WQ dBFDFYPQV ZJg0B\ eO&rt&2ʂ:3Q"exY+ Up*%qZLV((je0T7V\B5iz7V'& J`@M(PbUmĬ`",aDy#1k~2JZan" KR~VVf^h$#~!B/Sj&\]uBca-E[Q:Rn0\=E&BRrҼ%+QV 3:,MFY^^$]}f"")ٯ>b6wi;&IFYPg4kJoje0uU#qZajͼf&%'FZyY+L]ՋςzVZr3/kR.PӬO*GXy#1k%7V([j%f0̼Иu";gA> ;ۻIJ lE V_o&{`@pDTf->fW3-Dyn3LKf0=$KkA[IՇ?q 6];\N"g2UfLZvx$GPqk<8Bt\zw]Ϡ0+];RYwր@NˇeMX䍷$Zq B5s7 DEK-LQOb q g62mz^8!ǡBpk x\DAbsVgm|PjyvI`Ēs48dU8p>Ag55LT?kH: IB:ͼ!>ȨJHQ"p&ErJ'FEMA+ < fh?X 6y5+qcqdt QR=T( p $ :R ¢Aw,ozJ endstream endobj 4 0 obj 2185 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> /Font << /f-0-0 5 0 R /f-0-1 6 0 R /f-1-0 7 0 R >> >> endobj 8 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 1033.966553 262.272278 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 9 0 obj << /Length 10 0 R /Filter /FlateDecode /Length1 9796 >> stream xz{Wy筪G׳~xPd6lO\I]LKCqά2D&ڋzamG,Z{^%2eg,XG}.OyE՝,H1* . m5; jX2r*U,4$@d z N= Հ*+^'rog>zv&1YgؖetF8aXekkN,#:!X*s(g6,:n%}b-aDUPUk5b!%p=g8>mt9FbtvXЎlL($/ &Oa[~7-TE[^ cGl>6FX:Щtp؊`o-6_sJ%ծg_qpj6LKWJ2-{@,X]mhڋ~#VD82 X.74V-1Fݡp@@8߭~]`#ޙ?V޶=]Tbe%,cu'_]~a!jPlgolُ<'^:7yߵm@:z4pݮY}EN-9<[y1U 7{Q䍢iPBTu#0qG'ykVW'e^TN)>xy3fmO|֪\*/e$o0@DNjש[ Jbz4-@{j3XpÛN De$9"Uswx}eߣ#hC="3|%cqFt,Ys"թz>4 v2 +0 2(3Pâhic!1[05t]/)|v@s~‡Ný].փߺo[vQsJ;t2]ڊoY2M}PfdEl*#U[1"1|Zǧ7 c\W|3j cBr$bpyHJ鳏NчΌʱҐ8=Xĥ!OH<[ꚃ1?G)AGژP"&guY$hDBK b,VYJFGn;sƸ8Y })zm:.Su۲:TG{A@V쥗`Y)՞\u|-[X}󽿿w;S>{RUܲ՗GwDU; >ۧ>vh_ď7LaJ,-dJ?-2mR]LN t݃[Ԃ{dgv/: b,d>Կ>(tԭX6 V x,6Bolhl6KlK\,Q5C |8d,5y#J{x@\~=㻣Gp )Q9U`v;ψCRz5ʊ |?QG{POy\[fp c(H , i9ʳ? ctE,It2Nlz] ԆEJ+>';{9M\΃oñ1XI %6d7˯tCUmoUhȪ@G;DQ8a z^TD7ęx03 rf3'&S^q j̺+SB?ɳӕ'𾷭A1?@;PJm9>at [fwk h1^ A `  ܳp Ķr2>%P< D$45CoVX'Y  I fZX;\ɌE/GѰ6Vg>)WiWTf(YN4Yz9u9rK*(l"PΚ9U>yVEK?}\n+(ZȄROvo1T1Nu۳XP_bBӲל-P@ooQq^֯^U%bRזy54[v;6Hm!7fj+ӤUT Uq3'0 O4K GHMPh)>{X)7xA<\,lQHP0b4rš:>yve{/y79.[ųt+/МlOHE 8IZzHfs 7m͂v+H Z"IЁX ~E>H9_e! 9MVS-}S~? 먈F66]XԂe(5jxR+ SstU@6TLElZ] G؃] @yx[fs/Cd|IvBTؿ̋?Iwmv+baⱨ0}}Qeͯru;b1Wڞ@Kh=ԁ<.Z8qb~5Rbp"p4p2*t\nmoXa;6kR-R%B2pN`'HmRDD#}/wǃf2@ZDf0 775) H xxPr$ώ eK.6L9y 8ackN jxZbɔkOoUGoK[v>& )>GTo$*F0[>ѰKpϝDffk;@ KU+Qd qifBT]zxo;Dg/sJ^;S qdsQ_s РA b6g"R"̹ wLadd/YCZpB}O7ԾPyD}1ovf#fă@s>! Ͼ/ַ}:؝}흂@~O>yۏmHR:e՟'QXvr͙ͺ  fܟ:y{Hc{-;}NvHqc0H_O'ڴgw]Z2fy#0+5 v4H ,F/ 3JF V&O%J.lcFH_)@%µj|aY sszbNY7SKZ"ܜ v)( R_8Z.I9.{YT@X%Rx"p iqHµA(#Pʃ2@L7?u!D D [vgΪ+f6 PǗV_iD 70M!F}en"4f'BmWtT'A푶S^Aj 9 Nx!56 : S%iRЍ+_],V#]MG/,t6tZܻK'hrşBO1\8 QAKCs #d(ZPC#3Ŵ9bNɮoS0qwwB=G}z"|D_}O_x?P~ E32vw-hUf (@@B{w*62vڥ}Kգ6Dl4uM2aߵMWv_ Lވiw-eaSfr&G0okG㹌-/sWGyފ6[rn^?v,n#x WqHWNkf./cL/LSXѾgo,b< n PpW 7ëvܘ[a,#] v,n=2yZ$k7@ p2dRək/ϙK;36utlh!w#gX[7Ӱb5n o_述o5*ߏŶx&w}KU TVPnJN#rē-)X}nkNTn+HeRpvlb] 8 cCx@3:_j2S:wuWD_Bۘ]i*N1]4LoCSp_~ ƾѿNЫƻmx`yg>{!Y?  O;~@8 (ICO>4^i-9z[쇋ǎ&:-6gm˸Aپ^ޜ} ]GtaBQ;PF"4J endstream endobj 10 0 obj 6894 endobj 11 0 obj << /Length 12 0 R /Filter /FlateDecode >> stream x]n0 y CGIh%4u"zU'q$楱 ɇu+ 55Bъ,3=uN$!ݖ*H> vfI@ ^`}nޜ k08t{&$uۇ?θ$=\\wJaZo/?rH?kE%I##*5D&p\FF1sΙǜ[R:H5i8OIyd.I>#Ff$b"?l 1TSr=|鉹P ;xwMQm%7jzz#a!9GfGQtۯ endstream endobj 12 0 obj 352 endobj 13 0 obj << /Type /FontDescriptor /FontName /JJUGYW+Purisa /FontFamily (Purisa) /Flags 32 /FontBBox [ -630 -503 1231 1109 ] /ItalicAngle 0 /Ascent 1109 /Descent -503 /CapHeight 1109 /StemV 80 /StemH 80 /FontFile2 9 0 R >> endobj 5 0 obj << /Type /Font /Subtype /TrueType /BaseFont /JJUGYW+Purisa /FirstChar 32 /LastChar 121 /FontDescriptor 13 0 R /Encoding /WinAnsiEncoding /Widths [ 647 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 0 0 727 0 0 0 0 0 0 0 0 0 0 0 0 651 0 0 579 832 677 0 0 0 0 0 0 0 0 0 0 0 556 655 569 715 617 624 704 538 277 0 519 299 844 619 651 655 0 526 492 519 606 590 696 0 526 ] /ToUnicode 11 0 R >> endobj 14 0 obj << /Length 15 0 R /Filter /FlateDecode /Length1 1712 >> stream x]TMlG~kb?;8$^vmS; YN Ƣ 5DM~4i"@EYjU{h*j% GZrP 'U{> "(3KG?ȖO ]Ea…_~|׮"^8=W} HxR"N>{+ Rw͙:aC8[?HLg)bq'&".#~.a@!jz\GGr:49t#u?jZ5{x> 6}B]|H4K^ ME(/T5^ɳUV,8w1 <7V@戌fydg0Yq`J{C62X*bz&> stream x]j0 ~ Cqslrfhd8d7t0-$}?Yڿ2Ov >c\a)j.{Uo;öd{Q- ^\@C4:> endobj 19 0 obj << /Type /Font /Subtype /CIDFontType2 /BaseFont /ZEQRMX+Purisa /CIDSystemInfo << /Registry (Adobe) /Ordering (Identity) /Supplement 0 >> /FontDescriptor 18 0 R /W [0 [ 364 696 ]] >> endobj 6 0 obj << /Type /Font /Subtype /Type0 /BaseFont /ZEQRMX+Purisa /Encoding /Identity-H /DescendantFonts [ 19 0 R] /ToUnicode 16 0 R >> endobj 20 0 obj << /Length 21 0 R /Filter /FlateDecode /Length1 7832 >> stream x8y|EUzL&sBIBpe $`THl0(pD",E*"+#{Ip|vWzի!b$giWn&D>K0ii7cZ 9[B</o΢'=<ğ|ti|Ozδ':!){?6c~w]H/vVG:C: "Rw6Ά|aJWDҸ?e::B,BBq)Q0Ig@pXg$gB)ɤ˕ٓJ{e"SY(>⣩O? d4-V4&JKSS]ba2T8Vǻl:#bY4yȑQTF:# V @e6[ i !,UJCHq#<5[vҬȺ]s@{vҝBuӰ򵯿",aiUF)XH[|H"L# Vbt,DὨɧ)i²zO%Z3guFKQ,DA$P*W>$ֲ)h.h<4FӫNd ݩ3ީ݉y"NNj쭽YQhӟF[ SCFE+-AQmbԁY ;Ku ZubΜ5jxqq Sь8"K}u=ci D\VrTWgWd]6"}`S/zo&z}eъ5kV,mzW2N[zs^8쯅h=ۊ6HGXDFɀ/v ]ػ.ozckZ*LOSy=ĶÁʧZㄐ}GnX5[]-Uz" (.z7hVoX^VT/tRmE F?DdeA2&`#P+crZ ̠I"XIrkot2FmlHoI,t~]-I6tC 7軧6x4KhaN/U74m"Wm!|Vl-T0ZP2u/se. pS&RKɅ_]#UQ\zQ'RQO3'dAfNiA3p FJ*fGt%:IGQSj$7iAH0P8ϣzi;I;vbNWMA6 |6}y?Y/ . ^"$8D7iT㄄0z).SفvZ>Cܤh(?)&w\^`R $GwgH׼=Ffo|J!b!vtUe[w~ o*,\2o»M>Z_k:OU8T=]'nvWɾrj'`SdX$KHUPs"WeymM(˩fnAjJGn/#4L6rGfLЁt.ԡé>~E0Gf6I"egy⁘ 3{k6}ncdax}61Yƣ=-OcHKdvW15wL։)j&<w.۷t֮%N;o҃%6\|Q౲&Zwڊ[.hW m;﷘7:+6.Ll':I1=$:"J[ljc4EQ5S'9 jUT77T֔aG='GU/PviTs.BM xʱK>luiñճ>87^|{v;|X5"GMBBmZɂ5n)^[PD:F#l:R+{ފ)־)B#D(zDٷ#4uXjBGwШ(18kSI*iPi4m tUFT+#={k.cZs1\R_zэ{^-xX?5YG Ϙ#jǬXҭk]1cR7Ys0确mKvvA}$J9%:uDGJQ##% ëW)&g wػQZݽ?'O|zg23\Ȍ茘( XT+ @x@t &@KAa+L^rVMv3Gbi2oP/$^yGe5{r6KRuݣcS{%- ;֤>佚tnuiY"E+y KB`P-H~ތܶh@YBOএ]8[jC OOϞ6w91a>_(@7 ʖqO ӿ|Mbֆ`'uP R*qH"jXh ft3[I>+HNh>Lmd z1CV.HO ,$Q#Ccf8 |IArKcK8!R[#E>B-d0l{R9, 8>Yzþ*Y!'ŤlIEy8ƃ֓HiGEjfZHBP#~yg8[oخvB1Ez"9-Gjv#Li? -Eֵ-Ly(|xPIZ+@u7bwK0yc>,BVmO"|Y!V0Nd6&jlU0ܷ!NRuhisiO"h}Y{0Ze?~^j!&OFϠ}ǀ 6ڈPDCS>4rÝ]%{oޘnFC[vc" muaup÷)pߔAE q1˾./b_r" >pe6|5pS7|>uå2.w ;\|}R ͇zB="=,348ZS[|ȏ9,S8ŋl )a!q8,0ücټ2;=;洇G9NYI;p 0C.Ų"2{(qS'T+d”0d,L0ÃQn` 0h;`40alXw`g0tPabCra0bk`P āa@ ÀlA٠_B̟af~+ e!#H\/>(B;ЛCГC:8=z$Gá;n֍Cpd]C*6R:9|{]" Βj sKt@bPPv9 :▉:zY'ˎ^ d8ЎC[x3  l xO"O" X!C46CJ"99Dpp!`w&t̙0q+AFrlV3l;Ȭf6΂/ `V}h`DM ᠗AABSYOwp3NdBO(M" ܢR"W _ endstream endobj 21 0 obj 5387 endobj 22 0 obj << /Length 23 0 R /Filter /FlateDecode >> stream x]RMo0 W*hJC+!p؇ qZ@qI;_^޳8ٹyi#NEtdv0}fܮc$Jf9/qg;${sT{ FE]K ӽvAfɼm,˺E۟k UZ%3YCg vZVNl靹vQTBic@|`|@M3w cƀkN1PN֔) y.a ȳW%GE-wxO=aK5ה_[Ɩ4K1s]Mu5)[yUy t[1{ybѦGfJ<<] vN endstream endobj 23 0 obj 357 endobj 24 0 obj << /Type /FontDescriptor /FontName /RWVPFI+DejaVuSansMono /FontFamily (DejaVu Sans Mono) /Flags 32 /FontBBox [ -557 -374 717 1041 ] /ItalicAngle 0 /Ascent 928 /Descent -235 /CapHeight 1041 /StemV 80 /StemH 80 /FontFile2 20 0 R >> endobj 7 0 obj << /Type /Font /Subtype /TrueType /BaseFont /RWVPFI+DejaVuSansMono /FirstChar 32 /LastChar 125 /FontDescriptor 24 0 R /Encoding /WinAnsiEncoding /Widths [ 602 0 602 0 0 0 0 0 602 602 0 0 602 0 602 0 0 0 0 0 0 0 0 0 0 0 602 0 0 0 0 0 0 602 602 602 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 602 0 602 602 602 0 0 602 602 0 0 602 602 602 602 602 0 602 602 602 602 0 0 602 602 0 602 0 602 ] /ToUnicode 22 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 8 0 R ] /Count 1 >> endobj 25 0 obj << /Creator (cairo 1.12.2 (http://cairographics.org)) /Producer (cairo 1.12.2 (http://cairographics.org)) >> endobj 26 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 27 0000000000 65535 f 0000019980 00000 n 0000002300 00000 n 0000000015 00000 n 0000002277 00000 n 0000010405 00000 n 0000013115 00000 n 0000019516 00000 n 0000002447 00000 n 0000002676 00000 n 0000009665 00000 n 0000009689 00000 n 0000010120 00000 n 0000010143 00000 n 0000010851 00000 n 0000012273 00000 n 0000012297 00000 n 0000012599 00000 n 0000012622 00000 n 0000012884 00000 n 0000013271 00000 n 0000018754 00000 n 0000018778 00000 n 0000019214 00000 n 0000019237 00000 n 0000020045 00000 n 0000020173 00000 n trailer << /Size 27 /Root 26 0 R /Info 25 0 R >> startxref 20226 %%EOF snakemake-5.10.0/examples/idea/idea.png000066400000000000000000001247001361131222100176600ustar00rootroot00000000000000PNG  IHDR sBIT|d pHYs@VtEXtSoftwarewww.inkscape.org< IDATxwUՇ_B{7ޛ4. | Rt^ H")"I B}=s'sKM~<޳יs^{5٦+40ش=3ϻt$Q+l [m$ l \oF˓d2L& D7AHe'O | S:6'G'iLqW7jםe2L&L,t2 0C+ۙ4kv > &YϠ!I+Ip9$&itVt>pk%T:v[L&d2D i$"igI;J'}A u%h9K]]p>:WLg氽-l/Jkv`ұ].i&d2Lf2I% W%遗v3OGӁɣUyj02Dl$鷶4Xٻ_ K%.i+l+iJ$:wKHZV>%Ej'釒*X x;x&i>I֓y,5ARP*}t$MOSgFW] DV<t)l?<\D(WKS#`=d2LfRIkHx@*vP@R:ie:b⋤c`q)%]K0&tD-b$Bӄ)69_gJZ:$ߗڗu% lG(?WH*,02I:$|YKwu{UAie j4P( i% \"iRk K I9$V[TvZA `#۫[(\\Ѫ݇H$HIDZE둭Wlyۛ>>>&)_}ֶ);*W&d2HKQC4pҾY81]۰vV6Mn.Ap`%I+Du.z=$IyqJ>8߾m+MAbB~,1q.=pZpgxغHp#bB!GґD.iR'SƤy9v\؆4# K.>0UH 'v>[^:\7m)\$-F(sy]X-hЯK|۷IZgi)+k]V@RY/#͹k&#^L&d&a$V$JjdwÀm߫hIBLLOXUY/Z :?n!ͭH;(~ `vTodde!4YҊkrI?'u$\%i֨R`:=:DIڒMne{c*пJmVڞXx&#D=l6O1[#~z@$mYڷ(X ؖe]Bwh %eK}Iijbb5d&)RE]\_ 8KQș6h)%f`p) *DXc FqC\ҹ &4f[`I/'II:XR7o+=6#i`OfR>$^cSGB+yz!4Y.N"Cש2_-8p6V'dkN }KWX|KIZYKJdr'`L j8}tmL m `$Tf-~. YERg,tҾloE$8ٗ"V6K24R I3HZ0MvIOHǜP˲_`{}Ԓ}yGI&IZH<ް.ԮH=l&LHژ=iP%8eiM3};٘1AؾUz++Gş'$=Mڼ Uir9)m)Mش]$]KX>~R$ǫ&VH+$ɣ KJ| ;JmKċy$㬒'SҹB\#z Mc.5f;dzXyr,1fH?siZONj_܈GwU˓;$̶%%f&,f3%/WdW( 9OJGDi^$)nq֠ KR.zYLeI n."^k^oO]4d I ֵFb;ۧ'֘L=خ"2 v|֎w Xݯfj` ,[ڞ0yWBL>䵚ug hc?r=Tz1*QdSbBRڷpyzdxt*c AN!2mF] K!ON{8*`p[xp, Ͳ/Ri*vHo\7=+'E-K NiӫE󼛪Pt#]*pEjͨV=;XulK/Sn#cCϚƭw Zѽk0|B C/i ][X kf_q-1QXbeeݿ0)^te! >=[52 Eȃmoϳ6C=~1C^OgCg2O؁$ޖ[DPk!I껴%۶7 "J{_ {%Pj[k2,&ʞv&"ViIՐ7AGS#BtVSIKZ]Q.x;pj=٨tν$XC AQ8t$1LnqLW3a ˤb?3؊HHeb!+ I4q^m 1I/ z8pH"D(yXEfC_Aӷޫ]4L&5DdFJ$쁴8"1DKԔ Cb1dNRD7lDAͳH$8>X\:|DYZ=/g(>ǎD;K}&&G Ktu V7 gRL#?֓CqK)M4%(Nh5+{cMJcS͉DxDE_J cH:(ZҸDNvN ,I淈bd:D] R?t`ZERya+M3HZzV\cVe+]ұ\ Qb࿶7}{{e$nr" =k{e( ٪Ovqif#nc$Mw7Ubjۏ7K<6N'\ʾ0+#ir)}Rߣ?W'%m k~ؾ \!D:)[$x ?uI- $ӈ{όADlNXj_QKK}|8a>}.{K E/g͕i1"]f<<9,PnfNcnaRgR҃D2kL IaY14jo(̞ a'WWDJ%$ӷib74ݔ˱:_5LLM(f'W-AXKN\^X9rd2NVZ1XHoD&[si%k]%=C8ɱqc5/n2"VA-VStWčq K&}Rj%-T2kmHڄP&o&DsoD͊R5l()Eu}/[Jb ȵ Ӯ- "t1$HHy]L&3 iPtD!KSJ LGX k =[GSۯ.|S=a 34Pub׸6SU`"8Y#zyظԯ,YuFt'ʗ"\@0M3R5aYYl{G(<=.NVL&r*o Q:6mD-qn2|1^2>F*p*RA0T[KIlN4y-JeHdI4x8Q `Up<xAÄdTͻ ?*Y%>t;[֓"S>9DG@񝝬K+bK?dVdfCZc{(/VU$ݒ^"\w܏>jП뒞#迴h{_s?ll&JtgSmQj?մm碒%@(-|k>x_hM;^5!~ ->$MW>ȆjL&d2)fF/*B}uګK؟n{n*d2 n!qF#PD_-K{0ǯt i?#c]"e{iL 73Пྉ%(d2zi~@ =-i68Rl_ ,*iFRs~ʂ Sid2LL)i IZ?ZԍLUh+i"X=d. Q7 ”ui[f2L&31g,sr#I O LAP9S8[&d2$2Q'$d*L?i-KfDZޭ%/iu -;}u2%傕d2̤Fʪw( RzsKүNhZMI[E@GW*C] H')ِ2O&M$}@ԧ_YO~ƚ H&dz%JwSU&$j\}(i`;Bg.'|N:p헨1Omv$*>+%K`vdδ}nDqa{kd:z&i;`g`Ex*ojc`#˕ݷR\RH$¹t폐tJ!ǯSf5C*%?ul iv`>_7 78%+]CV@2L&koInOeFK x8 Ms*1 V~zSm&WwJ: w7nkMEY+鼤lmJLJ^R_חD;Y`=޴r6Ò#r ȟ d'IWKڬTk`I?nNnJDAğS.?H=wt=$ 3 Vοj0>wbHAu7٫ H&d20 o$1=#V䑴*#v?Vu3’ђN^(.{qZxp0]XJ iJ,)'edTj r$$]5mt3 0 Ia;uPJNx-v,f y"&/hR^/C~>պW5RܜFj[d+Zzuw\^o~u<&YfR2-KL>()$U !o5$o?`DzQLp?B9?."d2L&&Ud$|b&dyb2' soץ9fn!&jWYD<2%MMK& ?Sdn޶B{ 搴K"`_l&i+I}RP]3@ PD^H)_­dIHAif%+ 3HMvJp6aAx)=I.`^L`ł>bLI;7xY#O(6&k [Lr$E@ҼZD<Ik9%`Òc^'8-^r*8 7XҁDZv-4dj-d2ԯzTTNȲ(0s=pwX:1gu[ IYlTE:Dăbwz#8S#"fJ ?POv\ofPۇ828ozB9 m%\d$d2IE=jM2H fD`vDm\G AJp Q+T>u\f{:DX$%)L&d&p[8iV(#'Ms \;MW( sKXD",":?d2LC}K3LY2&V&jfLFT8̖t4 5`M a]x] Y:LJ<+ϛ\' ^J!+ L&8$#iaOš&R=_\}F>sKN5}j"WDZuù!,"{w#Yd2L!onL{I)} 2R͗vT0,K^0u$}^fxc8ƒ;YGtXYRlj,L&H$ f3FN p M!Z>j  ,2jlHFakY3c{twhzn4e娒̃m)p؂6lpu iWIwHکѲd2IJ:IҡiBWm$} =Ia$-u:*CTfuIJ0$ch;*Sf!H(e&VjI$\o{ \C` 1R_ $MV@&,k]9)=n%F  VBond&R7}Do+.!2,ڮ!VIs!ʹkYMET_(ع9wz$7"iv`o`|IĀ$,"suF-~bHM0l?m{gQIS‰^d`BVW>L:WzJHehJ_4π HLNH]mZ8RdAc~uUmd2&?֔45p{Bo~\J9\ӄ21K ߩCJYJŽ *4e,s`"73(py egotێf">"AۏwIOR@RDPDn_K:Y,{2BM(dw/]14XIa{b#=P {V d2e jpy\mH{~3_H"]]*rl#`*bı `K%Hx:< &ڶMaaIWIz)Wwѫ``p|r@9pq[<1J+e$"?ml_QF3gXX8%m3-> d24H+]#S;gtލ+?^/7ӮǏj$-DXIgjc~w=х qwbI 2*@zΕnâ`JzUR`I6 a419ɒ`@"j{z2_Uc`7KG_ɃŁG}\L&3dcºQV (M:87)Pt>R/?"$o%T3=I"F3`nnI[7Hj]Fo B҄6K0mϟ|\UrI+}:x[Ң6 !W'R+SKZ=?H_Y ")utci{"ߕՈ$i%Iח%M%4$ޱSKL |M QQ@$#$W=znGE0HWRxǁ1La9)&.ML&Zbupp(O.IqNn P PE*Q$-Az[*@35 ez tpQ||Uqؾ ؎p˼QviyvzW6 o~`i`Ҿ дox i%5dX,x3mO S ZO^dw`ʾ?\V:'齈)9̄˥}/J}JۃJVӹ+6w__U ±7{2sw gg!pOiS7`o5F_V=P:WhG"2uի\{[.n̛V.$ˆt0k_V%Lk?$Rf7U?!,+Z;U 〩$Xq?ULJ;2cb%ljb5 I>Ϥmjt}c +[~MYҚDƖ!:h{Rg+S&l_ʱL|-_6_ҌDr&Tπwl=!)gIꝁ- }H=&(Βn}WE5>7]u Nh;GbG":aj>P> nƄN٧w9~|H= aḦ و,S4`D,K?> lKϴa? _oJJ:$NLJH:P(v&ɔY?7pɭ 壴ﳖwgi6!܌ؾ"u ^ ֑LB F p82\?H%WfʎDC4ͳ=}-#b|$ag/>53aix5W^/,lV>_-02 xGI*8.coRWG< P%$뮶Wƞ)xr'bg)O&dՄr$=?,.iV IVf&攱p&YC/t= )}{41ؿqY[wզ G?wiʞt<&S0EUiNM|~I>!Q[\ ?۽[%m.hӮ,NJI;HwuaKWʆf|?&IڝPu}v H]5Ot*_)s #\j4Q-VO yKw&v$l:JAD_NNv۔|!~rՅyX$mL?PlD,?H&IoI^+Mﱽ gS_ޱ >&~쟑R>?mI>FPM,K$^^#a{<!3+Bi|ŅR݈.Kz< 3IQDIy:,nAM|ωU ,%H%y dEÉ'y_eZ#iatISH8QgZIE%ewF"2Y,ޠ*.?PLfMHLq0_eـ;(߼D^rz _t+a59XxPoOͥ߀ߤk09pN}9R^zDiD0d="u ்W~W_o weRzD-W6Zn\S ͞ɍ|F,lԠq{9c/rPXhIS "o[':(*Sv#$AX!&텅b0ʑѦX-bK۟s2S[zE0"[k$gޓ4B.M:jGLfb!eY2VTT=5[Ik*V芶U5XOjiZ|fNy#`t݊* 8}eDLײi y %>Q1~5`OB|!NOkWe2<^Y(_~Ѳdl۶[f:KG, '&<˖J:x-a{ ۫~e6:L*kpE$,wq _e|ȢYw"@H qNej*=3ݓ3tWݪ{J{ h##B=%MD5hm/73 ?#RJ:wk8 ء>'?}$A^5Yl)3# Q.?3H+{w8Ad8#4y6$ҿkv\ gf;\`9`3|t]ܡX+Ӆ s5,':{gk@މ"m$=W+b} qXO|ƮL9> W!ާ^q|~+_$jA!shnyOrZI _7<I=p;pJnx^F.kJyV*3Sbgˎձxpyf-O]/ȃ{3m>5'37pwMJx}!ޓ4?AHt"@WH$Āk+cJ\g|>Fx*5|%W0*ޑs~p׳naY~Lı,is`  n+وXwJa4af:'+Ƶ[ au3Tf60jJp3)Y4).#Z=;H9>  wXW<&i/`#?Y ?Yf6V0&΀WRl3]/S>#8a1ht|1{>m|I _qf8>0 ,ʇϔ.2xE A;ugn^3|=p|d|ߏAn~,\AuG2Q"-.+p}}Á$-+ ;{K/?ygWpw.4}uxO~>o+񉉭7yl=)=Qw/ZDNhKk0홹u ǟr~@*ď%`?IWI_3^My߾! w3* hp˳=\H|f3MRޅf]\I|g<_pיr>B\y p3+6F'問ṔLc5c  wfG(HolnQq<~cC\P KƧa.\Xwv^pJgKyBVobZ,"y|V6 ߬![۫]oJH^9Y ̆e $gfǦ7q >!pz8Y"%ni²41.od |g]%5>KǹWΛIv']W3 QWCϙa07~'rۊ/s;Mxu-ᕱ:93=yTzx8hӱ>PY]RyOX~~<Wf6"\ q}cc`>/ Sf/q7".da/tfZ8ժwQ~ާp`p[TI'Ⳝ'ֽ_\-A~fַnUq>< A?WnmԬ+N)q9.A  [7pH{w}lPm1Hpĭ 2g ^Q^qEq/\Y<ثѐYx<} 8iX Z65}>9\h@mgɨ2\+@bZϗ+Z=Zp8|bZӂgʰVь=KZ05rqW4x< _C 4Z7;2x;^Gޱ;RI+M[Bc{5@{<ך:qv.,Rk~gmV}^ W֌3 oϦO0PăOg6țp&/vk~7Y&WoݞZdzЩrv3~_A W طֲ4#ZP2ZRBXk-Kȳk N 4 5ey0~ XQ1>g3JRǗhE,$u@dT;0w<W"ѮHZW?ƽ*^)R?lROI77C%Cҭfdeɐ+y@>fvL y/S ĵ+wI;O+I(I,FgfP"Vn3H:P"#"#t$KUQ6f-cIԖxjRM$G8u^6ē $ɃЭ]OFaFà75KK|,}|e0 Ou?iXY( W?n)-ZLn#-1 $~*y=x!@<@|p=zfx3 #HEK4bf?y<=Oޒ:5u"Q­ʻk+PǪ0L^;gEdy~ L Ha-_U`tkLHUK`IUnf:^GaiTx-yLd&>k,f}kf[gQ,\hf3scU|pGM|4dօ)/5v]NJ PSžl|?M{޳g蘑eOPpj渇qoTٯW94j˳p5cfV|PL5MDK7\ƶMm[oM4fqtG?1iafwcj tD>jfX w5% 뜮"$F+6^E2TW69j>>pf$ 4if+>;>p;X]zVZ!*%|F{hP<~-Y>/t'0] P0k~74 ,@n]XdWZ+^ snYkntqM-xWPP5*q=- tƭzmZ܀+ @ s+}d*`` o]ce"?s;^qᖶq7?oq—tɐeH Hy2td.>0[p\gX]Қ*7 s[= O+KR~_0 j d\QR7p3<7qBA+Bij Wp1 d'/lv \+7űffoϸNܼ%Qb2^3#rV^:wlVKjfGܲ}miQ̎5ܲ:'MX8Zv oEDbb1Oq$nL$m$)Nfyv[bx .v>j$Zo,xj:3t]P@^A4+q_bPP| ?moXK[/W`8 8<88c|6z<.`udMFx,8-3RR܄L#q+%)>f~ƒDGE_ZCF}ᖖ+yWrot$KX*S X

uiDD 95HT},i_F OHtx&93jf),׋Q;7fqoiH:+6nPkP1a^x_)To+ĸv" #p&7> eU:,WgI.6eޯVR 3Z '΁fv/FCF }Ɔ> IDAT,q yr"{Jw; B&X7 z7&*2) O_ b6bxURWjehPH}%pY%M#<&ckt*Zv_PGq lZ"}C|O#3q(v7r;q"Es?0/F_m}x!fv ݧnގ-;70 neCt5~-ǽLIDb~2tS ϗ4e &%1p[ؤ} '/dZϬMXZ5$YcZ^QH;nUăom"Ρ{s٫m!_2RNLISot% Nxyz5Έ Q꒖VIITMzma?E HZw4{qvƷD=fI'| $^pĤJXh7Ÿ[gHک"%:f6w X53gd5p/ ӵι/pqyUo|+ ;lA3ިVIITSB3$2at(p2p9%uM++%W]:|FH*H$Jbfo[k%YcHM#p#k+ffC#!50/|r[W̆mRh1) jZ+wEs8 ǥ4yWV67U[3ŕ" #D"13{ $DF"$/0WB櫩PrErӮ)a*Ht6~)< Gf`aݢfcnY3}NfD"hI>ٖ-O$dו_ρujQRҕf2`i, `f?V2KG{?|IJfhD"Qo[H:$:w7nş ,\[-<$綨D"HL֘`{=XL(Gw7sD"Htt/sX I1x+iI8I\jHR@D"$m\, ̘AҔel׍* ۠aUg5Xl8-sM0qfv:^3`L Z#&URCHT I3I7D"cJ:VRvi38g`26׊P֝̎5OR@u`\aY`gIs@IR>V&Ɣ9k7!iITa?KD_{2_SK :d^`˟j_Q*f) maTI9JzLRh5ANoE\N1GEg5]-<RCRC$0{@4 e插:IZ4˼#i 3{xSHZ=YFu\%,_\ϒHZVR9 Aq&gţYfέF "i~IS7tE{Ie??u)g9FWK-iIWRkBҔ]CI>7y.$)/Cf=Shy47p2><,V,Y`BIyRz$ HU 5J8,eP-Wjx. ܎8fƳLij\֛ɭ{K)["Q-SydfCQ!K{zs*p+9f1"iaIK7FI$]gPҙiIKU) 1h^'DrW%-KyV$=(irNcfRbpﶋ+U`Y8gG?S;%ff_˛yy:`cDI)WxF-]oۥ, sa2eP2QaԱnD7|`54}/ Kl 灭ixSj0Α?3>}[t2ex XT7>>14#-u1^gUCh 8ގe;;@OTN9$|wW<*{ϵf?kMR!ff%`Y㘍IS0~h%ik"`|s\9UE8,ėm춖TT^P0UAOVpTK eU@$ vKͷ$=ڹzZղ} 4Y~Ti̞\DU0%|[e|^x^m\_,ϢXJBNZ/Z+|?ΌLlwL)8lIcdfv|nw@PgB1S!䔯`7|Lz2Ttf6p- 4pKf}d곂= } ֑ nY*MIU+'O|]N.Xa X\/'i >[6?x=+B Z) l.i+_6X xmφߏD%Lʳ tP2̋zcl32Z$mď|Q83D9of#w[wL8&o ^`/ Ag~IYj̈?N((0ʎs8>Y4 &⵼=[3IR>#$d+̬ٙqIk~%I7HJҺ%w򙑾Ngx0 ʏ<o`lIVǴvߊ]O)i]IO)ix9PlSK:^N-C(v'l };K7I C?>`/q<9ڻ&S4 Dza$/j%[>tanۯ;j3{2}$b~4yk򘐣sg/{@>z$nʧΘ k6tZW;&~-W&ߨ`k -V-/ڍJJU//f/N γ{ BWrDd1* Mi%brpx0gWnqt}gfew_` `IC8X_Ҧ;p4pp6G "5b`ApKN8 ?{fE(qIQ }ef 5S3q3;~330r3˻%$u<؛lQx+64~?p\;Y%qXkQ?]"v#Jׄ~~P.}ù^ɵYhx0qM`lfDqgcxW}?nA"Ncki4IX9՟v),; J+[),o  \ľ*>[#dI%#2ƀLO 1+hngN4ZɃ_ƒ_gm.gf_/4Iپfv/ H ź}_8X{fƕultp>ϖf} efo ŧ@oI3ٰhs>_`AÁPF/eWQu]9 cN$&g^t>$>[8NKj) ] |.xP2WZV'smkh,#6nɘ7܅O܄?kz{bB6gNo9+:4W@^->tpE$ oOVrAoyE%;`[fz#hڵx2~4e`f$= M\Q[ԲxdG!) d)0U쯗˸1r%ϻ>}0fއPN)O)]$:}O ڇ͑^;^f́웘\ijr~0Cqw3#fNVέ$<Jvn>'9Y l vV2ѱbaRnD,w? 7Ucufm+dx]Ҏfi|ors=^ +%HzX ڌ4гc1)/gyg🰈W^ Q4'Ogw_sQZ3י,fV*DY)`Bw+ Fqo-nfU.dKR@mITѸrj xW$d>Lgfw,ipܴQ| [ dof؄+T:W|B~08(&p5^/a&OQd;3_pWX>=r-Wkn 3w><(kk>ܢN/T"nj|V[$Dͬ?+ rnjܚmF^%n/Eo)tԶ,fvhg{gT-9ﺓ/ȷ#nyx O4`ˣ̷g<0t-bQnjgxdȥJ6ئ=/KU<'~Xo\ {x5& IDAT6RN1/"p k\MU*OZCs8WmHZO007^#ak\fIX7zDE]nҐQ>Ir+y<, lo ֐XDw ^o HN$ZXd84U=WRM)Un34EMߏUi?ܪhH#?+N\ؾTbfKz)&^|1I<5=-+i-7gKvSҐrz Mn9畏Nd?D{3k;7%U-ibRDۓDJD,"@Cy /E{NbBq,s'WpyGBp%~(<\8y^$ I[]~]>lu/y5n(\PJj$mM']($1q5N(@R@ZܖܭkAR)"Y+gGpUvd獠|g}. x 7YNcfF&_ugfIl_y efo=/0yUf$TE uc8Oeik#fc1<.3nϼ~̅x='gxWO\zּ>4T~~ Kcxиܶ?FU$- ̐Kxh3*i<$팤6G _>h5 ^ D;2Y) 1v,A^aȓޚwewtC*1r`3{Ղi:r=Isn~[.Y}_h.H׾ZP?[ۂ"p{kXnxqh[rȷ[d [xߤ4?O(6 i$A^8|k!|v|D3N@rHPg=9D^A|{xmǁKoZ;bt5YKC-Wv>;soi6bZJ:-n\vYۥi2Y;p7Nt؎ !gڂxQ'J()O#i-g A8Ue-U4\v Oc|k\<S> mwe- ȥfV_Œ$j&xx1ߣ% N=\d1tS+-3vg1gx54;Ëh:l p}grk~gdr*BIt@uS}f ۮ*g{Ig II8εYya_MIK:$L䐋^fZI{H(iܪ#l)l%])iŦsv gsq}hGRI疭\`Qe//cfvg"B}$nH_-iEIJZ 8XW)a@Ҟv yvoG`~ k4Z|yF҆q;K:THZ(~3'J-^H+i IGEX=>*q+\=[ffƨ? |[Z?7 h,)@I;HzHNΒΕOb$ڗaxK$[?a1^"36qMk,=gf>^7!3IJZ&ڌSl7Jvr\X- #pkqwZ\&3{)S>$m ^6'ΒtH&ȯ;$Ogfg{e0M3;иSҟڣJf^mωw3▗c%vdd)?t):kXoǠ{ &?O5֜ "^쁿4]{6/0q<_d/ +2km'zWA6];,33_K/<O n5㾪<5Bm*wOY 'i!Ws|<l[;8O>Ynd^쾜lN ر&.jF\-iwIw⿥hǟvgg~_d } VclWPxƽ>W*n--Y&')<)>;hjIP0gԻBY\5zR*퉙]Oܨ\z@>nɮw?R+&nےȖfvg-e EzcsT#+ iP=/"Cps<0b`0=w#uk|k r%p!k|'~C`f_ t <43u43+?l.|g(ٳiy`3;3,pƭK搤䙹GEp_SB2P){٬Tءs;>pymh"/I>oZ3bwp h2{Qp|wx|&qerA<_\Yp+…K3/񸊾*>;0*_?HH$2O A "Fi@ND@^rۡqHӀ̂`$B3 H0H,2Bcgש!IUszoΩs[xgfy:LfxxXfvivZl'y(8^?gCvj1@ $w'߉;pg깻`!>[ef67GE~ұoKl8ߟrQw^7 ^=$ץ-f{8?^!?+}mqs?P1'X]0^hyXV[YZp:ܗYxu]N=8,3̺+c]Y~ gԂH3(i0EeED`wM2p<> \ZD|zh/wklTU^HE-̞vo&>_lϭlPa?3e'`Dۄ}{Iq8pq5#'a u@"r=e`fI:^:%!M=#[G ,jsXо.FUٮ70fV;f\sִ_$\4c-O3|E,/ga%vlifIݗI&A<"%{e&r8jgR[ x"Q3qUJtAcܙ(Im򁌙]+],C|Nucd<_*> Wi)p!u=>r'yRH >?'6ϢՒxru`RxצG*Oqx.af7ږdy̅fvDf@.Ia3ƚپ °U]"iB+`;G[AiGG{_=UlEꝣWֲ*OQRS#$qo #ذ=^П&It$Id'Vzt@$i$-cXE](k愄x-'#z0 }3w٨R\o 0 cAsu–x ܛRP[g 3®qxm?ľ!w?QsL#g[q< ա[lI9E?+GIER\}؟E $YeIepa2>h> [׶x+~DA)Zjy!Λ$uT(J^Zο |ԟT:w8IW I\mS:p}f+S2Gnŝ1`wmf$/ bk 쇯U#'.7u&v||Eexpj3{dBIt3pfD%=ƻt)}LoՍdp;LƜ x 8Ij#b3{>BךYGLHϛ8 oY2 fv<0 ҵèf0OבS=\QՒiDݒRH/*\;R3p>$.3<%aqI0w4X&idJ_1`:%&]6N$IՎ(lY}`<'EGϢ2?6Qx|%}(p= '[]?Zc+Q̞43{ ;O/F ï MNt,P-6j-xr$ XHS{xn?N:!懢Y3qP e4VxO$`|x9`r%Hr%#gx> 䕼 1~mCmX_ n!i]IߏჃSN~'W BwRi_5wfjغ+ppP!XٵAIJ _LIb@9amJB%nʬ.00 x6<43{W${ICl^iHA3a=m 3jfzfvM7?\'^| >W&jW![$ Ǖ s$wSuV]o (3$m`fsKkꌬhI?Àsl|i&㑄^9fvji{g3{,>҉-Eۮxf6X\a]WfvIw_w5>\|Կi4qlfg5؎~vMk-p\+oj-U~~7pH: ID҄r¼l|e368^Rߟ1{.I&6|̞^ג$G! z)9OH IDAT:H6F7ږ21 02Tmh{/_xPf@E#fvu`%It1nu*~$C$ t$vt%P I*G4 xt[$}5añѶ%]#JIGJam$Lv %]$Wmk$}7 }Gt> /XyLMerm҄t -zϢIyɦ ՗5`f!Nl-.k3N.ɦ@pY﫡5du;p|&-_=;\i\Js8^5Yfv$x\BH\sx=#%!b3{ۼf6j+C]}pi#$mv|Wz^a./pv ^4h;`h} ,YǢ@Z);zc˘ًfZW:k≺U9 MZ-K]tu$mK? ę..'aH,i ty@~+iFN׋t@1\W<> /i-`͊0"l^1^aP}]0 jK-~G[qgi+j mgf `mId"/e])pg<<1Z%B|`G`<8I6i xa1X$ie߿j渢PZ,3%~*;Xrf•>zH=@xtpv=JZiy$iZm$I7o.ڛWZ̮#I[τW{[h[ci1G%} 8]d =q]|rG\ͬs: I䕅1Mx-urz? _^XU JjI*aXe!3'Ѷ:Q<|`6};^UwbΔm̝A.o\י-II[Tbk?(f6^]x|&FIf$-h ђvHi+I 3#VG᪛@?6VAҶ#]/^y`؋q ! lJwO2 ?љZ``%Ieh,ħ\v+ ⲲJ}g]duEUw,qMrGfvʼ"IqS$~&t|T,wCw(^`3%I$@: IZ i8l~^mppSYƶՐ"=^ӺUǭѿS43/3vpe$I$%$INUp ﭙ[x"a\]m058^jmI$I{I$6y\Vxh~kMXxuG҆xb\TJ$i^I$YA$OF xfF {ף,“5-I$t@$IVIEI`{P . p( p~_%I$N: I$U++҄bx}k>^z$I&$IB@Yx{Uqݕ4~/#i|0`h~sKYn:I$i-I$:Q(`h^w}vi+.p7t4ƺ$I'I$I }UAk)0=~{D#pǣ`  *s;$IV?I$#"Dk`? image/svg+xml Produce the files you want to have from some intermediateresult Tell Snakemake what filesyou want to be created rule: input: "A.txt", "B.txt", "C.txt" rule: input: "{sample}.inter" output: "{sample}.txt" shell: "somecommand {input} {output}" rule: input: "{sample}.in" output: "{sample}.inter" run: somepythoncode() Create a needed intermediate result Use wildcards to writegeneral rules for all samples Snakemake determinesthe dependenciesfor you snakemake-5.10.0/examples/latex/000077500000000000000000000000001361131222100164575ustar00rootroot00000000000000snakemake-5.10.0/examples/latex/Snakefile000066400000000000000000000006121361131222100203020ustar00rootroot00000000000000DOCUMENTS = ['document', 'response-to-editor'] TEXS = [doc+".tex" for doc in DOCUMENTS] PDFS = [doc+".pdf" for doc in DOCUMENTS] FIGURES = ['fig1.pdf'] include: 'tex.rules' rule all: input: PDFS rule zipit: output: 'upload.zip' input: TEXS, FIGURES, PDFS shell: 'zip -T {output} {input}' rule pdfclean: shell: "rm -f {PDFS}" snakemake-5.10.0/examples/latex/dag.png000066400000000000000000000224671361131222100177330ustar00rootroot00000000000000PNG  IHDRs{bKGD IDATxyt[ս/`YȲَؙ<$2)-ah1d( p)$+#ҮPhz&Ȣ-By-7*!yBI ! d۱!YdI5c˖%ٖu$Ykiw~:gc B™V$tBf9!DJ$3m@7h)݃hduls校H"qIDPHEȈBQJEf|СMvε0C7h`92 2Pt3jA؋A!Թka,V$%FP| ڰ'Z MBTx*$Lk[;dnཊnk7=.qP+H"^&DM' lF+L )`i2BoOls@]>w\X)Tr)ddm =CQϠ(1;"%d)O-ЏTt>A8,UrP3Յ0e9P9#zw_fbɸ`qߌ_B7C vFt ݠ ߴym xR\y2/ o‘f=XIJ ZFzo;])7.WC,𯳯Z xL#{ ,M5q}pJWK6|++iw2w0`׉6i3*K恣OYc?XùsPYY:{Q__J9s?}~|V͛7`pM/Яm~)= Nn 6l{P]]3g̙3?5X޶L^;\*BZћ}zzW2AP-)o̐+OaK}ul1&pBvE~GGq˶nrssYGGǶϝ;233ݖW]]߿p=_UUx=9p{cm߾%''7|1<8=Om`}r9?//oBcg6>c_mok/}u.M}zv::6l2O?vqzBL G^D 1r9'^m6ܹcGa/!GGGOx~:|c>]1ƲhО1O].kD{`_[nJ'N%xG08''g̈́q 4Vfc vo6>޽ n߻w/֭[w;& _1bcc}A.ofÁbt>2mWx# Ĥ2k9>{JNNf555nlʕfVYYd38x [ld1K/ m`z+jLղ[om۶7+W7YFF۳gcW^yeff;vxlӶ𴼱=3lʕL0FnfϯXeF5552o+o=0Bx3H2饗ؼy6ng{a .d2?1OS?ѭ]v~1&xgߏ/Nrk[yWJzۆޖ=m1YN\_DL1Eg>`YffFc2L>U_D},((`UUU'F~O1B(|cb{a---V՟Ҵ۲-}|LT(b?eHUJWO%X,3B5{{,#pٳgُcR؂ ءCڛj)ݩlq|;=Ӎi_S*ėܟګW͆ /J*ݙ51rH_R14D?˱~twwcxGﭼ틷e{㓭?1zjײRBx:1M>_mOHr!5c9~0LB+Wc͸͍7g?jkkaXPUUիWF]v-|I[lq{~͚5x'jjO`͚5=6oތ&'䟻ꪫsNL&477z1K19D3AK`m(鴹fjncO>uֹ[۲W=kپ>?LcvEo2{`_{`~LâYQQJ,;;ٳgʐzkW)Q_;- 233RdvUs6ZJ_ldcOv%~vp쏞ϊXtt4[l;|[{ʻ߾۲yꟾbײRBx:1MU={?=;UP+3>!sYɊ}pujQacIkabnx9pW9> ]ib ܒf;>NiFPIˌ¢$9#YY!BzoK&'J/SD/_k.;VRbzC?ֿduCu #3[$"(qB%bceqb]ebt'5b'/9'%ș7#!vߌ*qaL_]_~?nЊOw}8bWXd%D!E!8?́Ngی8f>}c$ńN ;cwU-TXP+AmC~؎.u=C8jDnN"p{/UMԒI~I ,MƵqB26>߅A/w b+~EՐgM5XƍpCv\f=ζaGnJaD܄*$BC9\rf2a`|,ݠ]ChdEAM8ejI%jyH Lհ́ ]- Z'^$6c)2P P`9]]]x"_C#++ yyyGEBYSS4 gE!9d&%CVcѢEg4Fi!.99"555p8h4X`Сr%p^mPTGEÀJ’%Kk(^HwKK,D>Q2(..Xގ:"BC{{;@,('G<̛7-wtt.201P[[|<. yIHH@II л(1D~(HPZZ##Ba`0 VsJŋqujjj㼊T*EII bcC2t451\šRĝTWWݑp9yEYYuz{{)G(ݎ*:/2JG<Ġ2koo/*++aSYHJLR$tQ2111(++Ctt4@ףzp%r^FYY%r2%R΋BzTTTf2fOr6!cQ2 `@EE?ㅄՊ  ȉo451Y,TVVd2Ww(%b i( xCS#QTTJKKP(b82⋧DN_#T*EYY✗rDyy9fɘf<..eeexC>044r  oxxr^7>>%%%HJ5%'HP\\'Iol||<))snDž O\4_YX& yaҥ|!5BD@ WTR `@k \T8&d*(!"K.BJFQ6HRaҥTL9uY$ON!o``d$2{ xb6 8700*̂ZŋqpG|8-BJJ ф:@J|"OII… )d>q"'pdϭejj*(d>ǹzFFl.\@ =zzzp!IQ2'^UUN'pTOӹ])33GE"%s+((@vv6kjj(@ww7jjj://;;GE"%s&77999FzggQN<''GE"%s2󑗗!pTᣣuuu|" D233 mڴZ-?C8pdeeQ'9 ,V˟`#v8}8Z-`n2 WbH D 7.A$!##:DB86["f_' eIxdN1XY ͡\Tҟz  () H.G'Dslᇰ () ȉ5w=Ij$}7޽GD2JoV#GQP~ OyJO = 3XIDAT'dNf8~)NpaZ 18;<0ksrvP}=:^{9VqP? >2eŋv-Y,hݹf & f׭|""sǨ^(F9aV8e+tz+ԏ<"pT@KɜL?pw݅ĻH&6sݿi⮻6e(b Cs%*׮Euׅ8ɓ_au>qH~$t I`ΞE[o&<D]=bR{f6 VUpĄuJyQ(/0JB&dN1<>@ߡC`6'@jh!dՀB@gBHD !9p8IENDB`snakemake-5.10.0/examples/latex/document.tex000066400000000000000000000000001361131222100210050ustar00rootroot00000000000000snakemake-5.10.0/examples/latex/fig1.pdf000066400000000000000000000000001361131222100177660ustar00rootroot00000000000000snakemake-5.10.0/examples/latex/response-to-editor.tex000066400000000000000000000000001361131222100227310ustar00rootroot00000000000000snakemake-5.10.0/examples/latex/tex.rules000066400000000000000000000011371361131222100203350ustar00rootroot00000000000000ruleorder: tex2pdf_with_bib > tex2pdf_without_bib rule tex2pdf_with_bib: input: '{name}.tex', '{name}.bib' output: '{name}.pdf' shell: """ pdflatex {wildcards.name} bibtex {wildcards.name} pdflatex {wildcards.name} pdflatex {wildcards.name} """ rule tex2pdf_without_bib: input: '{name}.tex' output: '{name}.pdf' shell: """ pdflatex {wildcards.name} pdflatex {wildcards.name} """ rule texclean: shell: "rm -f *.log *.aux *.bbl *.blg *.synctex.gz" snakemake-5.10.0/examples/mirna/000077500000000000000000000000001361131222100164505ustar00rootroot00000000000000snakemake-5.10.0/examples/mirna/dag.dot000066400000000000000000000227361361131222100177250ustar00rootroot00000000000000digraph snakemake_dag { 35[label = "do_readlengths_of_one_file"]; 34 -> 35; 103[label = "count_mirbase_mappable"]; 102 -> 103; 143[label = "count_mirna_expression_of_one_dataset"]; 142 -> 143; 141 -> 143; 42[label = "do_readlengths_pdfs"]; 41 -> 42; 164[label = "annotate"]; 147 -> 164; 90 -> 164; 2[label = "compute_readcounts\nds: 554"]; 10[label = "run_cutadapt\nds: 552"]; 34[label = "run_cutadapt\nds: 560"]; 153[label = "rna_type"]; 152 -> 153; 11[label = "do_readlengths_of_one_file"]; 10 -> 11; 138[label = "sort_a_bam_file"]; 110 -> 138; 38[label = "do_readlengths_of_one_file"]; 37 -> 38; 56[label = "do_readlengths_of_one_file"]; 55 -> 56; 36[label = "do_readlengths_pdfs"]; 35 -> 36; 137[label = "count_mirna_expression_of_one_dataset"]; 135 -> 137; 136 -> 137; 13[label = "run_cutadapt\nds: 553"]; 37[label = "run_cutadapt\nds: 561"]; 41[label = "do_readlengths_of_one_file"]; 40 -> 41; 109[label = "count_mirbase_mappable"]; 108 -> 109; 132[label = "sort_a_bam_file"]; 106 -> 132; 165[label = "rna_type"]; 164 -> 165; 18[label = "do_readlengths_pdfs"]; 17 -> 18; 40[label = "do_shortreads\nds: 552"]; 16[label = "run_cutadapt\nds: 554"]; 84[label = "map_ds_against_hg"]; 16 -> 84; 81 -> 84; 151[label = "rna_type"]; 150 -> 151; 155[label = "rna_type"]; 154 -> 155; 51[label = "do_readlengths_pdfs"]; 50 -> 51; 166[label = "annotate"]; 91 -> 166; 147 -> 166; 20[label = "do_readlengths_of_one_file"]; 19 -> 20; 43[label = "do_shortreads\nds: 553"]; 64[label = "do_shortreads\nds: 560"]; 0[label = "compute_readcounts\nds: 552"]; 139[label = "index_a_bam_file"]; 138 -> 139; 5[label = "compute_readcounts\nds: 557"]; 19[label = "run_cutadapt\nds: 555"]; 54[label = "do_readlengths_pdfs"]; 53 -> 54; 21[label = "do_readlengths_pdfs"]; 20 -> 21; 152[label = "annotate"]; 84 -> 152; 147 -> 152; 47[label = "do_readlengths_of_one_file"]; 46 -> 47; 46[label = "do_shortreads\nds: 554"]; 23[label = "do_readlengths_of_one_file"]; 22 -> 23; 150[label = "annotate"]; 147 -> 150; 83 -> 150; 24[label = "do_readlengths_pdfs"]; 23 -> 24; 57[label = "do_readlengths_pdfs"]; 56 -> 57; 118[label = "index_a_bam_file"]; 117 -> 118; 22[label = "run_cutadapt\nds: 556"]; 49[label = "do_shortreads\nds: 555"]; 140[label = "count_mirna_expression_of_one_dataset"]; 138 -> 140; 139 -> 140; 170[label = "all"]; 57 -> 170; 101 -> 170; 54 -> 170; 21 -> 170; 24 -> 170; 85 -> 170; 103 -> 170; 2 -> 170; 27 -> 170; 144 -> 170; 63 -> 170; 78 -> 170; 82 -> 170; 105 -> 170; 3 -> 170; 86 -> 170; 6 -> 170; 0 -> 170; 107 -> 170; 97 -> 170; 80 -> 170; 36 -> 170; 12 -> 170; 39 -> 170; 8 -> 170; 109 -> 170; 88 -> 170; 18 -> 170; 71 -> 170; 83 -> 170; 5 -> 170; 4 -> 170; 89 -> 170; 60 -> 170; 48 -> 170; 145 -> 170; 113 -> 170; 90 -> 170; 42 -> 170; 169 -> 170; 73 -> 170; 7 -> 170; 84 -> 170; 91 -> 170; 30 -> 170; 51 -> 170; 1 -> 170; 146 -> 170; 87 -> 170; 9 -> 170; 95 -> 170; 33 -> 170; 69 -> 170; 111 -> 170; 99 -> 170; 45 -> 170; 15 -> 170; 66 -> 170; 55[label = "do_shortreads\nds: 557"]; 26[label = "do_readlengths_of_one_file"]; 25 -> 26; 60[label = "do_readlengths_pdfs"]; 59 -> 60; 93[label = "build_bwa_index"]; 92 -> 93; 27[label = "do_readlengths_pdfs"]; 26 -> 27; 52[label = "do_shortreads\nds: 556"]; 135[label = "sort_a_bam_file"]; 108 -> 135; 4[label = "compute_readcounts\nds: 556"]; 59[label = "do_readlengths_of_one_file"]; 58 -> 59; 63[label = "do_readlengths_pdfs"]; 62 -> 63; 154[label = "annotate"]; 147 -> 154; 85 -> 154; 29[label = "do_readlengths_of_one_file"]; 28 -> 29; 6[label = "compute_readcounts\nds: 558"]; 14[label = "do_readlengths_of_one_file"]; 13 -> 14; 3[label = "compute_readcounts\nds: 555"]; 114[label = "sort_a_bam_file"]; 94 -> 114; 73[label = "do_readlengths_pdfs"]; 72 -> 73; 7[label = "compute_readcounts\nds: 559"]; 120[label = "sort_a_bam_file"]; 98 -> 120; 142[label = "index_a_bam_file"]; 141 -> 142; 58[label = "do_shortreads\nds: 558"]; 158[label = "annotate"]; 87 -> 158; 147 -> 158; 62[label = "do_readlengths_of_one_file"]; 61 -> 62; 9[label = "compute_readcounts\nds: 561"]; 28[label = "run_cutadapt\nds: 558"]; 157[label = "rna_type"]; 156 -> 157; 12[label = "do_readlengths_pdfs"]; 11 -> 12; 31[label = "run_cutadapt\nds: 559"]; 61[label = "do_shortreads\nds: 559"]; 168[label = "rnatypes"]; 165 -> 168; 151 -> 168; 149 -> 168; 153 -> 168; 161 -> 168; 163 -> 168; 159 -> 168; 157 -> 168; 155 -> 168; 167 -> 168; 65[label = "do_readlengths_of_one_file"]; 64 -> 65; 136[label = "index_a_bam_file"]; 135 -> 136; 126[label = "sort_a_bam_file"]; 102 -> 126; 50[label = "do_readlengths_of_one_file"]; 49 -> 50; 122[label = "count_mirna_expression_of_one_dataset"]; 121 -> 122; 120 -> 122; 147[label = "hgtrack"]; 39[label = "do_readlengths_pdfs"]; 38 -> 39; 144[label = "normalize_expressions"]; 128 -> 144; 116 -> 144; 122 -> 144; 119 -> 144; 137 -> 144; 131 -> 144; 125 -> 144; 143 -> 144; 140 -> 144; 134 -> 144; 113[label = "count_mirbase_mappable"]; 112 -> 113; 68[label = "do_readlengths_of_one_file"]; 67 -> 68; 45[label = "do_readlengths_pdfs"]; 44 -> 45; 82[label = "map_ds_against_hg"]; 10 -> 82; 81 -> 82; 86[label = "map_ds_against_hg"]; 22 -> 86; 81 -> 86; 117[label = "sort_a_bam_file"]; 96 -> 117; 159[label = "rna_type"]; 158 -> 159; 67[label = "do_shortreads\nds: 561"]; 8[label = "compute_readcounts\nds: 560"]; 88[label = "map_ds_against_hg"]; 28 -> 88; 81 -> 88; 99[label = "count_mirbase_mappable"]; 98 -> 99; 71[label = "do_readlengths_pdfs"]; 70 -> 71; 161[label = "rna_type"]; 160 -> 161; 25[label = "run_cutadapt\nds: 557"]; 89[label = "map_ds_against_hg"]; 31 -> 89; 81 -> 89; 32[label = "do_readlengths_of_one_file"]; 31 -> 32; 124[label = "index_a_bam_file"]; 123 -> 124; 90[label = "map_ds_against_hg"]; 34 -> 90; 81 -> 90; 141[label = "sort_a_bam_file"]; 112 -> 141; 131[label = "count_mirna_expression_of_one_dataset"]; 129 -> 131; 130 -> 131; 160[label = "annotate"]; 147 -> 160; 88 -> 160; 91[label = "map_ds_against_hg"]; 37 -> 91; 81 -> 91; 95[label = "count_mirbase_mappable"]; 94 -> 95; 97[label = "count_mirbase_mappable"]; 96 -> 97; 87[label = "map_ds_against_hg"]; 25 -> 87; 81 -> 87; 70[label = "do_readlengths_summary"]; 23 -> 70; 11 -> 70; 17 -> 70; 26 -> 70; 29 -> 70; 32 -> 70; 14 -> 70; 35 -> 70; 20 -> 70; 38 -> 70; 133[label = "index_a_bam_file"]; 132 -> 133; 149[label = "rna_type"]; 148 -> 149; 94[label = "map_ds_against_mirbase"]; 93 -> 94; 40 -> 94; 92 -> 94; 92[label = "compute_mirnas_with_context"]; 116[label = "count_mirna_expression_of_one_dataset"]; 114 -> 116; 115 -> 116; 125[label = "count_mirna_expression_of_one_dataset"]; 123 -> 125; 124 -> 125; 33[label = "do_readlengths_pdfs"]; 32 -> 33; 96[label = "map_ds_against_mirbase"]; 93 -> 96; 43 -> 96; 92 -> 96; 78[label = "do_readlengths_pdfs"]; 77 -> 78; 101[label = "count_mirbase_mappable"]; 100 -> 101; 17[label = "do_readlengths_of_one_file"]; 16 -> 17; 98[label = "map_ds_against_mirbase"]; 93 -> 98; 46 -> 98; 92 -> 98; 146[label = "correlate_seq_with_rtpcr"]; 144 -> 146; 119[label = "count_mirna_expression_of_one_dataset"]; 117 -> 119; 118 -> 119; 69[label = "do_readlengths_pdfs"]; 68 -> 69; 72[label = "do_readlengths_summary"]; 50 -> 72; 41 -> 72; 56 -> 72; 53 -> 72; 47 -> 72; 44 -> 72; 68 -> 72; 59 -> 72; 65 -> 72; 62 -> 72; 100[label = "map_ds_against_mirbase"]; 93 -> 100; 49 -> 100; 92 -> 100; 162[label = "annotate"]; 89 -> 162; 147 -> 162; 53[label = "do_readlengths_of_one_file"]; 52 -> 53; 105[label = "count_mirbase_mappable"]; 104 -> 105; 167[label = "rna_type"]; 166 -> 167; 48[label = "do_readlengths_pdfs"]; 47 -> 48; 102[label = "map_ds_against_mirbase"]; 93 -> 102; 52 -> 102; 92 -> 102; 107[label = "count_mirbase_mappable"]; 106 -> 107; 148[label = "annotate"]; 82 -> 148; 147 -> 148; 111[label = "count_mirbase_mappable"]; 110 -> 111; 169[label = "plot_rnatypes"]; 168 -> 169; 104[label = "map_ds_against_mirbase"]; 93 -> 104; 55 -> 104; 92 -> 104; 80[label = "plot_mirna_distancematrix_histogram"]; 79 -> 80; 127[label = "index_a_bam_file"]; 126 -> 127; 106[label = "map_ds_against_mirbase"]; 93 -> 106; 58 -> 106; 92 -> 106; 134[label = "count_mirna_expression_of_one_dataset"]; 133 -> 134; 132 -> 134; 108[label = "map_ds_against_mirbase"]; 93 -> 108; 61 -> 108; 92 -> 108; 77[label = "compute_mirna_lengths"]; 79[label = "compute_fasta_distancematrix\nprefix: mirbase/mirnas"]; 85[label = "map_ds_against_hg"]; 19 -> 85; 81 -> 85; 163[label = "rna_type"]; 162 -> 163; 110[label = "map_ds_against_mirbase"]; 64 -> 110; 92 -> 110; 93 -> 110; 66[label = "do_readlengths_pdfs"]; 65 -> 66; 129[label = "sort_a_bam_file"]; 104 -> 129; 121[label = "index_a_bam_file"]; 120 -> 121; 30[label = "do_readlengths_pdfs"]; 29 -> 30; 112[label = "map_ds_against_mirbase"]; 67 -> 112; 92 -> 112; 93 -> 112; 1[label = "compute_readcounts\nds: 553"]; 128[label = "count_mirna_expression_of_one_dataset"]; 126 -> 128; 127 -> 128; 130[label = "index_a_bam_file"]; 129 -> 130; 44[label = "do_readlengths_of_one_file"]; 43 -> 44; 81[label = "build_bwa_index\nprefix: hgref/hg1kv37.fasta.gz"]; 15[label = "do_readlengths_pdfs"]; 14 -> 15; 145[label = "differential_expressions"]; 144 -> 145; 156[label = "annotate"]; 147 -> 156; 86 -> 156; 83[label = "map_ds_against_hg"]; 13 -> 83; 81 -> 83; 123[label = "sort_a_bam_file"]; 100 -> 123; 115[label = "index_a_bam_file"]; 114 -> 115; } snakemake-5.10.0/examples/mirna/dag.png000066400000000000000000110377131361131222100177260ustar00rootroot00000000000000PNG  IHDRb NbKGD IDATxy|T3Y&df2L&;* ,BRB[z mj[{jmjֽEJQj `Y$HL&d{s܄M,=3dgT`P/#L?|i|iJz{η_zdaaaqqqXjX="K!\xģ.+ J)IRgggKK$I`rުC> n;t|755.$Tϧ@ p8cbbӸn!fV)rK=cbb϶`0T^7@U D677Kx|>\9+rWl'!hmm*`0ȧj:66V>Uhұ&**J> FDDt>uCfpCu ^yZgӭuymhλvIh[I% .kţtK&ox(<<\_DmAd/_loookk^$X5EzXEpS;N+)rw !]9):EXSN=hC`9|!%z18f9,W1,B=ˋE(_)'H<-3}G@|KEFF(F# CDD^?5bh4$=E @ .nean<8yG,و*rbXdE8R4 Gk>"/~ZOJ\ŏzGNX,rHďt:c?qqqF XFz4b@f3\."$B"r:;;E8tΩ"Vv~9BљXD 0UӉ> D{T"(x^()g񋨩I݂do!Kr?n[!t:H*u:F5 b2..NՊ^7OKzAd!`*E>OwttFũ {XdX233@҈)΍ 2qs!FZ]]m۫l6[uujZ\i4rrrrJJJZZZZZlVv > 2l22qzallFcttRPJ}}fNlbMtttZt1HMMR~D@v2zzULL̐!C*B8FZ.!:::$IRT匌ԴC*]>DpٵvX|&>cX(;3fłXqrhF999922R 2z&5noo`0ǟ-d,GzEkkkyyyUUUruu$)"""555!C(];"ׇmhhO娱 *66VNǟ-d9666+++++kԨQGcNt0DQSScZ]l6[S)WT.IIIiLLrN>-BǏ/))x<$ Çcǎ7nѣccc.+e\[[[[[[__p8v{]]]]]6>O^jEboqBB|:tPd4|SN}N*))9uTUUU0TÇ5j>yԨQ J Ad"y^&l"^3s&j&Idf6  j0!c"fY+=uTq/1"''gĉ999999J  24ml6[ϖS 7L&)))I1Qc4>>^ٽvXGGǐ!C&X,J `p! 0|ZjۭV-ju8>Oh4ɉIIIhXDbFe78#W\\g}CsA2 p%/ZNS BbYxxxzz,bV|'OrŒeڴiJ `! Aqϣ锣Ƒݢ0yޒݻwn߿߿}}}LLLNN.#G SLAd(Xj^UUe٪Ecc,,,l6b9pl6SRRL&ZVv#?%:th4Ϙ1c111J pDXEXkkk}>Xf0-KJJbIMMMLLQ㈈ew~gϞ/"222//Of5",--Z6-XZZ*w5$b$%%ovڹsݻ[ZZ vW_s5g϶X,JD~c"4d-p>111"+5KR˧z̴b9 \.rwnmjjjkkkooojjznlb04Mll^"`0hZh4 "/@OVUWUUԈ@h0d5NMM5fz߹s; Ǝ;{ٳgk_3 J"DP^ccnVpt744466xLV2DfZjZ9n{Nqy<rD#fv:bvŸIn|wr.94l4/Jm^]]]SSSQQ!2"plZ? mi,㕭\{پ}m8 D(9???&&F| .QSSp8Vkmm9r6+""d2 :T"O ZVM <G6t: BBC "&I'%%hA$+++EKJ;EOIIIMMQ㔔d866V@vwڵm۶m۶GGGO>}^{ĉU*80VEEEUFy^OJJ2Lfb$&&f1i2x_z ]8G!KFb_w$ɉl6'$$(8Є<ťiii"L"ev&>6L͛?ܹsM&83_NmmmUUHbT@ IRddt1Nn}][[kv{mm+jdJIIILLLIIX,☔d4* l6VZZZZZjZj644Qaaa%)))333tVJKKnݺu֏>vgffΙ3gΜ9tJWD8`0XUU_>}.Onii DrT 鉉~pnbbb\bIKKKKKKOOOKK2G[[[zzx.CGGX-hu\SS DGG͌hu̟Cڵk˖-7o>y^kڂ4@@~EEEhXdETь1b#F1bDfffU}}nhČX3l09ԋiu-]?޺- v/r"\[[+֨ꤤd1HIIM@W^^e˖-[l۶iӧOP:`" 'N?~%%%z8t.X^^.>OȔÇgffpOcWRT?Ox^V^^.UUUbPUU%-STEtOIIIKKKMMMNNNKKKLLTTJRyߛ6mڴiӉ' ܹs ,X`6\"Y8q< FFF9rܸqGcDVkEEEyyyyyyiiӧKKKkjjۀC %cJJJXX9ntҵk~I"###""~=qqqWhc|&v : =-/::l#""t:$IZZs nhZmdd-Wf m\YY)NF#>rgZlWLYYه~ر#77_I'^A (ĉǎ'NTTTAZ5˸qFID{{{YYKvI2223335<;;zf8c IDATѣ7 j?+DvqSSiiiimmu:bx:;;[ZZ:::Z[[Z[[;::DA5bF T*spFqqq?9Dg|677q 6{tQظh j!66Vz^/Aj4ƥhkkƢX|ǪTD0NMMMOO.ikk۱ch\VVfXoq֬YWmL`p" 7yC]N:jQDX$GA Vkt ,%]ް7#""l.12+pB3}"{ƻEOa04h2d<6y'n c8bbbz7NMMMMMRx~رc޻{/\`Ȑ!J 4@?p8Bǥ$ 2$KNNs '4|]vmfffw7!NkkkCȧ"a-|0FFDkޘj hDv=OkkkKKH* N_SiL[]]-reeecр\nӺ$$$([<dZ{_;v3gμ.\tiAi@ĉ;vvT1cu?~`PR``ڵkW_}!+ZNxKrBseee ѨѣGO8qԩcǎ=Gd xyC1bŋ/ꪫ "˫mϞ=;vعs7nܸiӦO:uԨQ*Jȑ#G)**\.$IQQQ#FL&Jjooa}/))ҲL8qذa;0:ujڵoVqqرc/^|meee)]Dr}ǻw.,,DuuuEEE.*****}>F7n\vvGFc'N8zhQQQiii r.9''gJ +w]n]aaano}̘1J@hjjڵkWaa֭[9rss̙3}iӦ+] Џutt:th߾}{ݻwouu$IÆ 0aUW]5a„Ç;zQKKKqqqQQѣG?쳣G:𫮺*??ʔ)SNqСzgee)S,Yxb> .^0|XUU޽{۷o߾CuttL6mڴi'O0a^WF`p,**OٳSN:5??ZVpy}k׮u[dɍ7ȧAb" 4G}ᇛ6m1c;VR)] _555mݺu˖-7o͝:u'''+] ;&۷$<<|ҤIϟ?ɓP0={o;wEoh4K(76nܸuÇk4 ̙3gܹÆ S: Gټy͛#IҔ)Sϟ?gΜ\ZtίPAii!CΝ;kb(].#ӹnݺ_0!!aK.2euWAdp..۸q;.\xO>=&&F~,6lMIIkE`0(]w);wlkkξ[,Ytix7xdܸqwqǷ./`0xСƍG 9su]wgff*]>}_^fMUU,Yru]uUJ5ةT*I~.Å;K_κuNg~~n-66Vp믾jCC5\s]w-\@ECׯ?yh7o^AA {}W|>m~["|^W,JY)8,ܲeի{x%KÇW.\F}믿aNhѢ;;;[麀^F. ߿~wJKK322n喅 N6-""Bo~s~˗/~qZ3 no[jXt/~#F(].իW^r֬Ywq-t]@ z7o޼nݺM6544L>믿ƍti_,_|˖-_~ӧ+]Q?B}`P_w]MM_"::Zpy;vN뮬,.U+wx 7>#w~I!kڵ6m۶m7n)^{m֬YC7o޶m|0|gΜ0s̗^z)4*RD~4TItʕz~ҤIׯ]<#{Æ 4ik&I<|?ɉ ]|1cX,%K8p;5jFȸ+?|e˖fee-Yd߾}͛7 6L׿踐-_ܮ]|s }I{W/Ǐ?cV>}ӧ. WXXٳ׬Yp8~n߾}ԨQ&Mz[ZZxtD`Pܲe˺u6nr-ZpŒ K'|\lO?O?eoްaC`0 ,Xe˖nlܸQyŶۤ8~㏇.۴iӂ r[? / f-_|ժU+/} vQV{ƚU*Ν;gΜ)IҽϞmS?nM8q߾}jl^~/;_K|/bѢE;w0a9x/f͚nV(K#2Y0ܳgϲeRSSo#G{ǎ۽{+H![oC\rժU4+lذA?ܾ}ڹsy$Izl٢#G>|{j6mz饗O3yF۷?VWWw{w}wʕUUUV\)9s{W]]|رz( >#7|SGinnxwD?aժUEEEرc>O\] % BoJ}Lj#ٓ`ͦt9r^xᅲ۷o8qܹsׯ_. 2` yLIƌ裏gϖ$W_ }:7] % *M'%%宻R(#|7|sxxxjjc=V[[tQQLk׾X,vҥK \n:wTkx 8qb&L O9rdĉuuu$T*)mRNjR/@*i0$I=g~9j;~1czɓYYYbɓG6[l)((%\TTtwٳG… _~CZ,./J>o勵 bJ}SO=^BڿVZ+V={x ”.η~KIIyGƎe˖gy2pԨd &IRg-a)%I ҷ$jϙ3dCuK!/@ p 7755 ;;'7oZ~wWZ%uV~@ ---^Å^^zV f>xz}ܹG~'\.ҥgF+VHJJۼ^իvի͛'G\acƌCIDW_}mw9vqJgySWUbٓh.SՋ-w5a„_#Fx衇VҥD_g'Nuxb˖-K.%(nSN;;;~$я~k.۽k׮kVKJ /?=zѣGx0ITPP Ic={~2RRR$IzG/<=\MMͳ>?QoQٳgK?ԩSNzGW\zɓ_}Ւ'O$>}Z[e˖=3۷osEEEӟDם z)+*]'N_z=(]3g[;| }رcJ/U w/nذA~w3ydݱcf̘1{5kDGG+] .|{ lٲC7nTT$>|8777џg?+EmG?џnw>̹O]<9lذGjn8Ǯ'?)Gy䷿7x9v}jϻKA.)*AǏ;wȑ#?c詳pرӧXo18uuuW~饗N:5k֬;nR.gk׮o1##77n\`0/k'NPT?OE`0 /Yĉ$3w]~?^RR2f̘ŋ?oIcV3Adg֬Y=\||$I@`ƍoƶmƍwM7w}:t+sΊ#GN>}̙-_'|رcQQQW_}w^y=~/ťJ}J0|ז/_>~͛7)]ݻw?|×/_~]w(]/i`pǎ/҆ zw;sȑJzG{~_t:+/|>{>˗?SjZП߿gyVXq]wO0QMMMڟ'OΚ5뮻馛H)}_T˗/_|СJ\(ȗÇx≷~;//g>}|g_|Ũ{{2dEap! @sĉ?ϯJۗ-[6j(pI{UVutt,Y䮻4iEW"z6 OvMuvvnذ_ܾ}{NN΃>x뭷)]W^yC}}#隸b"W{njlٲo:N5k֬Zرc'Nx⤤$g"DO| .\l5\tQh:;;z{n{衇ƎtQ"?~oɓ'+]߿vwǏ_h/K#2! ƍ{m۶7XxqTTuPF 8x͛7o駟T)SL6-??l6+] 'N(,,ܿ;N>m0fϞ=秤(] [>3g<+@C+*lذ'ؿWo…aaaJp:[nݲeݻKJJ#O>vX~ckiiٿݻݻgwWϟ?ԩJ[>C׿_:77W0pD zk֬y'JJJn|pҤIJOkllܷo߾}`0L6+_ʄ 333U*e_{{{qqqQQQQQ޽{>N:uHxGO>tGydĈJ 2Zr_Wsw?NOOW(?~޽{wСǏ{^V;~xJꪫ&Lt@P^^g}gEEEG=u7C IDAT 999&Mϟ2eJRRe__fͣ>Z^^y 2_r;OKKS(Aggܟȑ#NSǏ=:++kȑFX,J i^ɓ%%%'N8z$)333;;;''';;;;;;##Cb^7ill\bOStQ"pY466>=\0\lي+L&E***9RTTtرSNJzJ (ԩS%%%r򸼼Jd2F-gggb_,[g?o"DwMT'mҤM5h@i BP{ vqA z](\pW."(,}I&m%?Η榥(.lذSDX,^{7h4O=ɓ. f.((feeq~~~vvnjuF= w?Qvw¢3 㢢p(۷cW jdddL6m7|y:ttEh"pfܹs}]V;k֬I&*]4*B999"6(-.. :-ZYYd*))11.))),,,..vg%I3!cǎ=O?NS"4S8oNsѢE/byG|`fZJnO>펥NSBV+(kZDh@Nj5` JRjg[aM&fleeefvbZvf9Jz<h@sp8,X0g^?w &p D<\/_WNN3DDD(]x6"j6ؚh4Fөꀀ___Vۮ];vi4??Z뽼!f ((HS T*`hCrneee$9NV@+**vlNjVUUVWWi2\.l.--joTCh4AќuppZn1=s}W\KмD~'|r׮]Ǐ3gN||@U^^nDdVyvjI܆p]w"h7NR|;vQ젠 ///^/3-Ot:N};HOOᇷmKͭqDs۵kדO>/7_LJJR"@c2LuD:555l+<| u>(Ih\@ts~xGJKK)SйAdꗓ?ILL;w 7ܠtE2vyΝ۵kw}7%%E銠9 mDEEŜ9stz> --22F /l߾Ȑ!=XiiEAItD}ԩSsrrN:sLq{$\KN>޻ʠ#2?߿1c vرٳgBT*դI=:v؛no۩S. 1O<ĠA*++ׅ FEE)]L lܸرcIIIjMMEI\.50'<䓒$͞={⻻@ꫯJ߾}-ZԵkW+BSU@[wCs=7|_u}B^ظqbׯiF*X,'OѣρytQ@{iӦ͜9ꫯ>~# h\.ײeW\dɒuuE_ܻwf֭۫Jk֍ 25jԤI;SR)]Jtm3f׿5dȐcǎ)].6w޽{FFƆ -[f4. hmj /e˖>}|JWK 2عsg>}NOcJWAj9zjРA:uEmQXX?/\pȐ!'NP"4RߦM&Md6?q)]Iݻwz-⃼p̜9sСݻw߿?/O>_?a„3gVUU)].ʁ&M1w܇~XR)]:|SL߿W_}e4.Vr_~VMOO2e )dٺ{STTԽ{u)].Ad@k7rș3gΚ5_~˔9$&&n߾}С_ /PSStE8?-ާ~XXXgϞ'|ˋw@W_͟?Μ9F2JWr\J2L'O;}_|O͚fs:VbԘL&1cr%ڃr ՞oyڵ)@Iju@@@___V[{ V߮];FVϷ<֮]{mUV)] 2ڵkmVRRGxJDfl2"ƥVfv$Vtl6a+**v8 J$)((H,Je0I_@~QjOUVV9;[~>>>TAAA^^^z> @jZj5Ntbl04@@c=zt^^ʕ+ t987wޙ6mڐ!C.]t9fKJJLg3vDȬh\?1ܦ?Z(v==Eڻv[܄`h4AAAAg }||hq_?"ZӧOO47oޣ>*scТUTT˸Gz¬"ghZm``h>MnX,Vnl6T"*ߩh xxͤ&.k޼yO?}9# hIlr뭷|WShNg~~~vvvAAAvvv~~~NNH[ӥaaau)\"Vv뒒BwBl6_R䈈h4 &|k_}^Wԍ 2e5k/z={*]<D4wcƌ9}_|1l0&33ȑ#>tH8qBU*Uddd||G3&&&ZSNb "שS.]$$$pElƍ߿믿^r?"%K>gϞ=zٳgrrrǎf8p ---==}߾}&ۻG߿.hs/^ h /o߾}9rȑ#Grh JKK=WTTx,<[`kZi<\.\{Vca׳p̕vQ{9PSScXsZ%#4=Pfm~~~"W/2V[wظSH I50/א*rg}$[l"8~xpp^;r#FDFF*]\ڹUt]@XjLGvڹbTήuf%Ugxl:lZgl?̕-$w:=4mjCg5$h{!}OϷ,Kl @Wz_VʽHubڿXڿO&.U_jo 9ߏ_1zr/'H ͛SSSo|oB<:nܸ}ٲe7|@SٲeČ1BvW-‘#GW 6nX^^|-L81..N\y^"C6tt0Qmk]l*Yg(lMU[gقuVglm nN j)vrs y)E63g8XgSƊYO-9i)<:TO$'/ƸcsYN@;w1bI\  +**=ztFF˯Kرc}g}ս{'p =zPNdyp<-WTTlڴ7|c2xwO0Zh<2 !~^3=9ċ6vJգymN^|vGKT*@3!o\q>tFOD姲WAjw۝?޺;}1_ s}'NgyL@O~S>vzX34[nMMMꪫk";lذիW'%%)]\Z?ӂ 6l:iҤo95Y RZqt]vҥĉMֱcGB"o'o&C3<@Σ#L|fō#*esgGg\خLr򙍒59y;gϺ }>+q~qv<{D_f}EWiq^-ݧ^`61nXlҵ99v5\~K~5kΝ;W^y%''gڴiؕO,Oq)3o1•y\y/y]yc]yzXЕ7ݚZ4zvGEYė@]SVQTs E6Z=VNYjZӉhj`0i s:L yGNw<̙3믭駟8 byeel6: /ܹs￿y欬=z<)))ٚ/r˖-۶m;tPttt߾}} k֬y뭷222nƙ3gsWېM/(5ߞ={vǎ/^+o ^y+_wW>A=A޸W>ߕOwz~9 #ʃ<+X^y^03l6+++V_ZZZYYiX sT%޷ Z(ޮz뜣j^j=n/999;v&Mt-mAd@Sۿccc׮]+o&￟<`wz̛7oƌ?[\DR3f̷~r\믿~P IDATڵO"6 O?=gUV]E'OpB{׮]g^|{O?ZOmSLywz{Ͻtj]Jڸq!C$I?`ԃ>x<ջw۷ӖbVmHȣ=ztܸq>#${[g0y=A֕'}GxMhDen=L&E߆̩gCAAAmNh4Z`0&((H t:]``{fـV޹sgzz: Ad@:|СC֭[G/d-N޽k4o喛oyРAgK}'N|7{&.|wF뮻ƍקO{Ι3gڵ|>x}u].kҥ}QYY… HDxo喬_k6lPKHK.f۷SO=_ȗL:^{쮻/z'ǎ|W_}5??^zg$IꪫM֧OpO?tܹC &Ov;w|7o޼bŊc ٳg<822rÆ /g͚^چEoB\Qj|{/44T׋򜱻%%IڵkW߾}k/ӧ =[msyꩧjyOֳgO½{;44H: rQQQhhXRUUVU*UMMM/i *$zVWWnKIII|ССCAAA%%%k׮MMMu/'֜CmݺսQF}G!!!".&\.1jߚ՞s/4|bf^;wnqq҅6v)gfbx\wv˸3z>((HP{o|WڷoPPP鞐e˖#G\rʕ+{WUUIFl>g7XGJJJvFQv\kV%3&I0++K<4͵0첈/cbT;HcܸqӧOGt-mAd%WQQqM7͛7fpXVV,++$f9NIvWTTHTQQQ^^.I$9Nh VV$IǏ?FņNU;sѢE-BRRҦM>wyS7oO͛^tRIv*^q۷o߯V_D@@ƥ?>bwIteIt!IϟߩS'lܹӧOgϞ>}^ݻu/^|w7^4Z{ RٺukH!+000000&&/)--ǔd2:uJz#Av{o>%%~ 4".oG,Ϲz}􀀀~og=uT~g_~:wR9=^i&/(5Ck;wn:>hDIeydd2>}ӢՅNsа0qЮ]훖ֳgOki".{nܹ+WLMMU_t:Dtl6WVVZVb8N*ԝhٲe*O>O>aS .222$IJJJ;~yvŊs9|pRRx≆}=Zٳg\277WdٛCrƌ?j:t[o*IRMM͏?駟nذ[n7t?._ݻ?7fffv+2dȸq---m޼y?~_\\W_}w&''7^soBs˵lٲ)St}͚5#ҵ~g}vw,Z{UX,vd2byⲲRnfX,<6Lwat:Nk?Tz^< E<,wgT*oooJ5jԨI&1ۻǏ[y^x!00 . ٷo#}Nj^|EJ5eʔ)S4A@ |)ٳW_]|__yJWFt:=byyyb\]]-&k4Q'Ft8/c=f6}||# h|ǎ߿AVXA-NMMb1"Ff6 c1cn{G"! ^ zt:yߟ5O?slllbbo;p8&No߾¶+R{M9o?_z5cƌ[n3%0???770;;0'' /////B 踸ݩÇtcǎ+BZZ9FfXRRRj-[.*++MggZfbq<֠R ^bNz`p? jZEYYYg};߿wwq㣢!\Afnǎ_|QRR2jԨ~kQ(4SfYN?uTVVVNNNVVV~~'ɱ۷n߾}dd$}%Ir\3g|Ǖ# hLw޽cǎ8At:˙f`OPP` tOy؝'ԣmܸqҥ+V(++KIIkSSS/rzf[~U6lpر[o{U4TN3777;;;333''G>(((s:%""Bѣ}||/_t!Ad@c1cƂ ~嗁*] Z2EEEb-ljy~J ࡢ⧟~Zbźu늊ï뮻`0(] qUVZw8ݻw9r-ү_?K@kp8KJJrssOpCZN 2Lׯ_v͛>r:u4p^y]vRFl͛m۶uVܮ]/ꫯ9r|||8G9rȑ#G0;uԥK$Vtp.[l„ JҚD4>}$&&Z\+p8D¸P8.((' #)))پ}۷m +gϞɗ]vJRL8p@ZZZZZڶmSUU;p 0O>|;SU*U\\\RRRnu떔땮zuu͛7OBZ3rnƽ{޽;,,Lrp~fsNNN^^^nndÒ!fz{{GEEEFF?u>xm۶o߾{VVVt]{ѳghN|СCHOOX,$]veɽzJNNNNNСBfffFFƾ}D49##fyyy%$$Qwoϟ>} ^:e#G?c=t-m]UUU~~~ffgeeeeeA~~۾}X #33s޽iii?|#Gv$I t:}ȑÇ'O$)<<>>^\\\XXXTTT|FAA{\TTTRRn4E}b, hq믿ZjӦMUUUOMMMMMիW5kҥK;vB"[IIIϞ=SRRkiL&SFF_N$),,SN;w$tpV%بGX>?888,,,444(((888cѢL㒒bw]$__:"j? +++[~իWZy׏;k6~7\ZZjZDcn۶-===44TZ3ϗ$]vݺuKHHԩSǎz%W]]-%NOvR)VZXhZm``Nj`0 Qc@p8vd2l6VVVfXl6nX,Vnl61A3' #q,F:N=ܤZ۱cGppرco!C\9ׯDp.]zwYS%9΃۷/---==@$Nصkפ]v[zYl4W,OĊ4Ftj: Wնk߿]v/ @Vz///w=pwJe0[eeeYY$INf9a⊊rp8l6ZUUU`L&e6KKKńP5M@@`h4gr|1Jq'N/}%%%=wqye&00wߝ4i%+M# 8Gӧw[o)]KS+**ڵk[ IDAT:tRVwҥG={޽{핮ڄr&"MhkۭVH6<GE|wJZSر{p byyyzyi|Zt:Z@;w\x_|QYYy}= |m߾}VfD4TUUUW]UVV)]%W\\K&33S$سg=zٳk׮JW X&΁h\砦bmoDbΧ|||%I͞ul믿;qYf5$>>"|)D1?**JZ 6lٲt&''>|ܹh4JWࢨE{ス{ڵ__C)R^r\Jh***zzjJt9 r+VXȑ#QQQ#G>|aÕ@#NIIh47nwNg5y]ysrr֬YS555ۊ+V\ݱc1cƌ3Ϳx{_gtRg@ڵk׀|͇~XZsȑK.[,++gϞcƌ馛zt]СC6l|ѢESNX,40jtus: _.EEE/^l{|J@WNMMݿn7n8tHjk`ܹǏ_dI3L!?~|ʔ):tx iӦ]v Y#GСòeK4ʳ>t-c׮]ǏOHH_}ռ?xРAD hT*Մ ⋚(??Ǐ+UX+Fr=SNU]w]߾}߿x#GL2E*]bĉYYY6m/򊋋#@iѢE[n]tҵH$Yӧd2};juh^z葔{,'|)D™3gN2gϞJ"\?<11qɒ%Ύ;FśYu5jO?䱐 %teZv֬YJ"YI&\3gNHHt:%I*++s`Z+**w^^^zlz`0TޝEṴ2, ,k↚Zj.iZijJ-553S떥sr%џȪ /2l0sETd񰼞sΜ98TD"͓aȑk׮}Aǎ+Ϟ=`U_.]ڹs#G|>dee9RREGGߟb-P(FVrJhT*\.7L2d2rP(zRt*Jj4ʊ Fj񬬬jEh4˟-׋S$|dzrvvvTcccCg9D\.|s )W DEE͟?߼;77W=/@jvڹsǏ3[ɽ{|Xl1TUUd2LVVVVcVU*ZcZh E݂õp8c.kmm];~F0'e2]Sc@AݣشB0 O֖ r\P(IvډD"Hd1}IZqƙLcǎܺuG<С> ~3[Fnn=<<o Ji5%%%5rtVemmmr8H$ͭyiU TKmWBD"QR3MO+ꍨKJJj5"ɉl2`aaAx{{B233Dn\"!}-3BWXXXXXhN4dDt[[[[[vڽ莴uyAfffYYA:S@,;:::88(K$X,Y,V'''5 p:|G̖1{삂{Vsssrrr877077WlcNNNƆ@Sni4iѣGEEEz^^^BBWVVb1 %{xxɩgMK.B0&&D&xyy!DrSNEGG3{8pɓHBd=T*b% `Ȑ!tnP?l6ٛ)J|+(( +**f666^OpwwDe`}Y`y7ȍU9AW^ٹs'eL2~` ;Z-J߿OTff&mb\]]i1Xf.d|J1>|j !ĜK{n??|м?qA[jRi]^ktf̘q;w3XJeeeItT!JsssIg۷ap2LY+;;;;;"|}}}}}|}}}|||}}y<ӅuW\ ɑH$tͦM֬YSXXla iّ+V`6ڵH!4:޽{IIIiiiR433SBhrذa4|1b,ݽ_~ƇP> >|`0Bߎ`"%޽;;y9]\\h8& Ц曷oNIIf})jhryRRRRRRbbbRRRjjjUUUΝ^}||D"4_ZRSS  ~ӓb[.];622.tΝ;֚ vEGG4ĉcƌa]v͜93--cǎ ЦT_ll[233 !...]v  ` @ BBBBCCӧOwwwl~¨(T*O:ꫯ2[Xk 2@e0wz,-HOOqׯ_j0|}}{LNNNLi4;w$%%ܼyUUU4ܻwﰰ0<(6o޼rRE899Xb֚X2]0c۶m;x e۷/++ܹs̖Цr镕d2y\QQQYY6 x<[ZZ|WM[@˗zܹ+W=zs̘1Wݻ9G=zZqqqׯ_߰aCnnMXXX#""occl-Wnʲooo :"E=}w6l`h @}rVUJR* BJtz^TBJ^t*P( CUUZ&FVh̋敭55/ Bso[[[;;;BϷdXB.åh {{{B !"bee!vvvhh~r Ν;+ޯ_>}v@sKۖ_p!%%[;M&\.'3j\`Af Rr\| ^ygo'Lj*dɒK.ݺu٪ZڜnܸqV2tP>QfL.b)khPB 666\.s8[[[Ɔx<[[[ڑ ڋ"HikgI[YY HMxe)..޳gqMX7ifժU۷o̤}deeyzz2]Z+ 2@b2zmooyf+H$-Zt)@銋 9y\\\lncr%cvb'''s˜;@%kZFT*u:\.uQRUUU*ZRt:L&{Yj䒅Bϧښ 6---ix1hKKK#GY歷ޚ>BѣG ŬY.J]XX[TTWTTTTTd!(\\\$X,vww2; VV$5P5rtQVWUUd2NRj,3*JhZ\l@@ ,‚vnfC4[XX۳X,PHAgBM63!nLWtVI&֭[ - 1cƸqX`Xrի{KCuyzznڴiҥ˗/={ݻ+rLWWW \j=ӧݻ >L (--~9vLB\\\\1옞4G `0rEEEee%LFB;1Fif陆 B0LrR3@|Fkg^!(+CY'Rq/ߨ[4/HA{w<mʡC&NH4hР~{„ 5=WկիW7nЎ;w}=N׭[>lȐ!ɴuֽ{޻w?}{NFm)-(u߾{KKg+>_]L둛Gҽz_4@_;} RqXhM80y+z݄ &N8rHKKK0f̘[n={[nTpǏ?zh&d292**WF:u=tꛝ9sfȑAsnݺ_jաC̛>}zԨQϨm?C׿e>wrr2}4ɚY,֕+WH7mI͛7Uhhh\\\]Vm[ R#4 J͐\.0aBjjjlllǎ.Y9sfvvŋbϞ=2[U 2@P^^ީS3g[Zȿ%KzQTT.JRiFFFZZM#Bz;FTz~L/՟uT}=oby]oX= [16- ".ǯK@.ԥz]Ru %X)󨨨>i .+++N;_~=44lo{q8~{ĉaaaQQQ?y|{oL;vl۶MRmݺuΜ9E... .4iRNNΊ+._$,,L(fee޽{ڵ,));wy:uѣ[n}w׮];rȄ BU zůݻ_}˛bϮ.m)菦WyRկP(pӵ4 ׯߴiSnn.]?~lUm²e~T`r[ IDATYQ;::T* TJ_wŋVh^h!.cu_{]ru1_C]RͰi}233I 8իk|5`k׮}'ׯ7\po=w-\xСC]\\ jݥ^vz뭷!ݻwrMXXX||3j?7lذh"oϺuv-Bȍ76lؐk~]v="L:4(((000""B"mz~w5^}Ϯ.m)菦Wي4hPBBBHHӵ0ԩScƌ)//_BBµkט@[fG}t-$??c߾}&MbxNnKҼvXHHȊ+z=!֎m^]gjOXW***t?]"""FtQСMzz: "K$|ӥl 믿~666LB!G1ba yyy'O\zor;w}I<==r͚5SL9x%po_zg&L ILL6mڵk׶nJy뭷!viӦGj*kkkC}(C"dff/osͯA6l`ii9a„CmܸB rȑ'?cӦMwѣǼy]vBV6˿J/7mڴsηzw111gΜ9uԯ믏7nȐ!mB~~~iiit,H!yyybѢZ5IHH֭ۑ#Gj9rdō:x7nܼyۉ,SNaaa+eڱcGddiӦLccc ֣G{7יL^{ɓO7L#GըQN:b! aaaտ]tidd$=]C?ܲeK[G~g/k޼y?ywrr2;yرc1k{ /)˗^ 6Ŭ[s RSoUjnz׭[|+W>T*=rȑ#Gnݺeoo?jԨI&9[,]ԩSɄFr?^٘1cJKK{s禦0]HUVVv Ν;w=444444$$ޞ2B***}RRĉKKK¢jk &i۶m;ww [dɐ!CW[nݳgϽ{!ӧO={v#G"##Rɓ-ZToJrժUǎWTOn_"t>ɓJ2<<|ʹhw u?EY4*5+111w޽͛7Ϟ==zСC111ƍ{7a;v̝;WV :88Zj޼yL jCe={_|t!`HNN~͛7322hݻw޽G~h /^cLPWO d}Ujj5k+l޼9((߿߾}oœ&M9sfHHHUˈׯ733ˋҵkױcǮZZZZYY]xBݻ7nٳ'ӵleeeqqqׯ_yJrtt۷o޽{ѽ{wPtЄҖ.]zq N:3]!ܸF lr+W?GKOO?vؑ٭[YfM:U 4b *))qvvp>j(ggZZ?sĈ7nѣӵw}nݺf˗/_|9&&&--d2uԩ_~ׯ_Νڂ[[[N>}pE=|SFdggٳgw^-[6zFo4/]}G7xcܹkgooaÆٳgBfϞuyj DhL&S=N8t-g i1JJJ\r+WܻwfwuРA4|twה)S:`kkkKkD~֝;wΟ?ᘘH4eʔٳg4Źv/ܹs'$$>:u*isjdd$!/ܿ{.5@:rĉ?D"ѦM{=kijK.\pTBH@@tL%&&ٳ|>СF9r;ӥ@i4/9sٳ<oSNx9\rBȶm-ZP(.5@1aaa>>>dׯT*afd2%%%EEE׮]AAA} 4h3@KrsΝ;wիZ688xԨQ ޽H$b:xĸ3gDGGWVVGDD1_~;ۺu믿*ɦMhѢ_FC,Z(666..rQF3]W 2@kS3][nӦMLҌ?>*****5"""""bذa| h\BC,+ o߾ӧ/=z(666666&&֭[ڵ:t#"""ܘ.BZ޽{;wDDD,^xСLUW[l믋 !)))]vsN@@ux"*߿W^vb1zh;;fդ)iii'N8y---O]ve4hbccoܸ_YYܧO;883]#@ۢoܸNܹs޽ӻw@6t0LQQQ6lpBHHǍb9N:5fRJKK.\0djDhU7cƌw0]F+W~L pڵ'O8q"##W_3fù\.@RUUG>dXAAA]v СCL@\III)))wjz޽{D"|V:rHHHW_}5zh+z;wt%%%K.&voux"FK.{at-/UUU՟yӧO3fر{[h&z= ڵD"iOL&JRSS!$888888$$$$$cǎ-F +V8yd^V\tErǎ;v,!?^x1uxLf^*.%/^};cǎLPaÆ F+++SSS߯P(!ǧ]v,hڌ T*J322ӥRiII !ʪs~ippphh6'NܼysŊ#FxW֯_ߣGN,gffE BGdVh4ץ۷Zjz7e2ٹs.i k.--ѣǤI&MԾ}{K|sR*:Hs^^^ޞ^^^h SIIIVVVVVVvvvVVVFFFFFFvvh$H$s^ۻMuu֧~=iҤ:0]۷oϞ={Bѣ۵ksNjПoiiibZ͜9*Н;wvر{EMZYii޽{w5mڴzsLXPPY=CPpvvv{U,X,'mHyyy^^^aaa^^^AAAct\YYIzOOOgЬt~ia͚5fbLE/_~رBȲeN:tQ-Ç'Mt-59sfѥڵcơN:cǎ3gp܉'N>= $P),,-**)**ͥe !bX"b777ǜ֖ٹ@Ka4K+)))..6sssiXЍ$D"qssDก***֭[n:??~o}*yBfKjDhh;d2]K-;vW_MOzST4jSz޼1ǫKvtttvvvpp=֮];H4JL&̃iG?9ճ"ɉ8bw]`Att9sV^`˗/<oVdAdѣoFrrr`` ӵ^ݿ?Ӆ_EEŮ]~ L|8f./^_=ZB%%%%((*JlܸCK,?~mt]ߺumێ92`>#ȍAdo =z4Ӆs/ݝe3Oh41mmmjҒZw}788x„ ={ȑ#ÇߵkWhh(@JJVkZFSYYYQQhZZR*z^P dt$k0ʊ:rA( IDATOh֖x<+++>oii)lP(dX"f 'm\XXX||ĉ{_q/Hhd26P?"0eee۷o_t ӵ}:Mz}iiiii챲ɊD"r|9ۡCZ#IxU'4ml0wwNN]Y^^T*i@1jb1.3XZZnٲur|Æ rdsGdXL),,löY"$j_`=ӵ<ǽ{!>^`+ڵkkhk4MiiiqqqIIIcEEElذs=s7!p,t P'stt<~8ӅIf͚lٲ>Çg͚uҥE}׶M}FQ*z^$VUU=x ==]*fddHt}UPH4IB% p&)???P`p[YY]|o-))qvvp!C͛~ҥƭMA!666666::Bꤲ2;;%tD?O󣣣ԧh7xcܹ hYYYJcƌԩ#p rwwwwwׯ_rff&MGGG*r}|||ҥ ahTW^ ߿+W\]]qGGG[[\:vZcٶ 2lذW^d:hl `Xrիg͚i&;;;@PZv{ԩӂ f̘A*&%%%>F[VӧO7+|~#MyZ@9//)\*8p 33SYXX0U<T|ҥ+WaHNNNMPf 2@ qر{2]H]N:5333'O,J>7Y'P(̋*Jѱ^W*比J^㪪*Zmh4ұVh4M^7!6- ,˼hoooaaa^xVVVE;;;[[[󢍍 mYrFBO?tѢE#FXp!C_gjoNHHHJJJHHHMM Zxqppp.]ܚv2M't''''%%OلXL=zajN,9sW^=zC"B=zd2x 2@ qFoo7xBJ*o߾_tiڴiW^ jS4MWVVVTT(JN'iXVWUUr^O2O"2bOD-% >ZVL3 .<4 415i0Z&шMYYٳ[lhaaa0ߧO޽{-Tx9JKKi"7oTT#F8p`@ݸqcȐ!ݻ -[Z(77#&&o߾MZg+ft,UUU?߂RȄ7P(&O_'DS! ϧýuowş~isf3|ɓ;rb)..V*nnn("wbY|ԩSr9Y­[,Kwq9::>}:00[ dw*--mHp8RkRT&IoXfXb+/ϨӠ\./bSSӉ'm۶hѢw}wȑ bلں:QFd2Yt:vcccUUi4LFK8M8::\կﭞٹ3D"!!ՕB|x< !庺BlvߺW 7HOOx|fMQTJã3[("^'Oڼy3AN^^![7n3fD"_}ϩuVAAAaaaQQJ*)))...)))++b\.山2vUcf[TB0`?ٳg[qЖXBbcǎ;v}-Yd֭l6[(|'''fZۿ===On=ږr877nhVjkk ʮd2Z,^Oz=+`0455BBX,P(-X?MGqvvvrrfkCH8nS.]zO>dŊwPRRBJ("wתUcccrw<==q]9nܸ>|~pO~~~2'44v Ba?۵k3,Xq/3(GGǩSN:ƍ˗/^7xޣج9]___SSClnjj2 {U+++f3iKWWWBohh[VDtmfM"6iCB!qwwM>;  X/7oƍ7ot. >*m7NSv6 &Ij4ڟu:d2P(n|ggg@`pqqqrr K/4͛7wUVVFP=Boqܵ'Lo>RWWgm>:JedddRR/d2"FǏ?8o޼yr[e˖M6]v1..;v,!b]v۶m׮]# 4Y,=n4[f٠ʕ+wܙ<l7ZUt~CC;S~D2gΜ^xh4~ojjzWL>觟~zjccud}^^ÃtҚ5kΜ9SXX$$$нfyΝ2dȜ9sN'9rd׮]+//~G}GG;^r箺s666uwpǗ[RWޥ^kŊg~WWZP0k {E̕&fnTWWUWW F'gٴ& ]o nnnt] XwD"'iӦmܸ#EdLf]9;;[,p2 >>>~tc-\s'$$(~ 7ܜk׮]RvhP('M}1c0x٠bX,'N  m޼l6Ņ,K^^ހ:wx]]#<8r˹֎sss! $$$44t̘1vtd>w޹|'I?#ϟ1cSO={^vѣG^~3fX,͛7oذ^~~ڸq%K~?ꫯhbZh'NرcȐ!۹sq$ǭVܹ>1c}V\9eʔݻw/Y͛+W !F;wnllP(u֭[a:gܸqǎ۸qK/d}9XOW~ڎ\oߐk;l6L]t:lZ'D"H(H7Ѝ^~˗/[ ږe˖\=XMM}3(":zǮ]tvҥC_cw9mڴ;w>=QPP@ ǙYYY׮]khhpppP* qrrb:,@gϞ~`0;E Ϙf<<<***m{rJLLF!wSDh4td2q\e6[=Q:N(B8Nvɱ~ DZѣ&MEϜ>{gZcmذA"R6mlX膋 ]{ix]C:t~ߥ^k3g4\.,}[˂^tѺa}~!ZD,I^'O.))jgK/ן?~Ĉv ٟpͭZ-dBHAAŲ.qjz3gD 455egg_t)---###33SB"##njC pijjbXL+rpp0&va !mB&ݵzmBhoHY[ZwGG}?uuu-++ NMMqƾ}~'O۷/::?6Lf}8:~wLu.Bl6Zpwwwww|뛵moݺenW ϧd[֚{_ 3f 9~3<4LXQQ!J !EAw~c8tN*,,JٳBez"Ub6sss/6 IDATvʕj3eʔ(`J||7n|饗sLKھ;NƗ.ۇ pϡe??LnhhZhuuH]]"զ2}JR7-:WWסCzeeeޤ4PD]V^3 ;_~eϞ=ª■?`08::FFF͘1#66622%^% W^;waÂI3f8}w}0eʔ+W,^ѣ=ܙ3g֮]Ke디Z0}&M:Ex<ݻNyuO}%˖-p8SLٽ{+!'O&;v޽_~ܹs !۷o_jC5kVbboAAAJJ !&̞=_}t:]tt4֭[Nڸq˗*7/A]uǏg:ףMeH^^uvy~WWWz~ۼhGS*ƌ}d2BHiiiXXh4vp7ޣO?}뭷ISNmllܻwo1L?c[Ξ=zYZr ;vJڻw!C;x`q2qģG65iҤCX,B]v|xbz:Bg6e9s?i[#?mvԬY֬Yc0`@FF}vі?V/'ix]C:]|zŲ|w}w…|qZ\єYjQ[[k!Ji_֔hY BqǏ;v[ۚcX7ms){_=C(""__TTw?61bСCZUɯjzzzXXX4͹sRSSSSSFF}LEE3r3||||Mz0LAAAǏ_n]믓'O9sܹs999'::zĈ#G9r7fŋ?3e˖%%% .@V!UaŊK,ɣGf:yuuueFSZZZVVFGF}}uD"dL%J2Ņ袡C&&&X9<ҥKǎ-^zǏ_v~`:H566v\ަm#G>}ȑ#a?@?N>wߝqƍT*Nvt~A"⋯J`` ӡW?dꪱ"rw1Ϳۺu8  |ٳg Bs:HVtMeuYedH$r,ɼ||||>w͚5`0pܶ믿q){&7"2@tIaa!!+"۷ƍ?cObL]]/?~<33bEGG?ƍKLLħȔ _~ӦM_~ßy)S( @hjjr?}5kL6 @׷t:Jt%%%*>^|~*,,$ ];Y$ 룟)effƶ5G&U=<<혮_wR)S%l6[.wp_|4hРMegСC:zhuuǍ3F"0T*?O?vZ`9s'M4qvf\|ޛwTQQqџѣ?|ɰ0tH$D2LjT*׏?^VVf2L>o]GYPr___\l )$$ҥK!R.@pWPD`^}}ufΜ%*˫Z:u?8}tOŒzСfggN8q&Lg:F\.w„ &Lo>?ڵ/Ǐ8qId21.X,˗//\@7HáVZ,Ⲳ"Z]XXx٢&:S,rv= *6=dȐvdr$JZmSSͶ[~Ed޽[Ϛ5 ]URRG-_>>>>11G#(tC8p //O"L8>0aP(d: ')))))r#G9r_OHH1bȑ#O^`0?ٳ.]r3gb1z%d2[P[[KWP.))ˣǏ/))Ϸ֔ R d/3A&fZa6u:,8q#FP(wf:HWM>`08p3iMgժU?{555:ujǎ{)//|yÇ3oN:s̹s.^h4]]] 6r#F$$$3u֭ԳgϞ9sիMMM~~~#F>|ѣX,’bJUTTTZZ@ZWP^ƍ̙c0ں΍7lvttCBB KKK;wǙ T*UpppGfرaڴi=Y,ԝ;wڵtoԩSJ%qvv0a„ !MMMΝ;wΝ;.\bBCCtd~뙙%%%αǏ㏇;B@> .+++)))**RT\TT9ZP2OᅢZ ! /PD`냃GtnPRRrwdw}=M&ÇwO?t#FO<j`l6;22222.!j/\'''fH4xHNǚVZZJ;Ǚ IIIC rL'Wttt]yyy*$///;;~+((sD"R BTZQ9Nzzz[Ed777>_VVF?j O $^xq oJ%8ڵkϟ_p"u͛77n߼y300W_:ujTTӹ&'N8qD6&\r… 6:88lf65557nt//O0wߍA}#7oll,***,,/(((,,,((HMMݶmmAߟ?`, W8;;eddlhhhTT'#W\.w hK5+(_re%%%MMMGT@@@@@?e EDDdee3A&Bb1ȝovڧ~Z(2T*Be۶mӧO+H[n'O>z1czaNA 4vܺ7>z꺺:Bák̀膷7>Z$IpppHHرcNNNC$D71LEEE }|8AJbٞOۿpppXX}RO~Vj&MZdĉ({,{ȑf֥͛dU*UzzzYY]H$RT*MCCCyyyyyF))))---..㒒Z:Y;LHHxgnC.l4n۶N*T*H}||m)@w ihhuV```Rijj*!D,kZ'PD`N۱cW_}tnSRRuǏKۗdHy+6nꫯkX jhhPՅeeeEEEEEEeeeiii֙l6HJ֧,{q[TVVjZZ]~Zh4֧eee:zzzdڱ A2L& 6xEELoRqС kVM|>ۂ !5 !D,5\"23npytRL5k'R|8??~trrR(J2,,,<<\T*J??n. @啓3iҤV'HҊ "H233XfʹiܘmjWs81rHDe2m۶r˗/?#رcQlt)vX,ZGhUh4999֮jcc<E$|777www>/ ]]]]\\\\\B!twwr@''',܎J٬,^ojj2 FFUWWWWWWUUUVVJXSSC+N%rX,lbV]]]dI$666SSS7md4 !\.W^'$$$77Rd2zHj@gϞvZJJ AZlDfݻ?䓜~xʕFg X,T*J;2h4.K z޶銊Uf9wtttuurnnnG 888B) HD,K(*zڎ;{XUUFCCCuuu555555 Fd2 5nzH$o>* |mn/rivd2vM/]cǎJBͦ E0)88"!Dш⊊ ;?PD`ڵk lNV3ٳo"Y,Ο??++멧ڳgϠAinnnnnn~~~w{ m$FH |MzknEm7z r#UlH f/p 0`cڎWTTXyyyׯ_?p@ii)!b~#G.Aȕ&A7Vk׮իW3"/rǏo<'O|w.]裏DEEuA......\wsdLl7ɝ[p܈MD"HmիW|zcH)\\\\UUOVmWD?#q("[JJ {gjuE_511zTNN{キ{ŋC W{]6nK$f˥%c]v RHHbqFLLL˽& jQD[("[rri ǎ={vt}ڵk|ĉ￿G_/jl4oܸ_YOEd;IOOxҥK#***Ri[{O<)rss{e˖_§}stt,KAA&֭kjjrqq 䰰0RU!R)-"b;Ed;ٴiShhtj%I[{Ϟ=t7;v\x1**'X,|:ԔwC-\"ctt+souppϏiW*UUU⒒PDwy =Eն"sf̘ŗ0L[lo_8;;w lRT*ǍgTTٴ\]]MZTݙW~~~{駜F,_zվa߾}F^b:HOj!!!RyyyÆ ji.\m۶iӦuT( BN5yƍUUUHd%GDDd2@"H$j `ɓ&Mb:HOj"]:thO~Չ'Nf\k5955uӦM,+ <<<222***"""$$2֭[% ȝ"2@ᅬ;~v.\ ŝ;ɓ''Ot`rO=illq&geeٳgҥMMM</444"""***222""ϏVw|F#zlvpps> EdyfB1qDŢڪ_paذa;_|I&mݺw!#[\nXXXXXؓO>IGL&SAAիWҲlruÕJ%{緵CĘf^%M(",ټe˖ӧs8[0h2Zl6_xq…8w}/Ϛ5kժUc"T*JeRR1W^ 'eeePHWhWWWF@WTTF77{Riyy9Sբ|WoK/1iZBHߐ߸qCwbEUV[{ Zpsst:W^ݺukMM !D.Œ#"" wߟRXXrT*h4Ί@{PDY6lBBB҃)"_piwuuֽ[| Z`O"(11111>HIIQTwwȨ訨(, Л"r~~~EdF#H>PDA_f AzV;EK.EEEx={Gu[DkJOOߺukuuC```tttLLLtttttL&c064& [+JoܸPD[(";vx~ =K988\S??g}}D"=zѣ#**---;;ի)))|bqww  2dsSDh4P웫Cmڴiʔ)֜ZV(:884X,SNy*++|Ʉ׳X  BHJJO CFF%mٲjrBB5)"b|PD)iii_}AzV- +++:x3gVWWo۶''&&&&&ҧ&)''ǺdW_}U^^NqqqjrXX~+GK*ƺ:P'=et:]E䌌 "={vyLpí#**--Vw駟Z,@a&GDD8::2߻wo$ !EN@G466nݺub6m'x3gΜ9쁀BP(IIIiaaazz+W\rС?}qqqqqq!!!l6}IIICCkZH$Bn #>\^^/2-333;'|>Ы>#iee%%_|رc_druud*44do6Je]+"_vt}=bӦMGg:=hڈ'Oiii֭۲eP(t}@ 5jԨQSɔvۆ jjj8Nppp gggfc~EdXb"r砈 4O?t;tb`}}}NNNdd;wnBB³>3* /B_n%ٳe/9663בJNNN-wq877 @"B}...O>$Arpk׮LO8qic6N/y޽%b)VJ$V+u:u("t'|޹'&`VVcPPP;Z,?GMLLɀP; t:6b%ĸ0 BRK,kZRi0l]("to vRWWWSSӲ\aÆ pOhKnhhʢ?sΝC upp`:8SD+"D"b0Zڂ"2@7۲eRb'ZҲ˗/?~|lllOWxW^yH{ŋ7l>taÆ 6,>>^&1)ϷK,WTTBB^GPDN&)%%e,,v!-ə Ed믿j4ӧ3~Z]鯿 n+W;KGl6;<<<<<^ TWW_|wڵ`B\.OLL!C8991KKJJfC]tEd^g"]_"2@wJII1bb?CBB:l޽֭|p'...iiiA{ɋ-*//rAAA^rXXؽsC07 Ecccyyg]bX Eo "2@ܷoߊ+bW|>B)"o۶駟|6D4̈́^oX!Bxijj2 d4 !UUU;u~rܖC !...nnn" Y,XFqttggg*LDT3gΤ CRåR):BPBT*U[Ed 4Z`0>OOOPQ6lxghe^F[UUU555uuuuuu555LgҺlviVih4LJb-lwwwbrw_ 899;99z{{;99:;; '''wwwioE?cAD'e:S֭[6ζ-BⲇT*+)))))bXrrrΝ;wݻw/Z0bĈP QLQ(OZ-w Edm6X!<===<((Wioo䚚N,++ɡ---rL+˕JBP(>>>J>>>>~ȑc:/16 b><<o _=z2fCߨ"r{{={-[Su?t7׷;bD$X,fX  RT*wbTTT={h4}';n@!6… ǎ_{5WWט1ch4Ht^J%!sNDͭxPDMm;01]My3gT4Qbz`X cÈ;wuuxޮt&&}֋kjjjkkmcznjjr>>̗^zE.6l6yD] ȷEd$55UJ$0f9M&SE;w>>!??իW^-))JKK[ZZnnnRT.d2ZP(}||x<c,z d2oێ+B\B崗,規\.[pZ:YEUBs888xbm ^`X BP1]555.]JKKsG(Ji#ٹLjZVKioo?w-%Zjҥ#Fh4qqq6(7nt^Duuut"^\}w`01Ac2!E䢢"BH"˗ =Z[[/^{+W痗B8NPPvhࠠ D"Ytbt΃i%33Nt]\\|||JR0L:C0vi}~<;9((WKLLLLLK/d._矿k^^^QQQqqq&>>.8&".DQQɓ'WXtLsv}}};O?űv;wܵkW^^X,4iҦMz!\?JRt΋vƍtp|ȑ6B[@@@ς LP^h4AŴR<عylrbX&2q X,VPPPPPФI&N|+Wo߾j*2`pK߿?a0?-\.s)9==gXZVь3Cz9LFz^ z=x Nd'2afynkkkMM>6رc˖-t .l޼955BP̞={ݺucǎE3aXJRTj4⒒GZ,Bu&SvL&SMMs1q)!HGN2Xiii|r^^ŋ/^gB<<P(ϟe<8n6ARSSS⤸x߾}%%%bq@@R m;O2Fx#G9ұR[[%_xq֭74D2dȐQp)X+Ribbbbb"!ȑ#JMM]rw||ǍzWWWPH@((n[jjjPPP.d29ODpqqqرcG{-n;vlӦM۶m#̙37ߜ0aa:FRT*:_(2jrIIIEEŹsSYYiX>"6JRd2bML&N\&vn;V?X,a pOI$Ǐ?ޱRVVvҥ8qO?W2dHTTTdddTTTpp0~sx'xRXXx#G]/H$0aV8qbHHI_"r p{v7| BfsǧCY6++h}U}}~iӦ!C\ L>2dȐ!v{uuuEEEEEEiiieeeyyyAAѣGʚn֑JL&d B.rXtr4emjhZ"&M6NM>{_ov>?tPKTծ&ۢRT*SO=E?p@zzŋM&SPPVj&LJL'x@d.B磈}("ܞ'OΟ? l tlVTT9@ Çh}ŋ{/#F0 O,cbb:[___UUU]]} NWUUUUUΞ=t:j{d2Z~upJRX waFc2q7ƴL1}f̞=Ƴ?;rȇ~NKQQQC x&4hРA^xBHaaazzzZZڂ ZZZT*g8'l"P(4 < 'w'555,,,22 lv.Õw("ggg.ˎ@YݻwXnݣ>t.xpD"Hv֑oܸQ]]]SS^pѬmoow<ĹYr\P(7c|6SG]---F'zh4L&c'޸YX*vn278>?vرcMrEKݺulfXGBll&Jܜi&IL'dsu^zBL&.("w=sL-#Gt!;;;$$|NqqqbbCƏt([`Xr\.GDDnt`l6744TUU /lhh ssscXNx<yfX !zhXh0`b{{{xPXq177hi rsssssϞ=߸q"˝{!!!.pIkDŽÇ+W=z47::b1>$ɺ, }sh4("܆ÇWUU=L-gVVV*J z;G[h4.Xsp7nHHH7l6f`hhhX,v`0BZZZ !Ff9Zmmmn1Җppp0rwww, HG5Swm 7o]rKWZeٸ\СC######cbbq%YL"̛7~o/,,cW\tRT:n8V;iҤ@?d2YuuuuPh0x<!h4x> EdېΨCSSc"nPD>}D^:%%eÆ l6{饗r9ӡ' B!)ɓ'Oe׮]mmm@"cquuD"aَΞ=;}tf1ĉ/S}7x# Dƍ7n8{;%GFFzxx0Kj_oҥKU*kʔ)\.L&<յ_~zL&F9("tKjjVu MMMEd\6<

SE("ڷ~;e>tޅNDvꘗ \䱰O?0իW2nfzdžh6F#m2o򙽼ݝWlvw~hoo7iH<_~\.q_~|>ۛDBST**))鲲N8cǎD>>JR&d2\b}O&͘1^Sj^xYYY[lY|9Vմ11qƌ>>>L $b1LU^"r k:i&F͞6=]QD#DDD09vO?]YYf͚c`k׮]~իFqqq[[!8pĉJj||>zۻwoEEbxЏTw bO?4!h4feeeee1dQ 0?Y IDATz5>?iҤI&BvK222222>%Kxxx 6,>>^h4>t^KPPPrrrrrrSSwk ӧO衇p9ͺ:J. Ϸl jfZu.555yyyyyyjdw[fffRR^o͛t>%77ܹsv|DHLL 8p?GoaAAAAAAZֱhZKJJh/… ϟOMM5̈́ZJ8p b.>:,KV BL&өS222233SRRZ[[ E\\Vh4aaa9cm6[nnnZZ-%{zzN0ޥP( -i.3Pxh4|K("ܔ^߿ K577;&"Ãn^r%::hwM[[SRRƍ'`2@wlSN:uɓϟokkׯZ:t?L"pq8JRz!bۋΟ?O;vXzjÝrf@oZ-=ݥ̙3K.r|ذaqqqfnnnLx 옘+VTWW۷oݯs=5}h+\D3o2Fߟp} 7{n5{lRX^vf(]SRR㏟>}~l6ӉzǏhɒ%Æ h43I{_n]^^ݻ>cOOѣGO>}ܹLDRWWa;N1L kPDdڻwtޫ0sLD~:!d%m{j˖-sqٓvСZT:nܸwyg„  b:a>>>3f̘1c!bdee>|СCO?b {衇fΜp Rjz…tԩ̔VBAk40z]w%K޽{݋/hM6ś2Ed:otmϞ=VuL齜'":]]]v}7c:@/RYYk׮;v9rb?~ٲeǏ@:771cƌ3GSSSffCv޽vZD8{섄@oHNBh<~xffc?677Ǐ3fرk*_|f}v޽zŋYf͚5+&&TS$ImmmE.j0x<݁"2@׶o>n8XtޫC9**޾~z@@@_$e˖E"5]g4⋯^^^SL/NCJHHHHHx]cǎ;wΚ5kI&lc@/L2eBb8v_ѨT*ǍGKɃf:,{ܹs%mݺu֭oX,:uyz!wwwcE"\t:7 |>E]hll̙t^ӓޮs_\;aX-Z3ddd @MNN]tAvUSSw͟?-d`ʀ/^|իW߸qcԩ XjU%=ziiiuuuٯl^tihhB?/_tRZ^bE^^^AA gΜ)7oތX\WWy](z>o2z>U"2@X,]sE#FYՕDLjooꫯF}z/>}KP</^>}ot48NLL/?+Wx~;,,;!!aʕmmmLoT^z)##7!駟qqq֭d: bq`x8E;PDR)Az5Ddͦhy޽Æ ZO>}:qdZ⋰'|RRъ/(pSj֯_ʕaÆ͘1ud]**))iÆ %%%|J裏E"lXN pONNNKKOJ7+V\rpH$vúc"2݁"2@G{8[rLD6bb2Dn;cƌFGG/\p׮]/5M`X,7 &lB̞O<ĩSo2O3 $G)oKII3gλ{ytRH$JJJt:Ν;֮]ڿ:;sOI$6aNDFPDh޽("ߒc"r]]!_N0`nGYt믿x<0gj**//w d:0>{>lZr۴Z#Go~DЇX?۷op?6o:T&͞={ݺuΝC)JLLܼysmmcǦOu 1͍b0!RRRT*ztiFx1Vt&?~"̙3$www9//kܹsΝӦMkiia:WJy6lPPPpҥDqƄ3fY&''"6+??ŋ.]N&%&&nܸQ1ZDWuDdEd_8p@cc9sX,O2 BJKKvScǎ8pSj5qTVV6i$ZO?2X,zڵkGbcc{̟?_PZv޽SN y ΝK:zB6-<?, ݼy3!l6 ׿՝ŅK_ 3+11СCNJJJb: ܷBCC}+W\;%%%66V*Ι3>|21+jzŊΝ[dIyy… ,Xgt@\{QD>Vߺ`k׮eff2pss}э7.]2sL.W_1 +{=WWW0,11ڵk'OLgvuhI7_}շzy?SL!!C;z?'y֯_rss ߼y?NټyO믿vף p̙ݻw{|3~ʕ+=:~xB;CZn]qqR\vmYYٿ۷;?_~@ xϝ;RSSrssW\I֭#hkkkkk[b!di!Bƌ?744\xqҥv}u;wtOQQڵko9wZZ… BȡCnn;v'[~{'x's@OG[>?}-[Ì;yȑ#/^V4vFJhMt@G]]ݞ={nݺo>j͛7{looo@/2qtO?4nܸ]v͜9Z&1O@zꩼ,FQ ݻwҤI-?xlxiiiӧOg:N:d2ٳ'""8ERR`QךpvWWWe!B`08?x`Ǔ(*Xqׯ_CC}TEEZŋE"Qw۷oԩ4L|cC y~ z^ BVK6mZEn~r{o)Vٳ---*J3ǥ7B#~?lٶnx٨(ZDvZHHS6z]v͙3 }b!B 9Bp̙#Gfee L ts^ooh!BhŶ !Vjl6xlccc7H$\]]R)l3f3L6j[g1!v;tffrpbbb,Yr<ㅅ>T*]tiZZlf:)@&Ҫ6l@ye2Ybb͛m'kkk;,| mPDÇϛ7 }Ckk+XZZJzM'N߿zzRd:@2rSNUVV2RTݻw;VvCTT!O?wűے%K]o=e˺ ??b6mtݭwfΝ;\.NL^SծXeee_~ettmf̘T*M{,x@D_i$L&$͊s%PD۷GGG1oEd77749/2hΝSL8qb1qz9s/LfϞMIIIyw+**֭[yEB5k:th4;wޣ ZBHzzc HII9rHwL8zk׮]vڵk@:¬Y; 37|'qer7^~xݺuB0%%%::Z&͝;?g+鲑,Hxu9X,z98LD%VJvB+,],}åKj CCCy˗/_|Ν.\`63,X`@{>}?,byl6w\S?@BB{ ܣ"+mUU^{նm׉[[q{Չ TVa"q/P !!y=++KymٲEw˗^Oj4T啗?f <8uTǏ5cǎ]lٲaܹsnݪ].۔7,5eFjdz6n=(L6zׯ_?{Qxŋ.]zjiiNо>}رcϟg0!!!ƍ?ށ>S\N>r7mw)SP]Dd [nS) A, 믿~g/A ʕ+-Zf?##wN4mɒ%6lw+W&''O2{\.{ŏ="b޼yyyy$[AL&"A,Z())ٳgTi7ܹӯ_laÆuUwc+ǟ~iҥTeh_7Q9=~xҤIUUU[޽" ŭ[/\C==1" ۛN\*TPPpĉǏ_~0((h#Gק:4{BPwH$4iҞ={͛W_Q]F9yqҞr e2L&#'"b;;;Jh4y۷oG .]uY4+V޽{h!@?tիW߻w066e֭{4iݻb11kk \r%// vZ: 4SSS JKKDCxQvvvpp0Ab AQRDh4 ,صkסCfϞڵѣG?}_~q``۷NԆқ6y)]vyxx_x񢕕աZYhhΝ;srr^x|򪪪(HP]]MuLvj7n(..駟={6tPss9sܸq!hLLLJ֓Ed"B:H{P(`X<T&شrJ5eʔ={LJt ~zEEŀFu5Cژwwy=:==}Ÿ=tZ]vO>]^^~ӧ?64 IDATO>|MRR=4iDDč7233k׮ <)**ÇT F#+**( ֎IXɓcǎS[v"2D"!ں5c()S>}ٳ#Fhͻxxƍ .d>[n7nl8S47o m0҇KIImll-Z4r[ZXXP M>>>K.MLL,--=wRRR@@I@@@tt О8::.]ŋϞ=O}||<==WXk|>?edd<~888 \.g0 C;h"L&?~sΜ9GtlW\III>|+lmmg̘qR:66vРAݺutҿm۶9::R p8?l"֬Ykcc~رcmW_e˖.]nذtT&&&!PD...N  : \.',"X, P[[;~k׮9sfsH$ڰaÌ3|A͟??))ITR:?s޽摑...7nx… NnX[[=zQQQӦMo 1:>hР 6SND& 663>@p"2D&B:@&IuvFPX, ***lvAA%Fk{!!!ׯ_4hP+#@TSSAD"ٽ{w@@#b*~~~~iRRsHHsJJJRRRFFF]]1:t&MڵkX,:&@;bbcc:fg͚eiit1f4,"y<&"-=Pƍ:H#"ree%%ʪZTN4ƍϟ8p`+#@d2JjL&hӧO766>|ĉ=<< MAAK._|ҥL>zjQPPrb\.ё|deeEc*355 %"55… .\XhQUUG@@#pN Ц?~<66644x̘1ÇoiDJldd EdLѣh< Dbmm[WWttrnnn&6i4woܸq+VX &̜9ȨuBǑ\zҥKL&o߾ӧO>|x~R,...((X,{nZZZUUy@  B bllܺڵk׮]?sBq֭ .lڴd ϟ[kkk 0o Z\\9މ\.'ltڵETRM4Կ/ ͭh4Rhllx:ѧO>}QRyy{ܹsݻwJ$9jԨk:o՘LOIҌ|mM͛ l[SvrreСCz۷ooڴ髯255mmmN v988DEE}w9x#G6n;eʔI&|h&"d21o輪ϟ?~zK \YYIND.((6lXݣZ9ssΝ;2@K}9YLII!i4Z)ZL&SV/_ޞ\9tL&300ѣH$244lŇmNaaӧO}̛7 f/@Ӱ,夤캺: X,m Bz8 ȿQQQsDAAAC !O]zh4Z~nݺCX⫯9rdxxxpp0ͦ:#@g$RidFT<|ԨQ#GĘdF<<66v߾}',,lڴiTȮ]GI$ ڶm[dd)Lơ ԕ+W .]P]5kX,>2p@Xlgg׼wnݺŋo޼y޼yͻgNdff322bR$B;U;=ر#22 {{իWOXVVfee`llLu:˱'NPAAAaaaGӣ:@ݻ?Cw5k.\xCR tRqqq"-&Y,AFFFEEE4ٯw/~@ 2==]sիr \H$'...Upss?9k֬Vh4{{{{{aÆ鮯!J,,,,--j@ h~m\.JڷdX"UWW7hd޾O>&L -ZLoX,CR4CG~~~RRRzzY 4܌?*<<<<<ѣGOG{?f̘@`oܸѣ&LzAu@Ą ҂|B BYhh3h4SN]z5YګPZ}1P8gSS%K5]$$$?~…K3KMM1(pww''k  SSSV|֚msٽZd-x<G.|###.k``@գkꪪJr\_s\ZZ-l6YධҖ1suzwB[_r%>>>!!|Сd)ƆtmNjjÇۗ%?3sssst555'>>~̘1v:uUV޽{ԩTkPDѣGzsN>}^?̪;wzڵQFMLK&׷LoAtZݫW/rG}ej[J>n8@111aaa+;x{Z*22lmX@gggg׻woc*|vc KKK3|_YD622z@hЪ233BsFIuv߿K.+É߶m={>p**00իWw177o %%EsP(֭Dvvv8(Q[[;>YTRAX,[[zB&5bqBB©S._,z5vرc Ο?o߾SN<}#F0 ްaYf⚚S:uϏ J#pJJJ<<<>|WvݻwB6:55U[8&|CCCPIՑ7Qyyy!999* &iffp;˥@'booY]]xm۶vvvAAACeXT JGٷo_``~g9;;S311)--?{ԴTō=dR}S*EbSS;wVZݽ{juVVn8%% :$ӎmll >X, AHz㓓ӵף'4 h!gܸqƍS݋?}֭[F9vv]/^عsÇϜ9sܸq8mmAIII|>ITй\vTiT*ɬ!аgTٳg:tѢE͔H$gϞi ϟ?U(AnݺyzzcHԥKz@ 񩷞,(NP~A||D"h4ABN@A۷oUVegg>}:>>~uuu~~~!!!ǏG#:9_~eڵ.] 3004iRXXؠAN昘[Iy<^vv6% sIHH`21A=J_QQaff!;ܰaëWNbŊe˖BVgeevSRR hBpdAOO'6M:^Tk'㳳CJ A ]t8r'OQf===BaPPH$"1 m>>> ʵɎrVVZ&bOl{N:uSN;vlΜ9#F ;v,iFGGwÆ ?~< Jʺ< ===LDn\fT*Lfuu5.--e0?OMF-gddT*, }||α=ɤ:5|X\o\.ӝqMAL&̬ewwwWZҥK.\hgg MN:uٳgϙ3g̘1aaaFק:#5scbbBCC"""t`&&&AHRݧ`0zP ϧ:HANDp8t:='N8p`4{HhwjjjrjBV\.^[ڵ'ON;~5add&nY.]|>ՑX7 J ^|z@ h8A|╖Yfݺu3gokТjuyyyo\^^NnF,а)d0@&xR7nIhhiOu@j쐐Խ{޽{ڵ~~~aaa!!!Thq5KKK+tz]]]MM á,_Fh4Tgh G?~ܽ{wtVVV˖-7o3gݻ_|޽SC)ʲʪ*w+++***Je v IDATUUB5552y#x~G;v͛?f̘ѫW/sPIPƞ={ㅄ̟?ϫ@VRRbnn~CjWVTT_~/,((.`@Y$$$899RLT*544,++{f~~7oތrWRRRPP Hl?-h5 Bd2\.Y`V^5lrR]YY ][[[TTۓ֭ _ȕvvv t&33S;bWD|||򱽽}YG OOze2Y~~匌l䪯oggpۆfggh4FC޽O>YrGK?T]]-kjjd2YYYW<"+d<}:00Jӓdl6[*ߌҭ[5{TRvvݻwYPP@aoo_,^Xwܹs/_~ʕ>lm)l۶*))bvu֭T! oN{8~xhhW[($x"---55ŋdА쬽8Ϸ^OMMD")))![X,&Or]v \]]o'lHcXL{& αyEuLH$ȋ/t:]xzzٓNKҲ2TJ.TWW7܄ H:K:px<U4MYY9a\&k [ccc@]YX,>p={ӻw>siӦP 2jҥK111;˝8qbTTH$:O~ݕ&LXjU||1cơ BPP8y$A:q .ﴇWUU%&&PNɓ'>|1?...&b{xx%QKKKv:J2'''##իWiii999`8;;^zͭ-NMMIcCCCI;X(b1tr/*++kNkffm.b(VgllLZzuOw"T*Uպ{p8 ;ffff:ci47oڵرcuuuǏ5kְa,??7o>|xDD1گ_^we߾} ~O4lm@WSSs 6PPjdd26]VVfllN{())IHHعsg %$ʒ>|VWWrw.<<~ RRRϟ* ,--B?9N__P*EEED"r t]t133077-7rAtU[[K!C׿=z`D@|RP۠=աw괛x< ;;; +++kkkkkkܒ ,+$$$$$D,x޽{T8\ӧlbffvN8{2d#GZ.dRn߾Mo߾ 0`NNNTgP(RRRݻw֭7oz ww4hȐ!ŋ1Y>JAD"r±'9]+hJe^^X,.(( ƺm㢢"#\.Ғ%p8MT=jOk'blllЬK.N8qܹ={:e߿m۶ݻϝ;wڴizm… >}zݕ3f())y)S/_NQEd4ٳWXAuю{9#X,vrr:~8&"7ٳgϞMLL,//7110`k``@u@Faa[nܸq֭(.]~GYYY㔔 tSα2@VRR#ʒH$j faaaiiI mѕ%%%{ Pw;T.**bﲹ۷oϞ=_:thddL&չ(sڵ{;v`̘1c޼ynnnThG\p'Ojkk MU6EdݻKuŋ˗/ڵݻ޽ۻw&nvQ"Xh4;w>}ٳL&sF9rdqCڵkϟ?w\jjJR*AX.] X3_EEEiii_&d v@USS=I#/w&ͭy<4͕+WnzISSYfEDDS 22ѣ֭{葏>ydmڵklْ;s@ ؼy3U8[rb1JH"X[[_zu^^^{}e.]y^|}}wѢ!ۗ>|ѣ...dxذaj!4Qvvnݚ. 0iҤ /_|2===--\(//'biKNNNqrrDOwR\\=CDy{{{JXر#&&p̘1#FT̃6lpaSSӧϟ?i@[cǎZ*wѭ[7>KU6Ed׽{t(b֭[g񉍍-..nլ,gg礤Çtζ/777&&ȑ#NNNz:crܹsd~~~SL2e>h4O>%/_LKKH$A0 GGG7p(Lb L3K&ĄmJ:y-[\" ̙駟b5ЙI$}m޼0888""ߟPϱc&MT*uڲeˊ+jkk7 e("@G&Hlmm`t(Bݻ'Oׯ߁ E#mڴiŝwݰañc&Nگ_?fTSSg sFFFb@2PٳgO}ZYYImL.,6B*'޽{{ٽ{wWWNرc˖-G>|8N̙37nx񢻻{ddY8չKLL1bT*566֮ܿٳOHa Ed3o޼<ټ^|ñc4ԩSM?rGiфmRׯ_>},Zh„ -(Dm۶m۶M|xÆ ?899Q : \۷o߼yA8::=zŅ`PSuuuJJǏ?~LtH$8pJu̶ƍ9q͞1c… tBu(ܹs˖-|…  Du(=zԣG˗/}WgϞ})2:Z\.?1cT* d2JxMիGntmǏ 0y>|-wEUkl秧GfMefXӧO <<}fϞiӦk׮edd:thĈ)))suss6mڮ]233KAiϞ=Ͽ|r]]YA@@@rrڵklvE@{U\\o߾ &?&&!&&&;;;??ɓ˖->|8AK8ujkkz(i?'N__^^^%K}B%***))IPP2˗/=ydMM p(tNt:?>>>55u+Wtpp3g΋/ˠ Xa%$$xyyS"4MT*[2ZRUUvׯ{yyQӉ'ؽ{L&h4Ah4r[`AZZȑ#׭[Gu"hOrc###kkk_z{iӦ988PY{9iAl޼eKOO{ŧO.--y)S]`nn>wP2t:=(((11199oΦ:56lؐrw- >>G& 8NÊ3f ):&r+EѣGl6o^x1;7nb>N:v%K|TV"HodFFƢELMM?^RRQgΜ!"""S:&==\2999;;{ժUٳM:(zz}e˖B0((())\QQQ'O$bرnnnRhБr5dY|EEP^x"r !+\d\"rbbq>}Z6\۠P(ƍWXXx ooobΟ?ۛḹM2? ?hm6dsssss!CرCwy຺gϞL&\Ѧ_M3gtww744trr }>"JtRkk)S4}dTRh"ggg__X *++/^?7%3Cjds:;;r]? ͈F}֯_믿RZMQQѸqLMMgΜyeZݔg̘9rdܸqeÆ 2551bŋM<ʪz+_~<~ӽ-~TEoV()8&lj„ 4[nolZݭ[7ʷ=ﺑƏ}n'qsd2M\?Px"o%u5OS6mApp?zhK.uvv_J%(caatҴ۷ggg 6ɓM hƓϲ~|@32667@OO?CJYҲ իW xAsiV'N llmO?dhhBm=VGCjܜ\` 酆[hW6񱐛̛7OwΕM?rΜ9ڧ IDAT0GM0Awx,ol{722{%˛Ih9?JMM:ǏsiLLL޽&;wxnnn{U*?~~Uoo}:):K.q} udɒlecczTVVr~ sss ̙ѣ'N 8Plݺ ##/ѣGɋ-rAl߾ܜ|-bչݾkC 9uTnnnUUճgϾ ;Y~X,^nA+Wl=9r'N$̬ޚ/"5F'NalliӦǏ:tH$rJWhh(AiZWss?n/( 6ܹ CCùs^tʕ+#FмQV}@*:::33ڵkÆ #m4{1ێ!DŽM9ڿ?A BP8;;o Qu#ލ|c㚺7$ %{)ZUĊzPGV*8Z:Z.Z]ZԋP@DLBY?ν2"" NNNo'4޶G7Y6|d~ǥ͛OoE+,4= *s[s+<{lȐ!(gϞ-]^t"cbb BQ'aڋϼym477_r%EQiiiDR)?<.bĐe]r(iZӸʢ(iK__zE:;vٲe˓NQTҥK_V_om߹sFCCC|ko.N[;FI7n0`[EREK>,?$ d:tHSStxe)ܸqcffT*(ڳgJgĈE5=Flv!czгgϖ.JmX5U&l꣏>nwMQԌ3Tlֽ,%mxhPAw\*0TVBӃ+;57mDCCGϞ=eA* (;;;&9a„۷oN@؟b,--^~M:tzӧO2eJ}?Ν;DR)?M' s---xtM/_?Fճgϐe˖)ѣG,XPQQA\ן;w.55t*999((֭[ cҤIѣW]\\d?zU__ޟ'O8::VO)\t{nq+̴dff:88~5ǧP^^CQX,VQQidGh,K~j(6-ZsUzzŋIgˊQUUD <ڵk100(++{𡫫kZj jv!QZZOoD CiêjӢl-d~5a+TΚt4==F![ZUtx  jm TʺMSZ`ѭ~ܹQm>JKKO<9uTYD"pw}wm77)S?lSvv?p!&9}e˖+6X`ӧO^*ͭ_~r ShI:@pȑ#B~bB!EQ\.ILL4hPwh!S%J%~ݼyӧ^^^jjjΝ'5~!FٰaD"8qǏ|D")**zöt(}hKk m (bX"H$Yᠺ}\b2j@QE1LTZYYY__EU.<5{AYq(.^Ȼ/ZZ%*kV. UUUkjj 8NBѭ{U+Xx횼fUm{ܹʿW~***lw7L&֭[g^OY[[K:֑7osvv?mTTT4r("3hA(ܹt(*..(uĉ7^~ڵM;vL]]t>|HQԐ!C UV@O~2RVMQTVVl͛ z>D5ozuH I5hfruuu|1@jjjE9::ڵ+''G*={`;vtj3b.\֬JQիWe;l۶K۰*koi)[k2㚰 ŋSO?}wM ūʖnۮފϒ6/[=|G{v\*0~mz۟X;57m݄X,4iaEE,.++kɒ%7n,++#zݿҡ3שּׂmSWWdzd-DdjnݺU^^>qDA23o>]RRS77&L`08@:>|ӧǎ(*;;3gRjժRԔ2 IU}VVVVV͛wՆ[}~ 22rΝEM<Qp;v$$$VVV&''M?ҽI$={md tjtlڵiiiO< (jɫV;w-[ fϞMQѣG,YTVVTVYǏ(j˖-۷oJVed㚭5 +W޽O>~ҥKͷo~FjiUVVpV#Dovv9;.Pݧ2kTpFu_SXX8nܸK.={666999 ,ؽ{E```ff&\dݺu+../tttܹs'X@+5;UUUq\@@$U'@ BBBHΞ=KQӧ釖ϝ;`0^z1\nzz:>b T*HL#Ǐ?5X,4z[=o5Ⱥu z Ғ֚7lyc$[}_׭g***ɤ{'blٳ/]$_,o۶MCCcϟ9DYecSvtcVemW5-]3P&&&E?~(sssǕ;Tp_u酷]]ˣ=`T{]ߦ7^;7m][ee֭[lmmo޼I:NSQQabb6w܌ ҉KMM;wի H'e/4zlҥÇݻwDD`Jh۷eH ?d2)zCBB;&sss/,,$㯿 rrrb_pɓ'eϞH$}ѣGQQQϭZ8 M}cX|y???==#FرCE, /_nkk۳gӧM5[I;ӧiiU8VV&otum[͏T.))6mʕ+B5 *sx+Ueff~W<﫯&?r䈃{6]^EEŮ]LLLL n߾M:(8| 6ӧO>6l L1-[3ݻwBBѣIgN:ȑ {{{3ʊ|lUUUHh^TTԢE-Z*((2e {&''ܹԩSRtҤI3fx9zB-^Pܹӻw>l…suW\ٸq[V^=a„tt 'NZrÇ噚6n۶mݽz6l SZLŋIb1ɬWSS(f+Y*&%%vT:e <ȑ#4&,X_ }̙3 -dwѢ]vM9qīW=*tU۶m>|ٺu묬㳳kرco޼N4ibt.2RSSe###HG%CQTEEF9@ K١ ]J\\ѣUUUI$|YKKKϟ?0`@GS"zzzׯ__pg}6~/^N0z0R:ŋ/\qFq3xOLL ]]]WXq>O:cթ0:h2)ʐŋ/92k֬^z999;88?cǎe2{Էoy988߿_$@СCWZջwׯ_J"2˭p8UUUr);<ʕ+ƍ#,bQoC~uT:墪:99-]t(nݺ1y#GN_~yׯ_cǎ~ĉuuuΝo߾ BI-[L8gϞVVVK,y믗/_>|x„ v 3f̢Elll"##kkkI Øm6 @KE"&&"Ed:n޼Y]]=~xA>Xd2X,RWWWsrr]ވ#wccc` ҡړT}A ##DoyxxD;w;vАt. 8_N2%77wժUp8g޾}+WJKKI:cwoEP~!D":}'MdiiSSSsڵw-//pʕ+ DX[[GEE3fժUwF3kk^rÇ---,XM:޴LQ-a!:t!!!.\x1 ]ÇW\%uXqpp Hpĉ3gP5o޼ŋ[YY݈lK~~>]Y{Qllb}Ϟ= &yB0''ӧYYYO>ˣ(ӧsqqq۷/%Z~ꫯ 6X"((ᐎ@j@@Y~pwww77իWO0$mmJ-Dd-Dd"o cǒEEE]6000::ZCCCg噛_~}ԨQHOO߻wӧ?mڴO>wޤsA'&HO8qA͘1?GP]]]ZZZZZZVVֳg(xvrlmmIG򲲲1ŋB(ccc#HgeenVPUUwވPG:a.\ӧʕ+g̘i|&RRRbddhѢg̦PD.˗/ꕚ,]޽{7n8gΜYOO/55=ϟ??eʔr,X|'N7nСC6Ryy+W.]WXXاOO>dڴi666b3Y;7chhػwo333333 333Ox[ yyyw^^}:''={:666 Cy]EDD8qW^˖-/ yxx۷OVKKk)-2eP(%[ؽ{͛g̘cjjzݖܲeѣG={֑:/Px .\t)++K[[Ã.%NJG,?xҥK/^w>|o>}HhOEEEYYYϟ?ɡ;syyy EihhJh @7WRRB/tqqD"(J[[ܼw޽{c|JU^^k׮~`]]]ҡݱcǡC455.\dL¦LyqAAA{D)3+hhh "[ :u3gZsƌ555Νx]/]tҥ*+++77Ç;991L'&&޺u֭[ @"Jrrr^2/eUUիa^zillllllddO+PXRRRXXX\\\\\\TTT\\\XXXRR_RRR__OilllnnN:::do(WVV~WիPڵ+**d.\088t(hs)++7555)-+~ѣ?niiI:Kcǎ;v;wnsuu x]P(vիWo޼zÇ6/wy999߿u͛7D"a?#GGGׯt'˗%%%e 鎲Q722xdotsb^|I eJJJdr8O Z^汆߽{Ν;냂VX@EE޽{wUSS_\Ąt(hOIII7nܐhff6iҤ={|АT6"2tk֬9{lff& Ŷm~G//QF,//g0HaÆۓ Љ|R~WPP LQ]J600044/CCCٗ,ND6.)))---#۟`6nbbE@&ݻm۶' IDAT+V@XCCÉ'nٳS:99c=8z}egg[YYʦTHhqqq>>>St#bd BDϟ:;;wdѣGE9:::::VVVRRR\RQQAQá?gx>}-H$?NNN8qb~5RMMM.˗/>|H_~-}9:::'d_b")@^WWW^^^^^^QQOJuuR?#FFFo]**xmp8!!!K.=z7|k׮>lݺux#4tgjjj~3g'`?|x…WtdΘLoٲeȐ!nnn|󍇇hv<7p((- Z]xQSS?$tY,+>yܜtdjD vcXC=}tuu5=j7333##ٳ544_l6CnJ˿;''''''777''ŋ999555EX,  GGG}}}ҩTTTzٳg7)OȾ Хd.teYvikkӧl6kkSmm-=X'iӧ=AK077ׯK.Gnff&_p8:::ߣ^D۷o۱c4iͦȻw8xEVXrIG =>>>111<<|ҤI...˗/9s&S\E"QMM0&S]]`0PDnC*vBP__K,! =}MBBBHH_nC qww߾}{^~mddDdb<8k֬=lyyynnn^^ݣ+**/,,|l7==ѣrtn'!P4ګW^zWrJKK-EX,|ic"%3\+++fmmmK=NY},mmm&rUTTlVQQrL&I+yGUUUKKK]]EQtYt:|>_,˟H$UUU"gӭb'V",//D šږfkjjWz/N7;xzk^^^llov5D2rH__I&YZZvd A ٳ'<<\EEeŊK,O7onݺŋׯ4i#t#F(,,466m/oݺwٳg0:;w|AXƉR4##?l]ۃ>fJj=.\0`6d7=6??X[VVҦ3J.Wx>uuu1@D~@***dVVVb>/ ϗ5ece窱~mnn>`uGw1(SQQsm>Fj:P TJ555%g-躳 ,.FW4ZR:tyZvAd]֠٭it+nʾ;/黀FG/ozY+]vn˟h=۴`0ttt(d0uN)f{7,(((((_x~۴iӲe;e'''ڈ^z޼y6mڰaìYH977>ܲe˔)S\\\)S2ϗ/"sܪ*z.2h EdllllmmI^b1R\D.((Kz:bggsIX,֐!C~WvvMMM[[[ſebիWDž7}?. zZzD'tA meeEWt4=P,-i4ږ.1˟h[Z]OvJEN/[600hC˟orE"э7Ν;wod +W-X.%O:?0`ӧ]vܹj x#QAr)5<-..nܸqSt;,[*"gddPбѺPxܹ'$$c޼y?sMM͹sm~-Y,a@7QF(& x ---.R+ Ө4:ٗPB***cƌ3f̮]O:ڧO???}z~lق'֮]s%K|˖- lu ."˿癢(SUU6("@'VXXeA, bqKy?{LGGG__u ))))++?q9///ٷZKKkgΜvZhѼ@SSSSS} h|=zJ9rȑ####cbb۷qFWWי3gN6ĄtFcffvС7nݺu„ }}}I wޑ˗/߱cǚ5kvر|kjj1FEd.[UUիW/$.]>zhAT`0ODζ`]}}/z/"###&&׷Q{̙B%$$t2@7G7wޝʕo޼|,//'혛GEEXXXL4ƍCgaaɓ']2<>~ܸqtGt~Ew!N:iiiyܹ-[^^^L쪪qqq:Ɣ)SΜ9STTwʏ?tiii!C$$$Ǘ8?++t( 6nhooo>zt"2EQꘈ,{nyy ݑ|Y]]}^xaeeձ:P ӧ^|9--m<t4P":::KHH_nݵk^RRB:@k;ݻw^߾}.]ZVVF:y{Ξ4iҲe<@ UUUorEb"rPD*..tKJJ0YW\755g͚3vX#366~7 u޽{Ϛ5?$ UX,֜9s233Ž?nmme49(211ٽ{w^^޴iӂ"##Hx<^UTT0Yx:8///)7={FQMG'Sb򋻻Gß?61bġCrrrF?TTTNfK.]nQ( ,,,''g̙k׮߿H$" [***x]PDN())LJtn."O7!;;[]]Դã)pKKO?T[[;...##cҥڤ@gECݼy+WR)toz/"44nbt.duӧ/]ud-"3LLDn)ǫyxxMq"rvvv޽3??ʕ桡'Nȸp¸qbcc;w{zz^XXӧO}||\\\N:E:R044BN4)88oH<R~ f0 >S|al6 ݔD䖊Kܹsĉ6mz?O[[[ҹkrAAA7npppXpڵkIGx33ggg77DҡiddӧO}}}]\\QGxߴMDfXEa"rPDG*^zۛtE/^XYYux.Rillaò9`G:t #F8ydAAU;faa+JIGP)&&… `ԨQC(33̛7->>t(Dd\T*H$DR)3INN...F L]PSSkCNNy"n8q"EQ[ZZZ@@@pO>ݻwoAAĉz P%7~#Gܸq~ժUC(;;cǎ{{{5 h"2EQG,KZ"\|аt_EdUUFDb333tvv 466OLLe2H |Kf̘aggo߾:Zd22337o|[[={BҹCLLǏFv5ҡWUUSe\.ڨ@ Ky5Q|ǎ`0H龤R) CEEѹb;LDю/6lXRRҥKƎK:c0^^^qqqYYY&LX|QpppQQh-RWW_jUNNg}rJkkF]n1&&&99d̘1' ֖H$ ~w\uu5\ Eddjjj===I$ =١yyyEuuuuVVV/1cF~~/LefmmO?!!!/_$ E:::aaa~9szMҡE߾}cbbn޼)陔D:@W(r"2&"7"2t27nܨ"[J C"4LQT~~J^:>X+֭>}zvvvhhh=Hh++={xbѢE_zE:@ܹͣ:b/^,~ڵA?}t(M[[(>/Swrr%΂LTD={X~*k׮>}zNNNXX>\oh֭yyy[n=zڵkHhϓ'O>x!88ь:lر?w\ffvv6PUK(LDn\|㐉E5;9//̬c_V Hx'gѢE/^ؽ{ӧMLLIhȈ駟###E"\J`޿7otqq y5\]Dn:D栈 IaaǏ===IR)(ٱ],J]\\V\㰰0CCCҹڍj@@@jjjDDDllKKKIhZpppvv̙3W\ٷot(e`gϞ:t:,,t.΄0FEd.[]]M I||ڨQHODE䄄!C̛7oذa$ Р˝[n׿eccyfգGT___OOϔҡU5kքՑ90L.[YY)TUUl>&44t(eѣGO7.((ԩSC(EdLDVEd4KJJHJ*'TTTUPP J;c,((hyyygΜzAH FOO/,,,99yȐ!_~oݺE:@Lf@@gV\A% \,***%%'Pʫىȵ("7 Ed4._lhhؿA@DBLLLj+PsN[[ۘ]vM2t(`ggwԩ۷oϜ93??t(A3毿" @899ܺuKEEeȑ)))C(f'"KR @@*B:x&dS0dvxJVVV֭  jz!Cܼyˏ=n<򰵵={kת>^MM?$C MEd##OHRRҐ!ClٲiӦTooo҉==۳gʝ|FFٳ/^>ӓtheee"HIȿٳgO}bҡZdoowDF]͛ׯ_c0~~~)))!!!{qqq9yT*% rE5pPDn쒓KJJHf4\\\LQoeey>!C$''ЭΝ;6lpssKOO'@__ǏYf˖-.../^?7333""oI% f_ٳg3gtww{.PWKEdD"RR("|HAyt("2ͦ?CA]||1cL<~Q.tYEEE~cqq.H_IOO%hC y… .]:uׯ_NitttBBBNN5k>D"bI 6&&&!!իW ,--% Ed>/+C@@& jjj===I`0-"2tA]zĉQQQ<3@# <ׯ_BBDo0z_\xlD"7n\> @7Db#GLKK?~رckjjH'PD]]}ڵ3fhhhD/^|`<ed2={eSNnl6lTDp8tAZ||) ?ZEl77GpB^:\nTTԩSN>$''NHrrÇޖ'.\a"ի322>iӦyxxL:@R o=~xPZ&&&QQQׯ_^dɫWHxW<QfS ?~I:4ىȯ^"ryyԩSWXr5ssRxw666 .͛GOP*'Nw^q>󤤤FqC={622R(v\.oa***܈ʛw $!!AUUuȑ@c ."7ܣG}qEEEll:D*++% EQEbↆẺZ(bX 555LP^MM =mq\;ytuu***\.>Ip8mL֖pUUհ0ooiӦ9sɉt('oݺ`04558HRP8~={ TLYNf͚;wz{{\.Dd#l6&"7"2(x777G:D]D>z…  `bb^ 7HTUUUQQ!*++E"Qee%]•BP(D|>.l`Vea---uuTaalL+Ӳ4EQo{UTT2]w,KGGC,bp3h<:tÇ?c҉c֬Yf͢(޽{ݻ{;w|>TUUmhh/_5jTDD.G ݱ鉦ndd_d'd>A~`0>>iӦ/ػ︦7lȀB6d -* E:wݨZmk?ԅj?uV֭Xq CFII' 7y $W9rСCcbb܈ MAz===般u]h7Y.w3}7.\ؼyUH$R7=wbV,@$麺:PBa]]X,~SGa---2 h&@Y4S12aEߋI\abG Ʀֺ: D"T* 2(]PPFˠ3I]hh44̝@揶гXXXܹsg'N\h֭[.@@ "~DM[f4*H̭\n; .FqHJYz)^p]|>MW'amh4hdL& E__MwNz+uE-^믿޶m u@gh4 2ldde}**77,((B@;boꦎϞ=0a@  쎧&SDJt:BP(=4F4 Mh4}}} M444 >VhtS1lH$bd;R755|>p>Mf---*~T*JR(tBPTtMs@o}/_N醆д82C 8099رcVrttܰa >GRBHR==="+:@E%$${{{]h\.#ruu5a911qܸq׮]@'Wm¸Q(+l``&MPGQ+,B7555q655@ hjjBbX$D" M₂t->t:O0STtO-;5ǷhѢN4DW# y<ϯ<Jz;>(X&;===4]PAš]2EB!jJ_W5Ƴx\i+zBӍ0 |ID6mرcno߾۷]Jp8sk\@Y **!!! ū&QUUUaD޽{e˾˃tH$JƊ?.K󆆆l6+t:EfG[[[[[ (\WW' Բ@ @7b1C7bP.Bh4\M!f73kG7uuu?x>}tܸq^^^'O '"-D"˭(--ri㖖|a===<`jllܯ_?cccK ESqm]LXWOMLL 9ifffnnNPaoGP~ɓ'/]?#.x#** --zRQ>@dB[(FPȨV7?{3gNWT\MMMbZ@]khh7kՄ2(tahśb^ABP(*\ \Fd'2eGhf`FFF7nܘ3gNDD/EtE}HҒrJ77UVZ TSAd般N(==] ]x#Y1\]]UGDD_zu]N PKKR+EVQkWCCC###{{{ה Y$];!(Q3溺:&64ޞf'2KV첌Go9дhv{jjj]hT*rEЧ:`XL&)00f3L6mff`0t 2L&޴@kk++//reeegNNNYYǓdihh[eܤIFر#""@Y 2L0LSSJT \\\.D" "WUUuU;/^hjj&%%߿K ݧ痗K|>Q+i41[[[1χx1G[[]%ɄB!t.bśuuumWEP,200@ mm.z@H(YYYyi뚚^|~18ֶvpp1b PWW'v0555333333dJW߿S(;88888á qիW;>۳g+uGфB􉽺:@ =QTY rmmᇯ9)))""…  ***|>ǫxh)//d2`0 .+3622nP׀Y˲H$Bmuuu2//5 vSn+ڶf̘akk;~#G^pA__ LV\\8x`f666L&>=:bXryYYY›7or8 ,--Q.---!|;aXG={E<==ϟO?h4 0* #z8rݻk. o%Smm{r ,/ccc>pmI \.᠙Ʀ&&&fffhŎфPY(+NimmVhB J$5h4hJv]# Ç#FqㆅNyyy ,NG !C𱃃}Dbl6[)\__w}ɓ'kkk1 vtt0`G4n HMM=|ڵkϜ9=uT J  555般Lt!##b@!Ad\f͚ 6lذ>dt겲2/.---++C7+***++2$`X,6ݿÇ[XX0 &ѨSpZQY]]][[4G鲒ڨ};0~D񦱱1lll޽Wlpiii(vQZZaۈ#.]ڻ…hL0`gܬׯ| ,,,P(=NhƜ9sƏqƙ3g:thϞ=nnnD}Hة5E "TNBBх `$ɷ~{okJU JJJ b1Z@OO‚d;;;FL& JRTKK˷.)JUUU|>秧R=DMMM1Qf0(;bfff#G>|xBB LNN~H$rvvvssSa&&&&&&+**2_z-[ZZZT5 w9mڴEyyy͟?ӦMz}!*(, l6>B`V__OXY* > io@ ptt|Ս7W^>|Wd2YYYYqq1 ⦦& ԘL1c,---,,l444PTK|<\UU󫪪\nff&.WWW5 i558Ibbbxxxpp[>+Ru2))))))yyy||~ss3ZD"dSSS333SSS6`0X,j mhѣG$$$]**((qF\\\bbX,6114hдi)敟>}Z^^^^^؈TWW755sl6Ԕbr,KWWcmբwҥQF%&&ߟTBCCÝ;w v1d{{{Ŏͼ8p82 0*ǁgFz.GG7-  x<˭,++(--}1J0Kh4Qf2L&̌d2 Aw𠠠E/^L‡3LbkMOO… !!!!!!w޵%J.'&&:tҥKd QB#^@]F@@@@@/RYYw3fhiiEDD̞={С|A$iڴiFgΜݻɉJm 2RYYٳ͛7]x n|B `?M֯_aD"1;p8X,{{{{{0{{{~PennnnnPFx:|IcccQfX觹9b0ݽ!MMM֓'O^v}w?V4 1bĽ{̈c߿_ze˖']Jq!T1ӧO>9s&66600!22222$>Nܹsڴisuww_lُ?Ct]ެ 20T*ۨхwV[[a[;"+Vlٲe4Г444477+Ω BTCRB~eQQT*0EjB!dWۃXkkbMi4̶7@]\\ڽuP,++C͕RSS\.Sa6fl6EMur8cǎ=zt֭߿|N:4Ϊ ikkufvkQwȐ!AAAIIIFFFDWԍvܹcP8eʔۛ3V&&& ,X`Ǐ߿f͚e˖-Z&^^^<ػwϞ=gϞPZmd2}oAdE@$$$ 8J],[[[,Xpkĉ>I$H$BH$DbXH$dBP"▖憆Ʀ-FH$:N" PnCCCJt*JPT*~`}" QO`F&&O2ǝ*@G$@P_____VhZ(*Nr A)4H$JbD"dLԤP(Fd2Bd>JEp@@WWƦ{ʸ\nIIIYYYii͛7Q[e%bh‚djiiu d2aϟ?s̠A~嗠P؈a~ևO8h~t'Q0Pv#a .a!O.|m=%&OÔ͊? | >x+N뫩uF][>?~ɒ%UUU ,Xd ""a8pM6رcƍ۹s1c MCCロ8qbTTȑ#w B4mGd' _]x;< rSSӤIn߾}"A*//nWWW+?pmZZZ(Rt(uxhW @ IFnk"d2uuuk򬬬/^deeclkkwB,k5555iȈP(hD"bF8`VC[KJmP8P X(EݶIN744lMt333}S֖xruttX,dXL&?g0ᨫ-J֦4W\㫯ojjkhhhjjB'(ȋίPDXps.S0t?\%t+鎔-~+Jk+@xڬޮ| TNcEADNkR:::2\1(bXW^}Ǟiٲe:u͉͛=zovvD"򊊊BcorNFS':"2LܜbYZZE'?ŋϟgggfT*O>A={eP(++x7}}}<}kddǥhy0j*9PP(DeR [1F&MMMY,P sssx"TZQQQRRr\.r脪?6fN<E444Rˆ Ə~qd@JPWWԄa ZhjjBK655cEcccp.&JoDb0| T\۾>m]1G༩ΙL& }}}4 qD:::t:L3\žwވ#Ç]Kill Ȉ?~<式cǞ?^i\.#Gq]aaaW\A;D^v͛vȑ#+###8樫gϞzjXXX-\pϞ=ϋϟ?cccZzD"ݾ}; ð%KܹM5o޼+ᑒҙ7Vo{ί|^RAgΜ={וw7ܹ "@_de˖kS)0 *drۆ@@ Vm9r$˽s玳G)T*rE0`,uĈ(\f Fdz:.j.[VVpP>z8ad2Z^)v\TTaa]\\Fl6:Ԕr8bSVV7hl6ԔfX,B=:XPF*++cZee%P(VVVޠ077o9\.@咒4+77֭[d253I&͛7/88%@aX, (yaJUj(cbb7q&=A8*K4@IFH=Wj@3h4P(t:L( e``@tl٧|1c۵hSN}Ž{\]]}ƞ?^OOo&LLOOGܸqJΞ={rȑ#v̙NOt'r8 6ܺuk֭(, 4w۷O>f͚3g)ILL#:s/2}t.\7nٳg cbb~ VXi``PTTt?֭sa'O0,22r޼y~1 o``?gL&3!!᧟~JKK^n]wluvf{?pt)K*eĉӦM;s @ e˖iӦ͟?gś6m>Brsd2 AGd<///˗^bt/B~+++}}}SSSݭ***Q" b)..G0L{{{{{~?$H}}}mmmmm@ XxqzzL&$Nq7E'QwL. ԏmMXP(D"-yRD F4tSqBSSor_=ztʔ)DnܸHt-) ݻ߻w]}ٽ{VXe|ҥKcbbPo߾tR4'!!!88̬݇tzԱcRjj*OOjۼy5kڮy۶m˖-Cs~(//'O`m۶ק244^7vuuuqq D'$$>|x̙ӡ˷j;C:W{IeǏ1>|8ѵ@$\~իWh{~DW$|_5m455^֣@rWZEt-fΜի{Au]t)##Cq13b dTlR__ӌ ?ikk;888::::::991 >G"z*''''''77D.ؠh{mc U1yxgϞ̱P(0L[[OVVV6VAUUU(KKKl6]]]LMM..\UUUUU|~kvmijjd2]]]777//A3v/<n"H[qM*L&(+yWYlپ}{̙d y&&&UUUO>Pظ:## all>|J$ 1~H0Ll+QSS/tnm/^prrj\4'77O>5557n S: h0H$Ҙ1c:dddd2+**Х`rMd|xgn[}~S?|/Ayxx߿B ^mmի<w^++++`ן>}ϹtҘ1c j8,Dt!SH Xtڗ/_>`\~c7Od2 }iggggg؜>77srr^}v{twwGdOOOD"QFFFjjRJҴi v T<{ٳgYYY $9 `֬Yz Rب://ܹs777777WWWŭD"R<O$˫iB:d2mjgg؈B"\]]駟X,?cgաDr1 C ) >YuւѣG]r}RJq|55VT_*H]Ջ o1 ߞ(i7 H)Vׯomm=zjaaAP*++߿˗.\y;w.\0` 6_hEeL:[j?\?EW%dkk[ZZJt:~)SDFF:;;_~ŊY.?@PQ`hhضE )D"b9###$$ʕ+򜜜߿?999//0:?w']@2;[%''---ZZZԔdϟ?/((@ݞ]\\Fz@ HG=z}!j``:pYf(8 f0 p8ϟ?w%H]\\|||  C"xJ.\neeess3b_~Ʀ&&&>3g8[j7o[§@>^hRGƍGt!>,##c6l8uԁ|||. P(7{@._JTBBBСC!=ك(uD7:< t9==="URPZZZ^^^F>>(ҧؔL&wUU555_|J{yyAsS2---xwJ4|,??ZZZВP`0X,`faaѧF;_zo̙gΜQSDFF޽{׮]K.%4}wjjjƍ0`@zz͛oܸ_߻wM2ðcǎ_?kiiYXX={v֭Qyaa1eSSӮ۾m6 qƝ={vǎEDD`?lٲeŊ:ujΝ8py>S cǎaa,XP[[;`WWW--ÇuVڷn?Gc.It!MMͨcΝ;o[n!w6>)Ưy0 #uuII$##?')fͽݻ.\aXXXÇ⋰ǏC8C_~uuu%@@ ɓ{%''J$~ 2DGGA_TTTẗ́>޹ =z˗rAAACUv@TUUp8bkEEE$Ԕ`RQ6 =ϫ4 IDATTl<\GG R.>|8dȐ5klذZӶmVZuoZއ\.3f˗Η#Gq]aaaW\AqW޵kn޼ST <(\xݻ xkm9TzԼyۇϴ̤P(/_=zt[n~(nݺ_~ 쎭~ko! |K?7o޶mۖ,YBt-(\~رe˖ikk޽{رDW1N>_K$L5(( %%/''ёZ@̚5+'''99"gC SSSTjXX_|1zh\rٳg(m܊ խ[ZZ€7VMMM195>a %*~B(H >|h``@t9塚|ݺuݣƏ=L& ̄={7lڴ̌aBaTTԁ&MsNh@jjwAA >SSSܹs֦R Xss3N߳g7|Ct-fϞŋݻwǏ:ujHHH\\ѥ=ڹsmll{L$ad21Q4"jkkգiXaۀ544y{{6̙3=Mwzzz9++ov̙ z zk| rת{ &^ݝRRRfϞ]ZZqEW@rrr233]]]nnnd2A@!C( $*oBAd6c tS]H$ϟyϒ%KƏ.n z߿_ |W-$(jjj9O?422rt V2o޼0R233333322 r^^]****|-##իW:::...S__J۷o>|۶m/&UPPo߾XX_pN U\cc+WN!")--HNNg2L{{{ Ò+M@@M6>|B;o?2}ǏX"::Drٳg׮][TT4nܸN3 [ssSvܙ6nܸ[Z[[]PE|>֭i̙LtQL&zggg?L<Dinn~߿r ìP"~Ageeedddddtr]]Wغu5knݺ駟UCjhh8}o߾MPBBB,,,. eJJJ_~ƍCN2eҤID@oYfeffFEE].P$t;|p|f~b'OM@@@KK?B;3gγgRRRԢVZEッ.MEedd̝;SLY~=\;=`X|ѳg&%%d2\T ֌aX||e^zbŊիWd+a˖-[l144\|ٳ jD7 #GX[[oݺ5<< TɓF&3h ///hxө쫾P}o?~Qj<ܱ}PaaǏ>|ѣO6440aÆ~^r|رO᣽UUߣ={ *U[뎅8M*fff޾};111))I$چ5* O\cOLL+((000 'Oܻw/55511Q م6 1̙3Ν;߼y3ΡCL****..&0bz655]x7^0l̘1D" ( eXe˖w2;wðÇ755\.1 zzzر$ɢEF9x༼KBzw7ĉwy򥻻ŋinnᅢLܹs_zUTTt)S@ YES0 ۻw@ P@ϥlٲ+WܿIIIÇ7117o^jjjw@O>ous}|d2yԨQ{۴iSKK-[vܹr+`={vҥC ٹsT*駟^|ի={C6>>~޽{BBTBŊsZ[[*IAGd~ǣG]x'N:u*,++͛C__BkkkS!UUU\.ŋDӕ[!ɤRz `JryLL̊+,Ym6] >>ɩ7443&99'O>&)Rj{ ihh7owء㹏{UAAAPPЛ~(((صk׉'D"ј1cÇC<@=z==FDAt-:"%%%/^z(a"hٲevvv:Sg0 fccCR6n; HK?~<&&fVʹs眝]\\lp:^X"?>55ݻBVz7cbbLѼŇd:;;Y6t?~uѣG ffffdd4b]V'ϲ>+{F)%vf+xHGEvp}uxw|No;NXOV؈af'W֓:x5uo{{KKE=y$==>ٶmD"g\jСCkPMORNwާO ۷oϞ=[,ܹ388ĄfFEE8q"33<}D"8~UBCCY,Ipp]b1J7 ={L2Ry5;w@$ 8&R%khh>rх:~ի###}||0 ۷o\.r a"UȯEl}ݛ> hmm i{WXXXkk+z8pB mE"ܹmA?lll?͌PWWOOO?~⣮^P9wܶ {xx477wr't_U[[;''BD-NQxhjw7%''";hڵJk׮BR? ӫðG9a)|͊ Yrω.D%\~PS&ws:DODJUq_|E/Οe)Z_@KAڔ;Sse1!o=ݺRڷx'w[ak%9hr?6vx:x`_VQQb 2ᑙMLqi=EMMͭ[v1c --- ô'Oqƿɓ'BJPuBɓ''O+7774IKKs̙111nݪ%R;;v,a&L!1|||V\8g̙^^^bR5@0B@8qr9Dr3gP0kT"BlܸB&&&EFFs}aFR.]d a؁wᦦKii)< ҥKbW0, GObbbb8Ύ;0 駟:~GGӧOO80ccc9˗/:xܹsٳ'##_mڴ}$I.|HƍDOLL ×ٲe նבYtttaaaRRҰa0 D Owر}5q0^2D B-nV(*uV+ZuY TEP= m~.r }rIyEQffffffE]p1IF|1cD* Ɏjժׯ_W[~Æ ϯE)))-X 449rKKϭ^4jĺ37el!Q{W8jۆ?y'סϦM|)`@Ӛ+u< Sۅ;o׏<~6q-qȑZ[TYY{رe˖}W'd2d/_͛JУ͛7/_޾}w}7d}}}tZ|yHHH\\vٳZZZW\; `ذa ,h"[[[$?rh~Ν;ӝʕ+rrr , Mą{DGUTT;9uꔼOF}...~~~NKK_DQ/ҥK%V'ߣlݺj}G<<<,,,ihh4d;wPջw9~8ɓZ"1G)${]ב#G\.AIj" CVVd2رc%%%vRUUG_~!;ثWZ/ƌC^~WOߤ||#Ν;d2333Bbņ ޽{'\B{ DQ… k^j7sW#Z}(M3eHwԭջ:Jۆ?y'סϦM|)`@Ӛ+u< Sۅ;Çɗl6N Sˋ/̝;YGGOwjQUUGϞ={Ȑ!"E:;;ϛ7/ ŋ^@K@kdlܸqf͒rʮ]RLW*iw=ܥKK ٽ{Ǝ;}Ç3 ,X?>>>_~ǏN*-֯_ҥ/^[paTT`0ƏaMMM--O>ىٳV^^YW^Y[[Knݺ=7p=d ]t!svߐ5Q%ȷG0L@H͑MJ9lviiiCآ^~ݭ[ `_nRRR={VUUL&EQQDQ^^Ig˫/222lllF544oħOMB숈D.--p8 cLLLϞ=]jė[O1hԚsGudn,Czݣ]oQF??:27ރVް!wqpǑ̙I&y<^LM~ QTTÇ䔔JdeeLMMMLLLMM E^bMAAAffffffVVYN8wIYmii٥KUUU Ν;pBYY`777@+1cFyy s6n/_S߀t}ˍ;#,,mƌ"ߒd&p\%%%"JH$>)zäK.ݽ{7,,ҥK6luyɇXZ}IBpܸq?15/SUӐF __Zr[b_ԧ #`v IDAT2<OIII[!ruKE 6pѩ(**255%oo?很Leeeaa@ PVV;N{P(d B>/?C uk:27ql!]R{W8cjj=Do'Fh//%~{:ܶ(X,V+G^^رc۲e˦MZh+폪j>}#9S(fee%'':ΔTRLcH}}}CCCWOO522⌌쌌,Tgff...200hJh<<<.\8n8`uuuC@c>QRR(Pҧ)Ν;EeddcjׯEQ"hƍVVV4eFǏ/((;Hu111EO9WX!Հ2REg%}bs6oEM?w톬2Hȗ8bƩ.//r'>1|Vd2eeeY,ĉ\R^^}v #WW[mfgco޷xb;?M7ʕ+ #55 qFQc0"ŋ t: (ǧU 9߿?EQ/cǎ/E:*u_.[C27ql!QsK:ﺯ\?lʻ]a3{0А'^{ӗ>c%܆ܩ@ ?~Naaan(00PVV6&&Eґ>M|||Ə߯_?###w.l6Ĥo߾G\l֭[9r&$$}W@}͛7.]:|֭[f͚5jԨ>}tIuݿ/ZhͿן?K]6ٳZZZW\; 8??Jٻw/9RXX]Mw҃,-- QF9::8qZ_q#ީ%;`:thŊ&ӧς SSS?NQԻw(1cFdddpp0EQ3gΤ(&WսZ##>>>rrr۳g' ;jԨƵ i&+++ ]\\O>n8qӧ]688xɒ%&l"wwXCwݻw9sfeew9z!C㛔oP( 6l1Y\ee)SLMMWM0aŊsINN^fA|7Ge2&Mݺu[r5zGmٲENNB/҈2z5qLk!sn7]ǁwWUӔCjM4 RR@CxMް!w­&33s#-gɒ%oߞ6mZLL >h ڶ5D999OmNNN^^^nnǏ?~ܼ͛<ⲲZZZZZZ5't+Ehf"_&֭cdd\Cp8sy] ;Sg{`РA\. ,۷/EQ;vDK,qttlRmʯ_7F,_\$ BWWך׎=Z(J޼jI/xʕ+nv/h,`ɕa Y5dNSu>WrHF\\AϟOuN(nÇZ^n͖ǽ=bɢI.\E{_~~!EQW^%Euԩ!1{|#6m$''Kw ,//d2.\!>F رCAAwޗ/_bP(tss9ߐ,6Kw3qTV뚿tܶ1C:YsnM:xqU5æ hVٔzīuo?ީ[QQO?aiihFFƒ%KZgsEbbbn޼yĉu֑N1sε;881cΜ9>>>V矃>|ٛ7o?}ݻwhOJJJ>}'O߸qٳ WZ3{l#Fܼfܹȑ#Lnݺ'Nܼyٳgiiit]舄BCTTT:wAwh)۶m377svt6 g$.~رiӦѝj:h mm5yittt@@ʕ+[n~T#&&ȑ#}122,,iӦX  B۞B@P\\KJJJKKy<^YYYEE-**ul6[III]]L(++ٚL1Ҿ/_|ӦMt'fw͛7ggg\vmر5k٤ ovuṳ@>'''YFF&,,L[[e|||bbb=zu5kL>ŋU}qС%%%ׯ_֭qjhѢEU;5Q/_0`@O8[TT4z/^:uj̘1-UUU-[l޽6mZnqׯ?>n8EE/m\\ܮ]Ν;'Ə?}#F(((DNUТ233/\>>N:522ŋz8HQrQQQii)r\.~vIx&܈;EzUYYǣ$ʎؤZ^^f7o^|}ڵu\:wCBBѣ-"?%߿?l055m۶͟?lң>VVVBBUUUB&仍2i" gϞ9sН.\ĉ6lذaqy?WUU ]hm7={۷oϟ??qDHkkkkkݶGGݽ{ӧCBB&L4zhww#Fhjj6oTh^~}ڵ .DGGl77͛76d~~~G+2UUZ0EQ\. -։QLZ"=%'l\DM:ٳ;v찳sttuwwoI9}Ι3gȐ!52Qp8eeeBP|+@B!2C;TWZZ:f̘܈CC:7"]aihh|xAhh{֮]" 3qD*,ziAAA322rӧOԩ׼yK6hOLLLݻwСe˖ݸqvvvtfpD"QYY!sy->@URRɓ!CswwOLL}vuB!EQeee;$tmҥt-**j&LpvvG2SLIJJTٳg\x1A333m۶999;w yX[[GEE޽;4 ͦ̑rŒ2ڷtoC$-Zϟ۷oo%Y\QQBzX}vΝݺuꫯn޼Ρu]YY9444,,_~t)ejjzϛ7æƍvMAAAPPsJJʡC޽{d=z͡111XUV=yt'\GdC1V֯_?{kݺu۶m;{ĉxŋ?{Ç+VcnnzٞD;w޼yrSLڵ+ݹ]O?)o/^lnnNw.hK]t9q Ukjj}<ӧϛ7T| /FHzannNΘԞ|fܸqh^JKKOv؜mmmuofӦM֭.Y$666""bҥv駟[(g'߳gA:8gg#F={=,.]㨨 ݻWFFĉӦMkoݺu֭ВsssGGǁ:99u G;,.Ȩ"333WWWWWÇVh+R'--455mH$JJJ_<k׮}Ȑ!(8ZDdRL%%%d]]]CCC===}}}}}}]]]<MOOxdI}}N:jcc555zJOOYlن С=ztݟ>}}`z Μ9#yh})))ܹ۷S/::޽{܅ ݛ=*PUUUu{=|ɓ'<OMMm pttٳ'6nIÇ={utt 4fkkkP({.%Ƞ(‚T$;88ڪEZZZ\\Ǐ肂nݺ۷cƌק;#a'%})!5ʆFYKKKKKKWWK |WNN6ho{90Ȁf``@*qJF^dILL Y::w̙;vxwĉ6J$:tĉt'z{{{\R77iTPPbUUUe˖ߏ=ڱcʵkFݼQ&ɓȿ;//(ss^zٓե;&4P(LLLyٳgbbb  F׮]IKXYY~y NjfffkkkkkkgggkkkiibN ˗ϟ?/^xQPP@Qq߾}׷o޽{s8B#@T\nb IDAT!LQ)J"`?~+6(A<122PIIƻ. WUUEEED@(_AAA~~~SN;@c|r̙޽۱cq.vvv7n4hEQ ]"(DֳbŊ7n㪨puuMNN244lzsn۶M[[޽{Æ kƨqqqqqqqqq>|D={֭u׮]544N % SRR^~͛/_ŕZ_C& ?|ŋW^x˗ ݺu֭[.],---,,,--;"(==۷IIIo߾MLL|yRWW֭y+++Y #ϗuuuuuu555 Et$h j)\.W|+YYY0++%$$#((htgk׮SNiii-Zh ڢ ۷O0!88H-rҝ;w縺&$$H|#C!2>}_;H%&NēX",,ɓ'g޴iÇ\Q!ӟ={VQQQXXXQQQVVQPPPRR" )b2,KSSsfffEܔw޽y͛7 Eijjv޾k׮(2*)))>>?~mJJ@ (J[[_&&&:ug0tx<^ZZZZZڻwĕIIIddSRRڵk>}lmmmll;(?׾ZaUU jTOp8uuueeepTTT躏@Q@ (...***---)))--,)Θ cZglݺu{ydǏ9{?N2eʕݻw;{ٳ|#GƌCwȑ#̂sݳBdh%'O>>ڵW| //oaaУGKKK#I1y4NF{ ٳgv/^܈+`0/^|ݝP_'++Krͮ(  Մ<QXXҥKk C$rdRjKZL0AUU|m_̩S&NؐU,P(<===+++;;[$Ǐ yyy5{uuue jjj222***ZҥO^^STTYB*WO$"RPXX([TTD󪪪HNqqXZLVVVSSS\~ݩS^zk }}}###4Mr\$j},k&EndNMM`2;ʼC2nL2Ic.WVV&YUTTD 5{2Lk ccc EIZGGm"s^9F"H>"_IVᨼ\.˭^$5ǒ,"UYdL366WNW&uib)TkmK%'WYoM-A 49l(U$DR+ؐmmmFb F-}k211?|MWyyC;v&&& 4}}͛7\rڴi]t;t۶mk׮w=Ņhwrr;yd|Mf}$4P=zTYY9dt,999'Nh~]L&|/ltDnM<? zy=HM;w,,,Z-N@*QUPe(uWBqX&_dbuV FHZnb<)))ImZ4p&oQ7L:ԉ/6"4hРA~ׯ_vm .3f̘1c w @>>>ŋoݺEwh___//Ǐ/#Fpvv^bŘ1c'LN...s4h??6YYlv͎`tD&P !<<Ȉ HYYرcΝ;WF P($I(n;w|Ow(T+Df=JLwD(~7޽zjK|`0I;^Zηۡzjܹ]t9}%K{AQI?6檐Zavܙnݺٳg9rYYYtgtRRsmtsszw mddOw4b0QQQ)))=z~:݉:JJJJZ{葳3A: 6\rҥKVVV-~q!H$IZNXX[ݣߟyf***EM>͛dfiit;w兄\qǎ޽; H;wn߾ HfffAAA))) ,سgoZZݹ>wO>uww;v/9M%&SQQAMJ())VML{bhqO<)//G!r 駟8rsR`0B!G;PQQqA;;CR?gyyjKyyyG %ԩSϜ9SXXhaaannI~ @Qњ5k~d@ikklٲŋ^;@픕Ϝ92pDt,l6d "@ 700;HpyXbΜ9-&) )UUU(~m^<ݻw_pa׮]^TMlٲqFj!''')޽{cƌp A,--׬YCw@ Owh*ݻC=}{nnnQQQtGTT۷Ёp8JKKs(RPPNP -.<<[Gbb_=iҤm۶$;"vyҥ#GLLL<{l>}!f$##3lذ={FGG:uwޖk׮; INNnΝ͛7@VV3>>NNNW^E;|B֏=>}OEE݉:Ru@!2,>BVѣG[ZZo-WX\Gzj==7Λ7/==}߾}t F߾}oߞ?cƌ/ٳSN(SƎ;f̘%Kx<@`0nnnǏ;x vs6 rӧ{sutDF!2BdhY;H;&MTUUu%EEŖ)D(J$#r[XX߿ѢEZZZtaccիŋ _~Mw:hmAAAiiiw;439!!aذaEEEtnnn ;@;GK"9rrr(D&P -+<<\GGښ 휷w\\ZasL&S(2 @BF+..޴iݻ.]uVmmmsg1/)))_}Ց#GuMw@h%;w^l͛3222(((>>~„ VVV۶mC92Hcc㰰UV-[c@\GdYYY"(D`0Ҟ8qԩS666EtDnիWݻ?--TWW;4ptt x䉣֭[ >~Hw@hq?ƪU-sONNomfbbf͚stRhhh߾}cccN>)))1L.+9B!"@  tiϮ^?OFjBdXVt[W\\߹suֽ{ח ڨ޽{]t|ڵFFFnnnΝ;%%?tgu֌7СCݺu |ͪUȹwss y}@@@NNɓMMM{ԫWSL:tE|>Yel__߷o>|ӧnnn=;EQqXXتU-[6qĂ7G&Ƞ#2BdhAaaajjjvvvtirssnjʛ|݂ F]v Z?oٳ666};88{СCtеk-[>yO[[{W^taoo?pss#""Ǐ߫W@;77h aÆth?8N~6Bd1T@KDtibcc/\[wD&\I&XC zs񂂂Ǜ^ÇtGfn:}v@k#퐟={fkkv%((\СYXXDEEL|peeez0LPH:"\, ۵k{:u*,,ޞPyxxܹsǎ6mܹ߾}Kw4hcǎ۷oߺu, TUU׬Yo߾+Wxzz&&&ҝ :3gFEE;88<|8mOCq BKwcǎO>}Y"WUUX,ӧ?W&OLw"SYYYHII);ݻ#Gׯ_++LJҝ 7`wwe˖||Ç'Oks@ӣGOȐ!"Dm ͮYLU1~~U6oJw(D UUUJJJt'jmyyy˗/?qÅׯ;wLw"hB.rKJJtqqH$*,,($H(frrr rrrl6[VVȨl#f٪dZMM%@RWW󋌌 pqq177ϟwi}vkkGΛ7, Edee===gΜnذ{G޸qc^ʹsv޽bŊѣ8hlvee%EQeee̖v(D>m4SIII3gΜ}m޼y۶mwww% ޒ%K|||fΜ:!&&2mڴ7o;::nܸqذa@o`0 Ν;jԨPҡYYٚKl6]DnwB:^qqqff =AEEĉ?`0HL&߼yr .,X 00PIIt"ދ痔~> LSPPo ZqPa&Ip>S]]=¹.(^ +##ӿuuuUUU555555F9}||?o߾}9887Ntmƍ!!!;w%DӍ7-,,N:״iӆ 2i$҉DLl6ZBD"##Y, ݞ@ XxqiiGI/LEnjjbzoϟ_r|DDĨQH-W _z_TTD#//ammޯ_?DԭKZZZZZO>\\X.***))),,|qaaaII }<(YYY------MMkkkkTW߾}V\yԩ[[[{{{;88,:`MMMq@D1 ggggg簰 61oܸq@#ɓ'/^x߾}g 'mlllllz EdPDcjjK{ׯ8tC8."Syyy/ [v/V4===###33SU8g_~ZZZ3fVPPPQQuVx]EEEqqWFWbbb^z%젚_lvW@f,Yx;;;[ťW%8wQY@EGGY[[{yy9;;= |8D."WWWs8z ͮ0(bG:JEEŋLB:K7VXX8tP++˗/'O._\OO@WWھ};Pl7o\|9.x];NNNNJJJIIe0:::\.@8FWSScӓ)ׯ1Ǔ$(.. ڷoWZD:T&,,,߿O: t3< +҉{ٳ###ݻh"qDύSSS%ׯ_8q"8 xa"2tx>?rHAӧ?~\[E1 @@ODnjjÿ11ѣGTUU_=zǏ (RTT4666l؂ *qϟ?OJJJJJ{={L&dzx|2UUՀ77]vڵk޽+VXre>}HG ?^^^qqq‹]D]z#G-Z?!N )))>>nx񢥥EYYbڤcƗ/_&&&>y!!!C{jjjd3tGjjj?۷gϞ]v\CYYth_~8X[[ߺuѣG7ovttظq#\c1̀+++WWה˗/P]D.ҭZm:XTT ؅ _?|%Ysg/]z!UnnѣGgϞݯ_!Cxbܸq!!!o߾?|}}IHH̛7oϞ=UUU۶mҺvڌ3ՍoܸzٰaCNN>aÆrҹzm۶%%%]pt,--o޼sCCI& <8$$t.&Nѣzss *>,##CQd]]X"EdH=B.X[p!,GNDÇWUU}ԩSI']K.]reIIɪU+++ccc̙3p@I{\rS޼ys1cƄ9;;s8#G|a YYYooŸرcnnnŤs.C 3gwcc#,ЍxġC~w&&&#@1445jԄ I RRR,u,))Y[[K.@:RBBB]]ȟrԩ ,."{FӦMYnݻw555I'EGqttTQQ={vrrY}||{ @)SSN>}Z]]}ɒ%njhh {\xqFF֭[/\0p@77ҹzcǎ]GNJJ233C:܅ ~oo 8 EQ222NDDd EdXjjji/^K.III/Ass3"$$$=z}w:\AAΝ;G.))yᒒh[[[ ~͚5ÙVJHH/\P__O:#@7 ## 'Orm۶Ԑ+,XCpܐdlllL:4L&s۶m/^p¸qp&lߡ, Ed EdX֤StK{xɓ'Igwbbb|>bɣF"@{M:UGGg֭zzzo޼|_ɓ'^ڲeKii9s455===Irrr.]N:I6lPQQ!n($$$==}ȑ ,044<|0MPVVfjjO:Im&"aALL Ott,z"2g0}O2y󦲲2Dl݃ /߼ysɢ_uʔ)U455W\y˗={V__*1RRRڲeKVVĉ.\8t۷oéX"00tQ:fgg|rԑǛ3ɓf"8DB:˗/߾}"zٳmmmlB:K{cqqq@ c955:::իnnnq:Û7oSRRΝ+%%E:ͽr劘ؔ)S\SPGW}ݳgOVV>|H:TOwE:@t9==}#@gr劻~/;ʶLQ 4DGG 2t7'O6QL&EQ|>%]zRNN.!!tP[[秫{-[q.`0>H;v,Ţ )jLĉoܸ6f̘Ex7n СCZZZcƌ?~|RRP=5kvYZZJ: L#@b2ۑ#G߿O:@Wf ЁGASo~sΩDd>nݺ)S|7ZZZ;wgeeȐĜ8qbzDDɓI"l6"@ @B:JLLIZZZ̙fO:˧,LD~왍>|_ O_EEE͙3t`oEx7((J^^˭͛ٳgq\oo7{m]]]999333???xӧ3|z 'N`cǎIJJ9rd޼yCOQQQ&M222r劊 8b́aaa#F 趫%}w۶mkaooOQTUUInn[˗/_ZodҥlD%%%\.,$$,$$oQQQIIIӧO;3Y1jԨeZ$++۞+,YСC&_mٲ%44T7o:88|쾴Wkxzz'$$x}pd~)ڴiSSSSSS/EQlgfԨQ׮]+((~u >jw.}#""Ο?5cƌ;;d3Aۭ}EMLLH~LMM###ϝ;CCC࿽;wܳgHg^g.\p8cƌ?~I:to'N1bDzz:8HZZÉ---uuu"$ݻwfrtt\f ,uY Dv1w͛7>>3f(۷mJ |7L&ĉ"[mz(|޽{1C)))l6tv7o6}ؒnvv6EQNNN&M)))E-Zb1L&)&&fggGQ˗/:tHAA˖-jjj{mg;wXYY]x133^]wnaȐ!F&֖ +8Ξ={,--WXQYYI:Tw`0lrڵ8Yw^EEٌ3D8^_gٜ IDATn߾=}S5:̇E䦦&)Cٞ={ܹs)C:gE|,:]]]>|rqDZ677S|555ՔTTTkkk+++oܸO81%%wo_QQ!@%|> &K|&cc'N;wa'0auHwx3f  % 'Nܹs-jjj"}8YFF>Fڛu@EEEa򿊏6m4rHY>]D͉ӧOrJhh)SHQFFFO?00pرFFF=zرc7mD:EQpqqIMM=sL\\ђ%K I!::://oĈ/^ ðlZEft/X,YYYeff0t5cƌgϞƏGZ:m4ҡ*/^ܽ{wС 6޽;66ĄtulsÅUUU&&&.[laÆ[P@P^^nll\XXxu'''~ZKK+11QQQ_~}ĉ_~۶m]^t uuudee۳_,H6wn3v6-[<~/ ؾ}ّ#Gx<D }T-,,> @D4558qbo޼?*PM<933ŋx=CLLMAA֭|ꕖxd*辢B|3gzF `"2}J{{0gС_}URR,NNNdzfׯڵk۬9s昘*++:t͚5Ϟ=(jٲet ٳg6,//^⢤4r]vmݺvdϞ=k֬WUU={Ǐeee;p_a₂6mڴc:otttmmkC{1nƍ/~򥯯ohh͛HD]]=22r„ &Lػw/8É222&"c"2|I&IJJ^ptŋaÆ-]~"Ä7nǎE]r|<)t 9777,,Ȉ`!99ٳt5k/7o!kjj BBBD,66cǎ%oTTTڵkݒ˖-" `OO۷aj*tciii z!C%...E^;6|@@_t0{l.K_BǠIHHwNDrrr̼}6ZEAA!<<|̙=AOhaaqPwjj*ñ lii!7n"RPP+,,\vf`` =_1̠ڵk NDo6Y^x;ϋґ('MallL*@%))yȑgφ8qn:A#u+VgϞM:t"^DGG'<<|Ϟ=[l:thbb"D͛cbb"""H似,Xk``papGDDDEE >tOD׎[j2"2bcceeeqͿu۷OWWt֦LZZMMMӧOOLL>f!|3gfdd̘1cɒ%ǎCׯ_^z  a0/NJJ>|8F#++++[[[???AE>}-[fll|E@@:tqqquuuÇ8É(" %::zĈ,tS\\`ٳgϛ7t' |#""znggtR===???L(~xgH"**j޼yzzz/^ Ș7o^ >xwƍF$Hmڴ)""Ç;--C%''p£GL8͛b<885իWGZj] b>^^^~666QQQCӧOXXIBBBHhNDGEdtř("h߾}aaaNRPP SLҶm8p ;;.~jޠ6m]nݻw&MF2ߟ>}z~~~ǏOII;w.*$&&f֬YK, iӦDFF,,,¢F5~Dҡ@III;w{8Q 6ݺLODį9("g2Ç'D$%%666tLj"u9st6L&3'''66WUU8qVIIiׯ_'z(++ϟ??33#!!a\.t:dee>_zŋ"[[ѣGo޼tfccy޽7o :tƌYYYCb0ܺuw}D:@{|8YBBEdɓO8{̙+V[ߚ0aBTT͛7H'mbbbRRR, Ed@ zEEţGΝ;tQqMggG֓'O,,,JQTXXظq:I߿?|p $< fff,O>(++wMTAFFxYCCàAƎkkk;f̘>}|^rrnn.ϧ(JRRRCCCK֔zd"_s,--I!aaaǏ9r$,f߾}bbb+W_FaaSIIɍ7OD֘1cxD EdDٺcǞ={tN6,%%Qnkk۩#UUN}.(--}ezzzJJJPP?b8Ζ-[[&FwI )8p H677766瞧QɓǏ?zdr\ Ç;::u666{BMMM:Gl`` ''yzRWW׈@777qDȨQܹC:@(++۱c޽{edd<<<݅zɓ?~QF?9::8qBkw^hD111jjjh!~+%%uY(J\\N2@ǪM4)200,KJJgŊ222Bssssssvaa!]0} (566666611166^*%%%))))))99999(MMM ooo 333YYN!!!A׋?Vyyy aaa tAenfTTTnܸzkNذa×_~mccC: RRR }}}ǐm۶M4t.-ݛ;wW_}uiӦNlv|>T$Ld2CCCI Xrzu3>}:tЌ }}}"""FyOwiWW׳gΜ9ǫ}EvvvJJJjj*]ˠ\.WOOxꏍp8t򆆆찰f$$$eq(ٳgSN555=uco3a„GYZZa\իWOOOO+++;;~iȐ!s`0wԏ>oDZZ1>Eff~||<>vvvo߾}䉤$8]$11qȐ!/_422QQQtJ333;wxRRR,,466R%//O@$8pGk.///}1'''==]XfEIHHhhhhjjjkkkjj_SSSGGG]]S577z ???//////??^GZZhkw7o|8A9--^|8AydаdɒOO۷իWIgaaak׮MNN6mڎ;tttH'rʕ̙3RRRGLLL||pرctuu{L0>ELL :t( qqq LgtpѢEէOF VWWz1}()))pr3yȑ;v>|xGߓ044444lիWyyyt6!!!??^GJJ_~***SWWׯ BuuuEEEEEE%%%%%%t`jiiijjХpj/o Vyyy111¿EIII,l*x'O455>qDoNlllL:@dzKHHt钧']jպupr&ЦLp҉?Kl6EQ),YŋHAKJJHgRϟ?766NMM{lĈW޶m[n@4UVV&%%v@Qprث&6ܪ+//OdeeE <ɔcX222l6ELL=#[?LRUU\]]T[[PWWW__P[[XSSC?. o. ^ oГUTTP5'(5Zzf|#33sҤI%%%/^''ot;v .mo~n4h8Tppp@@ׯK\]]㥤#Edheee}z3,${MLL%xdSS/"YZZ쨨(qq2(OIIqyy9EQ,uW eee߿^oMMMee@ x=EQu)---%%%%%%---!!!###...++d2[7۴JJJeS^^̊ zf2=` ޽9sfLĹϟO:---FFFG>r,]!//o֭G555ݱcرcI'򪪪O^zƆt=!<GQԒ%K߿`0222#WmF_Yʊt< [E(]ds\׮]3999 yyy CWWWOOl޼ytXSSu%i=r>B,l=k3Ap,///o3A9%%%,,,''~&%%%&)kkk3Lo߾ߦoݺKd2=<'Me@X!C,YŋgΜa٤Cuo޼9((h׮]tss]lڴis $$$N:riӦ?~믿&z/6 ))I/uuud"2|^>˫رcyEw-Zh̙smt洴4c|\^^NQ444xzzz\.㡢 IJJ6˛޼yzrQQQXXXZZZuu5p2N.jjjN>}ر׮]kYt/W۷/8].!!ҥK k@d2߯ݻUVN}ndmmm"rSS("inn~Qo.޺u/_m}F|~W\b>> .DwruuUPP5kVYY1p"p =Y 444Se8 z{n޼w9wȤ^ޔ0`iggѣG6߾}񜜜;!!UKK˗/c|\^^NQr1UWW'@D566Lהsssp8'(˓ EEEK\\ܹsI:eeeڛ6mZv-,$|駟H'2w/\oݽ{)1!縮YF\\|׮])디a8;;(UUxdRQD~S-QSS֝lz1622ӛ7opqo>>8Ym&(|Memmm](Bǎ300Xxqff{Y+_,YB: aֱaaa} \Х###K;wZv5ddd[Df􁬺:bD󭭭I!`ٲe111AWX1{l{{ m]8NIIy5EQbbb:::\.Ύq\x6 [OP(577S%..ާO隲,]奪pGX=Sϛ7oǎ , >}zĉ 6\paݺunnnЫEGG]w8t"v"2}Ed6l ]׮] #E$ӆ}'۳gODŽqjjjAAAcc#EQ xvvv\.ihhHJJ GRRVӫ8· ؕɓ^K@ofΜYVVvy=ɓ'/^8{lYDŋ]\\7mڴ~ya@Qvddȑ#o߾_N=ɬ.A"2|!CvZ+((XbѣIg\>ٳ>}tX>jkk_x!,ӝr\ 9p8#@p8ffffffmcbb RRRNPѡс,--7olkk۱֜Ýlmmo޼L:Q0`if͚ Xl]v:N{:4&"^|ԨQv"K999?{PUTTꚝ}Ν/##""Z#޿_YYY---MMMՍ555 uuu 7RSSC⭗ɱX,yyy&`08PTTd2,KNNa9Y|>?77u8%%n1 ]]]===aXOOOKK #U___TT$,)z( Ll |-[:tz>;ꦦ7)E0oo FQϯhgŠϼ,̇$%%+'VxfDSl~c.ܚD&L}&8+11ڵkNNNGY&..n遁@2e}5u?  a#{ j[QU( 8ց֪u[먳VźE`"E;|_> ~ޛ;^7CNrq ܸq+w;шl_v-A~hB{\?CrOJWiiiQQ<-UTTT*FihhFu paJVV///+nv?.YH?iMMMMMMѢ ߿9NMMCqssǦfQT\XhyCCCvvhi/_믌 aFMMifff6BJƍںuiztp0 No0V p.nMl6o+:!fTS𴜜N>$ZKPUZ}i Vqܪ*lqqh5~ 0 gEbZŭV$ @|~I%_o7i" uTTԄ \]]>|hddDt.4` &l߾ YC}իW׬YciilٲQEE\.x֭YfyxxX]NCMA!2@Ro޼rNNND>aÈ"]:hffft-((:999yyy+tayPUUmTڋ iT6;lΖ$$$2k &++bttttttpg,KSSS6uuuxPTñc>."''l2BQʑVZ񬱱1n$gddP(\ˑf̘aaanݺٳg2nSYYYWWWSSSYYflvUUUuu5ͮl6fQ%d2w4(E, !QHpmZ :Er6K-'j[oYķ/*))QT< JUVV4 OPT|*Fhx SSǻDFF [ӧF": H#y;w?~ӦM~~~zד?.]:u'N,XDwRPPhZL&x!2V;v?~<## gʔ)?~4 |xeeeYYYyyyYY4TVV qf#FxN+++ST(Fkԡ, L5&F.?沲2'˗/x8j\trrrʸ^&m^CEE|󍻻۷Nt>>))IIIIII!YYYccc++Çϝ;wOkllܧz5R666M*))պ޽{yyy!YYYSSS+++airEa?~c\%##caaacc%JI駮nccS Ǘh ?~zDNNNQQQ_~^Zq᪨(mN+q2nyjj恦 4[hBSSSKKK]]g 1gΜ_~}D''Ovppصkׅ =խ["##fϞg&It.@ Aڵ8WiT,//O"deeZh4޽{"… _~-̸oc +++777ȵk׮;w. neee111/_⊋BK,qppppp%:&hʐ!C "\pqA۷o#""***H$a:*99YXp{k@)**ZXXX[[cUU:4 % rrr srrpqaaaQQQqqe$yyyѪA##FU</''\.J8p#5z <.$+Ԕ5{oCBBpWlUUUa0zzz CGGG[[ʧ%##sq3gZӧ_>,,lDgB,Yd֬Y?ݻ;v̝;譖-[r8#Gd@AADt FP(P qpp':H+--7oĉ"5@NN7~嗲۷wU^*55ŋ/_|ŧO|a֬Y΅}{YYYs"/_p]ׯᇪ*9%K^Ǹ%-##caaaccdkkk55.cƌsQ(M6mܸQCC8Г(**nٲe̙k֬?ow^GGGsٳeee̙SUUuYn:NAA!++Kt FBdh]ll1cNedd8@t"K#rqqqhhU^I?xΝ;ϗ0`1c6l0bWA/@"LLLLLLB</!!ף9sfd2nĉ222mGqVVAX,\kg..,,5;eX c0L&ׇ@v# `Y;+M\.IRY,p;+zo>&9k֬???u<~u=EDDDTTTPP-Zc---s:M__rP[[+Fx<(DVTWW'$$gΜy󦊊 Y.-ȡ e]狏{;wbbb|EF䤨Ht:@˗#=ztP551cƔ544M'Ol}}}OhL\d]__vÇ744=vCSl׮]]}@'//o`````0;;Pxc۷o333jM utt7^#$$L&X3)))-]4<<|ժUPO۫W^k׮T}<<<޽W_]vF`t:Ft FBdhś7ox<3AVyyYLF yyy$q!rvvÇuz˗\d2ǏlٲqA?Yf͚5kB߿wӧO|w}7w\'''@АUPP022711=z4p,d2Lf>ZVVoԑp֭\܏,̬,##jՇUR IDAT*Htδr_pa…DgD"y{{O4駟~ڽ{So>o}ԩ^6kq8K.۷ݻwzzz>>>C !:&O|\\zjZZZCCBHSSsv>.]Z__ //ft ;;;wxzzz<}}g.X q…;v$: 8箮G~щ@Cӛ"#l6AIn} eWbfffYz:7oFEE9sձ̙tOHU^^~O~ 7݉&''c\|ӳ200077g0D .o,P(((Gݾ}i<<}txx ѹ,::ÇD'=˭ CA]]V8 ]ӧODg1p@ ߱Em֎;}||#Tr;vP(7o^d\A:gϞzgx<@ hRz2Hqƍ7.99={CHIIz/)СC,Yboookka=jz)|n~=33NIN ^D"z{{%޽Oyaر΄ hѢZq邂͛*Y5NaiiyΝ+ zʕ7n EkNz:USSKh4"k:;;8q9zhm|h4={lwǎ9rRBB͛?D:uf@#>}z…ׯ4ÇD'TWW;vMKKkܹYYY^*.. 1bT!&=Is{%СC˥-X,֔)SBCCcbbJJJ_>bĈ7n9R[[{QQQf[bž}6mڴcǎn>tW>}^XXAshh#GΞ= -%zǏԌ5*//8g,\Bx<"@jkk%rrr֬YhggGtm {DSy頠 B8"@ غuoHHȓ'ÖN:B}Ϙ1ݻw#G|1qt\yŢEX,Vpp0<{laaG֮];dȐ>sBhɒ%0n ^^^OLLر#;;;ݻwƍyܮ@PV\yԩb@%''`1c Q$5778555%4B!2͛7`06oLtG8,5´,X}͛wqܹ[R(B2 .9::tssYfGѣG]\\\\\N8!ړD"H<oÆ W_(&I7o,ZBAA+&&grCBBX,֬YD4ٳ@##]vIC ( ޽{JJJܺuk}}}[N &xzzy$MJJ ɜ?2I۷o;;;1")))<<<77>>>]X={v̘1Ǐ^.a+-Ά 6LYYyРA׮]]AtM[eh5Q+Q̨3ڄbTӦM#H|>/$iƌ!Ŵ*[:;&oJ$'xҩoܑrQQ̙3Y,u&'$~C/jG7133 zYJJѣGwߥv[oذ_vŋ=z ˱Xcǎz8::Λ7P֎Zd f{DB k.]]]St7nHǏB]rʕ+fשPRRڵkW7g>$H<[5绻7}Ã˗nnE">}*M|}}E`ll\UU%¥K WyݴiD}v;Υge˚P__/E8auu5IteL&H~~~/_oVffqH$ҤIbcc-ꫯmHjfׯo;wR;D fEfkJ$s&ߠWWWGᑲ3gB H4@ʦg'bbjJ$!brogHSHtC??F\'o(Rb6~WYY٭[r8n;)իW]$$$`>55 DgR!C߂ ԩSkStro =Dtҭ[UfUTT*++9x>ŋQ#vuuM6@k4v0TCCcʕM+UTT,,,Yɓ'B }TTTyy'OƏ/hK+>mHhkk޽;--ٳgcƌA(j6[KD;&Au9qCCCCC1BڵkBiU6{vZL[C[a0{!:E[b,))!:HF&/^r ?>}#A>77UV$y+ц6$'\[-m"v4R+Q|掴 %lPM4 !tС_~!4k֬!Ŵ*=V--!>y?bcYHeo(ŕ#޿`0Ə/εzj99[nutO>?D"͞=;''8133)(( : ^222/^222BS$hm8Q@֯_h|exȑ#3gP(Ν366vvv7oXקMlbbBHBܹɓ_|!:}%%HSL9y򤆆fIII||pw988hjjB>}-^ަ߿p4))/IJJTSS+--dxeeex,GP.!<OH-eX(4ՒK~zȐ!.]"DgЅ222Ŭ ++p,X?|۷t:ZZZo߾upph䭬FfEEEx =X 4tI&*dPYY* ܀^xrƍȧO޸q~ͮ/7Ҩ}~>?yP}}}EEł=u!k.\.B皚8b[ẐX A}۷***C*kmi.+Bn(,D;f㍢Zb2wM(aJ]]`슊 ###EEE /ٵzh1 ﶶi޹ofmMvZP̋+7l0yyn;"D:rHee{zmcǎݻw/"@0aÇ7mtȑxxx f ѣG? PPP{Buuu%"phQLL̀ߝz#Hf=ÇgϞ-_hKOOWn`ff|ݻw蚸gkkkk;ĝyߤ$޽{H$ɓ'v!nݺ%@ׯupwf":B%''#_ޡHx1pnРAaaa^JII馿F=nJ-=$5dгgVhm6 T!!!)))۷o_n]BBƍVelûՇ:DPSv2~횾B4ImŕzG>|ɩ;J&Ϟ=;fOOϷov;ի?~k@_$++8lذI&yyy fL&3**L&H(Dn -uvv&:Egڲeƍ_ci9bee5~x"oĉ':}ZQQSwww$Y+ }ϟgddݻnGvʈ6$ _~vΜ9{}I]Ԫl4_t]bE[^yfM֯_ߦ&N$IHb4ݸqcK^KxĮK&Ϝ9Ct]NXVVD"ihhYÇͮ?l0yy;v?@ |//fKj˦$M *kvmmմtV3wM(AUZZ믿ϟGiU)-Fkw]#:wv\YYidd$ .Fmy5ijuf _VO}^/11qd2yڵ(++ׯ_nn.1ѣD!,,LIIΝ;DYfff~ P\\Lt EzjlBBN#S JKK#:HvL~Az ~-//!"ӧ2_|QWW;w;!___kkk:noowea$>ȑ#Fhhhhhh1رc[^YYfsssEEE xooouu#G۷OWp8fff3g,**|M'ɒV#Ytw͚5NQQQCC100ݻwp!2L ݽ{7}>ǏM)MMGFF  ok!ta;Zej掷 bT>>>Ç WF͜9S4OKJyZL[|Dosom*0LssիWs84ՓjG{̙3dett4qEEEVVVfffDgiZMMM6@ ̝;!ٛGddd!@hԨQ֭ٔ["Iݺ>nϞ=e@uu'K(**۳gnkAƶmrssO>]]]`0f͚u՚.J JOO߿֭[;E&:{aFFƔ)S뉎6~~~ϟ':3f̘wܹs߾}w%:@RO<)--7n\YYqta?YYٺ:BLZMll):ǻww )w"BdḷӧO鹹Mx񢴴~ͻF\yyy-:uɓ=zAt"=J9s&ڱcGAAΝ;ccc\.݆D?HCYo޼߿ccm۶ǎ:t(affv֭LJ tttzo+##P>|ã8` Y\,''? |ҥKGW!|>Q!2?ٳ"3gŋ=zDt"m߾ѣGW^_t:ѡ|kkkMMͯ:66322̙3SNŗ#""޽GtYjǏ6gϞ勵-[z\MfffKMMꫯl6q"ST )DHX 2x`tSNAWID"   9w\BIY}͚5DL`I gHWSSsܹ;wK~=9s̙JMM}o߾o>P~ 4`;;;;;;r= ={SԪN?;ix!sBBBBB»w yyAM4i...***Dl3WW'N̟?`DǑGV3f̻w>q .8p`„ D6**jўnݢRD'hTL") "xꕵu/#($$dժU666Dgmp!2.;88X[[MJ9::FFF޿Ϟ=cǎ[rٳ* 233:tĉs_^__Pz!E!cbbbbb:RSS%00!!!>>BmڴyD's-[LWWwDǑT@@wbb%Y4&++?mڴuM8_~144$:@[[HWWשSFDD.h:^SS#FBdF hD騅 >yÇ Dgm444vرl2d``Рnݺ`I8pWRRZxٳzto]~]WWSSS#:XXÇ333;;;+++ssssss33S&YYY))))))>|HHHGƦsiӦ+W|Dg3778q/BtDEE-_<###88xݺuP?~/_}Jhhɓ'SSSKdO?D`0A!21.rܓYc޸qcDg酴nK&Y,VNNέ[LCtȑ#G߿ό3LLLz<ٳK.%%%΁SNCH.;CJJJzz:g`XXXd\ljj &xaeeeSSSsssa'zzzD>'N6lY$o߾M6eee87ndXtww':@GyzzΘ1ԩSd28[ڵ+//Ǒzzz 6RRRRRR2555555331LSO>ɓ'/^ :N몪7lX@nݺsyzz:tDݿʔ)}>ל|aà( ^|ѣL--qƹ%%%x"33uttL.֦RP^^[XX]XXB EWW;7FFFc1F=yDII8HOO755pӉ@R[nݿ=:`w̙ pQqW_}fѣG^vlDBd@cSL %:H͘1#..¯ )y>}Otަ7o|QZZD6l؈#{ S?/^x˗/dee&MݢZѺL\WZZ*\MMMb 2LF)@Kx<^aa!3.((),,)((gxM `0X,FFF::: 5RSS 6tׯ_}Uaa/m___9s愅ЌC-_|Ϟ=0jhwwRH'NLMM511{.و3,11qΝDi;w\r%22D7nXp!Yz!EEE//////Pnn˗/?3gp\MMMGGGZIIIKKࠣ޿۸zUUaÆ\rȑVPP :& ]>T__l^bbb^^^AAkRTMMMMMM&---)D"uz¢4x};4hL&`Q$""b̘1W޿?qZjժQFzjСDgBYYܠ?~+?ri4ݠA,Ydmm =#//o`````PX,Z˗₂Jd2Y(YdYMMMMMMUUOu)QuuuYYYYYYyy9k Ȉakk+a0zzzrrrz)S#/\@tm=nܸ͛7/XG6V*))URR5kq@ h4@^ze``bNˋ":H'pKd22A277OOOde[[7oޜ;w.ZZZZXXXYYYXXXXXT|SCCCJJJbbbRRϟYjxxe֬Yn:m۶ hhhtf |>"C4fddl۶mpA;H$>vŋ]kQ?rȒ%K礤… !cccehh-gӷp쌌􌌌lXXX8;;/\RGGhNĔ' lmeeeԔq2ki<(M***+++4 ++++**4^~E+յiꕂ|2gΜE>>>֭;|phh(Yӈ#>qƋ/9rՕPC">\QQ7DFF:99t\\SS#\Bx<^_.D& 3 ":H{̘1#>>>!!? c~~~t:ܹss!:TTPP}TVVV^^ի^7]D^^eNd]]]]]]===`0:z),8c +---t:@$6ݨRDG\qAdӖ .PT*UX'T*^J*++t*B;qGfWVV԰슊Z6]^^^^^~e \/-ƟğO*M!p<<<>+]]]h˖-̤RDg!_|Y|{̙o>Qf̘Ǐ;::tMM͇%!!!.\f# "/_DGG1,mv &ܿDg4f@jqqqi);wڷz_/7++ feeUWWuY,.hAPZח(**.-((d2Y,bH$bFmmg֪*<]UUgkkk+**lvMMMeee]]`6^#d2YEEEAAJh4*RQQ!JJJ ,ST&''G)!7QVVQTTgH:6PSSr|~EEB !TYY񪫫9Nmmm}}=ͮÛUUUl6cH$h %//D"ikk_SSSYYYXmw,Ç?{ w&:h"ommmhhŋHD]]ĉ?}maaAtl6Fq8kYY.//:.(~ISGJJG=z޽,9hGc t|g9?nv,EQ>}rtIqˉex's%)Mh5Dy'b4" i222B]n;K&? އGż?O[ob7}_A"L~Ho&x7ЗJJJn߾d2jnnnaa1`4ի><22rppJMM%ZGYYzС}NEQGS\\8N}J: ۴ir{ha\.֭[!!!W\?9s Ҥ'((H*gUUǏ믒ܤKO㝭ʵѕ&Q+7Mf7.oIӵ/kӼaϲ Hhh%ƣm)K̍OXU4n3:=%bxw7ygyw`:8!n| eƃŭ bccoݺu֭Byyy KKKKKKEE]̃嗭[Ҽ?~?IIIquu}+|}} D >\XXݻm4P 4gϞ7ps>ЁǛN݊|}}WZr>n 555O޿JJʠAggg'!!AQɓ' Ɓ.\H6mnݺuwMWVVқ,s&ʗ- ^ +Cn<5zŢG2/ X,wޞd/L6{%.uK|o?4y({l鮭W^Exxx|ISSҲtƆzq{{{YG3x` Е=|pϟ߿%PNNN.::zС'NuVߟ߈7~d2ckkkuc("֦.ZtգG///AQ||̟EV7 :N7ݻN_L&,ƻl6tÇvvv˗/ kB(jԨQJJJzJLL-ddZZZ۷O\\|͚5k֬>Bmܸq֬Y666٤4ΎbOYYܹsW\ n<\vѣGؕ|YD\3P$ bC:whhhXdɄ LB: r ѣG.>`ّC-((8yW֮]OۗQs>}4cƌFm IDAT#FHIIYXX8r䈡uii)8MΟ?6z>}hѢ+W1"33t"fffv/]th5;ǼY("4h ;=< t>p jjjH4*++٣={l 8[[[!!Y,VsƾIII6lZb^|I:#t\"""{CCôi:`Çϟ'ڊ;lGee%P<<<СC[l!ZD1O333)CiieK: *..0e֮]~zkk뜜gϚ_chhvڇ}رc۶mݻwrr2%:ݻ_p!%%eʕ4bmmo>Am~~~G111wD~ǎ7n 'ZM EQ>|\E 6 ¿^~-..N?D{捻fDDի򂂂I //p>$%%͘1#22rРArrrΝ":CCSN߿ȑ#4dɒ&%%mKHHhժUYYY5jkYYP|jժUׯwqqv,o@Qr;Q9##~~~Prrrϟ}5kְX,ҹ >}{8!XYYݽ{t1zhCCAMMٳw޽s玎NDDD|j˖-K,9sf\\,4)"R("А#<==H_\.BBB&"7ٳg}wÇ_x***J:!C|255?ϰaTUU=<4N87a„oߒ?"2@ZJJ -R[[bŊYfO5SSS3`_˷ϫ(..֎Ș3g \~qK.MNNVWW_t\.$$$޼yh"Y&!!|6M: ?zhׯ1bӧOI'/bbbQQQRRRƍ+++#~ɬn|(aaa}Tt /77Ç&E߽{k.Af0L+,,ܼyc"""f̘] EQEq8&9NKKjrPBBBXX[n"""L&SLLLLLdtMXXXBBn __߬sEFF۷O]]~BQWW?s̄ N:-^xϞ=QQQSN%ڕɓ]\\֭[G@ZZڵk?ӴiӮ_.**J:|&(%$$ mo߾ݺuʕ+{E: cK|&c7778_UZZZYYӧ2tyyy߼fqq&HKK***+_F,%%խ[7 nݺIKKKJJҧeeeY,﫤7#@}ٳgϜ9}v555;;yӇt@h#G$(wޖC?iiiĜ8qb.\ 355% _ڵkÇwvv>u C"( :Eu[VVˋtlEQ<?~|Æ eee+W\r%SU[[[\\\TT߿W85QLwy.eXzt(EQ -jk֬1%KL<933#pttXdСCB7@000x7lG:|&Ed;gO8d2Ig4."sy{{|ǧ{o{]b)))ʪ 0.v޽x`)))wcM@79+>z.$$$//ԫW/UUUUUUTF9rȽ{޸q̙3V0aɓ1zW`0=:x`kkt׸&NsС}())?>**?8|9P|aԨQǎ3gҥKIǁjp S!99ɉtor Y{"WZ5z3g 8t".yyyo޼O~C|544 &//߳gOeeeNCq]YYYQQѻw ߿_XXݻ܇y󦪪L^TBFDDںǎaXgvrr266&Zą -ZA:`0-ZqF???z[VVVÇ򲴴tpp_1yzzs@4[D#,ݓ'OMLLHs矉%#p8E S|3/00P[[֤t yyy999/^O~( UUU555cWz:/ƭNZZZZZo߾iiii~~~^^^^^=:++ƍKJJwWZK !!cǎݻWUUESSt@hM:::SN533[x18?se˖>|x .400سg P]իn޼9l0qۘLfmm-sd2 / $$$I+WΝ;Ȉt(zEQ|58qˋᄆΝ;x댌/_Q%##CwXk޽1z`X,_~Ms8^c344ׯUFFD|AIIcٲeqqqǎ;x`@@㝜LB?.g#G$FFFf֬Y^t)^F͛7GDD|{pzpB---ddd̚5+11QEElŋ=zcƌ!:qq;v̚5k .w#$$9vX GѻBE?+"3L"2𩄄cǒN5޽۶mի#''`Q]\SSk׮}޻woȐ!t ݺu߿Y,І555555---#\.իWtŋvb222Пt۷o#"":sNcccqqq1O2FFFC ٷow޽lٲ'Oe1+WOVVVoƣWDnroIՙ&&&|ͺuX,իIhVWWM&޽ۿ;w&%% oްa9[`GLLL>Y^^tQḞ ԩS###={VVVv޽ 6HKKGDDX[[+**Ϛ5k޽O>%QRRzettŋ-ZA:8II~ŋ+V rss*(( :aaa// AAܹt(.GQQQp8?k(" RSSLMMIG)))ǎۺu+٩0DAAo .YD.))5jYvvx <'55u׮]ƍ3fLxxo\\\yyyJJR$4խ[Ç/o޼y˗>~f==={:998q۷t摑O>uqqx&LpE6M:CCÃ:t̙3dʆvlllPPߠAH'sҥ+Wx{{ى\.EdG+W;88>EQUUUD.FEEEDD(**бtP|֭[cbbJKK޽ (++/_Ν;hYPջwoSNL>]KKǏw;wŋ紴41DEEBBB `XaÆmٲ2Ȉ#;}CC ZbbAHG׮]}v@@o.t%%% E1uʔ)?zڵ۷'Nt:::DGGߦvg9r` 77ϟŋO>fllH:@W`092h kkbqEo2L6"2𝤤$.kjjJ:H3|||\]]Hg>|ryEdvxpΝ~]v?|D544:ujȐ!FFF>ܶm[QQ'OL#PRRruu̜(3>p86mPSSKNN1cDǏ9;;kjje˖Ȑ:3kkkԑ(RUU+((8x{-,,H炯ٹsmyy9 ,ԛ_@{RTTG#'%%NХ^~=??֬Yl6tlRDo Wbbb͛uֱX,Y߿OC阝nwƍ}X555҉u떑ѢEƏ}ɡC OJML[YYy1c ٚ쀙LSBB͛7MMM H ODD1##޽{bbbFFF A3Ϝ9SZZB* Ξ=K*tF666YYY{駟0uihh={6::zÆ ߚPDhbbB:E3֭[׫WK+))Ւ(b2Tg|uǏ/[7?UVV.\\SS3++kϞ=x _x۷;ss;w?ÇN"6lXTTTJJutt1RQQ9~sBBBPTT6mKȃ hdV4jԨ;w;vt/'"u7L[jSyyyp"ÇϜ9m6Yy>|坥uuu}zI&M:5!!O>\rܹs/^"r\E=zqtԩS͛9L<HKK=zիWԑ;'^#55H77Ddffjjjң;{ӆ \\\nݺE: PTsEdz"rz H\.wʕC!QUU﬘I>|4iҡCCCC 5?KOO2dlJJ̙3Iǁ"..(GGGQQQY~xPPХK.]4i$F,==#Gz[]]}ǎs߶lbbbbkkK2j(}}C())]|lllL:@iӦ3ffgg)r=V"2JLLk\'=z}vAkjjjĸ\.yuCC\ߐnjjɓK@^~~q޽۳gOq`п_ 2dԠA.\[JJJ핔֮]f7nL8QCCCRRxӦM3gd0>p  ƬYZ9))iVVV=շo_%%ٳgOK (bŊZZZt%1諢C!!!7~&*666!!ёtGYY900۷ׯ ٳGQQ\@Q%$$YYYpB"\\\N>]ZZJduhdV`0=eee'q]Eds ?A1c4449B:la߾}Zv͛7婩u֭tt1WWaÆ=b[ IDAT{{tVVVҤn^Ϯ[n۶m/vڵ &PUQQѯ_ׯ_ho|%nnn_r8p)"";==+""bܹEEDD899geeѣsΈ#Z~8;NfO_`ѢE &''oݺ}իW'N&?__}DGG?ĉg& 񣓓w^H{usskzm۶e˖Ŝ;wnѢEz:vXGۨ3***255׿z8\SS3$$>ѣ!CPU\\@4 8NRR #$$˗7ygM;D䆆E9;;{zz޸q-dZJJwB'GݹsWݻ7zhx_~믿6DӅ :$##o߾ߟ9sFOO/55uΝE)((Sqz___:Ԓ2EQ#FrJAAAeeeff5k\wK.nWܹsٗ/_ڲlss bccF-߼; ''-[`0?֭dzg6mt%]]˗F~˗#(lll0SVV|r\\ʕ+Igk&"'O'''wWTTv޽7x"11\ZZ*+++((厎W^ ƼO> ىȻw^|9}֭[把o߾(8%%%00pŊxWbnn~֭G:;;V8))>2yW߿.Yd٧NjaԂޫ̲?~lM&}k8q℃EQɃ 򈑑Qrrr#5~ٟw<833S__t:44߿u͚5C񯆆1cƼ>99[nǏKr+W8OtR\]]IgSfbټ-?ާO={K4("cǎ _7o ɑ#ߧȕElNaa ޾}{1cƐб8::~ʕ+4lᵤ7AXX`p8X,֧O?C_/}%JJJł#ݺuP__Z`0233eee[?ĉ0MW6~yX,_Ւ맯TFF(6M䈀ny[xWUU%!!5ydY:͛7̛7WIIt(>߿3g}n7fffjjj.tUo޼Y`ݻwWXyf҉:1oo;w޸q1o޼Wg(zqF#C &99_~ݻ]v\-NFLL%$DQTmm-\(,,_$##yLWl6p8{xhp8kk묬rCjn--~sLQ Hm0q_(aaaܽ{FEEikk{xx{t.~z̙v^ŋEEE.tUjjj7oܷo߁LLLRSSI'6o<}tlYs ަv|Ed~`ffF:$%%yۑCGygySDvȑ#x]kh@@SS?wˍ/0p@=mb^^^[n]vmzz ZEIJJ2!_]tIBBАtNFDD%;;~ݻ5k>}D:ߙ2eʢE߼yӞΚ5KJJѣ(tm %##CNNl͚5uuuCtJ #<<\KK_"2S/ק_999ڸqcnHgiRD] ϫCBBL2{+W|%1cªUHMFQT``={ pww(jŻw-)))++KKKۻw/ݠ(*&&f߾}wiIcRk׮-[ &L:&YRRtN[nOnݺm۶}7-/稜f|{_[b0a˗/333H޽{m۶Cڵܜt">ibbvv[}4iR- iii~~~˖- ? ..nر˗/߱c,|d֭'Nw K.M2`0"7IJJ۷/ EQɿ֭[BDP5zxx޽/V|ӤIlO:ˏLHHSPPYreppplٲ%55u޽ъ+Y;C[Eۛѳ~nҤI/_>|ݻ~ٳgŊڊ \ֺ6{ɓ'\t -dVKSS2ÇC [Ƿۢj޿ƍk֬177 Dϰa8oGLD(JLL`ÔLD;OdȐ߻wt(琐]]]ww &xbŊ},_<'',>>tlʔ)-ruuk.\XTTMT͜9Çt.\HKKk3p84 ]_rrs%~`/9r$22r4@4tи7oԌ9`Ϟ=s#{YYY˖-SVV7n܋/<(//O:aXO>UTT>~… ]r9ss󌌌SN6֖?-rӦM[v%KKI,&&& OPD/III "gҥdr"2h۷o 6mZ- W􂃃͛7| 6W^/'Ǐ;6e999WWW--̥KȐNennد_7nNlݺuÆ ܔ)SzkФ>|ܹ۷o8ۤt[l6mZQQ,]?1ϟ?gffw޺5k֐?LHHIMPP`ꐐ5k999ϊ|::::77GZXXXZZFDDTTT|իW{3fsLLFVVVLLLnn.&&""ᑜ9qD''bҡ+V3fΜ9mȼy ̙3G֭ÇI@@,k̙94[Dn7L;!dddד_/ݻw' ))&ͳ|ڵŋ{zz.[s:::uuuEq8h'''CCñcۗt^j޾}{W^IKKO8OPP۷yyyYYYׯ_/))//!!FUWWWTTT2ttt_~ҥ˗gΝ... t.`>|֭ j\\\vuƍI&Z[͛'OӧP <== _Ο?2zhҡ}zzz?~M6mѣGt!"$$5zhCCϞ=t(kͩ3fHNNѣ8]&"7L]-Z~=/_ ;| *LDnt˭0>>^RRV7/^x_??siii>}L:uMWXXz֭}iGZZZZZZEקӥ>|FPPPKK_~Ĩ.&+++===333=====Ep„ 7n433SWWoTbbbt񺺺sss_~]]]M_b}9AY[[[JJ=o@% bii:v؅ ڵ I%G7nʔ)ӧOoDDDBCC׭[G"@Ǐ׮];gΜK.Ȑ o666}klRDp8("@Wㆆ/7jkk;:: EJJr8^~͢&"[իjjjm@V^^x˗/%%%uttfϞEOn_z7YPPf6lΝ&&&__WXXFKfsss322233###nZ__d2{+6z^zŋ윜k^^Ñ700744'""")*--Ok111999eeeX,VtSYII U{/_~=$$+&&ɓ?Pa֢En޼9a„[ŃǏ?FDD >t(HRR222rׯ߹s'8]4ERRRݛ-UǏ9s&22RzFїE6z^}9!Ct1Y^.R%((oeeEwY,ׯo߾ѵd׮]Æ lڼ muuuO>MLLK7nػw/ҭ8]JVSSSUUUSSի, ͛7/_y ͦ(JNNY5O>&&&S[,.79^ZZdrVVVLLׯ9EQbbbvr㦲c0->>&&&m/$f"0mOS+*,,|]QQQqq7o 򊋋銭ưaxIo?,b79^[[[XXx2=D9//(aa=z4Lהuuu%$$H2zu9WWɓ  777>}m-2wܢ"ee[\{)mmmҡ:9sܻwY__o߾tML&f ]Yrrɓ,è7obOꮁWD[_v% /^4䔕 IDATQ%!!gnnNdddZe]yyy C͗`=ϟ?EXz oAAARRR~~~EEb t5YIIIIII^^^VVVVVb_񀄢ҿn{0***3gΤjjjJJJXLbDEE_Qiiin#EEE111/^efX_NPn7mڴrJlcƌ>bEE6Zeׯo%̙cjjٳgNٳ'%%eڴitzL&wVLLػ&q$d * Zw: :=jYqoe*^!x_>b$~]uDd AdP*uRKܹ"jhh ._߰ߊ >_YYY___]]]WWWSSS[[[WWW]]񪪪PԯΨNNNآZѪ)l6r\嵵yiii!nii)^>RB!s@(ʌ3zy3fT룩yQ__'N)q֭o0``lٲeс...'No bmm}z{{N릡QSS#m!v!::Ȉ(_ -[6x޽{+kPFFFt: 9W̛7o޼y W *++SRRD iii!--N: +qgcc#M&X:uYYP(taBaYY{h 80L&Eӵ%񴊗p_d y<^vvD刈 Qk6ݰ=Tʜ}zVV"ZsԩөSU! cffFv8 ;g]6d "6 @5rH%w* W\9bBn{ B4 ʷ"5k޾} YV$//͛79<!djjڹsgCcKKKP(6r&lԈO"##ӅB!B`XXXHTPVS o߾_|7dffZYY)SPإKWWSN)S4K,ٺu;wBƍ;ݻwZleek@t7o!T*qN9!'Ҕ߽{WQQW`6 իqן9sW^djxnݺܹsG'[^~ݵkׇȽq@ݻ7i$*z ___@%ڵkѢEw܁E3̘1̏g===B&&&lP/**XYӧO{(  ^ WD^lYii! @DQ۷oy<Bbuٵfs܆W #""q&ٰ- #u-66vڴi~ʕ+W^ B]]z 9sҥKn:T_>},YJ Ǝ3&66 m|- ѬxEd"" $"nT>v@{{{e P|ƍ{p(AuuuRR(Е 444mmmnj#*x u=@BS ʵ###322BH]]R&`Xϟ߹sҥK=j``@vPׂ -[6d ?mڴ n߾]OOObN>w͙3޽{'O#;(Hk׮/^FFFh4iM YhƏO8ymev _+//dz8%f͚5rȑ#G%B 9o޼CQ([[^z͘1'Y[[I3h&&J,/**O"""222:l6ae;;;(󽽽Ǎv̙^zT+vڵro|„ />sLppaҤI&L޽{xxQȎ2iii;w+44tڵdӚ@"\S+--}}Be֭[7eN l+"/_j޽r О~ZT͛7N ]].]:;;`N7<6)))iXA955fVP677P(J֭[llӿە+W^JJc0oO: uuuG}!HDЊtիW/3fLpp֭[544 ҸܹsƌzׯA"2m\ll,ALD>|p~~U#P>Dd|\hF.ȝ;ww)### hjjjtII Bd:OlC6rԈWP9B!`0,,,'Cm{h?X,ٳgׯ_nݺ#GJ9si|}}28{9rdPPݻwO>NvPfڴi< 533#;A"~HD5QQQfff鮦&44RN8Dd x<5kСCǏ/8mA?~O8NKK ֖c+++:NvZDCC'K,BN6設ȗ-[bii=RKϞ=ǏߣG˗/ۓJ۸qkז,Yrqt;wʷe@&Oܳgύ7Λ7ڭ{zzzDFFh4i$"2͊TZw(--]bz%$B_)A+U]]]WW'IJ[[["TGG j ՎSSSwCGGٱX,C< ePIIx弼ߗlv xV^}yӦMǴiӖ/_nmm-B ²2W.++Dr`0455eYFʷwZ5ѣG{xx9rddGtuu7dȐȷ)Slذaƍ\{ɍ7vO?ݽ{d Ν;׽{ׯ^pZDd*a"2 c(ĉ׬YjkkFc%tH{ߺu !s# qqqݺu۲e jeeeUUUUWW.FӣP(l6B 5556#ӓo?%% h4#plkkf@SRR"^>Y0̆ɶ:t @!555 Lj*jEjkk9ѨO4!Mth^|׽.//q<%qo[iiZ^֪ijj*>XHn[O"YCC_*jMKKK]]]|BthB xOX,*uuus=tҥKׯ_ۤ?~++GȱY@^|9~'OػwܹsoݺOv,9rd}}pbUTT>}zܸqƦd @[gCC+W 2D m߾}ʕiii&&&J(995..!>sLPFF%7F{1\VqyyysQQѧOē?Dtmmmd2544D "yjASpRѿi))F3000007!^hfffii dGǏEEqqVV+~c[[[+++Z|WP8U]]Ҳae^|yȑ/t:9244Iy,UUUeeeյD^<)8No0wa _ΩmixZ]]]KKKx#T*Ug,("VE|>B4+x? FYasS۲^xH#FAg&CG AGGda ;uؔbgg'n۶M-9Ν;mP3g^paժU ڧ;wƚJyw}WVV&:/abbRRRrɓ'SJhbbb֭ٸq̙3! =!dzDL_tرϟC*999yyyYYYyyyٹ999ZT*U<+gS_gϔ72Dnnׯ>,fqN677622"qHWYY"J8NKKKMMLZZZ:urvv];vd$WP͍LIIlɶvvvt:]z7#"".]w߅+hwJKKS)jkkkjjDmmmeeeEEEmm-^Dp. nQƆI!ф(]a-^֥MtX2x:ΥiiiqDvz'L&7[ds3fpqq3fץKtBvDpSN=z[6mСC?|`gg'febXgΜ9~Y߿I @xmHǗ⨩%"3L555?TAEdڲ7޽;++K }ڵkٲeiiiJ+11K.4VMMŋGF%$$xaر;wTp|1###===;;'PT Z CCC@%xO>feegee휜\!plll:tF@,J88FuATO@e2T}}}VVVff&AL46UX]]~aaavo*//Õ%%%2,<7JА((d27H1ʤH(TB3_6?mmm]]]%zIOOOWWO-۳QF%&&>|x̘1dk˅@ 4iRXX>|xСdEGGkժUV";ݻ%yyy'54ee\.W mڴi鐅NW //ʪWZERׯ_Bϟ?$%%$'''%%}'kjj}#F,D|juuu33IV]]_\\r?~tڵ,\(N[[ѱSNNNNx޾ՆU;\\\\\\s---xU No2A8)yƍnoݺuM777*ZRRRZZZRR'n3 q$611i*uRKKKWWdjkkM@iL&Az 4NxM<ĕflhBbV|N0~< 7n\tt롊[CܹMYB&Mtu{svv~?$'''''?~833 fccST`UUUrr(å555)((W;Rڏ¢I%K'&&&&&t޽CE\UTUWWWOOB(khk( nƶuuuo())Ȉ-,cZZZ s \ FxxgHHH\\ӧhEBBCC#G:;;˫iӦmذ͛WY444v:o޼W^>}Cd,^822200066b*DdaӧOׯ_dz:tЎꈉrv6mБ}~~~Ϟ=K-TZZ"vԩS'TVUUUJJ N)'%{xx(@ 9~M^^BJr8\';;;+4D颢Š 4MMӆUT'|>r>>>={ѣ@SVV3z% IDAT+KL?)墭TxRhZ}==={ݳgOccQ۷oMLLtbkk---[#: sss rrr srr D+khh'╫BT*`8VVV=zٳ+NJI,bR' ա۷Y|ꚛZeEY+֧O/_:G.]%;"BRrÃi&Ogff&6r999EEE-]tĈ'NܷomS{pTN)J; gh}!_~ʔ)VVV YkkT$,ѨEM2*:6?{ '?{ӧO\.wС}Ug/((O榦?hfBLLLMLLceeq8ϟws!;"ҹsyٲeC K#G 9qҥK  Ǝ;~nݺ={sd2|7/^`A޽G@"Et ƌ5 !:th߿VhG@}ӧO 4hРoF]]===]<͛7yyy!*plmmqcslmm EjřYYYYIOOQ(cccm,Q@BU3221@i KLJɴСt9$bݺu֭7ouuuuuu=wڜ7oޭ[qXڞ &$$$ݻwĉd2TUUzd !F9sfرxɸq>|w>|ؔ NfEEE͜9S]~-((>p~_555˗CrSxիWoܸK{bŊtɈ#F~[n޼sN >___b0vvv'Ovppptttpp5h SRRRSS322p1N>144Iu\.wĈ;eRJW]]-I#?Ϟ=422;88S(kZZZ;Ϝ9KvP*dߩS'#G( nݺ5ph.Jӵ,X&NNN;w Vr,;;;<<ٳ޽p8ƍ7n;qV͛gΜ PWW';4@Aiii_߿OIIGh:8RdG mYmmo!t K*ByfСuuuW\rd f͚~z4{˗-ڰ &lذadFFFǛ\x…  N>r_Nn`Jh8B9sT\@eȶ4-55_RRn]vݏ?(U˗/wqyCCñc=zG @`0 6lذꈈ3g/_|֬YP ***^~]QQ233ƃ;vh@Lf.]$J~xxxee%BҲk׮]vussڵZ+ի#F|7'O6l$,,l…'NtrrjyAAAK.=}4 ІYYYݿ?,,lѢE{;p@۶gww)Sܸq.iaPA"2mUtt :v쨸^‰VVV! B]?66ԩSgϞhJ T۷o왗ױcFn T&o߾={o&L ;:EzzzlllBBBBBB|||ZZA;wvuu0aN_#;RMb^^^^^^ sҥM6 B&{xxX,–ӧO1bի׮]KvD >qDpp[Vb~HDж]uԩgϞԩA(,̙3{޾}p;0Y(B"25k?w\ZZڵkPe8Nw1-- /P(M%"/ݻw=zBT=A\paŊFںukϞ= k׮]|3gvrGeCvhڠgϞ=yٳgyyy:tu񮮮;vp8dzUUUo޼OHHxbYYJuvvիwϞ=ɍY8v옩u몪6m΋QݻwwԩS-o~߿RR\J,lĈƍ֭[xx ȎEZj˿[www!D"2Dd@QPPrԾP( ;v]@JS__ORTB J6z\ŋk׮ݺuKaW^M8֭[dGAWYYy .<|P !J`0&O}ؐJݼyܹs?~5v}Κ5kŃ b-lo߾ǎ8ΣG/^ɓm۶ bŊ7nL<ŋdCA;LD@B ŋ)))+s;vLMME]m"5k||| U@ Xj'J=zhBP(Lvȑ9sܻwO Wׯ_ll͛p#Z;vl={ ͽ|+ YȊ2W\_RRRįyOfر[n}QYYYtt%KJKK.\hkk۱cGFFx<#EӧO+W P^^Nv8$[~=Fy?*:iҤcǎ'km]\rԩݻ2.=jjj'OLOOɎ| |>$"ڂKKK###E4. ׭[7zhxb{&]WWTE/_޺ukʕdIAm޼yƍ=ҥ yx"BõA蚚ܹsSRR<<< ֤fff5557nCzzÇ'NhmmMv@Z 5R̙3?TbǢE^'O<|_~FFFf͊&7B//~w999C.]]M6ݻŋ-omԩ1YڛCt.{@!lmmٳsk׮ 444E h;bccWIIIWVPU%" Œ *5`$[qq۷o={h"I&1 ciCC+Wl޼yɒ%/&;J?4dZZڂ ,-- .\P\\|ڵ;=T)_1c";6y{{fddzm$vrrz9ByYa>}̙3=r\Z:<|pʔ)cǎ?~}}= 'N7nԩS ɎL +"C"2-P\"2A#FpqqQDJMMϓ8Xj)A >d! O:5g---ϟW طok' +WtPUٟ&5m4GGGMMM3tgϞ}e˖999>~666ǏGUTT,Zcǎ% ;RSS/)޼ysРA666:::\.wݺu B,\ھ}mH>`ڴi l1eGGG<9{õ*;~x>}LMM \QDk E۷ot k>*kƠ$FČ1&2=z4Bܹ- ;wLPƍ'QeS{'k)ocrV/7Y:)]T:.J^1d\TT4a333gg˗7LœkWN5'X[[DEE,[f֭d%똙ݿۻGm޽{~ᇈ7"L&sǎǎ;x`߾}sssɎ۷o̙3L  h'@QZZJPoE4~e A+k.ccc>ן?˗/lrA]]]G+-T7bĈF$lt b v_.BS)_5(j46Q,17W BǏ;v !dll\TT$G NzRR^xoeHޅ8נ1d\^^p73gD IqJp ۳%Khii'$$FmmQdeU+Wia;:::;vKT@ggggdd R(cǎiΝۻwo7蠸Zښ 222޲P(tww6l[ζm,,,tn-Zedd/Z'99F>}TQQn:!B3gΌȸxb^ػw/BHGGg…qqq ,F߿o_>;;[l^r%;;211Fʸ9|YYY񇩩)B(44Tq gϞMMM;v,BPbŋ*$)Kg/^۽{w|||aaӧB=*N}}}.]ƍGv N, ͛7ѣGYWЯ%%`tAY޽[ZZz_3ʒhсƍ?~>} DF5[SD1dL(ˀĉ!lllB.]RʨѽbRR^xoeHh JCF?S`a7ʲS >|޽WȊϘ1CMMd@j[[ۉ'S*//3f F[f@ ;l֬Y,+==@O?Wew>kogf톆hի %&&FeӦM:tǏutto={''DR>}`|@P… <%zw%K`\o&l?|̘1vvvL&S/x[lܸ!$:'N٨Fxxx|UHR!ta###w\Ш";0Y` lll֬YBĕ+WB'Qٳ$(KbF۶m-#SSӦ6Q3E(Qz-8|Zf͹slll^|*..޲e#ۉ_~˯_&;?{OY 2lذ~)>>k׮ o:997)[n5g888%))):ubٟ?}BIIBH 5\BR!/D|,??FS3!--JYzT$gg琐|c@eeeut:R.@Ȩ8&&]%GYFEEEx ϧឍn"f Q[8&q@⢡CJLLחQeS{Ů $Őϖ7(%lviiiTԚ { |T~GQQٳgGEby̙}v*Jb$d9r7o Kԯ_;w+05zqΟ?Ev8MtttϞ=Eiiix#MLLH Mm[LL $_wލ^|[ !;;?h4 jkkpMM)S"כIɓwmڴ/_ƕ/K/iիW ͛7B0//6}^}FKdIh>@( BQAUU"zZ:A@QT ev+..ntCQ&B|P(QJ-8766FFFڲw!"w_Zkh-1+z/7gM^ IDAT_ɘaPs9rȾ}N*wغukVV֟vNM]/PWWG999m߾=== B8qUBhΜ9 _eգGlšKÅ5WjM[8&q@:v؆ ~GM׎_CtÖȷA9 ?ӯ5ko+N5'NaÆ㌯^d2GQ[[Kv,$ iI#4 hx<իTjHH#;䣾 ~߿bf?~IJ 6˗$63ݻw,hV\ꊧ Bl6ښ 'NlRc$SII ܴiفJIINNN Aݻ!O?/YDGG!~ySmll#9r$Bhƌ޽{ݯE-{^|ǎ۷o755E~3rnd Iٿ!u;wܹMG xzz>@(\Dd geef͚D.]I X5\Yvy? ''_~L&ɓ'd|R__׷X>(( 8pРAr h߿obbFv,GRRD?.=H'..ĨhS߯-Qz)6AtRQ0 l6266&c„ E~Pc/^LP(0`@W L4̉b$W\dŊ_W5kx#666~,7N%_ IUV5k{T[Ǔ%"3 5y[n5g,ld2\?#'sBpС.( MڡKÅ5׎j1|L(}@g PDD^?ZrSJ){'Fぷ$k]%GmPbaKG|ٳgo+_7l4`чŝjOm+++[~Je!c111FFFݺuٱ(ۥK(Ç[ٳgi4Z ++mIVVV= n޼Iv,Ν;TDy.\X+))wϟ?'70%Zڔ`ooo*z%6 ZEC4btuuY,ǏBO>%1|ŋkjjd۷=Bqss;tPYYT}B0<< )zI( /r\//[%J,lƨіvTT__cBBj^zV ݺuCn> %kfȷA Ǐobbdɒ5k?/T~mUJJʕ+lʕ+ȎqIIIdǢl hI:CC 61*7oJ]l |oupp$;%vBH555]ecA![srlsϟ?uS|@;4G &DDDPT7f̘Xr#T}~3иϝ;wܹٱP۷o9RWWk?Ο?Oİad2' l*"77ҥK'O|9Ù6mڜ9sl6qIO###qv"))u޽ӦMkv#sν}vrr2f_3gӧω'T|,effܹX޽{~~~EEE! h4A7ol Bmrfff/^ 8>&B;w.88T,,,9Bv8H4iҬYVZY~XXXL2Y!WWףGm߾=++kFFFƍ;şO**))i˖-VVV+V}vjjUT?UǏ9dnnqN:?'OR[͕k33wX>}pgڵAn庺@Cnnn^& ;vѣgΜYQQAv, 'b2 Ull,..޿ tmCÊAЈ# M7>x ##ydڵOwι}ĥϟ?_z=vqqS__OvHPZZzv޼y?^hQtttAAÇ d2Ɏ9ݻgaa~酅ڵ+11ٍL2ܹsr h_vӧϜ9ͷgϞ~@Dd55L!x{ȫ_u׮]j 'OsDD-,,411QSS7n8p &>Сu֕͟?SNdPx;vܽ{wӦMݻw';(mPIIɳgϞ={ɓ/_VUU;;;wڵk׮FFFd @΄Bajjj||||||BBBBBBzz:B¢W^={ >|xtt͛7GBa=tuu###BAAѣGwqM/ DAQYnADQBkVEGUp غj2Ԣ8uT8@*CV{ "3 @f\reܹ}􉉉100:@ ̜9իƍ:K;zIrr!C-޽ ]d ٤ Edbnn/*++MMM/_&'Y2{ϋ(** HcllLa*222<<ŋcǎ7nn( رcʚ4i ա@.Ԕp 'Kd; `tʞy)˥hfff ?p)S<~ڵk :4$%%9::^pa-;ɓkkk6,IKK:ujeeٳgz'IOMMUSS:K{),,KHHҧO7olݺϏl҄"2D[n]|y0<<<000''G[[MNS$9sFE]] ^zQsD7nطoߵk,,,fϞж|~BBӧϝ;GĂ VXannNu.ko޼!+k?~˗/ٳg^zeaaaaaaii٭[7x/_gdd|2###77 55}ֶ̘1#11:4̙3~j.\6mZVVZ/8`DQQQ߾}:DuRQQ~777r}VVV@@u&M("Ȉ=z$&&>gٳiBCC[6=***-ZZZAp8SyŁΞ=;[[ۙ3gzyyR :1Pxg0`ً-6t|555O>}iFFƿK%/oSWW%BCC/777##,x<A#$666A`ܹqqq3q۷o6lذ~ 7ld̑#G/_u6Muf9sիWǍGuvLflleذa|Om6iB@F;wӳLUUg;qℯWZ6=nnnxZee%Aߗ?mH ܾ}ӱ%%%_|Ÿq @u:JJJn޼ռ}zyy͜9gϞTGhwޑWN&j陚v޽{ݻc|2@sս}6777''׹rvv6%[nqϞ=Ʌ6x^^^ׯ_.wؑfhh؂ÿk׮Y3bccMLLlOMMՁ) f"WGx۷SLPD7n<{lZZZO% *Y34z^z&>|ڵaÆ7nҤI}:@[&;s999uuuAX,q)XLuv*%E.{N(allljjJ455%DzZhWrE۷AZp?3`!Cy6ٓ;uԜ~Ņ8STTԷo_wwCQ]߿9sݻ`҄J2"%%ήMN .@&2Lŋi\Z[[K]*''̈#FgϞ=y߿J__ںo߾&&&jp$&&&%%%%%7nڵGFWd#$7DlL}ׯ+**} vjhhg``Э[7l]vեh;< //ݻw޽]^^^AA7o OMLL%ǚ~ DQQ~0awL]<<<|}};qMѽ{kɒ%cǎݶmۺuN 7nqŪ\h555F>LD+W\vmO5x`CC֟ d]\\BBBq={۷AM8tχu?tqqqtt>|^6y3EEEqAAAaaa4MOOOOOȨk׮vvv# ŷjs==='M/9ҧOH;w 6͘1=$___KKKOOOGGSí}QX,ɗG SdGkv2p֟ ddŋ zuN͛젨ح[˗/ߟ 5kp?~p~嗍7D";;>}XZZJ[[[J4P(IOOEZZڳg8Neeɓmlllll0tҥK.}mUUUw߿_TT#5X\\,y------MMM*>ȼ򒒒ҒĿR\(EEE񯑮]6x^cbb<==Ǎ']=z,YdÆ ӦMSQQiֱ("4Ç=<< 3rH4In۷`ooo%%(D("@瓒bllVj|i~~ťC'y j,^PUURSSSUUUUUەt.RR`X(JAEEE']WW!POyѡ7nly]n#޽{bb%Kƌ{n???4sΝc2fכ"2tJ)))m2y׮]cǎm@2L ***nݺuL()UVVhjj|wh4 m۶n:rNNNNNN˗/'B dggvΟ?/;ׯ_ggggggdgggeeeggWUU`0LLLFl2++޽{RJAA[nMiMUVV624;;[J>TGSS,%ihhdeYA.3v:Dss:OL֋e紼\())!?S)yͧ.0 ;|.7o޼pB}}fᡪzԩ+WS<Yb?nffrʬݻwct ӧΝ;{"2~ȂV$##̙3'OlH +oܸ'Nx B=n=zԩS555vvvUPPPRR:}ĉ[prѣG=z?^,eeeggg;hhhw]__аk׮Լ}6///77ݻwyyyo߾}m~~>ȧhfff&&&深,] ;FFFMٙHHU2YCvv6YhA"YSf",K]]]EEbihhY,&&7 ?7 IDAT\nyyyMM PSSCDTWW 555\.TȻw.YWUU$/ s͢x)ww &ܺu֖Do9tPPPÇu Ś>}'PDhdgg㓚[7m۶^zTiS:ҬOD޻wi$ȶZFyšCv҅b! +++N ;v_}򥭭ݻuttPPP033|r^Ckjj~gIIINNNnn.٣򊋋Żikk4DWWWGGG]]]SSSMM컴al!>|Ç -\.<`tڕ,mٍ?а{&&&(@1LUsd/ZVVVSSC7%{YWWkhht555&bl6TQQQPPPSSAMryA,Nyy@ \(++ ⅊ >O~UWWԐb;V(CORYY񪪪jkkkjj?UTTldlқl6-YgXUUUYYY)))ׯ ]]]Q/bΟ??~qݹsʊDmdsY|y~uѣG9M;mSLLbccsR3-[v ޽{W6߰X,g,K  ǏE"}kNرpx,"˗/!PCׯ_WRR5kɓ';L<966'FDDI'y3UWWy&??ݻwܢg---5 C]]|/9OIIl uz5xx ,1R۲2@P^^ȲNyyy, -Fkccc{{{uoddA@9\@>Fx,A"||%(.,,(,6=Yw\ ;͒ d1<|)>O4\&wIDܥn O)/J"[U+^% H俑xU_$VxlK._:F$̌FIDxwM𱼼w޼y_B,--Fѣ[sr+W1Ν;{:Qܿڵk^ڬ{gϞv z{{{1ȑ#T'h N?zVXAu6DdIIIҥKo):::kT WRRzAqqĉ EA>|8v/֯_]vy{{􌉉h[l V6maaaaa>Ç%pee%9^=͑--- ل677' dO^m~:*mK%ZVlDeg?"Y6ݥK [`ƌ3f $JɉGA) ;t钫رcO3331{aÆ]~}ر:;w$@ ?~{I6dee~'cc%9b|y+"|N->Cee~aÆ6 ZYYҥK=:~xVVA'O3gpvvu;}EDDh3g.^xڹW^ MzBqBlƍTW~~~Tgi6],$:T@К"s޼yr6L /^x|߿׷K. y&L)nܸQXX8~x+ʦM|ݻuԌ3toN䍮n||[pp0q>iǎʫV:H|<\'"2@疒ֳga(49%%Z|QqR"%%%?ڵk'Mѿs@֭qFqqq||͛7nj=f̘ϟS:=z$$$deeؤ4MMM6ڵ+77GGDD_0y/8p`ӦMsέ:@:}%YD|>P҆"2@疒bccC}bbÇ;e edqA(((d2E"e᤮xӦM{711:#eeeWWם;w>|ٳg۶mSTTܲeu׬Ys@@uL,--=zӖ-[fdd7y%''S*{ׯ=ݻwThW\YUUEujp"2!gCQDRRRZ|={ 2lذ62|K'''FDdPXXXHY8)ƍMMM8v[v҅\ U}rʇnܸ_\reȑzzzgΜ)//:#tD666gϞ~ŋB!qڌΝ;O8?4(WWݻGEE_04bĈ;w 6,558 駟vEuB@SxOm EdDd ޼yCU6(..ްa١CթTbX!!!Ϟ={}hhhmm󵴴 s3B2jԨgϞ>}ߟ,mcРA7nl!t:ɓru-ݻ{͇~eg``s۷wgL("("tjiii-;<,,tԩm dXOOOL"ruuuppp=?~ 6Q :=== .8889rںG~~~7oxTga„ Ç7mDu6CBBB]/Ҋ_lɓpt4K,qppXtigWDf9())ӧ~رc+V`0m d[]]AHNDh #77d688{999ׯGf'Mt7o<|ݻcƌ֭gDDDYYbG>vCCCfLj*PCoggw  Ν;>}YfUWWSC%''8q,f|>'W1:ӧOrms>|d.\S#/:tFLϧ V{t钽}PPЗ_~s__ߖܢAAA>JJJ-Z&B͞=;88xTgi3Ϟ=i!gϞjTrnW\5j祈_EEETgiy,,."KIy("tb)))-8&<<|ѢE -+ $7\PH IRSS]]]=<< {nmmmC@fffwƍhii_q޽999T |W^hѵkר6,--7l@\)̙SUU׮䜫KJJ #[X4ϧȘ ñiO...oH GNDD"H6ȯ_QTT|'2E__qqqEEE={ܶm٠ABBB233R2wiӦ%%%QmlݺݻwGmzzz ,,,z|EZhh诿zm4C"" PDN!77E"ў={Onhh@ ,&,EdjccGGG_rwTY}t ANNɬ, A."'%%͚5FQ Nwtt ξqƘ1cN>Կ[|оϞ=kee5vׯ_S x&fvMA0Ǐnݺu…uuuT'  ܻw/ADdp8ƚ=pϞ=#FppphT D"Q^^ͮ]<Y R <==nee~T9zС? ɡ: 6}E55577b㴖ַ~ וNw̙ӧO52Gׯ߲eKff&Y"2B* p8Cp8VjH ^~]]]^o;9Y Q^^.d aaaVVVݻtҙ3g @`0FO?ÇNNN355 Ƞ: =ׯWVV?RV\s&gϞk*:ujRRׯ ֭311믩$dDd:޹^-m%:Ǐnee5yɓ'Ahkk.YD&]4ǯZjƌ?4iՉgoow^+++C\dddt/^x{{ B  駟8ť{Nj` fccs_Q,``"2tt^jn9???::zŊ4p8jjjjjj+"7nDGׯ߫Wn߾}a---C|ɜ8qbddϜ9ӥKUV{xx?6lmm/\pʕS-Zdnne˖Lg͚YRR˘1cN4]zzrEt:`0JJJN81p„u)**RX,֌3rrrvؑ7yd??ǏSZe!!!~s*vvv}]Svӧmddd{zh4ZPPЩS9U]]Mu"_}]hhhzz:YCјLf"2NG::ӯ_?C"""*++/_~@ddd())}L[RUU-//z&7o.\pNfoh{{#GFFFDy^zsMJJ:Kh;v\tݻM;&&߷y۷GUPP@u_{^lA>bI>pe4 Ed8M9t萧~;EyPPPP^^`0TTT>~/ 7!!!!!!44fS] |;wrkʕ\.,q C{mVy...}iT 222l6Tъnnn 9uꔆՉJSS7%%%!!aAAA޷oDT&aٗ.]b0'N:N 2dҤI7nl7k  sƍqƍ7.**8 v]RRBuOj,E案 p8ѷo&uҥ+Vk*^277j|"2*//zݻw{Uh4ՉA\\\vS޽{S>OWWի3g$oߞҔk{xxX~M S|||pHa```HHHVVYf%X,Сq8={4qL4]S|8|ӧO ֫Wࢢ"'9::FDDQ6oӧOvOoo痢I!4nW\9[ii)q@0_pիTgiEdP"2thE䨨(xvr͛7( +**O,//?{uh4a:8?ܾ}377: IDAT_t'O 1cƎ;VZKu3gNPPo|QFEGGK'4֭[/^9r۷oe̘1SL_U]]-^URRDdЊrssmmmH$ ={V{ynݺ A()))((PXDp8ݩ8;;:u*777000>>fԨQ.\ TG֮]_Λ7Tgiwf͊D sppHNNxC}q@ݻ777wTQDdRSS hD?ŋ~~~ BII !BEEEdPXWW'|Aӧ bggw޽={RS[n]ff7TTTNjhhTXXHu4Gqvvvssͥ:KΛ7/((D5kVNNNrrtgܻwzȐ!W^:ssknٲ%//,""2@'ptuu ;)=9]ȊA0LP( ٳg?^UUUdFsuu{򥧧={wp`0N<3e.Kuf۸qw;nvvvN*h UUՋ/O<:ȑ]]݀zEd%%%@qq8&C̼tҊ+; r9 "29,"|)lٲ}0! ziӦ[nٹNjD"PHu166^hі-[s111rLfDDD``%Kfر#::Tg(" <~EO2#(**L&D|Boپ}':K.\..::Z"C=fffΝ|w}Guf ,--=zh͞=,>>^:h4ZPP??,X A::εl6[bPDM)"sܣG.Ya|ACC|`YQQ|+ǦS;v,>>[:@>)**Κ5?pvv^xaPPPII 䚓ӡCoEu_d۫1::Zj.\xܹ~m„ T "PDLlmm?gTTT]]ݒ%K ćtttȗTTT>A\DDr77۷o_reԨQR@D=²RK~͟?Tgi 6p܃6ۜ9s.]TVV&T,'O??~E䚚gϞݾ}{Ȑ!ځ999{qㆥܹs={Fu.Cӣ'MԹ&Yld--/"::Zj͓u6lذwRd^@@@HHׯRLAq8[[#FH!ȏ--*dE`D䚚I&=z?߿} h"tҗ/_?>##o߾ T/jjj.]***5k@ :NS6ۜ9snݺ[ѹ~СCǎGuq+WڵMD人:I l8333..㐡q\2 wy&MRWW?sR@ ;wnԩӦM;qq@X;vDFF>x$y<u EdN#//EK'ȏ'm͜9399ʕ+ a9::޺u믿ҚLOOZ*h/.XSdF۽{Ka %jkkūxC5xN#:uzRK,"}`0tz7l'PVV& ())!B \.A555Apܺ: Ijkkꝙ<YMMM|͘F.X,6M. BCCNlEUUU[@ \\\\\\޽8rȱc :۷o߿;a„RWXo>cz\\\ O:$t֞5kVqqShC5k֚5k&NN>lԐ 555rg[: 8d ٵkWDrUTT/"h4Oǝ;w Y]RUUUUUUQQ444$544euuu UUU%%%uuuMMM֝x6s <,H]SSCV UUUx˧ L&SMMwWSSSSSSUU$WUUUUr\xX5 WW{EGG 8pѢETA{>qԩSW_QVZ#GZ쬭aR-ַo߿/prrv횥%Չ@믛7o>p?zL&FD"cy@ ׯߧv(((8sLXX4S\ "29Y$atڴi?3n b%%%[Y20m#š䂑_d쏪dYOO5穭*--!*+++*****Ireeeff&J@xfMe---qYMM\ʻijja@`0|||fΜyѝ;w8qbk֬\h1ww 6ٙ8W_ڵ뫯Lf͚g\Љ&%%M8qذa/_d%h[}7|߫W/)zEdXWW"2t,GYYS;8qBIIiΜ9LrE6,O.Ν;'-a,IahhاOqXpAө4QRRRRRj沲2qk\Z.--%W+++ UrʏOEUUU䲸,L~j@a2˖-[tiTTԺu~ǵk/C[ٲe zAݩk֬9pѣGWXsݴiӭ[ƍ'l7nܘ>}1cΜ9Fu")/޿ۏ?.M]Q\D&d Γ'O F h?M,7&'''%%ikklEqq բ"qB(&Iv ={W0NpsD$W+++ɟ\rީ2K%2Y'ɍm_t:gڴiv"ouVooo堭'O2dʔ)T'joHHo׺ 6,::EdNGEEŋwww?vٳN`lڴiܹׯ懮7RYY"2t,{__K3H$ E"QMMz"/hGEEشTA]]]nqaaa1I(RRRѱzb\rFiii`smmmEEEyy9T&aeee̒xf.)^eqYr܂f@=***AAA-ںu]v9;;SKFO{շo_iիwٳg7\jmm=dȐcǎA6lf-ZTVVO?QT+o3f^ȋ tAd^KNNF5Ǐآ@BRi4nLGdwykʕYYY 6P___\\,AQQQaaaqqqAAY[[Kikkkoooggf @okEV_jBNNŕ B{ :d9Y>] P()S?~Ϟ=_|޽{7m4}tǏ>}zåcƌל9s:6QF]|y̘1Ç?w o>8x`bb=(lddD #MC>{H$Z`A "TcD娵A9sL6J]]qqqj#qppx<8::l}> 333333srrzJR;\^^^VVV^^.D"QBBY^^T*]Tv(Y;bqvцrG3gΒ%KBCCM}v}N<ٻw>l۶md$GGǙ3g~WSN;ic2cƌ! tv7 qe+[XX' a A:FٳgС-tzuuIS=|B655}3gZZZٳ Kt*(//q^^QT|1cy<% ZߪDDRL$ $].//'ַ[exN{J߿||bŊ/_~ѢE0uziӦGEE]N>Ç:uj NJejje.z͛7/^U.N4q~yݻwL xeWNN@ P(!:ܧO>Od ɮ:=+++++Q(rqq1()))--{aiiH$"P("loofy<rrÖFRg̘1dȐիW/_ŋ{֭uuJSLsٳ}|||||.qQQQ7o4iBY:tP++'O._Іn߾=dȐ~]v͍@'6~۷'%%5u6gll`PT  ۷eذa_ FuuES+v8lllZONN^vի{u:/B@UUBJ:::FDD8;;nj@.:r\n3׻BP$j㋋kkk4ޞ%x<{{{.fl6}CMo6o͝;wk֬Yjuu>;vHLL|?~sr]ŋFYDǍwq"o>~]rӣD޽O>` IDATaA) "99[g~MMÇ?S1L&S"4DZEU*3g Znݛ Riff/qAAJBqxx9s4χyYZZZZZzzz6D")***--B8Linnr>lb:  _>}8r_>|8Eu2t:AAA3g<}tÖs̘1|M 2Bhxⅻ{6bn޼9bĈ\twdW:% 2|/nذc`0t2> (1j 4Mrk}_x3mP(@1!##!DṞK!.ܼ{.DFx&> Ejg>L҇s8rkJ;wѣWZ5bĈ#G޽ё:GG!C|| 4> uրt 8MHP1;.((P!.>|pwwwnݺՓCS+rqqP(iiiEEEUUUĚ8rO.̠RWWNfhmf9rd֬Y ,/VX/Z"<<|Æ Wٳgä>իWxxMGR'Lp1"oSSϿF:vɮt>ׯ_>}z}󃂂VZ_eXZB:tW^&LuV:{y'O,ݻ7Ϗ 2h N;wZ3gN&22W`hpC )))O{f]]ݑ#G> BVa b2uuuTFQ*&&&-\ٳ//hJA++++...)))oB$k? 8St8uDhT X,&~IR휴v$h6666666/nx&qpp066n/U?{ٳgϟ?~Yqq1BuРAK.uuuuuu1[/UTTTVVVR} e2xB",jˢR,UT6,C;Cu8oc9qZ6RVVV  +++6rMMM_ [˫ѥEEExٳg׮]+(( ^ r8.=p?h4ڸA|#G$S^MMM]]]UUL&ã/p_ሰ 7ђ \x,Baw9;mbMFZ#eKd_I]C'x7u-{G4(Ɗ`ff`02\1;s޽;w~O>x`hh(EuVVVG7n_]N#>I&edd|DP&M_[J޽|ܹ2lɒ%dW:Y&55h!! #r[DkUUURᨪMX[!7qt>57Eiu0\6qI>p|/|jGRR9מyYD2k,]Bh#2N>2V.\qUJeaaaaa@ (...(((...,,ĭ/JTv*fxv'Zpz`DO%''D 䇕nppOD.EHԔϟffff====5juIIIAAP(,***)))))&JKKsoDC;knnnaa¸c'U[[[8K$P^WTT7so\.f %pppka竔JeIII~~@ ѣG.x<k``@Aō=g&Lh]J,Rd:7 v uuuxB]]^+jkkWFw0Qj``pF*B {=װ/51AiFί,JFͯkiU1`0x|%' dzęfFmER.]hѢǎdץo߾tҁa{{[l9p΢)Sl޼ѣG!!!vw988,Y#iZ[[߼yڱc###Z %T*MUVVb9Me[XXtsss& ckkO#%. l". nɣT*Oi"8HC|"G xdZXXѬ_@HNN9 #Fl]FiI{=yzYu0R)r_-,,a*bx<2dx<ekkK3h/]h 3vr\ (@ x@ 1LMM|3_ ƚqnn.BںG>>>G޽'#N85_XXXPPWPPPTTDi477x</,,X,ׅW-↯8]TT$ }P($֙LKitA i4P#_SSS2M4m dx,NWWWK$*&NF#̌u2 ;;;"h7Qt1ఽ."&kkk#"HwSfܹ"L~0Lk@Ow!pN>}ԩ ^~}cǎ%(}hѢGEEE;;;]Pԏ?x֭srr^ AdmG T-[e@KP԰۷o/Z`h 6.,, *D3?BYYN%KKKKKK'''9xd=7{TZ__/HUUU$_UUUYYYVVD"stobbB\IOtJɾs^x^x@[7#&&&_|Y$\?o:Ԝebb?n8>>ݾWRYYY8s,HBFFFsmŝ=?3GgQYYN'gee?իW 5 BP=vQ___VVVVVVZZ*^DO4elNjJell2h }h5 +訯J:ȵ_ݕyyy L`jj0$e9`[Θ1ɓ'׮]yf:tu֭YF'P>mڴ8ަNd27^Vzⅻ{>vGd###JAdP/b2 @5lv= ì\.fX, NLLLLLLꆨM.'KKK֜_r: b1GPχ5x8QF4DChbӧO>}tJd2Gս{w>݀NwssssssJѣG@ƶw`t 2$>Tt: 7srrr<bX,帧uii)lM B8r)s>,㙚D@GYQQA9sĉsssȭ tA7n\~}RRo+:u 7 ߿9R*vvv7nhrR&--ݻw޽w^FFB*00U[I5ΗrCCÞ={ՋNTlRI}||z{ARi\\Ç=zѣB%NQfz탷IAAAJJJRRRRRRrrrZZBh>>>!!!>>>ACPOߥ3Y"ccK-yܪ*KKK htz```~BCCjt5D<3 BRw177rAܿgI|:Ε+WΝT*80l0_||>ٵdž vܙib֭6m*))144$6t[n5jN 8(vڰaT)B̙3qQRR/_QNNN|>eNNN&ÁPѱѥ,@ ())?ϟ3ʏx#/|߿0((hԨQ} ԷӁ rٳg?wޅ BL&u٣F}]r,ޚ#vz#FL0aϞ=666dvI&ݾ}[>x.Yd㏵O}JĎBL&_ΎARBaqqqQQQrFp8|>}6Trʲ76-7mڴK.yyyq- tM˗/߹s޽{̙ 7oBo߾ݻw߿ b[nSN駟ڽί˗/_vZUUu>}z ߩuYO|@@ׯ߸qƍ"`ԭ[7 \.飗^xhz1hРw}d]#Seeeyyy/斔 _AaQgbbBDBP;N*KoB  W#˫WΞ=[T۷oԨQd222z9wm۶],_ԩSYYY:{ᅮU:ӧO###.^^M9wܸq***,--Q㏙3g* |ƍ rT@Gd򌌌Z222 9aÆر;wmF*AAA:Ri]r%77B,%ݸu$S|||LḺcǖ-[giw+99yCl8)"\PP' /fMشiFꫯګO<|ŋ/_~)N [fMddB!@@>#Rd2Yll+W矟~$<<|ذaGI$W޸q뙙 #44tɒ% kqaHHHHHY^^swC ݻ7\,2 IDATk*--MOOc>+wɠq;e`Bo-;;;;;=z4T&4_~~~>#'nEi 2$%%eժUG>}] _~6mZXX^5GXt?=sL)L&"@p֭GDD?dWQhhFyɠAQ RT*(<^o% BԔ,RI===}||zJȮO*(S(GGGruumX,Vii)BNhKYܼݟH$*ERTT6wBѣG-[3!R 0iҤu5\`2bĈ}5Œ3=4h'feey{{Ok2}ɼ<77CFFF8@˻rʕ+W_^]]6iҤ'ۓ]x;wٳ7nPT!!!  CNBbƌ3vpw!H^xŋtϟ?&\1qDk@G4hիWmmm.#>?gΜk׶C\tiĈR{I^LT*mD^O]]]RRRKr g{' nR4333338 Hh'JnIwww>\J dnذaѢEz҆^&r RbTTDPTWW p|wL&VZ2n?&&&8ajjL& %Jht:d2 ccc5|wKKKerGGK.5€bccB ٳKL#s۷ĉ|>ҤI&M  .Љ?.\ :uԩS[28qٳc0cǎ9r$,55̙3gΜ333>|qF 9Kdgg'''?KJJOZݝ%6:bnN\26Ϛ5֭[֭[v-h wџO.\1bHSSӧOU:^VVVDDիWmll.I&d .CDDDD"ONN344"M! Buuubb"<~R466 >LVUU⥌ S@t>OGQՋ/ֹ;έ͛7O0b2OTWWbLVSS#H$ _UUʔN6 gsB8:7533k*{MӼcccznh5j,MthƑkPeeFi*uSSS333 SSS 333SSSSSSKKK&Okkk+++999yzz ¤FWrÇk4N>=v*W=zݻÇȮH|駍^,C Q*1113 B @+))PT111\.r~ٶmyvr޽|GGGPFF'BQT]]rӧhW%%%{ݻwX,2eŋ. 車Çݻ7##o߾͛0aA=TPPpEFF.X`pTWrss>}hLLLz%ٕx=I/%&&fffjC;c4==}ڴi۶m;w.~m׮] 'sʕC>|088)l/2sLk@  "&&]#]@ hz|||PPPFF;B(//#BgC"!TTT{=x ..̬gϞAAA8yb%.]w^ܑQ8:l0PYYYYYYiiiii)־Aj1VSSS&\]] o8QYYYUU}_&iggbl_b4Ų=A633[nƍRiWիW###1cʈ ƌs-Zh4?s͚5Ǐ_tihh(E.>::|mۆ@H$ڶmݻt3ΝMvQJ?]vԩG&K}}}||޽{}i'ogϞ%&&&&&trUUJ ӧOhh(>)(ʯ믿3f̾}ބiӦݺu+!!_oZxyy3ǎ[WW?URZZ:hРҥK8?㓔 l6!TUUennN? ŷoߎ{Ǐ FPPPPPP߾}RNddkZxiee8ZcVVV]`* D"QYYP($KKKE"H$#bllr9vttdXl6.rrr\]]=:}t!4nܸǏ#4M``Å H[o$&&Ο?u/\WcTWW9r?UTFH=!tڵ?833sŊWq ɶnݺuVkkO>dΜ9{ G?_}Mj; !e˖Çm۶9슺 Rɓ{r8 hx-[\e˖=??Vjk9=vA999?~Ge2 8p`xx }LvС+t.UUU8{E=#GfϞĽ>}@ '6B$ 4H"tN1>իWG !$BvvvT{֭{,+$$$$$w޽z233#F:ܜX LP T*ZR<?((_UOBuuH$¹䲲BS(T*p8<绸|'''CCCRDgѣ7n4KKKܹCPƍŋ$ǎ;Q8T*7ow^< #夾& vܹdf' )ʽ{_`9r$""\2gΜu}Gt:J : +((XzLJzi~D"ѥK.\p5D޿''':ŨNMĉzU[˽/>7Rt͘XT:rQF_H$-:vŋl.J? -ZDv-!P(&N}vbL&cX[lY`,C D111`#F077?~x;m!T[[;gtDH$o߾u͛75߀pFzXmg߿aobƌCv9o/7oޤFu9s]ZN:5o<w1???!__?|ߟZBh֭_}U~~ENZXXKba X,2dP(,Bh͚5gϞ}y;mիFj D7nܸ{׳tzppp߾}  מQ "'''======######--M(8I6]\\\\\>BZZڔ)S|}}}}}w@Dnm``ѽ{wOOOCvzK.vss={5kfddxzzٳgL&sСO<9w{wأG*'QYYȑ#s.-Iz W*ffǎ+VXlv(y]v˫d1cܻwoSNm:ao d ,]\fffvvvDDDSfdggOǎJcƌ2eu#\LLLjkk E A"??ܹs3gٳAFF￟{ӧ]jKJJ?~1)$Ӛ5kVZEqvv&6H,:(&&[ndHvĉiӦI$v:-ϟ??j(|sԩ7n$RȧV]k׮ŋGFFPTQPPٳwyDBv_DD8p> ""֭[!3akk .P(x2%ߦMLLLHbkҥMVՑ  > 9}[}(ʭ[Z\].\T8h &hnsiss W>QJM6]xS#3t:Bիמ={ ~&&&jot:#.]WH$|>_{ѢE^WRRbmm:r^!{{{\+k777lʕ)))dp!{r8?_΁=<<~7Baq52iQ}5lu k4M(%57Zk3|ʽngC7s"my%7hm5[IKn\'6cWVRRb SSӀ$i\LL < 9s!tq h4:NٳgUDUUUhh(ł8RibbҮ `o vht>'O;w Brĉh8vʕ+lkkO{6l8sLVVJqqqZlYxx8>MR'N^t2g̘hntzxx[.66!4aSSQFQ,R)/.Dcgg7o^BBBuuu^^_hك233[|yBBӧO-[d2B ;>noo7m9?:%%eA\; ~6ꫯZ}OO'NdeeEEE!lmmu|'UR3so_NLL,--=~7Bhƍ-|P(|}}'MDv!M͛7;v(--% Cۃ5:bٛ7oɉ8p B(<<W_5sxWѣGB...r\.OnaͰT*{9b D`dd_h~ijjׯP(˗/ן.! Ĉ7o2D:GY:|`SyV4k~}f_mٽ9nf<7`35[+4-~3m%Ou/ 333$$d_7$ ###vAv-$?eNNمh4ʹi|}};_v !NbUONN&@ŋ-,, {Dnt*۟~iPPFRaaa}ݓ'OJ%d2Yllw}7zh{{{i޽͛{wv֭t/~QQQݻwh ?Mv6^x7Hmy%7ofj~÷W hZfp߱?nKT^]\.`?ZwhcǾCӛW[[LH IDATLLP(W^%(J@:Xr]h?7{{DAѼr{j{N>}I@`ff1tH5D߿ӧ յO>{ A@@H$>RUUUhhhhhhXXXXXNut^7n\paZZW\\>׋:tG}TQQݽ{ӧOݹsܲ_={699BPbb… ݻoR(1c8pֶ<11ϏX?!!! V$Bϟ?,Z_)\r=p;A=<<ݻ[YYUTTdx !J/I3JT]59iuuuK]z{{/XZ@¦VR!ccc.kffCBD"BHT6%oee%k1~Д|W߰r[[)S(Jk!YII ? h P]]d2cbb:Nή,>>> @gQˏtlj;ԊF5uLol]z{ݦ+fn[?F3Zl7|+y@Lu+%Ou/C$q8'N?Zt֭SDGG]9CCC⋵kג] W? 22΄= Ƙ1c.;vغu˗/[bŃ)3gSrr2U@@HJJ>|D0t򜜜j_TTEҟueG;Jcfgg]8$$ l%w4׿{ɒ\2&&%$$Q===GAAaΝ<ŋ >``6LzE*ZVVFt!Myð#GUgXl֎2S( :t\iii 222)۶mWhD=7_\{YR+SGǍ55@9{,JvZhCP(d2yʔ)nݪٻw\mm-%7dbwco޷d Ο?sVMoȭ[H$ׯ_.xk~@DDDH$e```nn.ǻqDx"U ;;; |}}?ԞQFa?3۷@^ZlC{jkO]]3cccLMM1 SPP}9I=~ð4/obe_+AVVVFCt!to͓Zܛ|b+=+չ]x 7nF{9ѵt@\\NMJJ"Ϟ=Bx'ORX</ @CCC@駟>|@t-1 n7jԨ˗"""'O " LQQё#GP4mĉO...&.ERR$L0aŋfwݥCʯeѵ ۯ޾}"PF/_nii+//R)^0LGGex7n#e`-Ybruqq>:f%njuu蘀f^x1# lOמ)mʔ&6lm۹Ğ~2Lt!-Zu.&&a; 6 vosm2RKKWZZaXTT?440 m7`ߐ?SDDd`ÃT*UHHHVVӧ?Jp8}haÆEFFBrnnn-sM|b'Zl{f]?fvfQk}-NG'ޭEokؕO`]W@{x-Mm>łJun***o.''׷RHQQ#F￉(FlJJJ7oƧSQQ1rH55ϟ?] U&M6mZ5>zŋNNN4-(('P-V^^~Yggg20k֬WB~>Dd0;w,,,$ӽڊsA}hCW^͛7O\\\TTtŊyyyDWԂm۶ǏBöxo7 h40kZ|x+W@=~mpp0Gleee@@D;k)''gggwVoazzz3g/ckOמ)mʔ3$%%yyy 2DBBB^^b"##d}-tEavڔgSVV,rQ,kƌJJJ+Wlhh7c lll\1 9sf rAHt!EEE޽Оg}aҤI$ӄx]pܿJ)((899~]6l0II#F߿ש`'Zl{f]?f|Hoz=,ɛ,sz ;){% [߸mTv*--mRRRׯ&Njhhؼyowע̙###C6lPTT/Y ˇkgC Ǐnnnbbbk~պ@ p˗/eee<==. իe˖ج]v„ t<zGEEEhh޽{ Νm6~`999>D=3fLAALVVVrԩS1 {%D~ -`%%%l6ݻ,88DAddA~^.../_vuue(hx?sÆ D#(>~2i$QQю>799<kܸq4'RJ~~CCC_|5o<___YYYꪨٳgkjj]N頻|a;?􄂂--9s)[l &*qƱXǏ3 ٳUUU=ĘaߧP(}xQKIIIDDð:j$ qqq#F8{,TUUլY/^aH!_ZZ:66vnnnl6G7%''1ܹsBgdd4}N1 :ts LǠAO۷n/@}Y[[[N[[;::˗/6l)d ޾}KP At9GBBܹs=:|0eiӦb>sǏXÇ.CCÚ_D4R$i` =ޚs)//?p@NN %ST@dee؄]QǐH$0 KK .`f/_k.$&&Λ7@LLLKKŋlll\zN*))igI3gΤk׮p8rK.ZXX{yy|=-Y9իѺttݛZ[[KIIYZZ^~=CӞ6ml~z333 ?s+[קmw}!))9lذ[v e>}6mڌ3. 0,;;[SSUQQHOO=z4~B tttP}!rrr?7oKVVRz=AK^&&&߿s͛.Jާ F@:{obll{nCCH:v~)fllĉOyf.KtEz͚5WNMM%e˖šQss˗/X%%%ǍGt9` 2UYYYPPav&(ZAA|m6tD $$$\\\kCt$((HDDd„ DKFaffFrݻeddjkk , DOO///Z@SPP8tPOxb|GJJ?e ~u])x8ԙ뭭߼yzjT*566VOOZW\˴i8رcSSS)3Onͭzݚ5kx<ƍyMt߃*++geeYɡCUTT^vK.ahѢk׮شg5[<00ׯoܸѡ~̙3wz䉃aksux-ڞD:M[t۷6Yn+[ק~'dddY,˗߾}{򊈈xS$??`O2dH\\\EEɓsM DDDPWС&?;;W.^ӧOD՝Z.rnrttill|ŋ YYc.^X[[{֭DE08O>ߴwڕoAOFBBb„ (XVRR5eʔUV 6LBB|)..&OS|x):_CAxG B =\nFFFDDƍ Ӊ'DEE׮]ݹsp{˗/?~8dȐ d2m~٣oݺ4YQQC`IX e̘1DzaLR{Dhl6eff^ZEED";ի}IM".KЉŋhbbbS,,,|SWW|#rrrMܿ?>eI f=->?Eɱc.[ԩSL&+zMDž;MF8OA_+++gu:w߉mb;vhVlO++aؙ3g^mRcc#5X6ѣ"""ς  UWW8qؘD"9;;߻w:k="00PGGGHH=66@d2l٢&,,vmb#]xihhxӧ>Nwvv III./*//IPP… G)..aD֞fhh(++[ZZz=͟/ԩSfn``uo2Z\\466R(i}uNݦ?*ÇFFF[іmieetzaa0>3̃axUUU^kKKdox YQll͛lق̘1#==۷>>>=͛7݉)ZxyyyM>.aaas]d6uܽ{wN:u o߾};wΚ5k…( \nnnJJJJJJRRRJJJzz:300PVV&X!;;;===---###===##db&))9x!C 2TRRzTcc v޽p  BtE=999eff{NTT2Liii lcc#ap\,zVuuu(߄HOOGOdXƊH;& z}Drrr{ѝjwޝMtuorO5555kք}𡪪*!!!88CDD$::zGҖ^^^[ltRbbbEEd2cccWXf`` ..?q}}`\v'Nr+d]BCCCBBƎ[TTDtE=D";wlݺuD0|wY4Y]]c0O>eXNNNl6r@000~T*T*ut@llɓ'>}ѣ_~F]}ըQ222޾}Kt!*-- ðKJJHSN8۷H/ 4O6jժxð{urdi~:oAG[O+knnaؙ3gZLVr劜W(=X߿ORQGݸq庸`FP( ȑ#111{>|ƍ={GYYYxx>}tҌe˖Atm޽{jժ?zxx 4IIIm^@ի ##s9uh49so~ʕ7oTVV|ȑ#e˖:t D=YYYO,&&Fz@`XL&d~.,,DגIKKkhhhiiikk=ZKKKWWWWWf9ϟ?:u1cΞ=?]Q|0y䨨(*JtE;P(aÆuo4?vLѸ\ O 8p‹-Z`EXYYihh\vMUUr:u|bI&nݺ;v?ͿxǏ1 aC Ƨ;v oŞZVXo߾[n򊊊3,Xɓ߿кhWgҥG=:MY6֖mi}e7nܸ}W/~s玗sXXawrG񉡡sǏKKKO:5j( 6H;w.Z}֭[ӧORgΜ "hǻ}Çccc-[6sL4s>|0yC͘1r:U.{퐐d˖-k1аzꨨ(6pvH\\\tRIv܉:Eyӏ?ӳ駟<===.]ɓ'222&M8q>K׽AWf_7oϯjTz:-KNN޳gϻw233Tѣg͚5t6Zp͛7/N"2뜜--ϟ[[[gΜ9s+Woݺu׮]7n#~ѣGdddlllg~/;;;!!!>>o߾mllTTTuuu522"@}MLL ʒaɖ|0 c2 ^ʄG1j(WWW:Nt+--EL^AAAAAAQQQ^^ARQFYUUUII e )OQQJ4"!!e t@SQQAc`:uꔯCXXX?t|UB~ MMM1 SWWtRW>'>>iϟbwwwQQѰm644ǿw 5k_|ٽ @D|=$$رc)))cƌ)zAeeϥKƌsA@kx'N8;;߿Z@r/_PUU{nooo+¢LVDDaBB¦MDDDn޼]];vlذAEEeٲe˗/'.111>>ٳg/^(..0L[[URR"L%\.7==͛7o߾}7oH$!2h[^^ z*11`0LMMMMM bjj l6))))))eee1bȑ#F6lX?[w G~k0 AA)***GQQqРA0jjjJJJY,6fX%|I$Z.UUU\iSNu떡! ޸qݻw3220ׯgX\ h777yP?@z}{p8(gFԽ @D𿪪8P\\?b`yҥK\]]׮]@wNNNׯ733{˗ϝ;W]]6}tK.$$d_|3gΎ;$Ke>w޽o߾>99J O? \.7+++55Ç߿OKKhzzzzzzD ~9##驩蟔i;y@o{,`QQ>\\\\ZZ>222(H @WYYYVVV^^^VV~HƟEPÈ>*̼z#j3337oҏ;rJ&)//_XX>eʔޯ}͛7===}}}t3gUUUuoόQQQ&M;nnn?~ƥ& +--=rÇ-Z|r`}۷oncccff%))Iti+M]zzzvns/ 蜌ܹsfͺ}>Et===)SNEaVcc{aP(/~!CеFMMmРA, 󘘘z_~122b0ZZZW!X,VNNΗ/_>}ӧ --- !d!!!4!##ݻw))) 999ðAGWWWSSSCCNC#d2/_㌌ td344155511QUU%d&&& !!!+++)))!!!!!!%%ë h ébUUUs\.qqq=]AAаy'p ?~߾}DWϝ;goo~>k֬u֝={vʕ666D@yxx={vٲ7o& jjjrss[uuu(LR1 E,A m۶]rEAAaӦM̓[@86qĸ'N,]t򋣣#$?~w7oTUU[{CQQQDDDhh˗/-Z`MMMj*55ugRSSCBBШQו譎 It5kP(!!!~H${nkEEEhjj⽸VQQׯ_srra  B6mR__Y~<"""ꚚjjjZZZ***rrrJ؈e2_~+-,,D󈊊Ǐd4 cЏdeeeee6笪jcl|*4!##BɒRRRhEa)))iii4,...%%%,,@TSSS[[[VVQ  l4gUUՏJ444 4h4څ |>+%KwoMf Zl5k;ӯ0o|e˖k׮;wnttt/=ݻwoĉ˖-ԩS{i;?$%%/^Lt9K $--˗cvc Q 믿ܹcdd6uTAp… FDDܽ{wժU~~~&&&(lmm 0\YYÇo߾} &l޼U/b2CEǏg <Ib } r/^`&+**>|(##cfffffd沲&rL&lrrr-QPPDyX랆:EC}۷o|єjaaa%%%677wqqQSSm1]DRuttttt?TYYd2LfQQMIIAQZ|'0LJJJNNNVVLJ_7G}tyB)RMF#***ʦt:]YYYEEEUUUMMM^DDDUGfQ.ZQQQ[[5559΂<lj7[9{$"""4MTTTDDD\\L&KJJ ۤ&YBBBQT u}K^^^FFXYYp0 @׮6؈`Ob*FX.,++jhh{]]]mmmMM͏ ԣ$(P(aޱ X{{τ.*z…#G?~ǧ7Jtщ'*++چCtҲ2___y]/_to }$$$b(o哄! ϟm۶P(FFFߧi 8y@ð܂BHnIIIZZZiiiqqq>P$EVVVzGRQbPPrQ 1Cyy9PXOBxZCCtt QLLLW]]bX,4##f[l0_NFFD"2! O屚LD7<p}?UQQpQrrrrrr(IZQQEd2utԼCY y<:OQMMMqqqD)5GQtP@ftEODgx;MN)A 4#tn6ϯH$  mb'o&]z W^5vvvDWԝW\f^)S===/i߾}[x+6wht`J2@nݺ;w5jڵnnn}%hϟcbb^xŋ dlllkkBD͛O 6v̘1};wgggOǎg|8""bРAH f7Šn%QF0J+$%Pj. 0.~]I k?#6Eb[Gs 3g 233ý]\\<=='LDT*&&&K.MMM=zh٧TjHHÏ;k^p={mۦdggAdtD:rHmm㣣tK.m#2aMn@oeffYڵkCݳgرc{޾}ۤԺ:6xC2=[SRRP833)((5`]fw2554i1 ssszŋϟŋWVVRLwﺸ۫\tW>|8!!jҥSNGH[*`ʺuVxxD\7ٳgOW^Oٸquttzm%%%ZpcVZbzvRO8?~cccǗvcjT^^>::e(aeee222ݵAd臊6mt9MM۷x EǤ$KNJJBVVV644700000`0_ f jkk𱙙6-++P'cc7n3̛7aBB/_tuuǍ ''A*''N8|oߦMtRtS'dƍwF]>}zU HxCII)**JSSrG}}acbbH$R-wٯ_NMM-))AW{zzПN?3---8lذjZBB_=0PII" JEEΝ;=*--iӦ(;;;%%%-- +?}T\\aBzzz CUUk,Gu.+,,ohhh```hhhff&--Mtɽ$&&fܸqt:ÇiiiW7zyymݺUNNnǎ/ޓ'O~'GGG11[n CNNN``ӧdҥK`0"## Ύ uuuD29qIJ#F]Nxȑ#=`^[WF;;;GGGyyWTVV3ӧ^LLxY!SN0}讥&CݻWXXxs!(jrQfYY_,9R[[aB@Oa0N'H߿ ѡ鄬mkVVVvvvvv6>PXXfPPP@cgggM@ MMM:۷sܹ?d2|booO"1 CWRT H$R}}=Qϟ?ߵkݻw O^455Ν <VhiidwwwH$ qqq'Ovtt 4iusse˖\ٹג#F>|ѣG<==WXQUUz()){9;;?~XVV@{feeucd2d!!!.OǠGdV\d2}}}׬Y#//OtE磞bSL&0*E?~?KDDDCCDOOEuuu544Oe;jjj rss ߜ|GBBOgedd^HIIٳ篿b2A_|yΜ9eeebbbWw^JJJzzׯ K*]O<믿WZI&Å ''͛MHvvvW~>>>.\ #nPWWgaay^[ϟMRtzHH{mLjtt8=zǻM #GhTTT655%?d@Çe˖EGGϜ9QSS#"@A"TUUUUUmmm766G)JCCzϟ?F-ܧx:b0L}jooq]t'MMMG2Hvww8q"$hF;s挙rrT*Ӷϟ={v,tƌ'Oܲe}xx8w駟f̘qƍ~6phkkgffvoT*wt oz諊VZjeehaaAtEL&4ѣpܲT ön*..>h %%%()) 4 +***((HIIjq8Ғ2.**‡Q w tÕՕԔC~W[YY+W6ٳghx'))IAA0DRI$kllt޽{?~uȑC] H$iK._Lfxx$uɨQ,Y;XPYf>}zÆ ˗/E211w9s.\ $$DtEm ӧ&FD vܹ~99D"]`@mS2,!!k׮ѣGLwX,VIIIMM ,aaa999YY6OS | U5jT:F#jxQQQccyܹ>l۶ jii={/a\.7""⯿zٳg---. zH7 @{xxxdBHHHȈKIIIIIKKKKJJa 4LDEEĨTBi^OAAׯ]]]%l6un]]]]]]]UUU^^lvEEEuu+**l6^^^ޤA)))%OE@yѣ^b<.;|p|Ջ/&MaDB?Cu<֭[6mz?jjjJtQ@ diii777H$܈#^xbeeu]CCC+<11s]t˫`0NAdts~%%7]hhhh6T*Gdݻw˖-wD>NLL,((P(~MI$D277?q℅E;"ZZZmw26ȖVVVVVV)%%%,,,))YWWplv]]СC/\ """..a/c&**>6(3*ǚ%%%O}ns)E1 jhh0 ň1 p8H1aeee<TVV644TUU}K3dJ{Ȩeee_u=<}tY''ѣGw!--ѱCCCaatAp8 7n6yD333+ỉdaaa+++*d$""5˗//\~s AdG@@`N 2T*ÇXérq̮Ad>>>"F[jՃ\\\?.''uEA&11s򘏏oԨQ \SSm=cc{kii={¢=۷oFáYα`~~~"xH!#"&&Ql~*66VEE%99Ç]Z__GP:o %##A'$$b QYY{/=}>rI$燆޹sg6l7o%C ??3g\]]?|p+M666(ۼyJ\\\TTTZZշlْnݺW^M>]v}wLD&njjZPP Ν־t_ dຑ#Gnݺ5##hӦM222 HC… =ztΝy浶b]=|ӧ`:OOϗ/_t++>CnccSSSu9;TTT ֭[zOXXׯ_;''LZ*N;'%$$z?tJx|0eʔ3f8p@RR`tss0a͛srr`ddT]]miiY\\u9… 4-[ի3gdXjrss,Xd2.eee6]RR­vDp@_hjj4775jT^^???E569y,))9zhO>Y[[VUUq XܿYf~8>Dª00 ʕ+UUUϏ:"#2f􆠠 IP˗/mmm)))ϟ~C~~~yyyAAA:::XWo {r~ʮ]ۗ빖-[)Sz: AϞ=[z5ֵA&h芩C S"bccܹs֭H999+S1D=z+(ʩS233!yl6… qqqv8ĉ%COcccii鏂X,!!!DfW^?yd\\E?hΝDɅXWEGGX[['''c]/[~޲eX,O'>|ܹsϟ?OR?~\__04YZZ޺uҥKú.vDFD̠333T*b5554y|ĉ̆WWW]]]hS%%%3g\rʕ+MLL6nܨ) ͛u`ʕ++W/Ǐlw H~~>'`0xTm=qDwww*b !>>> x Ǐ+**.6lXtt㭭>}u9o߾={,LJJ9r$;>4{ӧOo߾XSTT,..ր  VXCTJJҥKO?ue0p1zvCCCl6AZAY,VcccGGGSS&AXXXPPPDDNj fqqqQQQm 111$/j:QQQgggggڈ[n-Z@ -X`邂X_xQRRͭ~={}||n߾Ӊs!//z N!kt:}޼yѦX2 2@ܫ "CGd\ ޸qJb]ΐd2jkk8Ѩ:6 ׯ_>BHDosE-55ǏL&SGG<ׇNrJAAGM>ڵ G~bee}=}:DG,lUVVAAA'OTRRº"Wښ9666v},@dyEEE8XRRÉ󋉉 qa"($$y>>$:::9ZnmmmkkkoooiiAO`?}5f(>>>$%%ODSPP&VYYy7oّH$'''a]#>,--nݺ`]QO\pa鑑hɒ%۶mCݻwŅws`(pgϞhIII#FL&qk4!!AAܹGl'6o}f?FL$ICCCIIh JJJrrr:,$$yFPh`ӭ,55ӧO 111hhhkkk;c-6yFdnnnkkk.HիW͝;ٳ222XW@|+Wd碢"ee|hllDDJJETWW6lr*..椓ǎ;f̘1cƌ;V]]ziYYYqfff}}=H400@[XXa]K655={Jb]@AAѣ߿ӝkkk?~<}ljj䔒\QQ+SSSnWݧh4g6nܸk.XǏ9QCC BBBjjjs@UU#F1| 8cgEEE tttT+|/_Ξ=Bܽ{WLL r~bYZZn:thܸq!""Gpxb[lú!^RRÇ666-,,l޼y[۷o;::N4? 2JqqSFFƩS,Yu9QKKKJJJrrrFFFFFF^^ɔ0Ϙ1cOX,:^XXl!""7fsss%ׯ_seee҉@Ynjnn^~ŋmmm/\ ''uE 5:::?9''GGGÇGIIIfff޲eXMMM\\܀^Ν;+W$/^1cV]]Ǝ?|iK&y,}s[>""++ NX,֫WBCCCBBMMMT dee. JMM9sGD,Ch 2vJqqqMq˗_reX2DIHH:tӳC=|ֶ],ݻ&&&III? ͛7===333UTT.|U|||bb۷o B;w!SRRRRRĶ333߾}撯]F"-,,[KRRR1??رcͩT*BX O||%K^u9 O>MMMI A*AzhkkC`pؾԴvګWzzz8pؘׯ_ "))glll24: _",,;oh߿ӧOO8`0uuuǍ7~qB@gaaaaaqСOn߾}Æ &&& ,"Mfii3+ ]]?sΝ \'++kkkf,XY:ssswwwWRR2e E4+C靶64L л\?eAAAk׮=tCO>E?~DDSS|„ fffZZZX8&$$$$$zFHHrҤIZinnNJJJHH$Fyl`` ""u[L&s ի_ =D& z򐰰0*OLLhFjooxɴiӸZx_sqq O|lqNN6lظqƍgdd7bׯ_ߌ7o޼~:==ׯh.\^^2_t]>>>kkk**~ӦMxȑ#.'Z[[ #""x4ETT\hh(ffܿ?11G ޱvZg/^|;~~~?9,N-l6;;; % [YY666Z[[:#$mI?WTThѢ???___/Yr7nx>[lSk 6C.~qSN\|8>SUUE  G]]c٣G2eIDEE該۷o >q…&&&83fTVV>yDOOr~ӧӧO7q,KMMM[[;..%@hkk.--MNNPMˋPo޼7n\aa IIIfffjjj? 2pͽ{.]x-mmmFiiÇ߿fmmٳg[[[C>PQQɓ<{NUUuԩ3g΄lioo 'y!//)ѣ%%%.T@@-[uuu.&--xذa=믿 rڵ7oD$22Ύ;EF[hQRRMODGGSSS++ɓ'7c] تbcc?% `bb2eʔiӦk4ӧOׯ_~ǏGp… jjjb]QWWDEE`]O8;;'$$deeb|??3g޼yStU]]mff&..K"u9C޽{^޿?f̘QF![ XRR3"0;v8p`ժUL666FDD񙙙͜9sƌ/_|ǏsssI$Ҽy-Z4a„!aX999 iiiyyy CZZz X ~Yuuݻw7nܸ{n!!!+`a0yyy!!! ,ׯ_NZ]]}̙;v rΝm˗/D7ƍu9OUVV޽{7222&&d?~VVVTQQ؂YYY;;;{{{+++80Pdgg߾};88@GGJa]!!!!""bԩXӝR+V|_RRRw ^L]o߆L``Gkkk5ZZZ޽3f ٺҕܨ 2Nwtt ź>ׯGFF23f8;;Ϙ1C\\WPP~ƍwލ1u]VJJ В&&&fcc3gΜٳgCb^zzƍSSS*pÇc]Jx",,lXӝӧO 9s&F+..wK k׮=tֵ ϟ?*//PEEE***ǏG@]]]BB_D^qrr |ͪ':;;;88H$ )((044\huVVV'yÇ"h``@P,,,!y{{g͚eoo?}tX`FEFFFFF|D"-]tŊW@а0&9uT*:ox3&M4DR큁ƍ366NHHشiSqqqll!ٳ'???99hdK5:u*D=zgZZB9ydfffCCCBBB@@J{ t֭`H!{lŋ;::,Y)dAEDDفd"" ((ND##W^A :9vبQMVUUuU:~ڵ9s@>d2+&&p!!!666GߐЬYKKK/\ֶtREEEWWhh#oܸu9?wd9{lYYY%%0^ GG-[[ŋX2$dGz?F{{;z]`~~;̙cǎÇߺuKTTxYjǏSSSׯ_ui?> N?|lԨQX}_|AdzfRPP7o^ttN@@@fffcc#7g #$%%]]]={VTT痕5uTeeeootCCﹹ]zr~}ӦM\\@@ɩݻ_~ۻw첲eJMwՙL栿ˊ&N͋EEE~) @SS@oY[[߿&&&jjj۷o/--ź4sXZZ>44D"655uN `]2zgg˗{yy%&&jiia]XnnƍY,֩Scq8#2 >}Zy`xxxZjƍQQQÇǺ"ZZZvޭw^vq>.D+~XmSSә3gھ)?{|qҤI<.233-[v=CCC]]]zC;~ۙ3g.ۇ Ȏ;x1Guuu}}y1>@7p8ܥKtttf͚UUUu9W2hl6{_Ad5 ƍkkkKNN?.^~=m4KK˲­[`]2##Wh4oo+Whhh_|Z[[9#GJIIquuuYYY m ѣGzj߾}{7L+?d2A|||7[ZZ޺u{>z䉖SPP`prի?> 9yyy e666t:_ -,,;6rH .444`]Zp8ܱcǶnݺf͚'Ob]H={\\KK N g IDATkRRRaaa\BDDtpp1V)**rD>>>< "xà }gࢃn۶mفXCo޼ٺuktt޽{MMMqʕݻwWWW/^_VVy&?jjj BIHHbjhlllnnnkkommmkkkkkkiiihh@onnFe"`0auuuh,,=JHHH$vϏD@$ q8$ bbb"()))""B$ʼnDw cǎÇSs Ŀ 7Gټy`NN "''m6//֖!$$߭x{.~ӧOSSNIKKc]O>a&Mz˗soÚwsst钝1<<뱱"""s]hɓˀvڳgϱcǼ;lUSSSJJ i?K\\f&޽{7a*zekVXĎ?l2 k{e葯_^ٿMUPPsΛ7oFFFΞ={, {xx899;vѣwٴiڵk{92|]BBhݎ 痎v&QPUU&4'IB"DHt2Q w;ԧO333/_#H~~>Bݻ; HkkkL&m0vD,,,.220,,lp@ϵٽz*88r@ ^:`„ +W;wc֮]uE/[xŋt͛7^h" CݻDOss֭[.[8ԩS󏻻;w_`u몫'OzbرAAA>>>X3h˿|+CNGdAx<! ?WQQ~c]>|xAAA , CΝ;׬Ys!˗/8qb40@1y楥b].o߾uֹsrRUUv/^H$;;{v'N勦ٳ7oތ7o^XXa5[nԳg&$$h4==[B۶mw^]]ɓO8lsV۸qݻw vZWW]vݽ{dXb?-]/r70Dk״|||DDD8mHGAAaׯ :p\m xA͛73Lr~EWWks? {{{AX߻w mmm.gpRPP\JHHsYPPeq|<\nnmCCCxxJTTwyyΝ;}||x<dݺuwqpp8zh7}4lvvv6'yÇSSSsss%****++|˗0))wѴ8'|舘˫Wt:DBΜD999YYYYYY999iii999 ~nӦM'Od0ǏPD"ٳ/ϟ߲eKMMMMMͰad2N rrrͼk.۷j_w[nݷo_ݢfΜ Hccϟ9?Zzӧ;rs}3Arr`EENMMM`` /I {ⅥeyժUgΜlTUU}=z2<==ϟ?nOKKۻw;w8z𡍍ͯKg]g6m x.ֵܑxZSSSWWWggg$Ç7nܸk.???kVccͅ ;gϦM6|  ߺu+11QOOZ666jȑ˗/߲e zWFFu;)))vvv w%Xuuu>>>׮]RǎSTTĺ"={lժUGA<>>>!!!---++NXXАQFr%_N2pÆ >|8å MXXXWO?:;;;w ,UUU*:^UUF9RRRRPPPVVVPPPRR" d2YVVv@/' @/={lƌx<@ \xMh}ӧw]YY(..>o޼'Nr`0nݺ?tS9 A#Fسgϟu-Ad99u͟?FڵVVV111ݻwǎ 6mrpps9;88HJJݻw„ 111{G۹~A--9s'矆?>peC!ٳg=AKKh˖-oFO:oٰaÇKϟa1LYY`] MO> suu2e \ xٳW޴iӁ[ׯ__xqJJ B,KII,!!ܜ#_N6(%%EFFrtCCÏ?jjjr(]]]*ʹxL&~p'! ?p员%K>}z}0c2DXlnn޼yӧg̘1u7oޤ#addddddlll`` ""a FEEF++++)))//Gch`{(**ˣp99cN Pׯ__bNHHZ`0:Gi4ZiiiIIIII NDw&M0TTTpÇ+W.\N8vF"IMMԿqܹ)))֘t:HĺѣG׭[n+++CB}ȑ#ׯGw8tr4:uLL˗.]ʙBp^lmm>|xi6fggׯ攔#Gp.!w=Ny?!((hѢEubhhzD3;w8::c] }eeeo |… z :]?~ժU6l8tֵ6=qD&,;8zǏsqX_yydJEE'Mˡ O~ȑ#?}3 l6{˖-ڹs]eo>???;;3gͼ#RX 77Jcc3fS( U[[Ki4Zyy9'sLtzyy9(dddeddeee) e˖EDDرcǎ\l]VZZJӋKKKKKKi4!BD@@L&rl6?HIIOLL?~| BۖѣG;Fl6Q466sww/**z)օ|뻁ת*Λ<7u$s?53|yy9Vf"""MMMJKKuuuSp8\ffTO ~䉍 Z7zxʆcC H555=VRRA&^q>>>&zD3ÇGGGO2Z vZyy9BqqqYp!|ŋ+V9|p-;;{رΝsssⰅjjjx0egg_zZE oCfff&&&GE$&& :Z[[,Yq%~N޿ѣk׮ź0L4)--mɒ%QQQ/F7h4Zqq1gaaaEEEyyy2>9Fs7[f>ØC0 \믿?m6__ߩSx ;??{KK˙3gO6WGGgʕ[l;w.p頻}655.wug͚5z?r>>>999:8!B iooTUUЯRSS{֯_n{n ={vKv3o^^޵kt-[oÕ?~ ȑ#G-.]zjjj\wz c][[[Bommmmm}ɻw9::Hy湸W܈D wŋ{׊bݸqcՆ"""vq333qqq##_p8&m۶cǢ"8W:fǏmllTUU(ݻ;HMM]lHTQQ5kVRRRϟ>\O8ɓ'o߾="""55577766ڵk666G2@988lذѣG}BW\yС;w}͛7nRUU}Ν;N,..>nܸŋ8p ""Ǐh78U__oooTZZD$5`'ɜ|||ǏuJJJ&OL$ pvvnll<<օ֜9s9rȉ'JKK;yUV!zGVUUgdd}z&񽽽yiCooݺFEE|vt˚5k:IٟfXC ;?3>?<#aaaӁA ==]CCC^^>66ZzNĜ>}z͚5֊;<uԦ&C t<.]vAohkk222ܭG~}3# h۷own:ozƄb;?j?Z-fԠo߿uA{r޽o䜊!\ =t=ӒKp/((; 0 DAAA{{p' ~~c]d2;- D[[544K1nYz z?8wlRhhhGϠ#2 <~0!!&d&O1 Ǐhǎ /_>x`aaa\\ɓ9|~ M#ȝ_P?<<ܹsNȨ IOO?x KK{455effn޼fرdO 1c=<<`:0{'9p[YYZwP_2# Xk#ЪEGBQJ*-ZNJ-8m*::P QȾSϘotJ y5\3ezkkk߼ysy;;;sK͟?Y8yÇ%$$&NeޑbbbBl6{ر999-/_XYYQ([^^O}Ν;G1mڴTk<竨lٲĉmٿrr… *--8bĈM6}!YRR2n8,XZ`Eaa!/3gΌrrrRPP?~|``:DN|(&&o߾>/EX,֒%KDDD6>~m۶iӦbsUTTN} 6wHHH1bbw,򠦦vر}ÇcT*F-\H$^zU5jTMMMcܿZk׊߿Z!gmmm-?@#"(T}.\HR5 ÑUTTԀ]+"w!}AHHdYbNNNbbbmRC}tDw .9<_߳vڼ*((IsssBR!{{{>}rqq߹dPFFO߳gرccbb;5gΜo߾?ĉBzٳgZb(@F}!ɓcdž򥱱jl㧯dG^n5cƌ'N>y:7 IDATN zѣG߿?lې066\[[o>##˗h4dll,nɒ%W^|yZZ޵n !TZZqL 2bnnnǎz.UUUmذaݺumz,|}ޞ(kw^r!D:x}}[>yѣ>+gGXhZd! h222""".\᧯$/KKK{yyyjjjZrWlllw4g 0jԨ+W9s˗ #333,,6//o֭Ç4i###.ݻw?zH]]}7EZZGGl`UWW[[[KHHDGGdsvvyV\ihhxE#ިlÆ  yⅮ.E Drrr.((MMMO8ѓ^ڻw!l٢# TUU?Ǐ+޵b "H$ЉDtD>K!߼y .ĻC"pypɒ'N;vE!Ν;흚uV,c!!!YYY W^Et]CC!\YYyzz"֬Y?TUU֦a~ !tȑ?~]|)EVVO>S!7v؛7oy9r$..b\tiذa/^?{jjj.۷o8p`„ _|{L7HVV`ؼ,6j(6,zX,֌3X,Vtt42:L&?~<++zT*u޽xׅ)\ڭBX@ǻPpl>y۶mɉh>>>͋ ͛7ŋwǻ$!!qȑ8A`:.B\\"""χw;MAAD" #2HD""aX)))=##ϻٙLСCy{<==O>~p3f((vk׮|w.˽w;vLR]]]q9LbbUTT lr DDD=ztKKKEEEEEEKKsu$z'F255]f͛7;~9Օ8p@ZZ#ڿrr… *--8bĈM6}!4s̨('''`W/`yٳgவu666SLyHNN{nEEE^^˗GtRmmm*vy ϫupphIIInnnݚBFYBB!dff;AUիB ƞ={o7{{{ 6r8\vmܹ e޽SLIOO[x1 //ݻ?.Z($$DOO288Ylٳg}||N:w-(00Ǐؒ]Ӿ LMMCCC._w-B)++ D"&BGrHUU+++.+++]Z@2n8 B@O^xӧO<<<.iiiIJJzX,I&M4qDLӇqR(ЫW;v陙:ͭYf*((x!k׮۷oI ݻw7otttȀˢE_."""++r … LOJKKΜ9.""2dȐYfkkkwdJ3zjDD%urĉ7:ujVy˗/gee)**v}{<wPPP||9޵>]ܹs>>>M??^:00P5 ) /'L`0^xR755-XB@YWWw!G=:99ɓ'Bacǎy1ٱcG}}֭[TC޽{ڊw@|||"##BCC{ \.neeetΝ{>}R Ź\nmm-c07nܰ[x;wYuuuO<ٶmrZZځ $))ommlٲ{^rٳg t"hkk{֭gƇĻ: ,=f͚`|+%h @'OXUUw-B)-- D"1l~!;;RLL,>>^[[rzǏǍ'ݸ!?bggW__w!\~}̘1***޽4iwT*֭[UUU999|͛gΜ111道#&&mcA䖖즹ykkkrrrkxŋosgp8l B_s%Ǐ RЧ466޽{d2yƌO<{nUU۷o333KKK9Nuu۷o^vZsssu޽ 6Lall,> !D ]&!!ï 2Djll$\.?']t~MFFɓ'xsf͚w?P(IIISNŻ횛׭[wyoo:!=<}dɒsmݺյgΫFm:"jo߾8qb (..={ԩS:=_bhh&vmllLNN~ŶmFcAOrىCSSɓ&Md333336rUZZ}Duuu*FPM@bo4UWW_v… &L"ƍ۽{?&jjj]H$bbo3b͛|(QqYYY'OVRR$6;v]H<^mСxW]yyܹs?|w9Gİ$֭[9=zt%иqϊ ]_VVּyfΜckk|@YY[B=ûK\\?233{]^__oggt5UYY第^؋d???777QQ$&&?!] (STT:.**JF>sL55N.))onn....--)'$$Vr T>XR@FF֯_~O>]ro˖-VVVŠڿ?Ydq@ :ujȑwqtth[l9r LMM/]lllw9jSSSMM|!Fiiio.:"@/o>~XNNrzTMM VQQO]^o޼qtt$H/_:t(_ **jaaaaacǎW(((o666xW N镕IIIp$w_R(,%%mc9^337nt\.յ(112@ XY6jjjB$@JJH$Ν;׮]onnnnnm`]v1LРALLLLLLjbbg &FB#G9r|n@!D;(dgg3 ,ܦ2v[kz޻w/88ٙB,^SKK <11B@w >}zhh(3cbb?~d2i4 -a699yԩwiYYY>|6lw2eB^^^QQQؽwޝ={vuuu't wތ3SnnnzzzFF 4Jh4ccc,aI&B'NGLLLTTtÆ \h5///---###---===33D"h4f``=\.˗/_|JKKrd2ĄFVt:Me&B$M)kkkCG˗/_xNc ̙VwwwN2*** 7nܸgϞ.u5kքBAɓ'WVV&%%QYY;qN`iiYRR/7o̓WЁ}۷oN:|w2BHRR!Ѐw!jhh AҲv .oii1c!ű^477eee¬ϟ?/,,ĦHHHkjjjkkkjjjhhhjjP(vef+++ ˱c$%%ӧO_ncG$lq---UUUm:(dffX,2ܾ.) o#NNNNNNYYY!!!.]:rɓArA BCC/^ԯ~f/33L&:t)R8566]|yx&<r1 ^ 񎔕UPP ؿm_YYY999))))))YYYp8`0X,NM EUU sR(uuu 6XH$}VTTtSN@~&###..SNeXRIE IDAT ]MW]]dbb":::| rڴiӴΟ? _zHHH466:D?~EfG|8%yyy%&&y aP@@xee%SSt׏5JFF&..z t^ t fY,VMMM]]]]]wsuuu  /Vmmmkk+:%%%I$DONIQc C^{cM/_b`6]i .\rv'O_}RMMU]]]|||y-[󌌌L ܲe˱cǼX@wHMM3f̪UExmݺ牉V^^ӦMC6LKK@pe "'RGIII]?~BTTޅ! ":4--@n޼СC)UTUU9rZ@W1(SSSCVV;w}L&;88,\pҤIz1yd&l2ׯϟ?Б#GNڵk^#\.7//?p[XXfEDD 8RgYo1V^03c &N߶jaaw-d2۳f͚SNc6l8qDbv}ݎȆ?sf4gΝxԩS6lX`App0D»л}aaa999 ,Xxu 3g~СCׯ?rE &ihhW\\ʕ+ .D]pam@:;;766`&ibbbjj)@lvff&1>!QQQCCC^ ps/舖333Y,v LnARaQQQ111FFFVZ|4uΞ0a!C߿O${, {Dz+8p`׮]ܾ}k&,+11*m6mN{_$...Ruuuiii&)2ׯ_'Mw9BcǎqF[Uo׮] ۇ*//GƋV\zj==o߾}600ŋ `ӦM؞Çckc"""MLLMO˗/><~8N_nɓ';~ 055y ߾}ʋ l.\rǏO2Z@'̟?ŋW\rÉ wrrZdmrn޼yҥ͛7?x@l\.WJJ̙3K,isW}}Խ{f͚믿,Y"!!Q__XddCMMwX?ydƌ/^tssg?b233yܜ[R 466&p8HTWWoAJjiid@`0nܸqt33/X@B)S888\t5a޺uӃhkk~XtGrUTT>|„ DYYyϞ=k֬A1LYYYuWA+X)SSTv,LMM-::ϼ G&kjj233 =FFF rJJ˗/{EDD/\ع38p`ΝcLg?g^ t6FAv/;(faPr>}dOOO---YfIkk븸8KK΍cǎÇرc߾} ڵѣ/_>|8޵C$_ޕ絴6oތjnn&!6-***B OSSӬYccc w9CJJCBBFɟ1ّ111ϟ?ojjݳgOXPP#%%br Bh˖--vZ6eeeGщ/_tppPWWKAs'._|ܹ$ ppQoo'OFDDdee%$$'gf"W^m\MM7?8""ALLV99+VaǎNJMM֧yf!Cxcccc m'~򥶶;L&i%Y[iiipp3g~:yϚ5 ~Rwuttܹsg?W^u۷ޢ;%OEEeΝ֭nnn;v@q\QQQ.[__/)))2 el߼y)6lح[lmmuttzCR߿M6a{u֭[&''1ѣG~!!!!==.S\\|YPU/d2mmm A^͛K.z.;D`ww/Zh妦xWs:s/^g !DR߽{~SSB"ahmmms@hiiim9rӐBD]]ϟ3ǹX L&OtPNOO.#I$/̟TctBݱcGTTTppWX!MtҒ%Kl3' :t(W'nffZ\\;sssBhhǍ7.GVTTte"}reMMMDr?~#G]N/3}gϮXBTTtΝxUsy}@@۷;ի---555 999̘1֭9r۷oO<1c/>>?~uuu.? 4(<<~ili!Drm~]f˻n@ښ8NOO/++CfmmvLP.Bd2lllfSSSIII111l6!$..A)JKKT '''''g߿iƍtx⪪-[P( lٲ۷wb6''s΅AzhhYNl2***] "H$^!$!!f*@yyy>}x[]paʕ۷oՉC&9t|OOӧO#_m}v7o>z(-,,yìTTTEUU̙3˟>}w9566FEE;wnҥx߹sGTTt…}ϯ\777cƌ3gɔߟ6tO>!~ !f;tӧ x{.^jժߛȓb?~322 59hz;:K*gee1L2ܾ|O֙daaѓg6ɓfff...}Y{H8]eeΝ;nډ?{lʥtzm۶|ԩ^^^}Mt[[[an KUUٳ322ܹ3yd?}ҥKgΜ;v5kΝ+!!w]f͚3g5kEDVVaRRR.o߾4hB(..k܌sμyK*##Eak O}}Oxc,sFB4 dK,Aw;D"LRutt`ihh7nܾ};8_r>}!--ER t͛X[[~GYYٳg&M ͞4iNOJJ0`o˖-qqqaɆgΜn*++WUU}k ߿?gΜ;wٳZXGGGp ݻw]]]MMMܹw9effΜ9fWTTaⲲx\EEEYYYyyy,oh/3---mbXZM^a444( B)jjjT~ clǐH$1yt:}圜ULnA9h 󞞞544Yh1 `466X,&d2Y,Vcc#khh6kkkgQR!To4^#@ /5o+}?&K_)EgB"dee$%% $''oǦ8b/޽;qč7 dɒM6ikk]zkk/^v빰 444"""~MMMݛ9sfwЭ6nx>2mW`tz?՚?~KK˝;wׯG%2 B-//oܸqC >L&aaak׮X2e !|ZXX\to߾ݻwٳo>1 STT\jպu^Bk׮ݹsի{W/d~ݴ4---X ##!TSSӾ 6eʔ9s|r=V<੯+((WqqqEEoµ***Tӯ>g0߾}EKJJJKKˋxH$B4hzߎpܼ}r3!555^??999,sNP`u_RSSSRRRRRu+bbbX:hT* Fjj*㜜,t%##c``9Z@/R]]]XXXXXe_~]ZZʛGYbŸq NJ्fXF'deeI$vd0I[z6~t:Mbت/d2^ۃ7o>|ժU...9x!E=y666t:˗뮮D"[4 ػxgTТe - mh哄RJB Ӫ"h)m(*}'d2<|ibssk0XҥKxgMee%H~-jii={l_&CCâqgDtrrܻw#>B{ҥKϟ?!uʕO> \t)q(׮]ܵk״iՓ,,,lwxǏOeꈰaa{pwwnjj铌̠%!333srr>}SZZbgg*_eeeieddFԨx(9??˗/?@O0aҤIS2ۋ֖M-8Ɗ(VVVEEEj1V|F"je˖rppthʋacbGfvV,c{D R aaaAAA\&-11'44TPPp[neG,}}}}})YYY˗/߿!󏥥PwYzuhhE΂.>Yf|2""Ąq1 \ `F 6;;˗P8hLLL N8w^;;;o*..>rH@@ { .,..~򥆆q@;v>s挊ٳvqĉɓ'[XXlܸ P}Ξ޽{1u^ݱcT!3Dqqqzzzzzwӱ1c(**N4i޼y 999/BBBBBBӧOXϟsrrBCC~J"&M2SP(Z{qII DBak֬ǎ +[KKKmmmUUԸX]]X?cccP(ӧOҚ5k1}D7+++?L]Am&QQQꢘ60tuuuuu^zڵ/Zi̙xG!jllu˗/֭[jnnGgƌc``ݻ (Demms;;;---111UHHYoڨt+ؔ `F>>>Ϟ=;HTWWs9...'''aeD?|pPPeXXX˗/FFF,,,ظNPRRڹsʕ+=uw}:uJBB;qqSk|||N8G׭[kFoSRR_z^[[?~}:$$DNNnΝ֭ٱc]OOK2ԂcKUQQZp  IWWWuuuEE啕iDEED"HyPRQQQ^^^ڇ+..?~|8zh),,qF77cǎ ιsN<}Nߨ9mƍ׽SHHHKK`r>}]YYʪ|~Ill,L?~5k$CTGGׯ_srrrssrrrozjQ2rlevvvՌ p5c$>>>k׮]L¾ !ZZZ&L`eeuy[pqqnܸq  *eeessWeP:t}k~ݻwc[ly'oθ Li֬Y퉉x~ҥxʕؐeffN8fӦMxAP/\py'''***N>}U^^ݻwoٲ'H֭ *..޾}CtG7n8{{={`>|nBPrss5>>>vvv֭!:ݻw~*++kiiiaa ד>GLL̽{[[[g͚eeetR!!!D&؍.H;O0VWWWEEEQQQAAAnn._qnnnss3B|ǧ&P_ݽ{733sܸqxg$w]jU{{;++kߚ[[[c#v6Bd啐wuuu LNN=z9sLLLLLL|7oDDDDFFfddprr.^xՆ}'fnmml20zzzz{{߿Æ )G511)**zŋݻh"f|ǏEKKˮP75jT[[[vvɓBǏ?z3Bã jhhzOeemf͚‚w.0<555dgg뻸 `jRʤIƍ **wXYTT[ZZ]X6qD(+P;">>>111+Wdgg;׈P^^-##=wYf۷oUTTlqUGL?~PWW!'/^000aaaK,ioo@8pݝI"pvuݽ{,>|H",XpBmma O>222255533=z4hiiYxW=z4k,0T߸qc„ G HGx",,lܹ<|Ҳ!djj*$$}l>>O:!t .a߿ʪ!Quuu'Nr ˆ hZOBx{{GEEʺXt*))IJJJIIIIIC[[VDfCP~%gdd!544gΜ9cƌ3w\a@AgddtۥP(?Մ ¤I=z</==]KKnnnxg >|6mǏ6'O,X؈}:vǷmviF'eP S||w5/N?.ufv@mmm_NHHxiRRRgg… 555 |x566x)SH==SNikk6''Ȉvvv߿1cׯ_'L=̹s***^t`}}}FFrkk>ݛ[ZZ*))KAƶ{n[[ۑs QL;Fsss@@^|EPzd̹7nVUU;#//ŋIIIlllJJJ***JJJ\\\xg}Ąa ێӧO2U61;fddz*--uԩsΝ5k???\EEŕ+W|||H$;5ر yTTIllٳlsj>>>GdgΜٻwoBBIUZ IDATY\ee%HCعsRT>}켽Y@!27s +2ovtt͜9S]]f%볺Ĥ7o޴ 陘,X@JJ `0?{ \]]Җ-[vĉ &Ijj… LrM++pyL?wܦMBzzz DEEkjj޽{ aɽ~ZCC0^~iӦ={ i p9τ.\ptt.BdLFFSbb <==njw"Cߟ0Âwhff&))U :qQmmm~~ٳg8p $$$??$o߾믿߿/++㓝]SSyfB!bcc KIIyinnڵk+**hl3CCٳgߺu̬>11qW!#XXXMlǏ= MB]]]֭V!#D"Bs;<\rEOOUȀÇ7naY 2$***w ;!ʕ+YfMII}ZZZmmmxx.T!'C31sxbCCeCH\hщ'RRR}'..nccŋfurr 훡z@@D;dee8qիW?s{---YYY߿ ,oͱi۱ENNN2L]`Dd޽{6~xtuueee%$$$%% M2eʔQFOՙ?:u̙3$$$ 𑛛khh(((1110l={466:88ݻY߿f͚5kرc|||XHݳgXӧOݛ>}vs rѣG߿XeaذR_ń{(--]zufffHHȜ9s`0"KRRҍ7ݻG ̌Ћ;`(nmm%HC+ <$=z(<<<11QVVv666{DQQQ[[['''C C۶m{6cdɒL:G޼ysLLLUUUuu5\ݻVVV!!!K,;RTT\z6;;[II)++  s2001 cbbb ybeeUVVvpp ϯ?v옂BJJL|r777??r܈///### 11:uƍo޼񱴴*+33S___JJ*66P,,,k׮ζ6mڝ;w VZykΜ9D"1>>~T!#455B?~~/vҚB\xȨG2BH$:q#Gu L&߹sg֭jjj<<< ֩ؽ """"""׮]&'k߾}***迓{1yƍRRRuvv޽{ҤID"ںcQI$F@@Biǎrrr'O'AO<111UWW?rWKII=}Ԕ:9-//OLLӧ2zP(mmm]]ݜooj$W!̙3G\\\HHhϟ?걷+^^^:::|||!!!7%>tz֣HOfĈ>!@PRR"2D ,--ѫգ}ho}-oO2m4\72\SSrJ"8yd77sƣiݐKA#<˻|2//˲6m+{rss/_9n8;;s 7^^^FFFyyyɓ'srr/YhQQQDdlXZZnڴή ,KDD#"srr":::~D Mpp0@|2A"H>| tuu]x6gBGYYyٲew~'OLβϟ?_VVz鼐ի?0d~~777`ʕa̙޽P(Ǐ'JIIB !s 2zׯwWQQ!? "##n>{kgϞ<~5=B!FFF?ebbB& ϭB||<kboo}222MMMt[iggG݀5##¢{><^$ ddd4~x`HMgB nݚL߫bCCCpAz';#wviChNQzk?PUVV "B555W}howxn~i}9C3(--ݽ֭[{yx48톴_\FONNȑ#$ DKJJٳgx'V~-''WUU=_~mmml;q+V06ZZZ i4-^ʪomB)))ݻwBKtFZ {Qlmm2\TT+WڵGu2BhԨQzzz...ϟ~ٛ7orss+++19nݺhѢ3fHHHtKXXXKKk͚5GsΫWF`SϞ=Yt)JXo߾#RRR7o|QLMMGlرc=<<( KPPPɆ/_.##s[KKK A :&N NDD!dgg\TTÙ3gR(K.!xyyoߞz9o(--o=zTZZܜg>ͱJHHxyy?^\\!t1:AQQݻ_~]b+"X}Ç!~~~__ꠠɓ'#=JNUU`aTؠBBB ?@JHH3f" wwu77-[^x7|ʟz׎S ^|9gܹsՄN}WDڙ'CumLGGGGG B($$GH^oMM-oO2 }~42|ر^_/ ?vC/.=oaD"5JKK8޽{ӧOG ASSS#//?c jWZZ:jԨsѹҥKLJIIaee~6ol``зpqqqbXXBАq| iii==aÇQQQ7n8rȖ-[ՉD" _BBBjjjf233[͛7nݺuO\]]mcccCmell4nܸ^'!⒑յtvv>{?M378` \n]gg'YѪYYYf͚ a ~EOO!doo]z4#ѽ 9 )5ۯz3OHgj…/"IWitiE7'>?CUVSS5(i7!>| **:KBB)@>y$ \_|555e;vnhhgcVVW {;@ٻwiֶ!-FEE?g("P~7(0Dgg >kh Ɔ߿7Ʀvlc"PGGGKK{nprrrsscyyyyyy# ƌ3(?^b׻ d>}255egg} FgϞ>}Ǐ111ةV۷o988ʄn{/^~zǏ_~=?? (33>99[$-~o233MF>##CUUUXX!I&u-~Rtt;hNN&''gĉuuuC}}=vYWWkXXX=5Dbee%w[4inn3f>IQQѮ]hlN"֯_w Z!ADDݻw=գkGڗFy+C}WD'CUVV6eʔQFa{UWqW77 }~42, kJ_z<69f͚wsݿo˖-bbbx^z5gΜkb#3Duu~q]]j`` 2ணc鼼ò}hcSSSЋ/ O+Fdlx{MHH*d08c` ܼy \xbڴi7oܴiSIIɭ[BBBΝjժsΉZZZ՟?FQ/`?:Z3f!૯Ǫ prС^M9x L677?qرcG]UUE$??u. b=S^K{naU//TDB{ݻwcƌԄ*baa!ɝX0B׆ƌYWD'C%(((**wiiѣGӹz<FO=Eg?7i*ۆ4^\3?NN΢" *uuɓ>>>>>>k׮uqqك?]pdɒ &0d۷orrr㣽̙3[ZZ=z}xu!# `ƌϟ߹s'q B[36uxDl p AAAW^; 1;w*dpte## ^z˗/666ᱱo߾=u'Xb e~v"KII2A^^~׮])))!l(`<ɓ'!;;;BA={vɼ0B!YYY`% t! [^ff&?y //]v _J.ڽ3f ^|c?Շ^gC{gfffvbĮgΜgE涴<{Q0D۩L666j!2BM~?5z 6۷o۷ h]]]СCiiiݷimm=t''JZZ^Q2}t+++PAAARRR_5 |xNWGDD痓Ǐϟ?o߾!$,,LP9IvڕsN^^^Е+W@FF!t٪*:,]!dkk{QtXBBۻ nرctѳ淑h5jٳg?^]]А㣢BԚ5kN`Z?~CCSSSNN.##L+Fٶm[\\\CCC\\)=3gNBBBaa'u9!zXه^Y{^ͯѓ}Bz:TϞ=#JJJ]]]ƍcaaW_=FǛ]=.:a0v *;vx~~ېƋKσGxtA#MMM?D;Ԑdoo?j(pw lnYPP2e $H$J{{;Yݻwܾ5^'##øtL qĉxg!& ]KK˲e˸޽effƹ,,,߿8ڶmĉBDN%55W BmmmsL2n0ꍋ B!FFF?kbbB&7y۶mI=zԣ޽{x˖-w"##}$χgo#Xcq߀#Tp kVVVWVVprrzxx'<d33^Nei}?X>!U]]$B7B?qw^%GGн:4q>w-;챲?]FiiM { L}q>lnn²g0SSS --}Ɇ 1]]]K,s)IbbblXFFʕ+YXX&Nw&{[[[...>>>GGDz2 %?~1cb]]Cvx9n~IIIŐC0ϳ8'8!\\\a\:Cj]ӧO۷ӧs;wo IDAT %6lpssf"//o…aaa~~~Νsrrmyy۷mllΝ;' 0i񕗗A]~TlП%$$뗗 ;,X`aa燍 511IHH;jmmh"j=}uŖ5k,\s0j̣"((( 33sڴi{챴daa;S|?,--&MwBSSSFFٳgM^^|ӕ]02l``PQQ>j(0 ߹s6mԇ[nݵkBLJJj̘1 ,+`<{l޽P B޻w/T!(MMMQQ/^=z4 tV!#$$$SSS^:iq7a'oZ[[CCC%$$h7~'fϞ>o޼bЋG͜9SXXŋP #Ĕ)S"""VZGU!!!cǎߺuy墢}dɒe˖KIIsʉv233dH`"$$ՕxŋYYYi.~ Ƹ_z;ocs (fG"޿:|AAS:::~u…߾}KKK;q℁;;;y]]Ǐ{NGGgΝ҇޹ RSSu֍7Ãfs痑 0,a3pmmm(D& e |`iiy޽3fڪBBB"!!AWWwÇ>>>7o^jɓ!sPIIɭ[nڴI@@\F`ee6mڤIF VIII^^^nnn^^^VVyUTTgʔ)Gyyϕ+WZ[[WZsI&455vvv&''ե4}]VRRrժUA`ZdϟӇ3g}hkll,))-rss 14#BdtttHͿtM6]x@ qO<0aŠ+ǎ;MIIYvmSSիWXcǏ_zʕ+i"WVV*f~.]|ryyԩS,--/_?|۷o;vXx1gfffeebS***bEXu 899 #K]]]n7X?B|||&LPPPPVV* ~ڵk^^^eee{;)**TUU}q5YÇ4.**:gΜ>XL\II,r gg>]h//o`` 8f̘r"Ќ  rڵo؅ Z[[_v |/zArr\28T544lݺ5((F]]]II{₩^>}իbbbqqqfLL&SkFkkkOnaaall>>P(X1FIIIUUظq㤥& xԩYYm۶4Xk۶m}IHHEzzussccc;x|,&GP+++߼yΎw>JLL0ƫWnjj %%%srr)@!2 ՗,Yw޼y3o<==`P(^^^w622ڴiӻwn߾mnnK?}t߾}#_O< Bmذ͛-D~Ŝ9s7/P(III]]]gϞ9s%PNNNrrrBBϋEDD `p2#oߊ 555cƌ%$$$%%EEE%$$š 0,544TTTTVVWWWVWWcƥ!666IIIrWZZoQ222Ν;$,,lgg}1c g>~xDDa@P455BBB~MYYرcgϞM&`((,,:uG>} 22rݺu_BWGOdɒ><|}jjjlcceJ QgjjjRR#233ͯ])dzً/O.%%ɓׯ/ZhƌGQTTĶ0bŊcΚ5kȐ!'N4i%u@ p8T*J(2 t}fAH$Rؗ)((0L& Hqqg={v)2lذCFFF8AhllLKKKIIyMJJJjjjss3B;v.,rrr_ojnnfn/33{ȨtRUUUSSSݨZ=rrrhw [[[;+ta``7:t(222**{ƍXw)gg_~˗]8ȤIFe˖۷oowwÇwΝ;Jt˖-r={&%%u9᪪0D"Ҡpzm_ 2رСCVVVX}@^^nLL $e ٳlٲĿgذa/_\h{@@֭[.C8@ P(eee8.''mn%7ܕѮܰ0S3h mm򒒒o>}A</+++//rJ'''%%% SXX&?|deelmm}}}Genn!HKV rQFY<[YYYVVpDxPxdJRT .]:bjjjO([\QQQ^^^[[+"%%%~hYSSSMMM[[[AA555k֜8q"""9sf``VCZZڵk#F2eJ|||?&Mlkk݃G Adox<رcC ٳgϺu.LZH$ǎ~:uֵ@P\\LP޽ 0z͞7o͛7ongg7a3 L&_paĉ˗/˗.}555P(/^͚5 oG60#rtuu]bЛy~x2f̘5E'oSSL9 d񇫫7簒p4"CGd Ca] Ͽ{.\K7M6f߿ɓ'Ǐ'X]ԩS ɓ''OuE@$͛7nnnqqqvӓmh.o߾6vpϟWTTliiCVVVVVׯϟ?6(h+,,K^^ϟ|RXXtuuMLLFcbbbjju6"ֵmsrrDkJKK+((PTEEE%%%4H&etOtYVV'_:mhhP(5l6b2a2g#+))W`0w܋ $H3g۷waoo?f̘I&qy?cnn~)S 6lҥ]8† \\\^z5|ovXnݺu,X>{A =z͛݃8͘1˗/Ϟ=M7;|rk׮mذ޽{())iҥSN ؾ}$,G䴴̚5ڵk?G;" Aÿ B_iiiSSSSSSJKK?|'4r?>>~رXQ222Ǐ9r_|y:uQbp8ܻwtuuLϟ:Az̾*-- #x<ɓfj +~hނĂ DҢ骪+9TTT~.t:]CCC[[l„ hi}}} ɿOFFFCCCCCSDYkkkZi]]]CCͮ///p8I<O&ddd( JEL&xEEEFedddeeD<@PTTD#QRRRPPo\ncccSS˭xf#bp8|>"mjjp/o2Jh Beee (((1\___o7ۧFDD̟??88xʕ .ú" IDAT~ӧxbi&wwׯ_|uܸq4MKK͛3V^y3fHoh]uGd\ٳoߎu-?-,,`iiigΜ?~?|پ8ʹilmm\2j(+yy/^D;;;4nS(6zξ˗/QQQeeeRRRh{]iii2|ѣGw$Mh’ҏ?"3p8N(X\B :8DK555}d|>ᴴaS_l6UUU*BєMLL)J@NNNNND9N>l( ȗ/_B!O6779v%HhvA_@DDԡY 4`4%w(: B^hxk߷ ZjkkE(wŢMhohX|M/V/:v _ H<O t:-eee{{~:&'''===###77ٳYYY @C  &ӧׯ_j*__~JѣL&ŋ SclmmwyկJII%%%^1bD7 \zsa]K'h. :! dff&''w L{ݽ{ ~'k`~… tGӧOl۶Niccٳ3g:::9rdΜ9XWZMM ByŶmqttܷoP(lF{J}@ CܹsN ƭ[zEEE33333P___UUU]]]QQF[E{ş׷HJ"Gi4 ͉š9ite46-M=+++ VGUU[RWWתxU- hY|Ax[EM;]eDߢvEOEnѩ8 D3 ⍴⍴UyP(f\.7;;%'&&;v sSTQ.]ׇ&`@133;q֭[}˗+++c]ZO~#fΜyJ_~iRSS:iҤ/EGGC'fΜd2.h4ZUUU "D<ߍ%M_J|||DDۈ#@9sf͚5>}:ֵ0 -Yܹs֭c2l6֭[...X444ッ޾}&QIJlYYB?|Б rǯ(hbɏ=G۷wE8̙3vtա,}$) Q _&_JA̝zkT*A֛_#}Q555B]F~ ,uG{[t|Voe[ݹ8y̎D"`05</??%gdd_x}#FFF\D}Z'hkk޽{ӦMܽ{ߚ5k444.G߼y>(((<;;{Z5ڳg֭[wڅu-BX,@ H$|ZHp#2L 脅  cǎa]HiӦyzzb] @  7n׃'N\pߤE\\\>}ZTTdkku9㙚""%%5nܸBX,VXXل X,ٳg?}تФINaui=իWcǎ=s 444 6LÇ TVV$]QQQ``і-[쒒bcc=<Zڇ䪪:;MZ H$Ad@rJ4ֵD+//wvvӻv횴4ݺukȐ!KLLYjURR)֥,vΝ3g',X,СCEkkjjTZZڪ;///}}g8qbm  t H400`2ONNN/** f0{qss344e0ӧO rׯtpL&ŋO>RnnnhVǟ9sFYYyMMM8sL##oΝcmmM$*++X>u-hHh!ч;"( g=.;{lcc۷c] HI&999?khhXjѣG/_d2|~ll#֥? :tڵYYYǏGoDiiiSSh PWWmw}W'q|s"##_~=bĈk׮p0sNNN5,+===###777==̙3? ::: |Ȑ! @ۿy&44bڵBO&(((ܺuks̹rJC Z` j+wܹseeecbb,Y2vXEEEMMMggg??ŕb]>5lذ˗/{nȐ!711ٿ K&]]?1115w\oa2EEECyf lܸQSSsX999YY.E-I$R ;HIIٳgώ;ē%sMSSSk?ƌ#++o߾ .P(Kѣ?pFZyyy hG﵇r,K]]7ĚP(q]]]?ddGHH˗srrn޼b ׯ_oܸYCCCYY+,,,&&&77W ` KKӧOqҤIAAAaaa XCƌ~۷owpT@@ɓ'Zm֦Rx\+#H+Wnݺu-PVV 2H 0@566zzz:::c] HGڵȑ#vvvX@UQQ1eʔ ._|nnnIII+WLAbbQ޽_b B]]VRRRXVV& }y3gδͽp¨Q @ --m``d2O>\WWWTTlccSRRw^777CCC9991}+W~}򼽽nݪb͛7wڵkW8ťb=+h?Ӝ9s-[V__u-mh]"D䫎D P!!!QQQ<m{ҥK~ܹs999wMNN^xqPPPJJ5֥Ieeonnnwo~i[700h;" ҏܹsVVVk֬3f̻w߯u]hР;w@m޽'օFUVVvvY<&(!!!"""<<ІiӦܹZ蟪OQVVի@uuH$4NxBѶlٲvڿ{Ϟ=X`uttt.444^e˖-[̦M֮]&Z/++TRR8f޼yǏq/\ΎjD&455uom_]t?,>>^2$3gάJJJݮaժUG]paTUU}qƄS^t|@ fXǺZ6.s8v,''_d2݁<J4@4$// Z'Ɋ貲2J=***[iii&)ZY\\,%gddƢiD(Ɣeee+7֭[r o~Snٲ :j[d >}z矑۶m_d2/_.//=??@Cڞ={vܹX ]눌&;"? Oݻº\AAA>|(۽yfܹeeeǎwŋ/_sN999K޽zƍ }Ao%DW7P__ M>|X~}ttkJJ5\ZZZ\\\^^^XXX^^^^^. Z(Fd2Y^*fy\/ T*A ᔔ ڷ'^xƦ憆.[__&Ѵt]]b 9A׷JQQ(]RꚚjjjjjjt:NMMMMMM5,KKNOO?sL^^@ ⍓'̙sڵ` KE̟?ҲI$Ҋ+vܹn:L;w hȐ!˖-[v-ɤP(XZׂh?qBΝ;p ȑ#~~~O{軄B}6l0bOO͛7cǎM8'jjj޹sGEEʤI޽~xss͛7oƍ=TXs``իWnj{#F`] %%%eeeťբݨT(n>h4JJJ[5nbFt@PSSPWWՅ0477 %'WЩT(6Ng$n߾e˖ׯ_;99m۶mȑXվ''W^{ 4(((( @|ѣ޾}[YYH cjj~ki~~~'--})OOOAƎ;|/_@؃cǎA Ç˖- 2+''gϟ?߸qcQQŋݣ԰.Ob0 'N7nܿ_DVVo_3337ADI6""b ĉ^^^x<_xyyy_|A EءM|555Դt&N"RRRfʊˋrss={˗zt7 _=R`0 @<|:ATk2`ffAGx&d26m4jԨ1clݺ"--}9[[͛m+))-YdϞ=˗/g2{mhhx1td} HԣMMM D"NeEO <~1**jXӧOF'. 22rƍ; qW\\ڔ˗/EEEKIIh##hŢ\2>=]@$Dd}xoJHH~1cL&%!!1((h֭mYUUxb[ZZH`@^  “'O${;IYYyΝ~~~{nȐ!fff=S& :+++SS;w`] HF;;gϞ _ݴiӧOܹp°0 uivqq͍ރSTMM7Ad ⛮_>mڴ>w155uO>={vXX&@ݻ. Bia522hX !?}$&3c%斖VVV#%KRwW IDATAWWW~H o k@ByfĈgΜ5kֵOffٻwשּׁ:5PCCc~g]Ɔ jjj=u! |||޼yw7nΝ;tGƍui Lccc;!m ***$=zt|||M}+\QQf͚gٽ~/r455{q8A,--L px<~РA rrr_|Asiii.\EDWW %6Xb$T*FfggrɉGijjBw$wniif͚9sHt~~~>>>p{[.**ҥKgtuu񣱱q q 6իWJN$CYYA$H2HDm/AGdy92|k u+V:88`] ˗/}||׮]kpj0yW^ݽ{wȑrLP(%%qҥFDD[رc .,..VWWm2zP(<}t`` ߾} $ZH@˗/_&%%3 kkkQ= PT4'>OPFXW '7/Q(CCCQ.TJJ fRSS#""Ο?offnݺٳgKCssرclWڎ̛7/))LNNN eͫVz$NuuCCCHWN2SMLLΝi&Atziiiϔ1"Ajkkܹu-tѭE?Jvq@qFkϫ߰aáCƍgiienn~!C`]Z?rg̘;wY,5k"""޺k׮TSNOdmmcǎegg=~xɒ%[nw=Ϟ=ūW8@9r$.5339@+o޼AIII9 %%gdd"B$Ddaff&''uW?^OO/00LJ@ lll_ƼYYYׯ_wssC״(++VСCVz)ֵ%%{vɓn݊ HEErUUUԈ1"A,Yrkjjb] |>ͮ/6rlvcccSSX,.[__ HsssCC MMMCzN%%%zyyyts@P(J999$ZI$E+dr KEEk``~УG,XPQQt۷ogeemٲe͚5ukii{ntth)))Æ \|[CCCׯ_g//?~]~%JFEEYYYa]H@ݻMMqơaÆDSJKKF/^x9_X̰X,Qdt!33S ]]]Q.ZUUzA ;qℶʕ+ddd.<|p„ ;vXn]M:0))If֬Yiiiٕ=_)$#GP(sw֠A.]ڎ?>,, AC&9NԈ1@<)dBaUTT l6M ?sL"x H!#CBB3j(ݻw[YY%%%Y[[c]"--}yOO~ݻ?r?"bhhͭ药8hjj{llŋ+**<`h X,۷cbb| \ ǎ+ispQT.-%'&&=z=]OREdtA__N3QQQ6lسgOPPΝ;WZ/ 7C:88ܹ3((z„ -((hĈ=5L&իB0>>~CW~ <*x<~7n)JeXE"DH$AZZZ2x|SMMM: P(f?]xxxNN͛7!hXPPttttuuUTTTUUTUUUUUUTTeSx|uiԩSIܹO?u8EEE())}s+z?P(9r|||<}СC%ׯ_{{{uiAϞ=ݻ9rd0sK.|3u`/|yAlll~={ Vova555+쥤>|  QRR}D???Ash䬬,4!%XYY><oɒ%X=bĈ3fDDD\zU(jiipÇ #)ovqq鵲$Dhhm222tuuϱxYlSSm! % gr?|ϟ? gddddddhh.)hhhO*WVV~_FOduuuæ}fѣG[IIIޯ^X~}``,֥iii6mǏ߿?bĈwtt|!f{㐐-[|p׮]w.--mjj"ɧN5kVw]VVvaxK.>>Tr54׮]=q@ xFJ$e0.\N>}P(r TIIN^^j9w\bbbrr2/_|͛7>}BC ظ_ޅ '''''l6UUUDZye"ؓ+VXQXXkH!Y\.wǎaaaϸqҌ. &--}ʕSN8166ֶS+++ 7SA_[ZZ:z@ }AϞ=}ӧ3hؿhh˗<8|I&CD$ x\rzzŋ9 d2mڂMMMwn`EQQ100pҥǏߵkWTT>_UU= E]̙3y<^{aCD"qK,;v,PΎ"Hr2 ***̼wލu-7?&%%o߶(**>Ғ`At4\RRu^ 1777Rt:]MMM[[N?jii)**r.\={6/ @_[__Ssݻwϝ;ZHzD##ѩM6m߾=++ؘ竪-[TUUI¯DIIӧOCCCWX! %W^f===L@In9HjN>}'O|PتH Y$..n۶mO>3g;r-@GX,4, (Y(JKKr Դ_f SN3fؼy7O]x1$$ɓ'ݕEnhh=oi4m*//WUU햪ggg /9r$00buj󋊊ݻ>UWW/+++--=P# ѣa] Aqqqqqq=& cA?l-6]ZZ*W.)))...---***//MIiFC⏚d2{ ː!Cf͚uС=2[UUU``'ƎK"bccLui}޾ٳg*** --O7nܱcGff 'O}qF#.]秧wE333,խZرc[DD!@Y8p_oQ7nXn]EEgϞu9lO'geeo544DdKK~6C\ŋ/_̜9sƍ +@ 055611IHHɱ͝:ujZZr+Hq\&ݻaÆ>}ó|W^>}zKKKBYdɇ>|>-((2VO p rek˗/7nܸvZBBvrr?~رc!q%PX^^F 򂂂rѻ7yyy]]]mmm---]]]---тG޺uΝ; F&ŋgdd888|oٲeҥp~/zرc'Nxvw񣉉vAA7wOOO777G$##& LysbUHTCCׯI $_8 @ |X̙3gn݂b4Z^^ D244olaawO@poߺnڴiٹ["hhhNLظe˖pu_d==WΘ1%%%?R @_W\\lllaÆ 6`R@JJʰa>~8x KNNF2~9S+$pvYYYu!{_~ƍ_&ɮ.\?~Opt:N[[[s򲲲/_աɵ&oݺ588RtDUUժUΝ;gcc3x/[l?x`ի'N|pBBBAA Fݩ6nx͚HGv~\u뢣i4Wmmmppptt4_hQPPPel6m|d2Ox۷oߵk׼y?@|||޿O&i5yӦM=| 0ammMR??e˖:uJ`Fvvv޽*//ŋQ/Ν;W]]=55U}[6L \\\D_˗8qihhN9}4%KN>HHH|q׮]7oz. 5,|]#""ҥK3gw,Fyɨ|rFF*%S(Aijj;ރ $ KIIYYYEFF*)),lClقfе IDATOÆ ;w0>#F` =]CCK]`())=zSv:99>66WR';@xK.vXbbbEEſkiio> 4449aضml6 0ԩS"67^TTd2SRRy<3ݻwE GEE]~=''gx "##wㄌ53fܹ;wd_PPЍ7RSSrrrWZ͛73FKKKUUqΜ9w^nn@-ݭ[Zf!cԔ"P0YY;v;wIJAAD"}oEdiiieSSS=@Edg\q[yyo*x'H$q@M>֭[M<󫫫+(((,,,(((((REEEWzzzzzz222]4tsǏkhhHIIeee͛7oϞ= tr[[[WWk׮5ҥKJE9(AJףa444P2z500@9ʪ(twҥ@abb8t#G 2Dܡ#oo{M0Ə_TTfZ`Ŋǎ0l7Yf QQQSLѹs玱qz9sԄ;A&VVVʼn9D"(W@EE&## annODKKLBB֏:L&BʢxooVD􊖱"|E)h4eee Ú%%%[!"6]"aSPPrl\nmmvxQiii999QPRRعz.%<G"tuudKKK[[ɦdmmwi4 D"=:,,&MLL3gNJJ ݱcW۱cj%==}ĉ_500P)B}-梢>|ų6775kww7~8q{Ak455ohrrruWDEE:@gsss8qڵknjv UUUt ':iii 輛P7Dd/^\x~S<<88822ݻ>>>ݭ\z3gܿ4 пJ⅓T]0-ZłfRXXvڛ7otcǎ͟?9t营mP-EEEmkyyy\\\```iii@@_]f_um۶tɒmmmqC߿ ̄>aaaEDD?2В7% :'^䖋Dwװݽ{WAAJ܁ЭաG2Y,ʠB(D+YfL&^NݓCQ&U,J0LJJ9<A9UG!_,[U]rOH"w%jllܳgJJJV^~ݻwͳF~ǏI$Ҙ1cLfBB!C:)l5gΜϝ;=ԩS7lذ~+W;2yCIJJN:͛`4JJJJFGG? ={رc _N =Οrsss_GWWr򲲲CFFL&(++ʢSVVF9P5$ ]R{yiii+))) IoaQةX,WNNNJJPia:zPFN2-܃:AԴ@AYV8Q&9oC58[ͤdzzm]-A?BAAKxd#eee%%% ލFyJr\III===666G#H+WܼysGe+++QrOee%%`@Vk~TUU߯_?TJ3NO,Hkiikhhhjj@k***LMMWZcǎ.UE>>>>SN]paٝ@"2\IIڵkm&X@X,8PXX8cƌER϶K{h4<)9//////'''//]Wd2*D"8ԩSMMM;wyDbh4ڗ/_Z㹹W^lH.[TT%ɵN600LTUUI"\3::2F@*++[M8I,++綢rrr+$//nM -F N0LKhݖN߿?Vn$#JӧOOLLLII>޿,XVB }MUUoqq1pر͛7GzxxHHH<{L__ ۾}6o<{GB?$&&19%knԁwC Ecc#:Ejm,ˈ* ҋRP1jlld0-+s%x?]PWWWsSy/^h}j@ 7m4GfUl<۸ ]rrrl6fߏ\.>|8DNOHHptt333njC&>}e>@`mm=w/^|ܸq6mrqqw\|K.ٳd۷oYYv1Fv^J1Lv}}+V̛7_~:S6.--EWPP0` R^XRRRlv^^^())A44ڔihhhkkkhhh}u2>,,@ z<|;:::99u5:ߓ`ϟ?>}ѣh몧'%4 3fC١_|0`Dd$"z?c&&&`<{/))T`{9rȾ}KwDRQxBpܦd]]EYXXh22~ZUU(edd|9ȑ#y<KNN;vݻwdR0 svv~UDDݻ]]])ŋgϞmVaH?.55ӗ.]jll3gƍE)EQĒ\[[۲kwe0nee+T`00 CnذaܸqѣpUUU_QԲ24G 0NY~?QYYY^^_⽬,55ŋEEEL& &## WlvrrrWiiieeeMLL:b;;; Iz EEE;;;;;;MIIIOOOII}lΞW;Q@"2*@"2!'';l߾=44ƍwD@M^i֭hhh*## D䬭G͒-*`6h l6%QT*xRkfffff6pҜw_aKK˰0GGF###ԆfUUUão߶@?|́;QF999ݾ}cƌ3fLZZӧO믿>h[n9s eǎgYOvM3F{iïId2Y3JDơG/[l…l"###'''??eㆆ4JsppqIJIII]~Ս{TTdQQ(6M&?+Czz:*OMM/_(**;|ejj Or333䄄1`WWWWW׶@g𨭭Cmjj{IDBCC#**ð3g,_|8m\СC^x!X.wʕcǎKJHH;ަ,;;/a@rLVVVVTTTRRGI$x<^MM nllG ?Y 陿r~tuu555uuuQvIف+qujjj>}ӧϟ? c baaannnl]]ݸqa t<;9%%%77d;;;[[[[[[ wEEEoٲ%$$Nck׮K͛7NNNo߾mȑ#-,,Μ9ӎ0TGDtK9s@ p\"(%%b䜝ɓ'+**;FWݿΝ;$wĉwh}H}}=::MQrsUUj 3H$r\EEE"r%$$At*AHee%a$%W<1#""+**TUUGbkk 9!!˗/_|Fpqqqqq3f̏QjtԩS;uF ǎ7o*++?ydر]pa…G( \x;+&&fժUiiiׯwD=^YYUVV0H$jhhhkkkkk4hiiOas8򢢢B0]XX"a SSSN}ùsLB6lذa,--{qn]XX!꣦VUUhN[>|CEEUUUܹYH$jOX,1w\eee++ѣGO>Bަ422ŋyyyd2sgG_^x"2jL&;88L8qذaRRR?/&##ceeeeeߓF:s :ѱ655z .Çw޽{7##CMMunnn Wpk׮rIII()y+V4itO2eӦM&Meee(ХF-cٝAEdzOɉJX~~~w655wD=FSWxα^5C+PB6T*0LSSnugooo``pq_~ݻ7o$&&r8SSS'''+QSSwB\.777ӧOIII?~|-F#Æ srr>|СCf;~U|}}o޼a؈#|Cjjj?zӳwޝ2eJUUwOb2nnn իWP}|| AMpBGGG ڭݻw/_LOO'HC=z68T*uׯ_o kkkO>}ԩ@BRJLLr6looO&)\.7""Ν;ᥥ&&&Æ I:Vss7o޽3e+33B|p'''uuuq@߾}oߦD |y9!$$DQH$Rhh̙31 1cիW;)B1Ddzt#G,]Tܱ!<ҥK7of0۷o_|y-ەlǏ_~͛J`bbbgggoooggggg.PUUE ͝Glll,0{!C=WcccLLǏ=z2l0;;;Эho߾}ٳg(Rѵ_ccvaa!@XhQqqG{ȑ#1 {QZ3v!|y<ެYݻK{{\ jhhHKKMMME555m]B$H\.w[lE=srr>|bdeeP/w-ɨzblllFFUWWY8N\\\ddddd7o]v9'//?nܸ &x{{khh;j߃ mSS/_\PPЭb]7\}P^^Q|||}}֨QGehh( .{ÇGEE̟?M`[W`nn?s…QFY $?4((h4 ~]pAQc#oo)Sܺu"#HD?~|QQQ||c+222V^ɓ3f߿⎨o޼ADZ#Gtqq2d4+W^|ÇވX["""ƍokk+XڃFݺuϟ?g2^^^ΐ| @P]]G=y򤢢B__sʔ)Gqo%$$>{,..N`Ν;MMMNjܹϞ=e[nݳgOXX.Fefffdddddd~b0 #ffffffǏ/++D"q8)SڵKH#l6;)) %%'%%X, ccckkk++++++kkk###tJ,+555)))%%G0LMMBs=QeeÇݻAG5j(===| :>~#GJII1ާrw`PV{WXnw`0&L{͛7'&&Yf„ =JTwϮ氰[[`[@[֯_ܹNjf| ݻү_ݻwϟ?F2 @/ruuw,}BSS޽{---;6|pqGM%&&>|Ço߾mnnwqqqqq9r$46ccccbb^~`0ǎ奮.777Ǐ;fݻw///oooOOOqh'.ѣ8--~iΜ9vvvMT7nܘ5k8pq*? J511/^psskuRNZn]MM79qℿӧ/^A} NGyx㜜F ,--) >σ|ѣIIIi̙-rppwhJVV֢EbbbMqt驛\I>~gϞ۷o:u ?d˖-֭X"999**JQ6nܸrJ bbb\\\F-b 8w=r-""bҥUUU_hϟ?xÇEEE***p@jnnxѻw\㽽Wzeeeǎ;w\yyٳLst=q aХ$W\|rnnСC-[6k֬_>((ڵk~~~.\Xr%fӦM/^d2B6III666o;;;/\رc$Kx1J>hIHH 8%c "|6l8zhcc#HrNNN1bDDXx`&;=zʕ+ cҤI3f3f t996OD䞢 ,,,44Ǐ666-ZpFݻ7h CCCGx޲e9t . o߾]fͧOo߾aƍ/^lٲ%<<<11QQ ׯ_aF᱉8qBJJ*;;[܁~MiӦ;F9sfر222a111l6[ܡ94ƍ ,0LCCcO>p8Ml<==G!(DERW\)''nݺLqGKߎ \/477?~xɒFFF'OlhhwPL>}yѣG444m۶ 8nkRd2رcBfWRR5e.ۑɉ YxDB;\###ŋDDD455c.gΜ!D"Q__ \nAAAddӧׯ_?yd{{{ҹ222sٺuS߿_TTԾma2׮] Yf6HD_D1bٳw^JΝ;TUU;n!44MCCCUUu̘1Ϟ=C\ɓ'G6r3gжzf${aÆ)**:88ܺuK`vAC655Z!445kٳ盱q8M6HJJ3Nj]` ޼y#JظGyyy(((X,ԩS1 P(ުB`6}t l 555g̘QYYͥ>+W,_=K3cƌo #d% 'dQuTBV7UTTϚG x?}ڳgj.Y[[{ڵ ⎫0qmL&sÆ $IWW7$$N*}vNw[OdiiiOjӦM<BZ_Ο?/%( IDAT%URRgǜ9sf[XX\tz\\\hhh@@bٳ{ƍ8Q󍊊}4m4I=x@xl|sa/_ڗ.]r vvv(_LUU0chh(axeS044d0—NWZS4֭?+Yg&(zWpt:D[N'd(PBF֭['//ogg$p󐔔$ϟל9s{Н,tB6og1cO`?%omccc|}st~sv߃,X @l...1etFڲe NII0ƦBshK```~|"@z 777"rJ(ꃰp???YYY)))??n^+,, qvv0LOO/ ##CAuvl8CCCUUSNg]m@m[>>''^܋ 555qqqϟ?O>}u-\pnnnֺIƈa&M}޽{qqqŐxm۶@ YjzgϞ0LNNnٲe555QQQcǎx'O0LQQzj N>FoH'zo߾hwww F("Bc 8999ӧO0LMMMڵkǦ{%<@|;>!,,H$޼yS܁=Ç1 댔7ojxaÆ ;330 ;[z<C;^ *NP^~-p'''G\\ڵk @$G}Ν:[***>|X܁|Õ+WdddFU^^.X]'9uoF^^^O?ihhoذf \nw~hlk(َհjQ7ym,ϟ?WWW8p Jw,#==ð8>|xaL&}n޼)!!d2[~tuvƂ@q+WzxxH$XxqHHHDDDNN<4-777..ӧ׮];yݻ׭[`ɓ':88kjj WD######{{{wwwɓ'͛7oūW_7n+]v|ŋϜ9o 222QQQرce˖mڴΝsNTTTRRRQQ409r$a˗/oш#0 [nիWc|iLzV}={af[/xqqq[pppSwLLLddd𭖪(=ð'gw{1 fΜ2HBKY k֬9{laa!>xnE_Bl[[N?ZB\Q}>iњ. $H^^^_~w盛ѷ3m혾kyyyiR<Û;D]O1c [[[qGTGKKk۶m>etQ]]-(Ç_jR6ћxmd'z3gťHII;^(99yɒ%(ks֭H;v99 .^XAvr'N]l٢EP]̉'6lPTT,X &,]=aK,9}4#!!]vݼyL$**Ņ˗8q0)) @`X[[xիW9r刢 N[YY⋀O\e~ӦMc=|Kⴜw-f;S7t߬#[l٩S>{]O:ssuؘFL&b喖mFM X,&Z@ `2ֈdX"DD"QQQQEEEYYYYY&" `G2L$D"Bp_̛7dCKK.--Elf}}}ff#⢩Iӑ/PNߪt:xF)((`0@.Rjll%|>wLbhNmჭmǒ% wJVh40xo]Dt2dee=b0lQ/`d~~HϷQSSkhhuVpY VG!^}/PRٮPSSc0)Z4'^vG WF/|%4"Viܸqc߾}K.500Mpd%FGG:uJݻw„ zzzO>ݳgOzzCvы-ǶmDu۲%==}{Q szz;w1 y$_LMMD\.wG@ ȀP1+555ގX___&C BPdm"ׁb,+x<ڽuuulK(nebb/ 6/FFF555zzzR hQZZZeuuummmd2A)EuV;wQFx `0A(eLW7&oy}OR(K.:Q$0}NNa @ HWEFFfgg;88coU!%JJJ\.,p׾!l^^^&L!0VIIرc@.{i###y%$$"@zYgggANY[?<AtY"[YYFڐ>,"q6"g_Ib޽`s{1!J8t(r)</?qH'M޻w^ wppOP}VrSN]|hʏ?H&2PTUUEEEm޼/477GmݻWTT&k{!D*n߾`ʄ YfMM O?$DBӧh/͉%*(( %w"m y[#{_|#k׮E… x,}-)FIKKCӳMIsHsbHU{=^13VJӨG|>? @[[`3k,3p8SVV^`{8P(_lM^"r'}JP(pHr劘=}`WIyeB,^QV@ 2{by/\,755!ѿV P UnܸbdmȰ% +V"  ff+wwwO?999ڊ*1D  111X,t2є<AԺ8/Z~FFU W_u[rw Ӷt4-Ms$ JǴ+?|i]nGBcuuu`X,EeĞݻw6D( L>qܬpB T@wkؕÇ ŋSRRnܸs@777As7nd2tٴi߳gǓ-rٳgڵk F||iӄB!I&̍7H$AwFd/_,))9|0xni 555߱nSM̙ ʕ+ vލI۷o#Z;;;_ֆ􉞎3A&@P]] ӦMSRRBd׮]ݖܭꂂt1!mjĮ(dzzL4[ݑjh<YNȅ9Ο6mVl2 T[ɒz*gBFikk]"?{ ߽{wCOq̙3Dٺu4oUbaO}:6Eۤ޽{6\2۷o4D;(A/_FX 2^Zt_333+mWH 6H$V4'OI߇ Kܽijj255%$$Dt_iNNMةQ oL}--- n@0#Gt!(J|| k:=Er.9E˅U+++ za.wV'<{L&/Z>uC Gn߾bE} d2ٳ_hѬY@ @@ IrFţg>s|CCCY2L8}O?EY#3׬YgY[vӷ~{APٳ4L&I|ܹsK.-?>XJNRL&lݻw;Dw#""֯_R7o斒"77"] zzzyFrZꚞ~hA#GlذmjUBbQTTloogX['nS1J?]5vڴiqqqϟ_|-fΜI:d7.\Ԥ&>|x߾} CII)""b͚5 ŋ&MB$%%MB޽ |]FFƐ@ FӧOrrr>|PQQrP(vvv`J rsslr}''k~r=³g^x177nڴiԩ`STT˗sss]x+D;/((o֯_ߋAw;wTUU577w)'f@K.=}~aaap;%333<<ܲLLL&Md''_vիW P(LNN?+WP{7o|}5y* U;w.>>'00HԱnOtwU{%to6ϟS(((( =9:7umz\xɓ|>ڵ[nne!333[l---%p8sycVZՕrT~ WOoy\.YH]$WfE(Y2[j۷]vǦܾ}ɓ'=<<~wxΝ۶mX`ԠL{rUwǎ{ t" 'YYY!!!޽۶mq8-@F------ ,X,& ZZZZ[[ ojj2b fGrX,B "_<O"#8GHBD@ IDATz44 X C D"DP(D"H$d2 ) D"> B &X[[={trr߳gCBBrss?V|~?$'@!22Xv[ z)w]r%Lp¸qdmx+8bXY[/s={HuOyy͛7gϞ-k[$$$DEE-X@􆞊V߿/Z֭[ߏt^?Deeeu{dX%%%hJHHɓ';oذÒKr&PQQfXbř3gTTTm^,9Ǐ$:رc߾}6pȽ쐐 .?~ٲe6־xh"WWWojjB4AnM8~#Gx2%%%㜜jA0j_]vŤ$- }W3gTWWH@">|0::۷D"q֬Y˗/2e <яddd,[߹sg!#x7wu ''QA Q__ ᷥ8&* jjj@R d@: mGN?] R{ VWWWSSWtB@:e׮]Wiii?9qDGGG {mbb"XUUUmii !!Ǐ9ƼiӦ?cǏsLfXX_tH0 o@a?2 LIIYdIUUՑ#GAwWUU)**r7n<~9s;6$ޢ \; $#9"gIc9F MHHR'N/{vȒ#z:tJY--_~eݺubWX!/zŋ|~FFF~kkkgddЫ0ߣNsrr2}Z]]9k֬9s`@ rݻ|||dmK}pcǎ=x_~ ~$d hiiuuut:,T*^Ŝʰ# 6-vdX,TArEK Hhhjjjjjjiiikkkiie---8_zĒ0qď?5ꫯH$?#e-[ťU555fK"C C8/7%%%gΜ 92####00d-kCzѣG=аaÆkJ_XzSO#k[ Cݻw߽{/477E?YYYNNN鹹x<~ܸqh\!A5kְn=yJ||ĉz2La٢c (++ 򈝃 @ h8 # T)S/^>>'O2e3 `ѣG삃/^<x!/;w.77(++}FEEEtuu']@P\.Fں: USSS2liiiihhS'׃3-555iAx<8U455uttttt <+ }R('Ne˖߿_;w~=X֦L&sMxQ&!ě7o_Br_ܲe˘1c em̸rw}K]]]YDFFΜ9sѢEcƌy葩ꄂGڐ`0˩T*w߅ZYY.HϘ6mڶmۜ^ fC._\vA@;v,66~~~6J/_R(w9jԨlt0fwN߸qA  'BaqqӧOeee<-^h3 (<bu'M4g777--5v1uEcnCwɃ iEEEYYYYYY`?ӡ(O/AppKN>iooۊU~!&&߲ͭeؚz\[[{[n;v=)eW#FFF K,:uqYf E=H >}ڶm۶mLwM>~KKKknnuVh"FAAwMfoYjUWC!rBBBªUw`ooNP111.]}ƍxb bEW[>r[ZZZ[[TMLpT*18L&xF"TUUI$BRHx|9F766"ᬭg͚ennngggooc)B * p8d2L ; ܔvbjG M H EIIIMM WSSSPPPUU%JJJ EAAL&ر2x<ބ ~YfÊ Yȸ~DDWTT! D~hoo/)))((/,,,(((,,,//GD"9888;;/[q$IB$ٳ;w?~??~ɓ&MÇϟ?F߳gɓ/=EEEEEEE__G{u+_p8 b)uttG:Hihh ?~c:jhh/411n 2꺸t^UU%ãG*++|tVVVFFFP%oxzz9rd7* _Hx 12$ cƌP(O<-C@{}_Hfe˖EGG߾}{ڴi]rttk֬9qY+ g̘+̙3WNcX<O43L@`0_Q%lEX$Ж# Q'@s\^^́MVC H bEҬvLK{߿/bB!L^jղelll$p ?~~Y, d2JKKK@B 8v===0cL qrr277b@ xڪɓ'Oi4H;vĉ 婤NhJ}}}G5QqY4I!=~)//<'1+**Q&PpdrD=|f& @@1lllʐ-FFFO<:ujR揊󭬬zD@nJMM} zTWW/^˗ o/`…%%ȑ#yΟ?Сy敗ܹBdkqƾ}.]j``h収>uJݻw >}gϞCرA7RԒK.>>!!!LSVVvwwwwwMh@Ǐ?޽{Ǐ`dd_!pҲr\RRfiq,,,XUUUֆC,B,'''x"!!an֖\PSS䪪*4~uM8}άew" j4+"NF>~-4-######===##ӧO+ٳMMMx(E g@L3h4:?rhMPy󦿄T*%AEIII4ثMp="C Cgoo?z7oږ!FLLҥKdkdmٰaɓ'cccA----""b %<<|g ^zq~ +,,رc>>>@+9sܾ}wӧO744D#G@ ڨ[]]-eϢUw%D4ӧOϟ?|r4斒 ۷o#""+**o֭[w'Ox{{f2c p֭AW ttL۝BPTpl_?~㌌ P8j(oo{{{ڎyaɯ^zubb" &M9sߨQdm`oXjUVVVbb"rر9''AB+++//$!$$L˳XfII֭[mۆ ș3gX, 4<3KsEEb:::bZ`D.[___YYYUUU^^Nˁh8?@H$&&&" nyyy3311A555Y @<}= %vU8[ϟ?`D%KVX4UWW~hJKKˁ5eeeTlbbbll.@ @d\L@Pi bll £ Z3K!KKKKJJJNNNNN.,, FFF(UGGGfB 855XYYyxx7/{DetXT4/K`ԔR:&|*x5koBǏ8##|^744ttttrrrpp~^drV 'ہ𼼼̂ t@0T p8'O\[[+'W.^yyy}6--ť_;wnժU999+! ߿k׮/ܹs겶H\paW\ J h@|}455333ѽ222\\\455A dff( pY &N"`4B>|`kk+Zd!X~===]B!X ͱ3gVuۓb_WӁhYbN:lٲ>%h4ZthqCNE䊊ӧO߰aÔ)SĦH'%%%%%~:%%ECCcܸqNNN2TTT|2111%%]__ܸqƍ;vPٴ3g꒟N[YY^z E(DPWWR.]ZZZŋ -\͛7999`˗>>>ƃҦBCCC~~~nnn~~~^^^nn.: @ Offf柡P(:::Iimmhuuu@[\\ |> 666`Rr§ϠގH@pj 5#mmm4w$xXn MNN^zu``P(.))d Vd2Y__̳Ӄ 0FhjVUUU[[[QQQ[[:366jcQ1L&rAtttmmm}捝ݨQGXNgggegg"bll77AG A}C|YOM֖!Fuuu,+<==fϞݗ!8͛ķo߲X,%%%{{{<I(@hllG7nDDD:tXZZJ֭[_5!/_yF4 !yf[ZZ={Vֶ ,Y:#"" 333" ܹɓ/^ڵkΝHZaDij,82]KK duD(zzz{FFF555zzzBd]18X,777}}O'pNE|>E IDATwb +מZ[[Dh WVV Aeff&&&yץ 899ﴵdee<}8B/++ 8 H]\\\\\!@[KKK^^*8466"B z;677W!at< ͖>ohh?&QFDIII[[hAQс. luj㪪*V[[K.h@y Ah677#X <!r////**BO朜iD"ܴem/7t%_`PD_;a„W^O^ׯ?zϋ/zT]zz7o)ޕeXꫮLj/_S˩T*,,,){ݱcZT=)+c޽{ӦMV)ǫE"""ʖ,Y"ĝF[<~nv^; j-,,d2lkkkcc#edKJJ󳳳8//DIGR"""8PSS3毿ZdhK^x199<۷oP|||WD555wرnݺnа`|ȎmllLMMe8S"| _8:99Q@㜜xŚ aD2t:Fԁ(*髬D)#DZZZښBWl6N֢jZ:^'hkkkkkvtn``@ dG(~֭bG BXXXxzz:::~}-..( *;&&&6"KPkjjĢeT*[WkYYYNNNbϚ5kfϞݩN>~#^^^c?aB 2eddeee&L1cƌ3cCPΟ?p¾d2TMb̚5O<:ujߍ+Da2Xz5T!K_2nܸk׮A]?hȳgY<?wܛ7o=zT4… _z ȢEoinW?~QYY? HQQvCCӧO/Z۩!!!qqqk֬illtvv=zbIIIBBӧNomܸAk׮EFFmO+ _~e׮]}N{A-l=B{yyCִi'`&3#####ݻwϟgn2>3\HE`AAAAAA^^^QQvvvm6ggg9qV:}***7ncǎꜜTpÇ p=z/ 2ҐaCFZZZZZZnmmmKKK]4ET*Ut]Ȱ`̎ %KAA}gA@DŽݼyݻwBrhbb2oy{8@ssE;6X,GGǒ4%$$ɓg?Bpƌb;Μ9N4Æ >HYڻzq;vطo_\Px}ĭ[߿-۞ Ͷ4i-rz-ZZZ8->|{9st)eeeyyy@sY^^. X)UWW A TVV@IIUX[[̿:{,|ܹ>))699y̘1Ͽr ?}$:A988ߺuKO>={l4xIII@|T__乺Frsrr_~GAǎ;~xKKK%梂c >nllDDEEx8F F aKiXl@ וR1a{{莨312F"TUUUUUO2DBL&b577J;A$bNX $$ܹs8peee`L622222244444466@t:^UUU]]]VVV^^^YYY^^^VV &&&@@ > rDde`0D5MMM .fffL&L(@ C@ѣd7iҤ Ξ=NߒիW^,RkQWW?xʕ+͛cǖ#hѢ˗/߹s' __h4ŦM~Y"TWWϛ7/--̙3 ,9rDDDįZRR"+N }9B?p'}BaTT˗sss]x+U!#vܹRKKKooo@ bnkJXb{Νf;!2 ٟ>}RRR2114iҒ%K.]S{{ٳg%'%w s…~!//OW7 QxQDRQ]] gւu&Ɏ Ld MF5%"TWW1X(++C,,,VDGGh4--Z[[ Ml"hii [ZZN6ɓŢ2ٿkʕHoG %8&>n8oooooowww$$&&⩩3gΜ9sĉA QqNNcXSSS1ͱ1 i a,KPz$g0;t`BR(D"Q(2 d]& ,Dg '؈ꉁ,iSSX rWN]wC@F,+;;;+++&&lGBxx@CC\ \w@>xc0 G ZZZ:::0fh:^]]][[[[[t#rm``02]K@FW^]p4/7z_U1D !]!eGccc]bŊI&D28uԦMLfU6m$MtWWB Aϝ;wƍ["W@!2"޸qǏЩdkeeh'''Y#GQF͚5+22Rֶ@F\.h$ȋAeʔ)zzz/_!GT.tD"_}}}===CCC]]݁8MMMՕFEEF@p Q课> 7o hѢ/^tMGGfp8}} DDD \.rƌN{LH& ߾}Ǐ+((x{{M>%AhmmMHHSQQx`gw@.BFܭaEEE x.b3QbIt`X,MAAȔUTT2P*+++d"LPLRKw"miiijjp8p8/fijjjiip8L&0 tӒAps J֯ £geeeee B`iiqP~-[Еl43@5_յ444FlijjA$?P___VVC$X,=***zzz`AL̐HTTڵk@!<qƅ ?DOŲP\t脮dj$ׯ={6==fӦM-#11[,kquu1cƾ}{{l{{{A֬Y^tiل>o 9ӧOQQQT!KСC۷o:u+WV1˖-|ӧOS,pN<9h5(mlllllқ"@(0 老B0 555a(H444/uuut: |\^^ƨ"EEE]]]www5kA=E ĬXA˗/ߺuKw[[fFvpEuNwFѱ[2HKKvڍ7JKKGk׮ɓ'7ѣGKKKcccς-k3!#@d2{i͡a23"444z#cX@ |2Ll6[TY]]8Q&) ŒH$<O$eSQQH$x.%Ǭ ^*))WZt\~E C&tAL@ @X,'U18c h!mmmIډD(x8P3HP(,..IMMCNNNAASRRrpp gX ETb_~y!KK˞֮nkkU檪*:j[QkVVй׋)T*I555p}] LVPPP( ߿okk6mZO#P(lhhŭ_0 -9!`rTcc#Kt*ZWUUuttQRR7nԩSmll&:X`0x<]AA?> hFXN y3g֬Y믿nذᅦN=z4y})JW];UEEE #af?C ruttT\.7,,ɓaaaPWԑwZYY)5CVP+̞={߾}΃ViBBB[[F 7q\-S[UUUUUGm䳏 555uuu V) p9CR$*TTTD(666644q]]]MM нH$)Sm vёaϞ=ZhQKKʕ+̙#!˗/322lmm[[[sܽ{.]ظݍ0Lii"WTT>}AAA|󍋋 =LLLV\rʶk׮m޼yݺuSLY` #Dpax@<P>ߩCYA P("QQEi[[[kk+ͦbRQ62 @,4͢ @ vp&ŔT*}+ith!Z8[Idѭ(@ŋ. #tU#ޱT.eBlG:Ra0Q:8JJJ-[z H/hiiIOOGe:dVVV}H$ bO85@֪p@14`@gYt ! EAAL& 8փZAAf9rdڵ~Wz BDn"`^xC,//'1@%p;FYG:pVżٳgߏܳg]``Yzbq\'|&Me˖)S:2xxzzzzzٳСC[l9y9i$Y%wH$SSӬ,FP4'@(D@ ŋǏ~ aܹiiiW^ 9rJRR|̀WWW*8B丸8[[[}}A"EEEcc㮴X,;VUU޽[n4yd333 \VWW' V&@ d2|Af[, J3f1X,ԴFWuuu, .]4vQFڵѣb0烃~tޝC˚J[v))v-7D.h"I -eɾƘil=,~ש9vN㓐(++G>|-!!vڛ7oΝ; @77EwwwBBBTTԮ]9k.X y⁅!Q>TSږC^ luu/sjH iWyi7ޖF:8mA_H{2A#GP hiYYY555͚5 p8===,'NUU+5***<Ǐ?}TQQA&Hlmm%࣭D"ђ}$#;cA[ɣ-:ԃ;`8LyyyccŢEaaaaaaA/((8Z+p8ӓvI%%e˖-_.=Aׯ_r%//Prrk֭[7n((( zzznnnFFFPԐȂA444llllmmi`D"?~ѣGyyyo#XPPبjժÇSff+W***͛G;h?:= QTT$''7{l'''L[nhhprrz򥨨ř3g/#%ʔA0Vt,''g̙ <11R%%%勚o #C$=ںdɒ@ ڑ?{ be222߷ocF}xJS? D +JB C~~>LPvvU7aQT*UDD{׮]hMF/VWW aZ:::/gZШ۾}{DD &hMN:@~~~aaaAAA~~~~~~'y0`\BWDDD0&F] 0&ڂZV`^pTjiiIƠ.蠭???}kFF::;;}||,X:)o޼Yp… ߿/))T]]-##C&544ϟAZ_hh>C$>|1ggggkkkxS;R IDAT6lػw.AMv0A/3#~x$$ H4ArssAq~~ׯ_;::444@cvp0T;iE}ċ/8qBOO/44TCCc-\^^^tttbbbVV%,MrQQQ7o^zk5k<|N*JRMMMܵbŊD' ݳgOPPA;88ip֬Y޽31sjVofcc{… 0;::f̘A_eݗ.]B^kZII<;;>|Aoo"3k,EEEZ/&"~Y{?~ljjHl˴? f u>~ӦMs̉F{{{{{2ځ@rEEӺzƂ 5vwwwuu?`[[[MMMWW׀N[ACcxc}: xWQGG[LvŴOLRRR`O$W^mllh"F~0? .79%%%PTT,//YJqww/++UP1%--}ĉ#GDEEYYYX𰬬҈@Axgⱸ8Z@)Zq^^(x, 0sL''',:Zylk׮ݾ};#Yʜ9sAJP{{{?f^n*''7''Ν;gΜrI&ρbX,ĉeeeqqq ,033gf< >~m۶ݻw\k׮=|o֭8NWW7''$$'' nݺJ=~844*{-"""gΜY`̳gN>? 8p\VVv{xxz ˂퓢:EFF"sNiӦ555eff^xdee:dee=WdѢEnz HD~J})HD~ /='4ׯ_^vmEE/|}}LD166ߟl;vl_WZ5mڴ'N >|OO?ŒdߚhA: A3l޼9333//&"}]vNǏu: o2;wlݺLa) "--0ཛྷ ,@znkk+`A/&K$_>h*G$FY A>b| &0AZZZ.]"#g vvv>? miiWTTtWW7o|a4kݵkǏ7o|qw) >[nļ~L&dIIIqqqہ> fLTL+< >:QiNf>?AAMH555 8++H$RǠ汬,ڑ۷??[ĉǏF˙[l)**ԧ/*9.PԈʆ{ ٳg qqq횚8\WWwb-QQQׯ_Y}DGGǛ7o& _@1uSJJܹsaxWUUCSS311qUa_BٳgO a0 AUU윜A1L5~FAAr$ 2=gΜlmmm#_|7nprrB;![tivvvppoݺeddvP9DDD`a  &<O+u 62hRRRs177iǪ\򊊊0}ΝH!2e!srrnذa2ۖ-[.]w^[[[__> 0p===iii ׮];y򤢢 |CXee>}:~ ]666;nOrCΎVl?=99yŊ4̓?vԇg]]]jjjjj* lllaaalA333Zk A1({+l2*k+" h"A^lgiްbA;wLLL0ƈ*//;D!UD`0Ϧk0X˙3gTUU7mڄv eff&&& "" Hkk+ځ@TKK 3S9>}sNuߺuŋsmذի#_$ ݹsgV\\ A)nNNN??tYYٺ:4a촲z򥷷3(FMG7nKr6Ǟ={֯_}v .Q OoooWW* ^`a   *OvJp8 ͍7,gbŊ6ի[VVVR(6";222111 AAAWF1ccccc Pccc333 P&jAyL>0 eժUΝSPPeC3gƦz*66VGGs-3BEEEAAׯT*/99YEEnԩk_BB . u,g/X`f_cS^^~ vvvMMwM wtt%nnn2 \\\T*qWWWoooKK H&mmmD" wttV9 ~nnn>>>...O/**)((򃅄899A ''')AAAMgggNN/_@6MMMvCVRRrȑhAAA\srrDEE ggKXXNЙ|VVVAAARRRh`X,{ĉ䄄;;?sܹ8n͚5 hACMM {NUUXICC͛7׮] WZZ۷}||h;F_fJII~9A??iӦ=aaa}ck}JLLtqq{En߾͛O.Z.Zӧ WT215x dDWggga-ڣL&;;;\&"ClNRSS[~=ځ/^X[[cO~MWW7++kH% ǻ]"|K}1yyyw}o߈DɁXa8fPO>p8t*,,HME|)<<<::ի lr9") pBtI($$Jfddv"@(**S^*--fFNǵ Ҳ.--MQQ0(JTTTzzzFFFQQٳΝ J ihhҾ tG>|̼rJZZZEEyH$ѣG[[[,YHAwrrڲeKGGg\\L޵kÇc_{g#)))00QMMmժUՔQƶ~ii͛7SxJâ0AAAءP(㬬Af͚bq8e沄^mmC,R2 O</oڴJ%b5#""`mmmoomgg`]hoooooҒ쬩innnfff``"Blܸɓfff`֭o޼y&9{lrrMBBBټy3  xx;::޽EGGG[[ׯ_sу.\pA#//_ZZz͛7.> ~ iy՟>}ల믿D-^OAAspٲe rɘφ%2{ 13ÇEDD cG[[;))i܆6i y. g v ,$22g͚5h2.SG# >7wYfsY../^477B<AAAD]]]JJ&>Ʀ<==N"Ўtbjoowss痖 !T*H$ cϪԨG<ŵpBz%%%QWW?tPZZLF;4SΛ7XB8ufرc?~>Ǹiy޽ˎ1YޮD6 BRŴμv 3_ i`ӧO, ԕ\rhɂ9œz{{/]Ĝa 766fgg۱XY[[ӂ ލ7Z[[q:`dddnܸ^QQx ׮]F,C@@ɓhBWvܙY^^~}*zAߟ 0U}ۑ򪬬5;] . (}A wڵk資6`H졿y}ADDDrssܹ:uH^nځ@AAMd===NNNƴ%ō333(HYYYAAи޾};x9!"//?Ι3ӳp~D"yjjj`kǎqqqhAC999eeaBBzIJeRSSiw]r%..nhhBB=Jlܸqƌ⺺...999O$qqq8NLLl/^r{{C:]w4,GGGMMM~~~ݻw߽{Qr}}LvZ_KO^^A;x1 ƐbH$]\\TUU7l@;q}UVqpp +*//G$--mTUU!񪪪gΜqqql޼y1q?&\ s]a [n~:ڱod2Y^^@gt20zƈ 4+//gt Ȁu!>|͟??889?!b^))) Ç R-Laff6~gwqnn&^Acc'"r㝝ww{M6vEx???oooAl쉈7333ܣ;CA>?sRSS]Յv AAAGuuu||'%r555mllΟ?_UUvν{444=|psrrrppF*LD"_@@`̙]d333...vvv?~ A]]`lmmG^`{NMN&/_,***--B By9$p1 E[<,-kmm=X}߅ EgΜphv++?zhҥh3a0ggǏ=zTBBpF_CC˗/EEE-,,Μ9C/BJOO(**={ܹsPXXb `m )"".***((G)--`uEx~svv6m-L>}ƒ hpG^ bbbxs=3rŊϟ?={6L;vp7ttt̙3 Aڂ\\\h7ܹ3c_C Ο?f͚C7ϟ?m<O;KHHH\\\RR#&&& =xߣGV\ć &---"""**BlܸGOO _^tIEEԩS#iGVV۷ fժUC:]TT\רT*;;;Q ̜93==866655իW:::?௒A1 eժUΝSPP-AicpרϞ̣D"!B&5=6F   hRi?~ H<<–Yrrr2HLHH^lA(p$ݻw󏷷 <, m۶5klذaΝOx4xFИPC_bX_mmk׮^ZSSaÆ_^TT4F؄F3XLD >}zƌVVVh+Vddd(++!""h (++=t萕ULLLdHAv0mڴ̋/{š H^^^^^^lllnnn=;nUTT$%%`<RSSS]]ΆJ,<<<>|Һum b[n\paÆ hAА-^zj؅YYن27&F"}}}[[[Yd޹<OOO ۷oЎ GDSSs999Dbgg'UmȎL~XX0Kbb ؎C|άYRRR_}vcXFF77ov AAA,3'''++  hjj؀cuuux!zzzݽ{ښ ׬Y3cƌ֭۸-Ztwq|+a@@@^^^tttbbbhhaBݝ###]e˖uIKKA@ 44t۶mo޼H^>Du߾}8;;044|ñXɚ5k֬YxFFFGǏ=<<t M^^9?z<ӧBFWVVv _y匌 A=zt-@WZGVVvyRϞ=zjBB??mtt/ bY555^^^#lJVVǏèw+""2Ȩ333ccc =x ;88*((#""A߿#iӦA6oތ HDDHݴi˗^zu gdd .8pA(+_XZZbc1%%e---:::\\\eee_~zvv6Q(K.AAAD& A1y\SS ??C#>>> 9LTmۚ>}ɉ `RRRnZ~p–-[jX,=qDYYӧOlmm,X`fffmm=y^Jh㳵}͛7=<<\]] -[rʙ3g⾴YXʏw$jkk=z޾hѢx72ր3]]]}}}Tirrr{WXN:prrrx!!!aժU{Ϟ=ȹʕ+ʟ?`= iqg.^斕5c t#   BK}}ii߾}`0ӧO`qJFDD?~СC̬{ȑO.YiSN\|YQQ툆ٳg qqq횚8|;ؔ+V\Ȉ 5 ѣGeee_~u&w͛7DYYy$,Y1rʕ+%%%o޼ H^^nVVH`A0T\\vZcA͉'N:uOc*$$!22rh2:~ Ʀz@ 8qsx=+))wvvG]{ΝW\xou bmmL%%%u*--#G0x Hlݤx/뀲.]t]NNM6Κ5 B---QQjPe1uA ʕ+#]Ȩŋè =Z>}tڵ/_,\dCBB矂A444lllhk?8sLllluu5 xgϞaիW߿YD"-!!cɒ%`eOFG=A\/_pss+**.^x˖-#:+a@AA1SOOO^^9ǠౄH;bBBBh ׯ}!?߽{7===O:5s̜!s֭;vH$GGǠ r&k9dr|円FS0 -#YBBBRRRRR>S$.߹!::ҥK޽={ݻ׮];~A\[[[aao6*m޿lkjj +Wxxx466J0jll\dIGGǏ555gϞ={_hAAA4JJJӎD";;:(u ÂZYYٱcܹcddJKKgϞmll5!/ Hx񢧧' E;Q%&&fdddfffii '0@,&)))999==}ƌrrrhAQgggFFFzzzzzw:;;w###SSStL<NNN#id֭ ~zWPMMh$ &AP?~yfHHBR.\ C;I… eeeIIIsA;Zz5HDల6`hhǏA`ۻw9sddd233SRR1551RNNN#,ɓLqOLLrRRRRRR<ǷTWW7kkkyxxdeeeddD&3jd;nܸJC4a,^)22rDD\\@ t^^^<?ZKBBիWs￷oߎvD?:;;o߾9C   XSwwO@1H>niiADZZz汚`:1466zyy]|YNN.<<|ƍ̿׬Y#--}5''ۚ5kv9w\'N׈`X,{ĉD[[۝;wp8Al߾ VWTT}6######88H$***Ξ={ƌ:::bbbh A@KnnnNNNzzznn.Lϟm``5W`ejjj#lDXX]i0+"Cj~: G7H$Ҷm߿mffv8@p_~gD:::f̘QVVFxe俊︺" =o޼1@CCXF|\;vعs˜Y5k֬y:+++hK,))9d2W^}۷~z8}&Ç{{{o޼->|ߗ.]SUU5޽qF4acǎ]pa.]RVVF;11>X0AAAGT*5??>L&RǠ汨(Bܹsȹs6oތV-[AMM *zA))?FSssgbccf͚efffnn>+|B񙙙o߾}]vvvyy9 3gI3fPUU4<՟?sQQD700;w)SsR?G҈G|||nn.#VVV&%%!$!!!''WYY9XLD t+++{{{ٳX ǯ[˗/F;IwÆ III5ǩ''/^ Zܹs /|}ڵk/_,//WUU500Xp! %LDʺzׯTTT6mڴc!3{{{wu֭7n0R$&w=F@qWuuu v WP]v?~^hBD/>>rԩ߾}XYY$%%UUU50 <?ޫ͛]vܹhG4`"8[ϟo޼Ϗ   XMSSSvv6-MYYs>K pL>|޽{ &$$Çh0ٳÇ7oxW{zzbbb/^ .A%;;;'''''';;D"jiiiiiO6V Jׯ_~-,,,,,mhh@DAAa֬Y:kXܹsBBBoPP8::xA!!!II`"2͛%%%,-,,'O$E&]]]O> ahllɉZ|9 lE[ZZ~ TUUю}200عs?aG d2900áΘP( YYY+V077_| ACzzz|R ~ jy+**$(ˣ/1O[[ׯ_ #",,6}tPV|֬Yh !_vmgg'// =tPKK #߿ÇPryؽ& A(hiiQRRrwwwssC;jll455NIID;Φ7oޜxY>ИXn;;{bb"E%###));w;+P(ijjohhۍ fww7NNN~~~),,k``(iii8ALaaButtrssݻiii , iXPPl2ggg87 S7nKKK۷d   L]]݇hi߾}`0ӧOMhp'O,,,ܱco gVVVNIIlSFEkk[hhʕ+\ :rrrⲳMMMW^mjj*$$vh44 ?_~D_YYYIIϿp4Rŋ6 ''2}FӧLv|\$wƍD"#ܞ?GAT*v &"C Μ9sIUYYlٲޔx drHH*Emq̙3ϟ Ebcc۷ׯ_J@wwwcccyyyDDÇԔ999Armm-)xxx$$$L")))!!!--M,))91SMM mmm377;;;EEEI$OaaaDD#-|qΜ9%%%K#JMIIOJJRUUݸqu`dhtHϟGEEݿA?s޽***hAAAWOOO^^9eAu1IIə3gc,6.AeeeLkk'OjhhB ,YXg$^~moo_[[{ɽ{b0#[񱱱^`0K,F;4o߾kւ{EDDhyJJJSL@5p?մҚf)((?~(++#3fXb~~wZYY ˗3#>>>77e' A٩x)cae˖ <}TFFpֶ}X[[[???8~ "''g֭>>>{A1m۶YVQQ_zB۷oԩ555------mF]]B/***###******++ i^X:cƌ }EO|`0711177B;:  &) RPPrAqyy9Lեcaw@J{{ٳg3͠W^-]ȑ#'OD;D"]tѣʡSi򢣣WXannnjj* vh$mmmNmMMMCCCCCC}}}cccCCCccc]]E)<8s Cx<,IIIIIINh2e H82e՞))) X,d$j`uuu?v rrr_ee%)**;&DdbNYYY˖-׏ o߾uppϷTTTD;"MIIIYYY[n񑔔D;"A} XZZrJ777CCC \SSSQQA4 HVY, Az{{WZ;1ꨭMLLȨ@]]]HHÇѫWfO>MDdz%%%`1ϟwtt̟?PSS.AǴo߾}MYY   srrhilll O:A " ۶mcg^`{`F)))ٵk׳glmm}}}'Giii|||tttFF77%\z{{ARr}}=T%oiɩMMM---`vvvZR?????0`(jmmhkk`>ϸ~~~;eZl)))IIIVA&bccccc,,,,,,,Yz ԩSw)((v ]]]?655.]:yd}}=)))9ūY LD !ӦM355 F;fxⅹITTN6PԘcǎ}jUtHHH8}tff'455юbbb111h2TUUݻwjkkic uuu`+)))---iiiLrdak;x<$==]]]=##cL?}<<zݻY!<o``@$322X0B\r]JJ*$$?@;"466>~8:::%%D"͝;YYYˣ4 B_ _;;;A,@H~`0`'///777HYUZDDD0 g!md^pՕ@ tww# www'D"'Bv" Z==='ߢ O1}VFGNNNlll\\\NNu,X0_DD}wwQT..ЃEUWW777Oa`"21ϭ[vQTT4:?nnnzp8g}:gΜ]v]yyyݹs'11ZbŊ 8p@CCTSS&yr+++aggwiBzkjjʾmEf þrm_AAAII{?+|gS(iըZIPKm˗/_|ݻw4`̜9X2qqqÇg9ܳgҥKٳg/_x< w @ϟǻN9u3g@ G 7oʕ+wQSD[n޽{„ xܽ{7%xMyf7oޤP(W^lix2-,,r|>|ɴ>RRRh2LR) LhhB q%&&={ fǍѣCۻ#~ᇼ<: O<%%W0kkkkkkE"X, gϞ=x`KKK >~{Ź?~Ӈmmm]V-Pmm̱)9:۷ol5q={ 8($$dݺuw;v,޵-w-// P(|ɼz*122`0?ܿhllD=PQ`s߯t nIwJ喍%yhIYVTTDQ-w &SMM͉'zyy=㝍f999y'˖-۸q7tܹsE"zhjjB.r &y;6wӓ`:u RȽCMMͅ BCC߽{7jԨ+Wzzz_nQTTy1rJ"{{{SN]Hwk׮A͜9Nڠie y$?7ܲ>:o/ѣG ٳg]p*بz̙#Fݺu&&& Ollѣ|vg܃TUU/]|lfС)))iiih4mmmKKKssssss ###CC64Nsss333?}ӧT7iaaaooooookkkaa!++w^1;;[9CZZP(TPP߿?t 6ĸo۶mȐ!xW5ƍoMt;w8p)<<X͛+W|QKK͍`7n@z$gggΟ?z-X9o޼6dРA'Nׯ_6mZCCceeLcc6{77"nnn>`0fϞ A^CIIѣG3gΤh˗/_hLƻ:v^:zŋUTT,Y|r}}}W^ٳBO>~z0FѬ?AII +,,dم2OLL,(((,,lhh@[ʒd BRQCe\uVE$ $C-))A$99~Ƿ2ٳg?^ri ~QF$V>}qFQQzL&@QPPUXXX3RKK _uu7o$>F}T*Ύ`رł^.##~v횳s;;ul`0 %%3gz{{>+VX`Qϟ?uTmeff?d-ZhݼrSٳ֭# aW^ݳgOrr??l0 ePðZ.pJKKz) +D"%@hT*Ouuu@RcǖÇT*+L `:"744k"Æ ;|p+#dee!ܒ.nݻw֗X,eX\.իWDhiiijjH$555"H$qQ^^^⒒& њj Bh*.===]]]:n``c@W*,,LNNF㔔J)))KKKkkk;ׇSo >IiӦIKK]Tdff>u~~~˖-6lؒ%K8 #hҘ[[nРA3g/^xܹ3f͟?ѢE&&&x}={ H=Nuʕ .]H'sٳΝ>BZZy׮]p… ׮]>v &ܹCiӦ9\דyfEEŢE.gb… |Ϝ93p@GQQKtrӰrJJJLL ---E)((hhh|6LNRcww:{ihht'%% 80 aX]]$qƜCCoG V]իaaarrr Mquu|r;ﵨ (cV[[ry<^HnQQQjjjIIIaa!ɷ)YYY"NlBMMMFFD"gTTTbJJJVVV ¦k***D"QyyX,.++,//D eeeqkFrrrQZCCJAh^޿/5!!5AJ,㑑'N ^`Am6lߩAXEEESSSSSӯl#E{%}$9પ꺺Ϋ_MMMJJJ]]]JJYؘH$t4yx(sիiii ;;;WWW+++;;;w@|He˖UV)))]wtϞ= vCRRR^^^Ǐ Fhh( 4C PCGGG߸q#$$L&?`?l>`0D"FIQ~~dr6 }աDEqq':;kԩ .s̜9s0LFFڼZ@PSS#TWWrJKED"QVVD"s1@_P[[+iu@t2bǦ="0譄BSvU^^yf__vN-Z~] " rܹYfZXXx{{/|gϞ/^ܼy͛ϟP(&M2eѣ9A'--'N;v,44t>>>SLH$X6'DbeeekD'k.aɒ%x)bcc ƌ3= 'r@BKKkΜ9sů_{}Fkkkggg''ÇF=BaRRӧOxy$<<\,ÇÇYTQQϞ=RQQqtt\l y_YzPxܹEC͝;wmI&?E IDAT@onmm)ΖlL&/aÆo瘝DCCKv>|m%K,^Դf3fXjF?*))IY͟?Ŏ<_0`2;wŋ7oC{xzzjhh\xݡ蹔fΜ=hР7n޼aiiiii믿_L&sBё`L>]WW? .\pׯ###wܹxb???CCC455I$RvvvD|/]HKLLpuu=wE3{l4{yyy||< %ϵ 8pmmmmll.IORRRvvvccݻlmm{wdZƫWڵӧO?wE>AEEx<UPPbx<^~~O8NAA2BAd}}}]]]===:Ntb~Ç ?9s&Ť| a@hyذaBիW#G߿VVV"H_ڞD">|x֬Y0zXGIիW\.0%%AY[[3 ;;;kkk+\ϧO~KKK+j xH$]> zYXX͛o޼yĉ!C]TUSS&m۶5k֠f}x0''/'초6I Gʶ'"Yﴴ7n]Hმ۠AL&@{ۣ(|ܹ\XaL6555555iN'&kjj0 #666Ktߜð{x233嗫W:99=x`ԨQxW@kh4gkkk\nVVr˗/FAAACCFSԦ p~P555 Ђ"''V7(,鈌acBB7wUXXJJJBBB;VYY}3zߣ1y~DWWM6Y[[+ַnݚ4iו+W6o|ȑÇ] hcc?~QQQǎ?~|`2L&3$$DKK͍`?z@7䔙y7޽{Æ ˖-j%t͛7r"aZ>5 @xylllll,ޅtqFEE)))]+,--QcE N`(r_~񊊊6ҪOUUUMMH$H$I4JMM %ZXVV&ykԴðʊ&%+++і N <ե&&&9>ׯ_ǻRXXe˖3gӧO O!d2yРA-*..p8yyyhGnn.~yNN$觡!Z-88xӦMBW& XfQS(^MÆ +**411Ү\H$܂?~\^^~͚5VRWWǻ(USSk9FR (ʐ!C<<|xС...xax+++++pܤׯD" T*F3J522#JKK,YrM555yy#G] zzzuDF'('$$|%b0 eSSS/]:k,' \cccJJJq^^P($VVV F&++kBGDDٳ{S1^@S'NLIIٹsnffwQ=_^^dɒKzxxL<].#i&__ߣG۷/,,lѢEj-gtHv_eee#2]vxaJKKǏ/''tseξUeeeWWW@r)YAAGzw… wcc˗7oޜeMMM'qfĤVUUm%dILDUU?INN9s&͖|.E?l60===>aܲ#2@MHH?v/-->`Xݻw9sFGG'((hŊx3PÇ߿///0j(lffDgq8;v9sFWW7227E??丸8؋)))̜9߰a u>>>>>>\z#>}z =Hܴiӊ+N<pŋ/T*&heXm"KII#2aVDVhh(L1cޅtQF?~RȠRSSûѣGUTT-Zw!)11q͚5 3f71 999F욮ohh`Xyyyyyyh!)))//IKKi4ZwO$m۶MKKzҥnc(((0LGG'9904~McԬaÆ_ŢP(&VAA={"""(JhhO?]?~Ď_zr1 SRR4hU1q۷9sFKKȑ# ,}:N8qU[[[knРAϞ= ۼyKN8aoowQWMM̓Lm֬Y5k5@ߥO?9sfݺuׯWQQFdddX,ְariiieetD0LAAYG䆆6iwAd:XEE7o  )SA ejjj"""-ZkOgeeEGG|rxW@_!''N\.777EsrrsrrI @022%mjj… _~^]]Μ9>/%%~oDI1 :thXXXMM͗mv}H;wnڴi?h ðR bqeeP(jhht@b1ju222$J999eeeyyyEEE@ 啕TTT I$ Zn^kt0B$E"Q;͎ƒаI~kTl;5rD" mEto bjӇ|%㌌ziii kkk;;;kkk&)4ɓ$F1 x Lw-yxx,[lذa}0թ<<<<<<A\\\TTTxx;<<<ݝCC~kFDD;vl֭>>>={X6H$#r rcc#tD|ٳgbޅt3f[)..uuuJr١O__B$&&~)ӧ vrĉ;w ;vXlx<ry<|>痔Hzab&%%,K&UUUQfW`XUQQA8~/AY4ԶJZ8WTT>}dDAAA]]]GGFd===2Ltttح***$]:Vʪ*@hA iG/h(E!T555*aJbMՋetqK-Y A \.WFo_PGg4 H${@ /kEEE@ AaaaNN 222vvvVVVvvv45=*++e˖+V6&M7nܡC`9rÆ ƍc0GEW;Aqvvvvvd2!!!'Nd0ƍŔ6mo>??Çر`]WWhg:"7hHw̼uޅtUV]v2d޵#Gׯ_B%99yʕ K,ٹsgwc h dkkkkktP(dXM'?yٳWf_USS^}Mv+..FGZ[^5vppHLL~ANNEgD"ɓ'w]VVy+W@l1eXhbH:AQ(FP]\\d24|@;$egh2/_,(((,,lJRt>N300nt (//<,++D˕aUUUMMMyy@ RtB7H M P8^Pkۦ [?񔔔!+۬|6;`4&hʪmo"(%%(**L*DB***꒧H$Զh:rX,޼yo>ȗM4IWW… },2|ܹٳg/_zݿ=DOgmmmmm}(&驨8zh1eʔ}FCC# `Ŋw={vPPP`` t:ɓ'm~9Dj}YrNm;@F]H{mݺ5""իGƻ@RVVUVv|>Æ .\7n\JJ 45222223fLYYYiii gϞEV )%6uJJJZt7oSVVVlMMMJDn@qȐ!O~bygۑ֮]ŋYfٳHOOG?sssa4pԨQd2Bd::::::_٦삂S^^rss򊊊ft:NsLLL M)_QSSSVVVZZZZZ>D+**$iclm400q'H=ج,: _(!ǏkhhTTTHZzKP?f4-U^^Q>UTTH$+Duuu5554-4}߀窬2('1oٲɓϟ?YDRSSSSS%㴴4.aN7111229r$JBħ }ꜜ&Ο?rrr\2&[YY}iW Ei2nЬ獼|DK+DB=\QpUQQ)2N]ZZZm(u"̔5&G IDATn9NIf ZZZڽ> zڐ uF!eqqq?hx%%Yfy{{[YYm۶mÆ 0ktu:::2iӦy%t ޺ukڵ۶m[zuL|~}}}!H7K1"",,|ĉx.6m:|05zAAA=BBBŠ+RRR~嗍7fPÇKֈD찰'Oؘ`0oaaѿws*4fꢕ(r!CHII|oiii ]p{޻wp8޽{w޽{I~G^]fmQɓ'OFmll `ccccccmmG|ꢢIԸKfTQ UIP[[KD@7A"l?KCO'zɍg deeC&%)d_=@  \z뵵.>|ԩSL&sx#[[ۄ͛7_|ĉONU\\uƍ_u#F:u)S$WtOOω'=zt۶mǎۻw/Fb1622jˉDb+*7 "f{"1ϟ?в[r˗^zx苂z\;䊊-[=zٳgxWxEDD(**~Ç>} 0` 0]fRVV.//&>Dׯߋ/Z ùslR^^g___ ŋ/^$'''''cmccj*+++(2teAAJ>!!!22R HKK ]$%$$07yxx0 '''IJ;wnժUV]|]0L7DP[ո%"XQQњ-vDF=#239pž{:55uҤI#G _8|0a~~~xZiiiK.}ʕ+wܩwE@$_,mPQ*joo4G&) \NZZL&7m]]gGV|  hK@Rt>N,[ZD"˗?}4s7nXXX]TWCBB$ cȑ6l7nܼy󂂂.qvvvvv>tЛ7oϐ:>e80dȐ'On߾֭[ǎsssûh "#B a!tr@0ٳggdd]K[x~Bٹkff0+VF+mllZnSgg第 옘cv111˗//((صk׊+64%cbbbbb^~- 0jԨ#F888|_" ?|ٳ766DJƻ@ܼ<2eXyyyF2:::Mv:::3 @hll|>???KfIn (t&''GN :tgݺu]?;>|xCCӧOa$hw۷ rd2)))ZZZnnn c0tjժWOAAAxW.#F0`@XXX^{Y___ɽѯ1caW\0 ,2dȋ/ZZZ7oĻrqqƻ@x|@ ]7<'--m֭HOO߽{ +++lٲf͚ ZVV>{J#Fط@AdYYٳgϦKVrܒWWW^ti钕 "Waa+Liӂuuu⨨7n?G5j \X.%%Ç=*))2eȑ#t׫LMMMOOOKKKOOOMMxY%%%CCC.+(([9tgPdPG^^A} MMMLMMt:ޅ6B䠠 >~z}}}‡H$LLL|ٗf'5jjjvy;vꞲLf||ĉ. z{ҥ>>>xvsέ}v^{ӧѼy󪪪P0==lIIIm 2하QF]w &MzӧO[{[[SNuuuuv 2dHxxp8;w:uBP~/^ᅬ?ן4igwPǏ=B+-,,>++)SK̚5ƍXXݸqW$ ۷o{%K\|966Z}ILLƻ/z… lvPPWw.Mx4MSScƌٻw!CPɓ;wDEE͘1ãC.[YY͜9***ZijjZVVVXX̝ٗ;쯿122Zh֭[_O{Zŋs I@_vѣqqq'N2e ޥ+;;͛7n܈5kq- YIIIRR۷o߾}Ç: PʭiH$]/]i.9555;;0--zw!!!AAAUUUVZnLƻ(/_ٳ0t Pyfss'N <gݹsd޻wOFFcp`ɓ%K۷[JJ Ohh;tC?蘛-[ ø\.F311L3;@v),,?|ҥKm߾֭['NĻ@1cܻwoܸqxy֭;qCCCÇ:::]aXFFj%]]]oo%K% "_vs׮]hQuu5Kfì~=zhȑΝ;fn]&777<<ɓ?q ψ{{{__Yf)**]hN(1 cgkkkkkkiioff' ܴd4RVVQ@z555G k׮]b6Eڵk3fطoBYYY˗/oN |^iiiTTTtt;w#`0m?y***,,,<Yɀaô7oތRt:]H6̌罽q]v jxΎ/,,$Hƍ7n7x~h`cuʕ:6~okk DDD.233 O~ "w0_ݻ֭/_ccӧOUkk8x3gᝨkX1q\\awdeIII#Fj\A {fee)))ᝥRSSNjkk{%60|477㝥KrJ qUUU=zatttAAȑ#8 ؛ۛ7o' ~R\\͛ϟ{NkkkϜ9`|2}}}i4]\\R))Om~zkccw*V\\nmmmeeehhϞ=[l77իW'Ow.1ի="FH7ntϑ{XXǏBt:H$ \@ϝ={dpU!ϙ3gϟ/|:t ܼsN]]]EEŌ B0QVV;ҥK NquuDU!nnncǎY׷sppTVVrpp0&H$&Kiii rssMփWC2`8ԼsNppp||qܭ[B.\hllĞApss[.++?---;T[[ojj*&&t҂ׯ_WTTܽ{w˖-FFFP< F`ac{5/Bal`;wÇ~q##SJHH,_<::^lBPnjk׮;v@r[\\\wޅ*d0lmm,--sssNaddt—/_Z[[ߺukԩqqq!bjj6uTwwww"fH$XQQQ%555]Ԅ&T*[dgP @egg?~xڵx醪* 117o2/ V]]o߾KKKedd?><<*yy Çgdd8;;sss܎9aÆ3gtkAiiiNNNֶw;JNNFrqq,vτ477{nٲei`7f͚8qbzzEeCa=NFaܸq9h4ڸq% IDAT#+߽{䤪:rHyyykk넄xkk;$%%-ZTQQҲi&]]`PMM͛nj#//?[v I$Ῐ,hԩo߾1cFFFqE||Kz鑑FFF'O,** Y`Pfg3f̐133¦t???ccc111111cc GTw@,Oۭ[F٨TJ6M:Icin:^^^E%&&2e 3dB,*so>J2|AO.[x(QVV޲eKlllvv ㏯_1J/_l(Ak޿*fBB7oNGCC[lɱ*^`s5***++K__ŋx'%))gGvCG 薆wwwt; [[[k׮fQKKKPPBh„ jjj!==7n vcܹs,?| +V0`Wjkk3Y*++ !+AAA^%fY^^ޤIoܸ10[v#"~GݿY`3TWW˷}hݺuVf͚EtzIIɨQBAAA!qqr3E ^xѭ]vqqi׏M\z5cd{{KEFF`_j7.#I&30C6`ضmے/?k,`ii809߼y:}hٳgw|‚Fa˶{u:ˤd>e[F֯_n]f4v7"]]]];>-vǖM0ٳ7%|/<&;|AO.b|1cprr۷DǏ;v=zcc#މؚDb/#H7n;o߾8qА@ 9*((.Ύˋ"[Yj̙3{Iol~~~ŒD"g[d[Zz&(( :z(GDDANӗ-[&{222XGPNUUթSTUUD.{ԩS޽KRN׏H$۷YyʕmӫD3YJ`61 nݺ% 0iҤfiِobccg̘211f8pBHJJĉmWr-ӧSRRʮ^ba_|!܌t:޽{;wDwk;X\?UUׯ~Ce˖nEb2GwXm֭[Bd...L޸q;KAAAUUU(An+00!4rȵkFGGS(ϟg"ܒ6mڄX-iaw_K=h4:&..QXXح̽i3It46իkkknݺehhvǖD__!̽(ӎ+g '[xkiiiһlu;ݻ.7mjjXPPC[_<2=~}~L2/t?L;L\Vvgoa"--M\\l6ϺUVqss {yywV>>..ÇpPჶvDEEɘ-**Z^^:{uetF>}qL2&yr)))...^bleܹ"""LjwlY0a0gW$s/?J2irA&O.+;ճKccϟO6 ,*11#22RIIi׮]xRRR͛wE8Y\\իݱN@YYYxx۷D Caa]FFFPPva';e˖Q({1͛T*藕-(( >~~~ 9//o޼y'O; ͛7_~|cܻw'ɯ^?~,##WZZ*))}~XS)GwXSիW CG"4kB.؛7deMz[MO0!>>>;;;<<ٳg/^ڻw/XIi9]߾0b/.dGv6enn< OOx ׯϟ?*YoaaUȀ%''?~|ǎ7oWWW;-qqqgggggʻw޼yyժUseRg򤥥ccc׮];۷:tJII!6N,s`;ݡt '244tժUxZCCüy󄅅G;vX| ں}v[[۹s}ee˗/ˏ1"55uo߾MHHXx0BpqqݺuK\\SAFF1۩B䲲2nm/66VKKÇP dff❢(**"߿Ϙrݶ3`1ێ;<ڵadr ¤I7o=POԠU?W]rĠ DOO!n~bǻ6_ܴm?~jvc+/^M {QV^xX|v䲲S-<Μ9>P2F{È# 7FFmbb D"pЂ YAA!gY涵`! e0N>>ZZZ%ƍR D"1&&vvv!gg第t+6s]8p9VtɔavvZEEE*wuZvĉ>>V(2HLowfe˗3fXr%Y#sss6''gKKࠡqR;;ٳgƍ|N;wnDDDt:>IJJi(+o7nxԩv ߃Fƙ̽o3{ߤ3ouMwǖ-[=vc oW؇%]~`黩?]TC^ff߿}Çx{{.Zh˖-8$8xQT;;ĸ8eeeC%%%۷orʒ%K!ɓ'aaaݫRWWwppXd7szv&^7o޼y.xibM:X˺߿w" ӦM{56pۺ~… .]wggǏgdd T`s/C[eee]]]]]]mm-BƦWUUcuuuXYpUUFc#F`/cV I$Ra-g&a{Fai:f!!!^^^^^^~~~AAA^^ޑ#G cӅ~UZZz+W}VDDDFF&//B\ёǏ[ZZzyyu'\VV ++񓏟ƍ/^|_ %NNNw_liiYz˗O8nݺZ- I'N׿sί.vx-DFoܸ1&&FPPKxyypssM>}ٲe&LXpk Ⱉt۷o.\<0F+W444͛փߖ;vDDD̘1GTT^aVد>~8m45VL$yxx/^lbb]+T*رcvss[f G tz```ppϟ gΜ=gҥKWZնfۇRSSsttܼysvS8"DnҙL?|pϟ)++;88&u\/^"x}¾(ae^.dvS={ a)))ׯ_WQQ 022;QP('N8sLMM"ޡ5kքDEEO"22rݺu^^^˖-;GT*5!!!,,ڵkeeeXEETTT8󧵵uFFƽ{ 񊡧7mڴ#Gtw :泽}VOOQy}ťPZZZQQl2SC޽;""‚1Jxxȑ{.Z#:ӧQF]|O644888<<,,&&L(`(rppPUU})\]x9vu%~q __ߺ: KZZZe UV\\|MM͝;w:::bcy 1ŋDM֮]K&5۷#<>oذ%M6]|yxg%%%9;;iϞ=[ne>t 7n())*J?w`ڵ111]ٳgfͪdϟSSSǏ={'O>|lY!6˗/sss/Z dggϏpw d2NڵkYpp0EMMMaaaIIIaaaqqqQQQIIKk IDAT -13???L*PG-**ڱ~fgZέb$vSSc#G2^-RRR222d2522ƍó>?CW(JlllTTTtttzz͛gϞ=T{Ç]F&===` sss*d0$ikk'$$ڵի&M;_FFFFFFǏ*ϝ;o>"~SWW;#/EE5k|=XP@@!T]]ͼy҃--8Kts~7v>;zꬬ7o~Q bŊ{xxl߾~o?t:۷oEEE߿/..Ɗ 899d2V$bddԱ[wxz3f jjj˱.˱III%%%匙 WCCCNNުU/^ 5U}zzzWvB(##CNNNHHm!rmm-B人 wwwJuuuYYYoF#633z`5j˗7oެcT||?~lmm} Yׯ_~}EEţGϝ; hhh3h4ڧO^z۬,ĉN8allɉw͛mdd0bйϟ?ٳgp Gcƌ3fB"!!!!!!))WFXXQ=gCCsA`ppp055ݹs߀Aۊ䤤+W888Bb%$$(˗I(pF*))n!rzDNBd\F @*++G~ztׯVVV.]; ҥK_~6gi4_￟9scQUUUVVVfffFFFֿcAAAee}B12Y4'&&U'3^mN>ȑ#߿}vzz:Ǐٳuv &%%|gԶmܺӧO3fPWW9rd/@Gqqq#G\tR6Q]]+W<|PJJIZZ\***ejʚjjjwoee!S& ^YYY驩%%%QFiiiiKCCcX]z֭#Gˢ\ii!L~)SllիKJJ7lg?~ Ζ;w.T$/_ZZZΙ3'$$d~---xiӺ F#H7n[V[VVV!g2 #2] !K.;H'jkkmmmrB_|ݻw{sFJNNNNNNJJbNɩ:k֬ua5d2/췱[[[L4?22{H1chiiikkkiiiii DzfMM͸qh4Zqq1Q B; 7n\ۑdee &&&cǎ>ˋWLLʕ+'O7o \pQVVv(*:k֬psss.Μ9Qښu](..] Z4LϬ6z+vve^ T[[{ooB{{{'jwÖ<==o~MuuuC|hhhhhhcW^񑕕d1u;w̝;w/_S]H$A `Źsٰh" 38`+T*uӦMg϶m͝;ٳg1-- +;NNNNMM#H&LشiرcW0$IYYYYYX)))N;BHJJJ_cƌngdddV\]PPO "$$P(T*wݻw(++߭ML^^>""; ?~`CUTT?> k„ -RVV;&TjRRRtt_zmffv9++QFD7nܸq-ZMimmK^xJ$222222Rccc~~~AAAAAA^^vvvv?BJJJJJJ7nĚa~m6&//.\ڰaڵk&[[W^<𸻻ϟ?YKKk:]E}||ddd͛0ew 0͜9+WtRiXT_]]|D$7tiiigΜ;H'8ɓ/^ pBzzzHHHo(66vd29!!a̘1A!''իWiiiT*uԨQ&L_jՄ 444" ˘?%%ÇGmnn7^\]]߿AUUUvlmիVj;QVV666DFF v0B$i4Zkk+6F!Z[[߽{[wwwNNNYYY}}};;YfA NOOO~B433sss={67$H$mΜ9---msrrbbbBGƊe%'''++ mEKJJcƍF_d2YVVV^^~ƌVj%$$MΆ^~}knn344믿L2a8@?~WXJ׫,,,DEEۗ׭[gjj***WPUUBN9$$յJdeeɳv٭ϟ?bGSUUUCCyH$Rkk]jjj[ZZQd)) *7o޼~?~lmm4iCOOhKʔsssɘMXXXRRQK&%%%{hFFFFFF\! TjYYVg\ZZ߿466bsH$qqqIIIIIIUUYfJIIgkw566>}:==˗l؁ѐ:yd$%%"""֭[Qfcc#""baa0gNNN3N ))u$$$z#ruuuqss-DF1 &\zJ._ QUUeggwh/--ٳ7*BYpallիWi+쩶ٳg>|Q~~>L={vPP1Tu>̙ DR?~ٳG---&M2777773//OKKdʕ/_#!ԶF9rdd2zdeetzAA2͵:::C2̬edd`'^R㎋D~1SSSlB!))͛7o޼~ÇBRRRǏ0av"` iiiiii鵵ZZZFFF7oSUU>1rHuuNnjjn/##֍1BTTTTTL&KLLL\\\ &/TqqO>}ߟNL>.t_2F9r$Żn++d8|ڋ/z{{ϟ?… x..\uV__+emmmllgϞ+W^~̙3rrrxlDQQ599͛7o޼|Ô)Sa… K.߸qclL&gdd`􈌝b="t:`_W^;{ݻw$$$͜96!!k̴ill{Vl ܾ};44T*UOO\[[0EGGc|MTTvɒ%SN嘘"IIɟ? }VOO!ebbakk'UUv)-->}Eӗ-[oJSNNNNNǏ?}TYYPUUPTTTTTTWWИ1cFJJJ5 ''iooeKEEś7oRRRRRRҲZ[[999UUUǏCLee/_e!qaϾvxt:Qܱm6D"mQrےeaaaaaa++?~0'ƍ6mь3;J FR^^^VVwkNqSN6mRPP;0rUV_, ^rvv۞={nw"n޼giii{{~mp={vݺuNNN}Gwwe˖Q({1MQQq;v@>}ѣnnn=~{~ӧO߿?tAСC=*dyE?U͟?_ZZٳg222 Bcbb޽KR̙sҥٳg5 hveP\7bAw}!q޽@%KZj ]5jԼyBBBF{Μ9BXgrm4::pkܹU]9 srr $%%:cii鎥ƍKOOR]"(&&+W8vӧOX)))'O,))Aqpp*+++)))KAAF |Uc7***B$IIIiX񱼼< @ d2y񿚧m`J?}ĸh0ۢX7T*5%%%==LJ@ HKKO4iҤIPY@%mˋ`m7Ob%%%QQQqqq117wN߿'::ZEEsѢE8իWݻ)S$%%?~ڵkΝ4iޡ;ڷo_^^^xxxXX؉'DDD,,,aZvmffY߮L&:_`q! yh~… cƌ5kA_dd={N>m``whvݺu+V:uj˗/ 9rdl}4449s&33SGGرcP zf8cǎ;vw;RRRvNokkٳH$h4F׆/_t=?Ն_geeշCLmmmff&8''ׯ !;vXEEťK2:<feT*knnn=ͭ͘ҮÇ7n`԰JJJIKKHKKCkmm-../(((,,+(((((/F5#T!KJJJJJl555+WbX2NAo?B|||||||||BBBm~~~AAA66'v!BTWWcw1vuuuUUvzЅGvJ0jhh|3gRRRLMM---VÇ,Yw^0prrر~͚5SLqqq)ɹ~a҃ IDAT޽{aaa666vvvK.2e |KJJϟ˾C[BBJHH[ DWA?;!MXlʲ8P:@ъU+Ȑ:Z'VQqŭԊ( V I !qzy z93@ЇlAd&CCC;Ow_~eڴii&}umذaڵQQQ{ݶm[yyiNtm" 6a@077 jl_bjkk;;;;;;K/d[XX7o,))eP D222244422"HT*UKK b1d0L&`hB:ғd2GabbbaaѫW/KKMƘL&F+)))--ht:RIIKmBĉ.]jjj*HTUU@$J=()P^^^KKK[[յTTT2PQQA+hiihkk/r\nmm-555|>J|x1xFICC]Nj!Օݻ? |x޽{aaa%%%g^x+tee 6 >< &&BȺ/xo߾]|90__ߤ$---<|\L&sҤIÆ N@WAsrrPc9.))AZH}oO>g^nI;D$[SS𖔔N #;;d,a BDtOkkkwIɐP(doQژ`4d)JZAQ__Oӥc|HMMMM*jddLR333O8xb8 `hak_x<3+Ar\WUUUSSd2kkkkjj|>mrA[[[MMMEEEGGGUUUEE& (60 m%// a555( \___SS# 9X,0 D\.W x:>_[[69ҷrrrXUUU7H$%xCq_Odggo۶jjj!!!n/##wx=zҥǏ?pu֭[xܹ={L0C^^Ӟiii/ "3޽{jC--vDƃJJJKwQrз &M4f߻wOօ9s$''gddЎ***yyvmuuI?~…#G;X~7޽}P__t˗/EFFq8˗/DyI_x'Owʕnnn )VZ\YY9lذ={Ŋ|H۸q#IWbٳ鹹FFF..._W+g2}}}7mڄ_x٘ggg[[[\rĉhfNɏZ ie µk^v-'']G竏͛7ݻrkkk__˗waOo޼QSSLaٺwވ#LMMCBBn*''W]]]\\#K.5#GZZZV@)| BR---Bo|ŋ)**Ʌ,\¢ΰ<)GɆUVVJ$NNMMMYYe1 'PBTzaޡ/j!4 OÝ؉I_1p8BMD"  _hXz%' c7>b3=jjj_j魪*=zse-6@}vȐ!LJJKh_7n \n?hHNHH355駟 $>~:hР/˓H$RVV>sL@@@6tPPPk0 ;zܹs\2vXYM8ԩSSSS߮dرׯ_ׯ_;ӧOOJJu9m^ه!Byy7^p_ڵko}+ҁ㜜:a@077RTY+**PWQm2׋'ECy Qx O}5C:9 EB[MMͯV@w" FII N/**BMY"###*d2UgϞL****))-Y$44NmV:*0bh[]H[.UE_~QXzBt]6^^|>?!!᯿߿… ܧFO~~Anܸ.VGGGG%Ϝ9333???OOOY?~[F466СCl6oΝvF9B$''C DUUu˖-_hc*vvvvvv֭CSNٳY4h޽{͛gee.}d2hV:i~B%%%ua^eH888̞=[UH$ Ͷ5jH$u-д%K镗>߿3dȐvm%%XYM x4ŋ&899<}J[.]]ƗDWqrrjr[ 0 ZpÇ[~W;vh»`vΝ drKNm/6Y䞛yd9#F`vQá%6H$:t iH$޽Cihh1\RAA!'';\b>jiiEDDsQx<ދ/Ο?lٲ'Nx'`0_~ر 6K+VSS4hP``… O:ü<1 B~ϟ?w4&-[ S^^ngggkkbd] =@ իݻwe]ڲ,--1 jw~WMMoըQ̙ڭ={aXaaaM4iʔ)hM>-UvVr6F{ꕣǏe~͖D"{˗/ e[ 4)++9&&&<_*۷ FykR(24,Z0L]]Y9|plll^^WddرcG6lXUUǏ  n޼?ڵǵ݌X,NKKKLLpF:u* u0Ϟ=SWW]+WjwھybMo RSS===Rp_{mlldB0,::֭[>2sH$}7o^{sݺu6lXvmTTJ+v{4Ĥ{QP>0Ktڵkb͛MLL444 BRÇK.<|ҥKQQQoʛmK~aoOZ~IW}idpx0Z]j'PRRҩiii4[[[ X,"juuuk V\YXX)d@'T]]srr>~ׯ [YY+***$KKKJKKt:zR0LEEeT-M:3H!L?~\(gϞENfܸqL&ѣGB_~ʕ+[n uQ"vB丸ףDr`` jߏo>|[vE&߿ڭ0 p8ͯT]]OcV__;y՝:uj….{ъ+n*ZigΜyIjjjŋ/޳g϶mۖ,Y;*zo߾ ]]]Yqrss1 BKի-Bӗ/_ e6˗mW^K.]tiffӍ7jW^_N\zU{h/@ rrvGO'xΝGΚ5NC< --m/ѡR,l޼yر_ݡ6W^9v;srr1 f vvv>>>xc"(SxeeeeeeL&dt4KјL&5VVV&FFFd2iܸqhȈBEzz={.^p}Zn„ <d]=ϠAf͚uܹX333YƉӧK5-ssӧO?}mD*++kV!/QRR?C͘ @wvڵ_~Eeh~3DI\.wӧOwssϘ1 ߾.d+W\rd]K1b}v:?{ݻ\3::ZAA Jk. ~駯„ /_l-n&&&EEE}+̌VTT0aBRRΝ;v-ϝg/=_9ٰ;wl}*)) B999 KKKgg3f̱ tmXBdt<[ϖr\|e]]]2lhhhdd?P(2L"H$ O@ߟvĉI&c O>MIIi~j{nooﰰ0͛7Y62A,{{{!##):ŋ9rݻwR7''ի= |RRɓWZ~V

|Zݾ}{,+##S5t?x8''Ǐ#? ɱ?#bFi4F,GkDR) %ҳ=އ񺺺iӦ:99ɺ(D~ȑѣG˺xQQQ111 8x`߾}e]VD"J$'$$X,H j ruuuO>mۧRw5jnZZZ1117c^xqZZZzz:a>|vwwOMMmC2'NL6Melܸ?HMMuqqaЌtOOσ߸+&`0nݺegg.uE?O\\iֆVbիWO:u];; &,\@ Xlٕ+W8ΰabEFF޿_[[wͨm!˗/9^zyxx )++ :4((WJjjibqJJ>}zΜ9?VWWoUVFFF{NIIv횏Ovvm3|СCe0 \۷ol6a666xz`@' `0X,Ţh `xwCCC2LP j IDATTjY4$|ɓ'222.\"-\p߾}gϞ8qk4Ç;vWݡDr||s8?Juin"??),,lӦMm͛7{]޽[o5Ί+n߾aXaa?ކ:;'"?7n2Ν;cƌٻwohhj }}olQF߾}ܼ _^^b 6f2, MXϟ?([L"H$vq{tW8~xuu3e]hȸ$oooYhD"_xݻ'O,@TWWw˗/s\Hxѣ!!!󏃃Cke2$C iՆ}6mښ5kY'**~ a4Ȩ^jmÇ˪:uiY_jժ,kkoOVV֘1cd7H$R{ץkٲe=9uȣFZjCvvvBBkjjΞ=;eY:;Θ1>$ BYpe4''A(XYY%$$`vС&) ߿lǟ>}cee%a{:tibtfl4E9992L$D"JP(H϶mJI$Wٳ޽{͛5kB***VXqСq۷Tn߹s'11ҥK555(|mE"rBB6m۶fٸqɓ'0 cX999-ӂ 2իW?I"ˋFxF9tZ/ްa÷'--mIIIU^70s7oވD##ٳg7f~vsxzz>|t~w hϟ|jD"1 tst-?@L&d|>DUUU__u,DBp>}:66͛7 0adɒݻw'$$@wUYfɒ%tgx"9))?00;ӧOn߾=rnK"֬YѪzfپ}} 0 ѱCk+d]E|| ؼyszzzjjj aᦦVܼysާNRVVnں--W^HTRRvZ77Yf۷o%ӧϸqV\yRȠSgϞ߿_5mڴNޗ͛ ˹s:uRMM -WSS [jUO߾}ٳv߃H$\.|WWW'~MMM}}}h0Hёjn02loooggAtZƷ, Ms\魴 T KeuR@yCΞ=+''?9s^EX~ݻO> )d!Cdffnݺ5**ܹs0`ݖOeeKΟ?t˗{{{?:[Z{ڵm"XnY]]:JJJ]+-Puf 0Phllu ===Ԑ(1JRh"P:pC1 #ҁc[[[**=H$b0PTPT B$=@VD"/_5gΜYfȺ.MnݺrC͞=[ֵD"_hʞ={&N(@b.\p'OhjjN0aԩ#FPPPuiNܹsjʔ)b811U[}ͬsȑ _<Ѫuf aةS  D" RTT]ZZ:bĈSNA a޽+W""+v]nPPPPPPii Dqǎ i ,8zի[j[DEDf@݃W+>>m``~ⅇǚ5kZL ܹs $ɢE{QaQWWd2KKKqYYNg0h!XIIőbq{&BgԔH$zMjj;JJJB!@P(}0` MDN>իǏ)**:99wd2yժU/ wwwyy'O1HܲeKhhkZZ"""޽; NaXJJȑ#?۱՝1w梉BX BffffffϢݱ삂GBSQQݻw>}tg|₂qNNN0@ [ZZcKKKSSS%KR`q@gPTTtرǏ899͝;wڴiZZZ ܹs]nݺuqqqβP.]:y˗/&MȔo߶|بfwڵsfֹt҄ rrr_/w!D5jJrrr:99ܹs4tcѫWիWk 3gLJJx񢷷(# Nh46&Y>TPP055P(T*BD4nuuuUTT$իW׮]իf  ƍ]E''''oVRRӳ8pe@3D"Q^^^VV ~0sssGGGWWW777UUUYW^WXX(^~;2330 ϟP(4Mziiihec:t(99ϯVEE#O; /_|ׯ+++1 SWWGV|0339s؋/ ŋ_3s…)SյʡC-[VQQ:ׯ_7nA1LuAt%%%fff |耀Ǐ~а -oooGEEv[@0cƌk׮]zuȐ!ߣF1Fc0QcCCC BPH$JEI$B!|/.Μ9}ÇI$5XMIII(-Zӳ%߿ͬ 'a'O611hÇyyyyyy999 }qtttHu?\TT#|Izzy%EEEfff7ncXzzz<((H >|ÇvܹiӦ5~&Dϟ?G㬬,_NGGG[[[SSSxz޿%%%H$Hvtttss322Gnrl6a***ҁc4˛70nNKx@w%H޽wUPO?>ztKgΜ ZfktDrСKƎ3F%O:t(hU/!!a̙Mj0<^uu@ hQ:9"nǎ6mĉfͺqѣ;Zcǎ-,,DC\]]Ç^:hРT^2۸T:y\[[VSUU566&T*L&d9622244444lCȑ#[l)++ XhѣGQNQQ0PH /^s8mm_}hyyyhtx555MŠG)..3ǹyyyoƦw=gMM Dںukxx8Ŏϟ8C0#Gς KKKlٲpB|cϟߝN\gRSSSSS>}Z]]ҷo_<گ_?vm0L4D"133tss4h|kw+Hǟ>}*** rrrҁcKKKSSV]lB!ar8&gi|:::D"QGGM7?!S@'QTTtфeعsOr 6Ⱥ@˖-믿H$+=X,NKKKLLܑuvv;w]:Z/^Da&|ĉ={v-T^3D"Qaa!FӧO,ެK]]̌JR(ҒBoʰȑ#7of2sY| %H<OQQE]]iƭ=@ .(( n\VV{e~~P(0ݢ 33 .((/((@0LSSmllPpkkkTӧOhhI``{233T*ڕ+W|}}={6p@ 2+VH'55Ç d߾}ׯg2s:] ǻ[RSSB!Lvwwtwwwrr'A7fP rss}VPP`ffaF m89AhC 鰃׻tЙUVVz{{9rUr\[n999}0 FmCh$aji,2g;D"9{̙b < |&ڡC/^|Ay{{]pAz4ѴiN>-|Æ gΜy>.Ǐׯ_~Çkkk?Ⱥ: c ###--sL@@@ƌCMtrrիa慅fS hN|hkkeh4>.))a0(XaB111qtt122P( E__h{-_<33s٫V255mr-[Sڜ'O~IIIi/]|A,oݺ]QD}d322 jkk *** d2511QWWoh9QVV%%%t:=Qte#G;|[XXtu[uu͛7cbbÇWXiӦ)d  LF- 0ĉ? .411maEE\ɀaD"y^9bĈ1cq@ED"ѳgn޼y֭cǎbUUUq̘1۱ t}qܚJ񪪪8ĕըK1m7ԍXGGMoqYy3CbŊɓ'Õ=Grrr@@3 PSS۲eK```ppE֭[Ã@YYǧ6%%%>>~#G8q" @76~+W0,//bZuRSS:JJJףYM}}}G AdsI$/w[;wDGGEyqPV044444D100 H!HVrU^^d2Y,fXhfbbB"LLLlllPntMQ'|ZN>M LaF2eҥK`0H$!nnn6l`2~voI/0=sLqq"x\. Ԡx1P:s>544444PhRCCC?D",+>{l\\\FFYf̘/{'OΞ={޼yB~%K\p!66vԨQ. ɕɉs̙7oވ#PRН?~Νo zm "VM_dH]D"u Fzz{VVV߾};eee>|xBBBF H۷o|ѣGw~kcUUUqqqqii)zE d1DjW\ywwm۶5rwܹ~jj*rww4hЀUY hhh+))%K^Z^X`0t:FCk`0PTziUUU}}}V&_A 6]QQOHcHLn[{oYߏD"2d!C FFFN$ ]aXMM͘1cvޭwȑӧ7v^=~}޿ojj0uTgggY<>g^v~ԨQ-j$lvmm-hBz/>D"&_/Ms7n_rE,6L^^^֥v_5<<<&&Rh4ov{JwŋO<=& dC۷@C޽{w_Zӧ0YYYt:L&@|xb7;v,ź{.HF>}Bӟ?FWwsss BR---Ѵ%HnD"Q\\իڰw޹sۅÇ_jg~b~^xvVBP(ʹ|rL&dpL&JT' JUWWQSSSWWRWWWWWDMF'A Jpr8|r.+5cAPOK̆ e(--ݻwN0lժU^JOOҟp8/^@ATVV_^]]Ν;A䊊 ?_u½{>|X(Θ1h/***'N8q"MNN>vȑ####OI sBx:6]WW񪫫/g5éF݋+++ܳz)DBuuuԴ-_yIJ2wwݻw+666""bIT*511ʕ+{޲eܹst6zzzwbbޤIf̘tuJJJ#F}v m舌õ#r]],  2D";}vJJ |"׮]jժ懥 EEE(d#Gx<~ ƴ,--=<ijj3fʕ^^^dĉK.m[ Z&444444zuuuM6E555L&TjjjPF?444P(YEEEUUUMMMYY%//C ttt嵴455c0 50 0uuNrQq# 1 F9પ*Xa͖H$"ŋQ觶ף=Н"eeed"H"z-l ?3kޤԠ4"J%VQT+8XXQ"bQT *]KN@ZH1/ ߇\dg8ɹ:ѣG'Owȑcǎiiiu5ĄJ+M8C"N8`0eddWyyy]#C$W\rʴWWם;w]v۶mbbb =nwZĄ0BBBNjphXDD9Kpx!#AyyCCC322/_+z{{_0ؙϝ;w޽...AAA. wsssssˋ:ٳg孬d :cc[ݓLOOܜ'q? g0ݿÇcǎ꺸 w,X͛7󋊊 cvvvNNٳg=Z^^^^^^AAaԨQlll,}5Jzzz:ujܹ=l`0޼yO~WnnBŋSLIKK#H!lٲ{m=y۷/Z.\ XUUٳg9B Gĭ}/b{C _~ {ߊ+lmmM6B>߼yY] `(IIIY~}jj-[ӓ(NP?}tիWr,ܱc&&&Μ9wMPzE=t萣cW_`ٳ322pP.]8q[[[/_y򰰰[n988F 8ai$,, =A7?>nܸ'={_|iiiBhĉ?]v-N!BBB8\VV>߿СCjjj .duEC}5T744FGG?zY`YLLlΝNNN۶m[PPP@@ܹsY] [[[)WFh>TSSճjjj`...%%%@ qqqϿt钙Dǎۺu+@GG'99ɓ111O644duQtD"Hݻw~:44444WCCL&L'ݻד DEEEowA$O3é#2$#QKKKLLn>|HIIr`~SRRRZZ[TTq899SRR&Ml2|@&##UVVΛ7F=|pxǺRYYiӦe˖3v?pƍ'O  HSTTzA+++11hBrrrT*D&~~~̑lll'O~ BH^^_\2--}`ײʿ*zMDDȑ#;v044ܲeˁG`:K$|N1#<@0^q$,,ɩ!Ɨ>axxF} 222""nnnƍcu]`ٷo߮]>m6V888ܬ]]]mll='dWzzzzzzǎKNNP(NڳgҥKY] ݻ_tDuuu{{{D",srrf 2C۷WX1JLL OjJJJJpW>>>uuuiii%%%CCC%%L11NSȍ#rBBºuxxxjlܜp,..~`" ;ŋWIܴi"vvvy, SSѣG|r\\ 0 BBBV\׫ pq\aÆׯ__rEDDEŷyƷ̧13m0'''HŹa!!!%%%PH1~Nic[ %22299YDDdɒ%3f̀#Oɞ={si'''Vdeecbb]\\TUUx 44t׮][l'+W`u.͙3'$$H$v?RBBW??L 899ѷ\0Ad0EFF @С~͚5 ,7~E s/ή1)hrΝ q y7JuuSttM:E nܸsƍF N4%5@o}),,֭[STTSxyy$%%FQQܝ;wvgϞr))iiaٕÇЁнɓMLLfϞ.90Bߧ8qkjj R[[SWW^__oi41LRqnrss0eᑐp;axKHH7$>m `p*// {Ċ+:4m48?qᠠuֱ0ann>s?ŋgΜQSScuQ;;a]]ݵk"##miffrJSSS̙3i4ڳg  "3yBGdؿ{y_=@`0yyy{EEE%%%ii鎁c99ihhptt\xO˨244R>)䴴EUWW_tiٲesF={p ޶l"!!q֭SN)ŸG>JYF'nnѣGw삂g-3IϏƿ` }//1رcǑ#GΞ=ZÊΝ;OB+++)ʥKWZ5c X!##ӟq^O${ S:"0%$$QtttHHHll,\|k>~/_ F=fEEŹs*~l?[^^^T*500#) 6lkjj\BP?666CCÓ'OZZZ4 Nqqq[n=AZ!!!PlllMM N/**WCXXXYY˗999&LWr7o em-[3Ç.]`0 ۷>~׮]KHH?zrS&Mb/(((((~ÇgΜIN:_tٳg޽6m Bڷo_tt4~tΝ&LX`8;;9s7o޼y36PPP|Y6nܘ͌0pOfé_EB84C8%q&ǂq!$((Ύ [< K111 ݻ ի_ Nm|rVtuu={vIoooyܹ. wvvvvv 3g#MiӦ=}p8zD +.]7ocV?)++svv^nE0 2qcc#BHTTtܸqƍ366VRRcYY>4=y$000$$dԨQݏ)人~rxaWWWym666SSϛ /kjjڰa;;_ϟXTTW__߹sիW/_zB(;;'-hӽx"BhƍzѣGBAAAoXv=p¹snܸȜ$**j!?6IfΜm6]]]aaCڵ+11\vK,;~w˗'NsΨ(CCC11[￟kW\9s挰}f̘!--}{ IDATڵ߄0c 2|3g2#¸g03"c|||8=U:jkkuVXXXBBB[[Ϟ=kaa' ?tRV888,,,\\\ mll;P$&&XPP={vы-Zz `D?tzEDD~#rQQQc:yxxoݺ{_+wZ>~ 3ή1|%%%%%%YYPssڵkY]]mdd42Sd2˗/ׯ_:ujXXxh###|Àcqqə3gzF_̨NHH(==Çׯ_KMM4^SS… }ׯ uW^Ӆ H$ŋ/^@_x6BΝ;??#GlٲoLKK$[nussU_v {87>cǎcǎu{?9=zt͚5x%$$ cccYDFYf= 0߸qB$$$N6 D9:|…+WxM i&gggE aaaaaad2^QQ0M66++KCC[ZZ}0a撇"%..!diiٯ{pBlllbb"4`$kmm@Sqq1NGIII)++?j7믿JJJܹ0*jaaQVVBqʕ+'Lo> B&TcdrvҥpUU+W>FٳgMMM#""޼yS455qF!gap(,,TTT`n{ݻw!D ,--ϝ;'&&87.Z+} PGսOKKKww^Ϝ߂[tкu֭[(3 |'++?[***  E #))B|YWWj} ~NwttrO/}L&YgΜ~D":t ɓ{'+WObHZZZD"199Adqq񪪪^MN$eϻlE #2CՕ+WEDDo?~ܰaA0qvv6ގTUUUVV666;v,K]ܫW>'MMM>|HLL~+A===O^ZZjii9a//+Vɱ:{ϟ?s+++B.\$:::ǎ)t:͛oP]]@!-->\v޽{׮]VWWJ,,,<(//O$˥ϼ13mii>AB~850jkkQ`:˗󕕕׮]K&I$CveH!XBXXߟL&;99ikkرӓu ܹCP<==ݍd%KY]#ǤI>}vG6ߓȜDfcu [n-[vAW^=vخNt ~z###%%=z433sћ6mu֧OZZZ=z?xxxdABnkkspp6mS7Ú̲?~6`\eeByyy-z۷oo)dTSS3sL_h_TTgϞ888 tuuKJJ:=eر|||Ϟ=auH:uO>|Bo߾S͛7ѷxqxxxǑ.\@t-8|t:r䈆@8w\^BXRtttB!!!auu!t ))0᪠ѣ<ڽ{7ikk[j/]'G_f``;qD&C77yhhhIIٳgBK.NDiӦ%''txeeefmwDf~9 @Gd0ű[ZZ.^| XؘC^^^eeeeee{{{eeeIIIV >|x-[ ZZZ,--SRRw\sv]aJ544ijj >}bggWTT@EGG/]TLL !K ޼yө۸qrrrlmmU *g3fݻ#""X[ɤI B>}BZɓ'AAA!PXXZiΞ=kccCy]r۶mCEFF+:rǒ%Kqm|f?...wݰaCMM㹸=z򪫫׬Yl 󣣣_x!,,laamdd'o|͝;wf͚rqrr[[[L>㢢 > $$dgggggWRRBP(%KlmmO>08pR\\abbbo߾D"qwD& ; 'N]ϟ333q!1fUUUUUUozz}olٲ;wܽ{wԩYKTTTSS柏cǮYd ׯ4:[ZZFFF.]ee֭7055}vG,Xoĉx [<==8Гv|-ΧOfnTTT|H߿liI<ӗvڿ'g3t:}ҥϞ=KOOEP222d2L6mP}Դ͛zzz.:P(7ndcccu9˗/(((,_|͚5pUSS#..pn>}://3S(e˖hnޠ;w:y򤫫ѣGz :"ݻO?fooO!*ی􌌌"HIIIIIICCߑau}AKKk˖-]7nܸ~N!3={6..ҲZNNƍFbuuԪ/^)dP^^!χ tmm_`fnnmoo;ÇӧO9s&LF7oEDDdff"mmm׭[WvZIIOWxc . >Dڼy3 J@@//o|||X]]}Ŋƍ~:\ 0׫˗/rssq- Y---A]]wv=ٳJ-[Xr9]]]]]ݮ%NNNNNN] Mo+:)U`#G9r~w=ғzG--UVV$%%N8ׯ! Ǐ899 L~!$--M"Ə|r Y NJJzyW#Gxzz8q~kWΝ;qDnn… ;%{ꕉɜ9s !?>{֐5kEEEmmmJJJMMMHJJɽyc977!TZZ:S߿رcvqd09sFNN.66)zcXIIiҥfff?Ν;/1cFLL Cф Ϝ9u 333V@?utttttʊ  vvvY ৴RSS "ȕ=_vAZ|GSpix 2(a,MKK۷oUTT|r@UUp򸪪 !$((6u5khjj+((!h񇎎o߾}߾}7nOAAѣGϟ?ֶnݺ 6t{SSә3gB 0B\reҥ ɜ!%%7oﲮnJJJ-III\\\8<۷e.ofA`x^z%&&v,~oJJJZYYٳg40RDGGZ2<<VJ.666+V޽r֬YOVUUeu]/55ݻw޽ס'Oܻw>L\]PRRzUWcDDDpBzy!] rD_uuݻw|涶6)S}>9#PCCCffwp8--!$ @"455---I$ѣY]$++w>aWWW//.EFF999mܸQ^^ӘmmH0;99ǛմQQQxp':::.]SN!AdLVVܹs;}'߿rM6ikk40l'%%M4)$$L&Ñ`xիW\ჴE=:k,?_ӧ`':::;vعs'777ݹsBxzzYZZ‡tE[[;55½L  "P PG ,--|Ç䤦 _ƭ322jjjB&Lӳ H""".v8u֓'O~Ç˗/9v?~8 cǎgՙIJJ^rwD6n)Syyycǎ~޽{TB`ܼysz?<5jϞ=/^!H˖-[|2D{{._|ʕŋ'%%M6u0466޸q#>>zzz+W$$եȑ#۷o߶m/@`u9g RRRN<}SN(77y@@@\\\XXزe˄͛D[[-M{ȸ#2J(pN_ٳGEEog`Xjjjcfw;%HW V; 999'O.\hiiyܹ!&="")))fff=200jpKK9J}0())=zHVVo3f̃Dѡ驩3f@͟??!!aDuD{͚5O<|r``l2kkkEEEV䨨( R^^t1cư4FX r}}?=t萻;kfmmfddD&%%%Y]DXXήŋoXXѣWXM0ikkTWWv5FLLWAdHiyA!Ni&Lu־aJ&%%%##ׯ!yyydccb?WWW!!!??z٬Y†nwvE??+W?:::݌g0W~}RRҨQN0T{ !܌+~Ѐ9PTooo^czߨ1 HK*00;; '''Hmm`кwUccYTTTʚ7oޭ[tuu#///!!Dy󦬬{xacc9s̙3߿رCUUdfcu`+..}۷ݻWSSl2UUUVRZZ>{̙3. tt:}ӦMgΜ9uꔳ3~$++qF555M6 Ӡyyyww;v<~8<<<((ófͲYd0hkk#޾};gΜƈ999#2CU||:: 9_~ >vXII/_ܓk9rB\zUCCcׯx{]]Jmll+N766Xp]]N~<<@ }^POQ%''Pi`` ׌3LLLfϞAS]]]rro߾?gΜ{70\z5>>ӧ-rss344aui P[[ۚ5kWXr` ϛ7ﯿڶmB9}Y]@ 6('NHHHwvv޸q1n#djjj7Adqq/_jZ"̀Ad+`~9 /0]zu}|񣧧1%%7?~lmmeccSSS۴iDАau#]yy5k:=TVVfdd$!!0,?,//#{ħOܹܼ槷8g lllgdFMжc`$`sWA횚|y*EDDEDDDEE-NRZ!!!&!!>u^WW7!!!TYYٳM6!!8;wܾ}۶m#'O6006m~Wo0'%%%''?y$--Nkjj.Xn_P(-rww744ċ$Z[[^zEkkkVСC+Vpvvuqqٷo`qss/Ydɒ%Ցaaa .5jʕ+촴X] ,11^I$~z#G cݺuJJJ^^^}8-_nnnzz:3y\ZZ144tww'H***mY~ѣG;m1117nr^ikkp¾}JKK<==zJ2L&nگE˗/_r/t1*&&6n8c'|||p{1OUWW[M3N7666440?~=ˎO'UBBB\\\\\\BBBJJJQFŅ,q͛7IKKGGG߾233BD"qڴi]e9|pSSĿ`_G8 UVZFs/vvv4}t]]]CuX~MJJ˗/?g̘cVBӓ)J|||^^%KY]Qŋ?}J"X] @311پ}ҥK.\H&''vvv˗/ϟHuرf՜D"KիRRR}5aQQѶm6o.**bdsrrJIIᐨnaUȄYwJKEEn]QQLII)++`5j,3fp? {𡭭myy9W``ڵk`jjj߽{w}}=B_ƍMJJJLL #O$ @@kkk7nܸi&UUUuuu555|>A233񝚚vvv---'''|.--bn߾wƍMMe˖YYYM8@ : ?{lhu$~"%%쬡}vOOOhnF8=====Ç߾};,,lӦMnnn涶 c4-##CWW:HħJ "CGdָ8oo>IJJ꯿9`::vvvUUU==[M0ֺ ]~~~75kݛ0ajA>p@YYn:z_'77kԩ}^HP^^ϼSPP)))TVV={v>RRRpbn---۟?O>%%%ST~?r5jֲe&N^~gjjzqSS^] z葥cccEDDX] "fzѣGw}3g볺(X 177P(F"]4TUUyxx޽{pqqqQ]].?D"\\\17C!ѣGK, #""n޼^^޾)yzQOOOYY}aaa7nxQ[dd+W;x//t2ˡ::nccշ? 0''''''+++&;;,3s=_@VPP2j􉉉.\$$$jeee555!t޳gNػw-[G2##cԨQ$ŋ!t-###"8`W~=$ӣEEEBRRRVVVVVVQQTT䂂WPP? 222#xߢeee/_aaa\MMM]]]UU\[[[bb"JKK‰!0DEEYZZ•{6l>}ѣGY]'""蘝}ҥ ;;;{{QF@  &&y\\\̎d.a`غz֘1cdr777ggYfɄ җ/_|իWo޼ ҥKuttX]&cEEE6mruu6mZ'O}R666ӧ[YY驪B %$${I =~YXOx{{GGGϜ9399Y__O!_~jnn~={CUUUKK?PWWWSSSTT}^` eeeeee333z;<--۷'N3BHFFFc.uuuӧtӏ?>^VV`AAIBD"Qt:ɓС)==ٳaaa_~]hѧO:`DbllA`T}*##J2---N 988899D"777/////os|||?,JjN2o~yP5V(((v\`U`0322,,, EDDX]}ӦMAAA'NذaDXXI[[{˖-w^tC&drnnn7._VXX@ ''`:"n.ȭ\\\:"0]zUEES_S]]ag}͛7IIIOmoo?=o߮za'iooMjjjrrrZZZEE״i-ZgϞSH$zx#$$dddTӛ7oRRRn߾o> Ôtuu~YrOO͛7sss999YXX|}}O+斗0/]ʞffQ(uL(//?uThhhCCILL̂ බܺuSD"QPPn(yGԖ/_niiØgJ"33ƴ4555zc ˖-[wرpBssSNIKKӻ.@?yӧOCCC환,,,FgQ~"H " hGB/D`Ag.]tڵhhZ[[QOԚUUӧ_~骪@HHHRR˗/PZ}}ExxxݻAP(.]ڹsg{{ӰǏG옘/^tvv{{{ihhKUMMMMMmڵ566ƍMMM""" ,Xhт J}5|~~~*|r??? 9ɓ'\WWw֭?d2eT*ɓ'G}𡬬׺uF.\tax^={]PPgiiy̙sqƄL333114IIIzつdXX؊+\\\L4&fffccccc[n̟?D"Zjƍ.***^EPPn;D׷O1"ݻ',,ioowtt477_bŰ RSSRRRuuunݪ7}t...zW^^===555њ%K0v"u֮]<<=s̽{zzzLMM/^hbb€-ɖJһCdca>}իWϞ=K"lmm7n(//#G\~H$8p`ԩ.233srr&M455֭[UUU6m0LQQ]&ɿZGܠW8;;BZZZbbb |||899]Jzyy8p=0?~};;;z#X]]]DDĩSϟ/##cccӻ@r~~~gg7 ~o(} *f`DMll,33JhkkWaWVWW󤤤7otww\@EENsN &00W\;w.}kõ:tHLLݻ(7޼y=42tҕ+Wjkk}Ϝ9sɜ--+V@ ͯed'OzYvZcǎ]vз6Ç544ܲF!aXhh… GC |riӦ޽:;;o>s̗/_y Rȿ*:scl2e/_2&&&yyyW\a2J}-;; MMM}ߊX1!!!~~<..3?WVVNMM -((2 B~z߾}rrr;v`ee Ⅷ'… 7oބ22G>{RSS׷E0f߽{WRRrӦMvvvpyɓ>~BGd ú :"q+&&gvRUU:"WaQVVٳ\"8}E.CG@oPRRR֬YvZ___zau\ɓ'ңW^;=:J{ZtV߻$$$8;;_MVVv MRROdd$33sppKWAAASSS{{;movDF3kr]](L/>>><>}۷a ѣG111qqqUUU222 .622wuMNNy{{{ZZԩS]f͚yɝ;w^zɓ. 1¢͛.\?IV\vZ999z~9$i rgggKK J33;;;Qq%99'{msrrݻw[ii+W֯_cccŋsssH!sN4IDNNIhh(N[lQVVKOOzꨥ1 uVwwիG׳MhkkO4777}B̟?_EEǏJ!O6-,, ðm۶)**׿I899,,,RSSSYYYBBbժU555,FBBBEE˫ ի6mRRRZjUZZ`ʫV\)..ݍG`===^^iӦݾ}{0@c09e_SNigvwEhkk 111o޼JLLwE`ff "ŋOڵv%m~S͵)++LKKh`@[lQTTvttloo߻woAAAaaӧ-,, ~eϞ=2++-[&N`;;jz#!!e˖̧OΘ1СCJJJVVVQQQ;,+"cCM"+x咻F`qssSQQ=cv* ? Ç!!!qpp~_Egg֬YzzzTjEENkk+} tBW\9VGc@ 8qʊ_@e˖zݥٳg6pvvF^^4552i&w[l{+oll.~?znz{{Ç{8?yCxN{ݤ#v?;GGǾQ3+++[nѻoۼy&H GkP>""v3SSٳgZ=x;홙_QQQHH9aڞIII]]]. ƶx0߿/### .f0LRRrǎ. *nݪo} ðMRRѣ}eMuuu *++`ͫQQQQVVVC{{{W555O=ezónݺu֕^|9444 @[[{͚5BBB.g!!! ׷uD#2jLйq0D (77wҤIIIIhٳ'000;;W#CjooE||Ǐ߾}KR͛7w\CCCڐ ,==]OOî eŊ***#(RUtqkjjnܸ$++kaaEttt9z Ú*}+++ @ QU  cggG%3???nxyy{w`ggoR W9JOII9tP}^ 0͛7ӦMFKK͛7./_ ,))Ar kkkim6گ_mm파ow166VSSSUU511\Vff&Cm߾Çoݺy䉱xyywghA!<,ف>'OΟ?n:j06Buvv&''ӻY@@`Ϟ=ח/_ХB99/_͖,YRQQ=211fccF]EE޽{Ϟ=+%%ckk W`@tZ[[[[[rSS2JmhhD>50a>z HBP_A>J^/Q/ghhhpGilllMڡ<<<,,ۢW? ?/#d|3|Eܢ;wDGGiiiϜ9 @L&߽{ɓ6lw9;GGׯ_;;;C#3~BIHH P(FFF0, zzzXYYo޼khkkӵkVXa^jj*~u 2?>]wss1ǿ}BƺCh #rrr< ;v8rH\\ܜ9sh7kkk#ɍ\hooohh]@Wb1H3]G_Ń(㋮I*K:x,ȁן8qŋ^,BRWZxWTT+.HѳӃ&&&t0..nѢEl+ƚL "9WYY{f`%$$hc4 a\\\=ep .,--w- ,8{ƍd2J{uuO>ϟϞ=;::̬?|K"pÄL&>|8 {׮]6l5RT?hkrssetW5(+ 3`Ї2 8O [ZZrwSLLL}qqqIII8/9"/G1I~(Eh㧞ڝ 雲yxVzFO#&m`?_hLLL<<};<== 4#o++kWW״ilmm-[&%%֗H1B]ǽyݻwS(KKH$nnJ Q+UCC#99wލݻwN3Yq|x 0 v pkiz?]LŃѴh]9 0T999YNNCPP[k?Bټy 1 CiYY >qľ}:;;wܹyf+_ B(..,++(//(++ͽ[!!!%%%<.///j d2pF$[ZZzeJJJ𛴹7...iiiQQQIIIqqq --Me[ZZѫގ--i> sq I$o G x\o/^0a'OIHHP(|ڴiGW:i899N. ~QQ4 (''Ҳ#99YMM`gg`kkk33'Nt ~~~gΛ7a QQQo%77D"><{o))ʕ+CkגRSSG LNLL244366VWW9kjjBvEߖ4666667Q-.$E\\իWmmm>}RVVDPS666t杍 ?LJzz UWW]|ywܙ0a°'=x}./VDDvڽuF0FEJJ j8g͚U^^NF`Pի߿ Q|eh9::ϛ6mڵkh f JJJ 𳴼RRRRRRbbb(D+***((8? L&aW^VVVQ0nnn$---++KmQ@VBMMMh1 P]DDo~3ljtcZuF|3IO&i#սZzhF yyy_773F#oe9o߶1c͛70>_IYY$UUՀ??XYCCՁ1o„ CyzD"B! ccbb255}m8}a/ 0?;;;555-[fbb tvv+@ hѹo$&5:#///|||lllh&uuuKK+W{`͚5/^>> 5k̝;òe˼O>=ё۷oߎa$((eҥG0lɒ%ݿUFFFO4k,TTTaX.~rKK̠ VVV++;w>|xh>x/_BCCmmmw>Ǐ7mT__?uT555"ϟ?>33ٳfY鮻;55СC⦦&NN;wYF]]{ (̌Vãd֭aaa9QUUUnnϟ[111YYY3f,_\NNNVVOTTz \`ƊB/,,LNN.,,ħ8qĉ'LeOgWkkk}}}}}}CC7ho~sF...tvE'ѲXI4d#JmZb IDAThh }cx4omB p }AWWWt9sdee>|Ν;OեwQylllW^=}thh Co #reeAd4LNcMfkk[QQ?:::FGG ë<***>>>11ZFF|޼y9h^Ӣ>>KA(mdY~rի_xѡ+..?ʗӝ߾}k. RbbbnnnvGA}gݵk7p,~𩭭533LKKw9͛7ӦMpZ][[cǎO>]]ݙ3gFDDdggR?388xϞ=AAAfff0 ݻqSSalll xU^^^VVVVV xII z%%%D555uuu訩Oߴq,#_rufuZi.`hnnՉ B.."""4]޽{ǏrqNNN/_tuuݳg `Q(*jaa`ddo򊍍f[+AjYYYm ##yfԓkW^}ⅾ g,0111w}SRRΜ9s5H!?]]]Ϟ=dee?}`쬭-9#@@gQXAAi|||軼׮]{1)@wh^okk۷o_PPZrrΨ= }𡺺zܹ. :qÅBCC?effv޽˗/?yDUUj֭(l?ؽ{.[>|(((`cc3g}'$''ZB3T ðT>>>UUʩS뛚xyyB0LRR2޲eKMMM/Z;~}VVVvvv[[@SQQ144Q]ߌ"Ѡ씶6J2L:77722}SWWWWWWSSSSSSUUESh{ŋ+++ꪪf|{ffflmmKyyQ+ǎ(zĘǏܹ1ĕ+Wvuu7ndbbwEt_?1&&& 555.\022z+ҒH$FDD;;;v튊BUNWdeeٳgG+))پ}7~#Gл"߼yW^z ϏxME!+++++>}bffVUUљ1c@ƜҪʎ|cKEEEEDDht<XI;kDOO~^^^IIIQQQiiiQQQIIIqqq ]ဟhmm-&&v=EEEz`D4448qB__ɓJ>ܹsAo`hh w%&&Ν;os]vݿݻwܕxxO@EEeŊ>>>A  x x<O~HTFSGGGRRRTTTtttAA//IPP4?P*~6jL{tGXXXHHHIIIHHHDDDDDDHH#J_D"8p?rqqstty`jjؽh=gΜ??666`kԩ>|vZxx8a֭w]` >y$ŋ׬YÀ'G:::9VSSB|;wMBB"..JΞ={d+9 ԩS"""111JJKKC? ӦMSSSw>H$DZp!ӧwޥz*,,[KK uuu1mڸ -%%%***%%"}MI{,qH$JJJJJJYmm-~漲 (++G/hx,666R&&&&)))&&&h-Ƿnjff#`?zڵk5553 (`ٳgϞ}ر+W9sfΜ9&MڸqI@@nnGdVVV#29AQQQ?zroooOOω'DU`|)))d2YKK|̙0S6hllr@Mq[[=#''Ŵc!!_mǏ?<---""b޽ϟ7009~ӧO̙3 3mll]Ovppw޽qF+++!!!T@kh4Q=?~d fͲrUn~`^zr5;v0앧/^Qss3muuufffK,~DDDjkk[ZZFJOO߸qOmC?_~ɓ'O555ttt&L@#"k֬0333E߿DRLbdddll-))/ȠqAK FHOOW``M 455SSSϞ=u1333zxӧ/xxx̛7 t988{}KPPpxD"MΎa~s?-0}1$$G%,,cǎ /^feeqpp̙3' 2壯o$SVVVYYw)++mƀaJSXbbb >xxx޽cÆ nnnk׮vppx'㟌nhh֦w!?L&{e*//عs]-ZǨeee333ooo&&&zQyy333544;j*ɍSUU&]]]333ۏ=:677www3f寿:y򤁁}&ѣ'O屳n޼yܹ:::0x("8cƌ3fϟ?OHH?z( ̙3,X0sLxwk/,,Dc>&haaaֶvbbbp0Lﶵ4ZTT~௙ĉ`ƈyٙ3g6l@r&&&KKK 33ǏQɓ'ݿ?44tŊk֬qttwulkkkooGjmm`Ad x+))),,,""sW__[__bjjh``0z04xz|K~~~̄ o(s,***,, nCeoockk;sL􃶷>|XKK+==]]]}qX}J rggsssLLL,,,BоєqqqyeMMM~òg4g@c{=zeeeuƚb܌aD.**铺:xxx1 c rffMII7oLTUUݻwݻO<1cʕ+͛ 00ӧ /_[x%K͛c5ǫϟ?9777''-466bƆw#GNNN=@_&M4iRoUWW:ЮP(_ĉ'>yDLLݽB633377"{hkkÇKJJϟKK1(..~}PPaMMM T=H7o w 5~~~gώ+))qݻwSRRMLLBCCyXFʕ+W\aǏܹsΝ3g,ZNJ]T*(m眜 ØeeetuuQNFFZb@zooo_sss߿Y]]a++<%G?BBB.=͙3ݻwsɓ']ĉncc#**jgg0`0&LwwO@tD!&%%;wu̙ƍ#TI=+,,f͚WWVV֘ALLLRRRJJjʔ) , H1◓?~|կ^BoYFH[[ۮ]=jhhxAd>}z􈛛/'OLNNPYNNqr$o߾+))|5@RO:u}^^^ m۶<݀^޽{UV [QAAknnCVV۷o޿ڐPRRRRR233C  F;;zF---IKKO:U]]}ĉ,,pۺ\\\BCC===!ÎsٲeAAAЗ C$-,,,,,JKK/_|)h 0 STTxbxy܀H$Ad"H  CxIWWzjjj/WW׉'\a`ˣd*K3d2@-IIIIJJHٳgH$<&Hpuq477^Դ8.._54svvv^|sA$))lٲodff^h͛790???;J{.&&&>>Ν;---<<<3gן5k֌3F4OIIIRRRJJʋ/޿C"f͚u)SSSqqqzS=zd``^d2ayyy;v PD"\ӳw޿{֬YQQQ诮ҥKO͝5kŋ-[à HΝ;w\PPӢE P~ݻ*9e _w% CׯY}) ;;zy񭢢:##ڵkhè(I&2N%)))OOOwwUV ;88X_Є zM2 rD<%ヲ ƶ8--hwQԌLNN΅ OwwwYYYQQQQQ3......A۰H$$--#---%%%---)) ' %K888?^[[{֭[ǎi&&&\~z&&իW? ԩSN䴴0___ }}Sjhh~3 yۛ7oXYYfϞ奧'--MG[[ÇߏnvwwcF ֮];yݻw:t5H Dfdvqqu뒒<<< Շ:y$++ݝ;wTTT]"HvzAhhΝ;WZ饣### Z^^aʕ+544444 NNNNNNlmm~ݻwﲲ"##TTTtuu&A/_\t)HLIIРw9żyݻ}[n:uadlllll\^^7H^x1u9z $D툌atD!РK.GEGG?zׯRRRK. wu ??˗/qEEEOOaFFF$ -KKK,)@>> fff"""vvv0zRPPK?m 2:"?g\0BIjDj)ATĆ`A; ]v*6u]*{1B |+//?eʔ˗/O:u0 _O;HIIh4]]]''']]ݡC´cG92]vݻ111>}6;v:uj…?籨 ~~~X,ѣ+Vזp7nܶm9qĦM&$ S\\/ܹ7@uuuttttt4wňDСCGiiiillljjJP&lAL&3;;;+++;;@jhh{<<|PBB_jpYիW 9bꪤ~ Μ9Çׯ_wqqw8Hpuup⎥ƌO?8qbx 'Ny7nTWWϜ9޽{ ? zCz ݿnnǏssիWZ@ ; ^jq||}:K WDNUDd? L 222gΜq-[(++WB\ntt7vLR׭[K#z|8.\s΋/}VwHH)Sp/_xo޼xy7.^⎢ ?֭+))i=ɔ|rց?CƾIIIAOccc~~~TTԣG6o>~Æ D"QUUѣٸq#ٸqc9BA:O&{>2 hhhu0oiiBz/Fk=~Փ'OG7_~Ϟ=lܸO-2DLQfNLLΝK*)) ƾ}Wo۫WT*Dtywϟ?3Lq  Nx__ߞmfa0%K-,,`28nܸqIIIB賨DA0H$ >x---=d;;oڴڵko߾ f!?O^pgڵӧORX,gR(kkyyyy_uuub>fee;::Ȑ!C$%%/^+dN:URR.@_Dbgӿz[,Ν;[_pE'_E#֭k}Bkii ȊW^TTT:?өMlaȐ!f:l\v-?w\={օmb6_KRRZ)0kR]v q.\ ++k``pʕ> ={v.;y֓->Dm 9 A6lbf؝:fvz넯Ojڊ !qvAcW('l7JȂYqq֭[I$ebb鴂1cDQAAzP~~>}:H`0G1;|hjjJNN{ҥK  H&G۷>ڵL&/ZgOR߿tl_YlԩSr/_vڵ:::m nb&&&RO/^rʁ6l0s#FhhhvI&uuuG9}˗{yy=zʕ+O>Bʈ/_7nhnn{Dرc/^/8qaaa(+YWW7|p33{jjj<ׯ_A&`$|jjJ5"%%%~~~Y8_55Çgee~„ {{{8yyy'NPSSlyyӧO'$$޺uN~78ϵkC p8gȐ!رcCBBlvrrcǎ&ބ>lΝ;߾}?>B/t*$!c: ]SSiHODϗy{{`0?3*(… 𗈻{hhheew&MΞ= 6oL&8v:ԅF@ɓ'/\c׮]ǎtңG矤 }2333&&իWw?~w//UVOǏ755UUUp;fkk;cƌ+WرW^}uJJJUU7GԤi&Esrr^xElii+WJ+$$DVVvȑ}:AĉrrrGJ6sݎ?k;ƟÂL8p%F ̴i<c7oɭb6&}8ڵkϟ?9ʪS! ӅWX삃Xlaa։|a?==}}_⎺3f ã$;;;֭[Gn޼$/v:ԅFm,<3{QͭpհB?|…ƒÙ8qZyr;&!!䄲OA>PSSo>III++hq ϟ܈DҥKcbbEEE]6>}ZEEEvۼ^8p@__GDD-Zԅh! *--USS{ь3pLLL~;ਨ(iii''ӧO>vu7@544&%%%%%%&&&%%yyy333######MMMqnnnhhh铎N4ꚛ{iWWi2339s>lbbrҥaÆ1444$''ʰat:011ח6m%$$$$$I${006]ZZZZZZ^^^^^^VVVZZ߿3LZI$,+N$I$,D"Hrrr222pX^^#lveeeMMMMMMuuuUUUMMMmmmUUUuu5_YYffv;+)QVVVUUÂŏ>iӦ;vl/^}YYB^p!##C6O:osԙ3g6o޼`^] f͚[㟈8& {WPPׯFFFFTjqq1| F$få LLLINNNj/^KpEid2nss3왤,dLxHt hѢ&q"faaap<oܸql6L&MlryyylleI "!!A@||%B)++m}Z^;ARSS[,$n3{.uꄄ>2s .()) }c _% eb;\Pț+Fu#|9s`?YyꕄKǏAT?Իw͘1a q3bbb`P(t:}Μ9t:!}iڵl6͛7=ظgϞcǎ=}Fu~BEEE/[ӧO۶m۳gORSSMOOoll$VVV&&&&&&t:]]]Æ { }e8ǂ3XZZzҥK+VҎwzzzPPPaa;v}Q+رczzzp̅ !7>S_a `0Gw,bO:I cɒ%sxO?t%KqqXXŋO>zRxx#AAA:z=|̏?CgIHH]˵{B BTqEuuu 2T n?O/&B3?f`A8yyA5Ǐ755upp?~>>W G?~„ zzzWvssSPPwtHh4Z_Dd\ x<hՙ}^vovmhhܴiSXXXss@cccBB•+W6m4vXB lllN<Z^^.0m666666l---VRR׷G"(ϟ?FFF訫xVԔ=}t~U`"8zh777߿kkkO>=v'AeeeIJJ^paΜ9JJJiiiS)))3f,YDf?~xyyyfff=8 f$)!!A܁-ۼ)8b &M<<[x<722Z`yLLLZ>󟢢"Je=+vvvX,6,,;Guݾ}{ڵG_T `u˖-{ƍ+jY 񉉉 0EOVV (**Ag0K,~ݻw̙b2wލ70bĈq9rÖ7l}ΝCXYYT̉C믿`7I Hnݺ5m4q = &oww8ŗ/_g̘A :lBB‰'y<̙3]\\Q/栻cO>xƍQQQ4mժUׯ}U/^811ȑ#=ueAAzIhhG~~={n !pЀgϞYf͚5( ?/%%ueQ9zS`߿?wܦ&WQQAP,,,z zqË;6 _~?>Br?| jժ~\\\XXiOYYjVVVVVVAb͜9^|;G{8իW=?uN6-77ѣGNNNibc(99ZXX]vذaÆ kѭ$ Hٷo<: w୮l$J>|cXG?~2eeHH  99 68;;ͩ5􁄄˗gddܻw履~w8qז577rɓ'o߾}YfD''ٳg;::*))l ȏ˗/Ϟ={Att4Drvv޿=whm|ƍutt" t}\\Çw֭gώ5JA!X,!---00pqq455wtLn,''d2ElH$ֶ7URRpߥ@ݻw&M2O```QQ_E՟577}ɓ'0ظWVV~PPPPWWZd <wVVV644466|S^]]T___WW \../_PSSpoA8'++b$L' rrr˦}ayyȑ#oܸ!))I"$%%aS222rrrp6#p8^^^N>}ŋ; s ["&&f̙?2dHmhhHNNNIISSS:|SnݺNw'A1z'OH$ŋۜGZZgl(lvbbWee%@NNG:uÇDb 2xHIIׯ 9x%Ky)Zw⽡JJJ9C;[QQǏrT0 9MMM8ׯ-R["Hd2YJJJNNUWW7ҥKddddeeI;0NzۤIn޼)ʺJKKSSSaqzzzcc#344d0L{5lA.hhh9r %ɂE9<~yԨQ8Gj}iĈ<@Y ]`I&k׮ݿ]\]]Q2>|JJJ.]Zd 6WQu֭[fllllllFaff' DǏ?cnٲ¢?믿VX!)) &;A ==ɓ'Zf |{͚5o޼9EtttV^cX,VSS3;;xyyy~PDb}}=m`1\s~(`޼y3e!3;vYHJ׋=V/**=Ã`T1 r:thʕb|*"x:z* Kp8 *"#>|sv]~(..֭[ٺV;wXp8?~OZZZSS`0x555UVV2L&)dX1$'''XLR 8+''Y\.wĉ\.ݻw,d$|Y,d2"""|||ZOa f0̂8&o"999Leyyyz)){lw݇:z-[#lv||<@pXpppX @|%  |f~Edpddd:l?22rԨQ2~];> Sh4{ .߿iڵSL$  ɼzSSSnjΝ; ))))..f=}* av:Cp\SS` bnnlٲaÆY[[kkk;NKJJZhQAA;w͛'pAg,_0A9g^|ԩS'O5k?yyyl怶|l__.|H/^;::9D"H)΢b/Lf~~>ibQn/q7;Lƕ+W޿ÇC NIIǰ1D<611 HHJJrqq񣬬lʲX,vz%Njܾ};_~zٳg588N;.%<oiiiiiS\~}˖-;wB%99͛7oڴ͛7cƌpss o1R^^>++K`qmmmS=M b[kk^~|r0ddgg뻸̛7;p8QQQ>|͕033?@`jll{EE ze%$$ZRT!͛׮]7nwlnn޿Lrҥ}FoǴX\xeyyy%%%%%%eeew˺/vܜcnnvbB177_h,DsoA~0YGį%%r r"rzzzEEם񹹹&M200x) {p8\VVև0Luu˗dee\\\QY>~Ç޽`0&L8s̢EЮ8hjjjjjz􌌌W^={>B/++o @__?ẗ́ KOO PVV/J'F uuu{>VRRvڧOzyy/ b&MڶmMviqDž HK !---00pǎ+6n܈:g%4-'' v"rS ᚛qr$44\S{B iii7o NMMUWW_tҥK28???222222:::&&J9sԨQ QMM 0.++ *//%D`ʦСC-n?".˗/P(^Ԃ/NLL \fM+!!"z KŹ SSSc'[( BQjظDx^Hц:gcF7W ?X,6czu)))UTT###%%%aÇZ ӧ< { $$$0 gXo߾}6h#G={3J@zIMMMxxxhhhhhhll,H:u+WMrRւ#a\FFFFFǏ3229 Fhii p rrrrsspvv64smmm~ϟ?m6;l0q  NMMիV^^^;wDw200صk˗O~bѣo>|pt5.[ZZZVVVRRRRRcXLfbbs.S-ĸ]Hwrwu[nhO> eaf3lsaGǰX,@hjjrttTSSSVVVSSSUUUQQR***m쵵 08...99HJJ1W^ 2pX8;; BIOOu$"[ZZJKKxօh~Ɛ{  %xy$//޹'%hkkѣkΫˡ IDAT r08?p䴵i4i4c#k֬y֭[}||Й 27.>>̙3v}v``ĉ mSQQo߾}Q33ѣG{{{O>?T1hjjZ,"2?: o߾k=vϞ=˖-344zOqq;w###edd`Qp8\ﭔŅ|N3 ??ѣG`qAAL,i0 ^xeeelhh3#3ĻEHo[lYnn뽼WXhL&'(\x199Y[[4--?D}}}UUUqq1|^MYYyĈ>>> C]]fO6f#UUUnݚ5kVBEd{{{@III]]]\ŋҢPzAahh`x<^}}=m#ED?'>>~ӦMǏP(3*JRǎ+8eggŠ999?ͅdeeUUU544TTT`kk⢢R~a*cgg3mmm ĦM&M1iҤu8p@RS1IIIWWWWWÇϜ9SOOok֬Acw&˳Ꮤx\B{Ȱ`E人ل#FSLoQ7oGEEٳg{{{O4}9222222::Ù7{ԨQ^+,,,OqqqAAAii)HTQQEXi4ڨQӎ!n "vW^?~MD\$==}wܙ7o^7@IKKkhhϢjkk/^(8OeeeDDć} `0ׯ_yF]]]EEիWGX,YYپ8A>UTTdbbRYYyʕ tvN%"WWWܹ B"ݻ>}+]]. tJii"|N<p+Vpׯ_?dkkk333t%$$|ǏL&tիWO6J;F`0SǾ z޷o=))) s) BQUUQXNNn̘1cǎFݝ!Q X-d2<̜9JkkkkkkB-TTT߻wo͚5G<AMơ7nغu{9D.==;w|+7gooo+--Cxxbbb8ȑ#=< "x<^UUu2@URRRlmmY,֑#G<==BrL&fwݻw,755ȨS'o޼ܹse˺- KfffJJJjjjfffff&L%t:]WWwԩ[nՅp7xym۶ ɥИ={ٳ\.7+++)))555)))$$ϏHKKt:n``/''˯һx<^~~~FFFzzzFFFZZZRRRVVSPP011111?>N733P(Z\hu.`IIIyyyll," I>}JHHr ev%FOX,&YYYd2RLKHH#m>wھ}A"a[[ŋ8qBIIIq!"իW-,,f̘=j(q6hii '"XOH$ OD\.[*20HZeff:uw ޠJHH~[ ,--;-d2#""kjj`o-9rQc0777'''//s HKKkiiihhhkk[YYAMMMxWAzҥK9=-A ,zĉ 㥦 '%%X,)))2k.ahhصk;wu֝;wy9 S Q{V94MGG&_(AD>|8|.{ tx屢BDT:..\ 2pl㔔"hGvss9KH[`0֭TX,vСC5kؘӧ>3jeeehkkS @CCC^^^^^޷ouuu"oddafffbbD"-dʗ644^xD"hܹjjj d2YAAAFFL&d'Wss3Ūbl6[0<wI$<|JB.ol#޽{6l?vvvw8 |288СCk֬AWf#֭sss{왯رco>ef||~"- @BBo"2xto2H}ֶu_ϿUפ]t֭[...ZZZ=~ff&?ׯ:::cǎuuu=zQ^߿aqvv6?OBB#֞00dY/G^^&%a$IVVv^ FWWWW__0Jl6O%*Ƃm!bQXXvڧO>|e~# (͛o>ׯ;(A:bw\W2HT@Pp[ZE[pWjgZԢb (* B$@ M<\}\9:?MߋƵTl(8˫A'| j[G233͛Gn߾}OHp?sƍ7nD0@(>P| y<'|%.-ߴbŊ/`ںɎܜNϜ9S\\ w@e TGFFݼyRK$ exR E"Q~~~CCC_|DթJ p999aÆ޽{Ȑ!svvvvvVO(}e~~~UUG__N[[[[YYY[[tEәL&ɴ͸]M]]]aaaQQQQQQaaaqq@ ())%%%P pE PDGP*),,χU H$&0qӦMSnP]]- _ՐN@duuuuuT*m|!epA__*MLL MMM OR lr@CCD"J]J RJ"HRX\[[+JE"MLL0q̏T*#^F~嗵kךČ?^ ћR?~HHCBBmۆfnA zO8pdɒ+WQya@Tb-6d2M zzz2cQ ѣGk{xx|:DEE9s޽{ e֬YǏR|̓ߢ"5q~׷G ?g-FGpD///ا'cmqoB׏5JT޹sG`>:66q ?[[[ooPƘxccc"hhho``ghhH$x<:5ЏȈD"Q(2 ` P묾uTUUA= P]]-=>JR,kORSS#jkkRi]]]sW744400PC%=300000PCdɒ[n-[ h7 gΜo/_ّjDw8~ر۷/\nuh=#JJJwT*MH$:"S@BdD/ݻ&&&^^^wܹy[zՊL&y3g_.Ǎw _KHHsN|||RRD"h֭www>R4)bq\.;h ;,72{͛78PnXv#Μ9ceem=zBùzyy}^^^ƭ?aO?NU#>//O]ɓK. ة y<8N4̙3Ǐ?gT,#N2-&&'I(//mQ Ϟ=d}6$$dժUAAA-@p^zv 0-00|2=57111qsskn)DRPPPRRRRRx=R]]䙡(sCJp8(SWA0evK#XPU___[[.Pp-7Fk8ߔ  b/ fXp&5nrfw#H$L~zNHh1jD"J"QuE),JKK5"ǀPU_fX PC m &Ć@qvnՀ*[UF7$*[D fq84r+H&) a-O 322q2BF psmٲe#Gr" NXzo{ǏϘ1cƍfBdilWf{ Dt53T*L3 m=0(d2,0))H$6S^1J`"ւ58f/Z6&>>̙3W\;wӵ{yjA$Aqrr\.9rd``KOӄDddd@*_x<@tD9RNkV&§]ءZ[=XyQQQtt4 h0Ù}Å]~}Æ 7nioqWP[[˄x<Ayyy{{{wdjժ60bXɓ6uL&D9''edd\:Ph">22r=sP"JH$"Xt`]wX]֝Z_]91m4``;nJJT*ªF,WRv;5vؼ}:`ʔ)$?~溺Cu@ TLp ǰH$:99Ac>f:ryEEEMMH$H$I]/tŚ:"k.57XSWNkᲙ}D|cb@PPP  BaAAMP8b, [kyꕻ;0f@@Gՙ%hXbn5Zzz_F_EڠўvXF˃$##cҥ|Ν;{Q 0@ ϟ$%%}7?TB zB IDAT/^ܱcGFFĉ7ozRB\reԩXСCۚ3L>@ \xɭ}ʕ+#G|A~~>T?jП^TTWW744ꪪjП^ 3CID2LXUn@"陘XXX4?ΨR`bj6`ZRqokk;l0uA8;6癚ÛnP^^~]|P(Ǝt뺎"u1\.//x6moo4a(r$IpI >jS5'(VsBqppRUUUiii]H$dL&T*L2dL&{EVVA?'HRSS|X8::x#GNS$8FhA& ¢ .[SSS57773378u322hP;6x_~P(drMM QɊ7o`ky-,,X,f0͍6Jw^xm߿/ .ֶUxÇ9sťf(J﯑^YY.y|oL}:hfԧN4u˖-ݐ5L͚V44 CV$֦Ipee%6nN]YYYZZai4 c]/B HyW\¢N7x}7778 (55pO{ƍG?vrTWWc7odff¿9FFF|>?00/`GM"DW fl ? +++.D"q8&bGgfff-Zb[7_z4u bbb-[VVVv vxl@ D"qŊ3f̀I&:tx^zu׮]|I@@@xxѣua@=MBd V "#@]]]0UUUՍPCRMԠT*ed2tք;===* ģ٬yʚE"QCCCMM;wRRRX,9ԧOBzᴛ4 `hjF>;;3Ԅ>999322&ӦMwppNKގJ\bY/KJJ0`*i|>|5FR1yWk_bPd:plmm9颹wU*۷o1W^UWW 4(00pڵ^^^SK6YYY .\d_|uh4R E^^zQ3g@q7ox<޿?cƌܹ#ʖbX&. X`qKT*BbSkqiE33 Z^[[NK$8Ӌ ׯ_cp8ZZZZXXXXXXZZtq=pID"Z_%<k1ǏH$-_޽{-Z4k֬3gvrѩ4e6ydl"Ds₂ 15D(,cu+5n_~E! B~~~HH̙k.&(`2Oꫯ.]׬Yߣ:^ݻ;v6l͛t.aٍ߿ 5[WGOOS#!rߧcJKKᲆXîR___] 14 /@fmjR `xLMMX"(ʺ:X _p ?013־߿|_'ɶvvv 266C````mmmmmer(M "('''>>>;;[(B lllڡ...~)<~H$...^^^ )JSL߷o_Ws!\.3KTT&gddܽ{_~$IêNZŋѣG9r޽M*쫫qaqQQQqqqOt:*P===-,,IO>i[?4in ߅pY P(Vl6p8t:M\?wBŋwݥM~VVV%%%fffWVV&''jh"t@ ڋJVO܂LAѓJ%%%%%%BPCm\\\5cZ[[3LƘԘb6#i({n۶f߽{# @ Lj#=aÆs:thܸq @ѣG=իW۷o3fL7l0}ttIIIQOi#r9COQ\\ ::A1? ЌʊF6g:uG漺B;wH$dI-*??<;;;11ʊNCUh~mzz:T*p2̏R=79g}}}nnnnnn>}bD&.J߸lHHHlB!C屗׀LLL!ZhyӦMϞ=C#qpl6f1B=t…MP(qqq 5jT* P|UH$NNNn7qݬV]] (A+t:`l& l63l۶m߾}$ɓ`neeU\\ߜ#rll,æU*ս{´W^}vDO&-- geeeffº Ǜ3gx@ ݌R[ ]5&&&t:fX[[`5#,Yɓ'˗/ߺu+2B BWH+VL>?~|PPѣG9B mc.]r>죕#[[[߼yS=ԴMBdeHܣ{Ї&bߥRc0PeEj7][R)4QTLS\\%>l6bAV&p0'Bw^DmmmJJJrrrrrW^z%NNNNNNPzzd2~G饥x}LL  prrH$"(---%%E& 8?4hРAx<.2,m"?z觟~:v@9r|||BBȑ#}jtѹk+czeff&$$TWWh4U#+VxGGs5 tzMMMmmms5CUqjjjIIȑ#;Guٲe]0@hP(RSS11WVV- \x1,K!#yyy33Va1 F 2Uo SD[l9|׳g<<EuC`JPx<RT*@ׯM'd2ֶɭ$777;;e~SP G׸899"yĻwJH~iѢEVh ^MMMsB۷oϟ?._ c͝-$$"22+BE P(| &8*((M~ _~nnn'Onnnl66"DGTihW ͆bEƘx@ .E.9r$""$**jɺ@ k׮=zWA!6ȯ_޶mg}֯_7~$rd6]___ZZIh4H$R&k9"\d2Cqvv6T \ 666vvv>>>p.fJxs@`0JRuqhF BJP(vj`e:A?޿ɓ'O<~899Y&QO2 Rx.rLSҠ4/^Oϐ!C||||||z¢5UpX|ȑn!RSS1캺:)U(8D"d2JEPݽ======H$3@!ol^UUI!W\ʂ,--}O{۷x'Ntu=5)DNKK;v,\qĉv7nܿoxT##HRSS5YYYPHGP\]]y<ޜ9sx<ECPd28Yqqq4hp,)ɴp8 X,e@ t@|ܺukʕ֭P@ =ٳg/_{EuDo J*Xj"cc$DB 4KKKKKKYՙرc2̾p#=}}}wxS}}}^^6 '''99 J````ooL|Dݾ};66ݻeeemСK.޷ }?~'Nlٲù3fر#GmSoyyyk=z_e2UET'%%%%%%&&z|0vvv3fҥKN駞 BLLLӿz\.߸q)D"CT*ŋ h4ZUU?zWRB4zm(++{ѪUbt2@teee%%%MKJJ'ׯ%N"cEӱY9,_&LvttuD@ Z!&&ڵk˖-sqqټyeV7ɑl6`0JD"Qk EPH&ِ#rPVV5ǰ?---##@ѠqԨQ\.~!B&7 544`匌茌 Xښad jjj߿ǩzzzC 6lDP jAAGAQQQJJ &8~mAA#ijjگ_?77@>fmm ! +?  U,Jx}رcuP=@9Ǐn:cƌ!CDDD7Nqu>X &DiphW[[ۤ΍D"!!r'PXXzOJJJNN~B AAAҲcC ݌zBUEw^~~J222ׯիW\{Bpww ڿaÚt.D f?sСp )SL6mzzzOk{y}TTTs>|㄄ׯ_+ 333wwwERӤ:111Rˡ?999&&f޽ &&&>>>C?`jj鑼}vݺu׮]p&&&w5jT_MXZZ6)DnhhݻKFell o>sLTTTk#wac(>| 5<F:dhb\㳤c}CCC :naaaooceeeeetTB D#&&fժU+VشiS @ z8 %""b֬Y˖-:t_|o>sss]Dž@ Ð!C'Ol޼yF%lvB X̬V==: H22ݻw{*KKK yݝmmm>8[w$::Z(x8=8#iW\&Lwܸq-J6f͛7o<۷o\rI*:yiӦ;̅B?1/!J_xѣǏz{{O8q֭xD :ss󀀀*RSS_xè۷\]]}}}}}}A+Wnss=zT]] +u111'Nh|D"YxԩSN"J%?  EUUL&ihhH$uuuRN!HeXMbdd1~ؘH$*h4RĄH$h4ccc###cccԟ[JeNN͛7B\.[x1hccC$x/РV]U\VV֤Ը\P`GH$333sssikk;h KKK(/DNHLL\f͝;wƏ@tgggKf͚~ڵkΜ93$t|||n޼m۶ 2$00p׮]u IDAT\5#&!2V==*l !rs?|0>>>>>l3fׯj3E mBIg͚SJJJ.\~~~~~~R~Çܹcdd4iҤ .L0!]͍֭rԩS Νl2.nNNvڦM,--7lE7o744#]ѝH/GxbUUرc'N8vض vڮ]W_}Aӻ$3mm<$66E$,n*9":BQTTTjuD"i|u0D222" E__u8Q(rzJUUBW*"ĔM1?a( CD,~Z]s 5&&&wss x|>9 DWP[[[YYYYYYQQKa eRȘ ,,,o @ ddd]~}ذa?{j@|ps~'7o^`ɓ'9ү_?]Dž@ ]~zooӧرQqu,+''[%muDnr+DwH$CBGvvv޽{7bĈ+Vx{{s\4~@t.VVVcƌ3f \OIIyq\\O?w <>ʓ'O;v,;;{ї.] j3Z1aaaaaaO>~ѣG-[;Z Bֶ^x_xOg/Ν;1117oˣƍ;uY,C ?ǏN ͛7wܹy d2٠A&L0aooo-JuΝ;\ @t)2L]XeR*KPbl @ RRRqѓ'OuD@ :*z/2$$3$$dh:w޽?c o֯_onn:|z J,Gdu!2+8Jվ#b۷۷oD//aÆ >u!,п &L4iȐ!}~8uꔞ޼y,YuD[Q*111Gy󦍍͚5k+8DʲW;5Çr9R]]uܹ+O8q„ F ]D"{.Kmaa1eʔٳg6L]H$gΜ9x`jjjjjg̘cҥG}iAii)|ԩSlٳg?3cjjj=zJ3P[[󁂂bR ĵt:\j[UUUyyyii)&MEEEX>Ųrvvv}{J.8˓8Bc(8x@t2L$D"X]X\QQQSS~,Dhfff4@ nn׮]?#Bٶmџ@ <*̙3WӋ;w#B B.>}zӦM$<<|彷%رcׯR|̙37mUUU7o7n\aaaO<:'O.Zرc_u'UXXx?3!!7B)7oۭͭ_ﱴ2_}5}={vʕHssO>}r֭?3ΝͶRhs=튛֓&M4iRvv}V^{-[̙3&lll޿?66W!*޽{_P(Əᅬ7LסJzыӋBUUZp'555::ܹs'Np8g^hQ]]݁Ν;\SS͛ӧGEE 0@7 zBcݻw B@@ĉSN8իKHH@*n:---55ݻw߿㒒c2P:i$SSS9fX?fLLLLLL\ns;D"@ {tYYYVVիWb1H$r8NvvvvqqquuxT\UU+uz OCF  T ƉpňťIaN@ T*/^iӦ%Klڴ M2@ G;wnPPЖ-[ϟԩ#G 5B$,X0gΜGFDDٳgK.퍝 ,j$I[0 dq9"WWW={ܹs FFF'OOǎ#9t.Aw]=?!##QQQ P 4!u‰'{}9s &D֠hǎ?A;ޑ;v,<<2?t_=zhZZ.\g!qE/NmGBrH$R(wwW^544|Waaavvv~gffx<,q…J~{Q?J*;wC$''{.-----@"\.Txu`]G6>}㓕L4No9L&zh{-8R[n-\h"8P\\{noD(;655uvvvuu hf[]annnnn>h DX ⩩iiigffL&#+* 5pD>T{yy͙39zTZQQQTZWW-t: CbZ3`#@ ֭[߽{W__=$6@ .ŋGٴiqС':(~v3f̘8qO?ԜbCCI$RCC V{5ڄ)))۷o?\\\o>{@ aX˗/_|ׯOҥK͵" +V1cڵk'Mtaa@j p۷o0 44tƍ)Nx8%JjB6IOOOCa[Q5Euu+ۅ  E^N:5rH:nff6f̘;wtRyرaÆYXXXXX 6_~Q*؁544XԩS**,,FFF_ "֬Y`0f͚UZZFڿ & 80""B*>;;r<<<ݝH$jo߾C{yy]rETϞ=[`mPPÇ ٳ!!! q֬Y=j1SSSSMJJ$%%a'755dgg7Ηޚ|H?-Wo2m4v `ƌ-P^^qFSSS:~P(4001cFttt}}}s8oonE:^ĵZRR2sLn:\ަ'P{+1_ 񪖂57#y󦡡_|^ܵ)Hrr2yfl?qqqT*u…kMUJPlڴ O:5//"\3٦JER%v8i&>NYG{ -ix;&n`-#CLL=wciSN,fffr<''H$={Vŋ[[[toSN}3g޽;66\":Rqҥ>00`P(#FذaÍ7*++[6\rҥ;w.^ϏFb@ k?~<..@ ڒϟ߻w/::ܹsǏ߹sWX`@777(ЀB0LWWסC?~̙K,YvΝ; .}!7n.SBBBwr;hdOܴdɒƛ<==[#gHv4V-[\׈8VuŊZ28*11ڵ pݻwÔDm[oxk򥝎?~Z%rJQQ;?}4I*H˙ .p .sQ8VxCCk,rGGǒ EБ"NjUUtlK.mb.1Q/ZƪW57nvHܹsg[W^mi]gnnrgϞ5B9s&L>|pwى -RZ|q:RX,QߊeyXRPhNu;?ڿV{"hԩD"?u,-p%z4m4J4jV޽#gΜ@{'/^7oo/_~|]Gnr˗/:4k,}\\ƻ{Phi(3ŋܹ3:::337Dߦ<333)))..tݻyUV-^888x~~~<FMLG h4߿qゃ-ZiӦ={?~ŋ7n333P@ syix`f@ hhhؿ[t*++WXA"c]֭Vwr[lst XjUC hB!߿@ I@]͉' o{H$رcU*ձcaaaIII+WS?~/^9c Fի+CX,1ͻb&r J>|899|>u܍vgGcjǎ c׮]QG>|xtttAAAMMMJJʺuÇ[---_uRRRMMMnn+WZs<W<===<<aӞ|iS?WormhhhhhrIիgiH$<odd4{X G{mS_ݶm[ob(1_ IcU˫Mw\l۶jw} yyy$"KKи87l@&cccuj٦JERZ|q:R-[/~&2Z iߏx;&nmw-㞆R c˗3[RIRCCß~O?ꓣ^;aÆD<~ @|<={vgԨQ~~~, ~„ +WuTTTTllgRSSX,m@ hJ2::c"@h.kjjn@t).]677?qD40ꫯƌ^x77˽cƌYpaNL&@ ` 044in;;}յKKK-,,`\.'H8NTnݺ5qD|hѢ[ 1uuuG4h+t622:w/"ݻ߿FEE?Nʶ;̙3I$_Q7nX|yCCao޼>|%K]ֵɻx|kko]':::q?rߓ?7FZ0KVw~Qb>>>w {IbbbFFFAAAEEEEEEaaǏCCC_s}yxxlذaɒ%SN;vZTSSsq}}%K 80)))44?|0$$$66ɓN:SSxoo˗/]PPpiuu{/fmN>o*dټ9Jq/T^^,Brq4n8w_H"1 KLLQEyy9:ԗݴiSff+? .ѣɓ'p8}}}.+@f+** 233nݺsqOOO7Zj3g;v:J_Q9<OR,,,F9vؙ3gΟ?ժU7n믿;sǏ'&&WTT'==}ժUT*u޽Ţ?w^qqqKKW^@Xja.K юR ÄLlذ&zcxKה)Sbj hhh}~?ׯm z~W[eBCC~~~iiiUVV~` .3  J]n]\\i2qttX```& Ty'wqm<}ڵ?lB1} YRk;"t~!.;gt%$WWÇ7x5IIɪ.-ȭ]w]{jNn{0zۚv7a4ڭNkhhNrJ ä~tx1 l{u:!|vS6l';yx۔1BGGwYYYKKK|rkkk55 64l'OE:z(JURR:zhuu+{z\SSsѣG%O蝡\ٽ}V倮ř߿OLL|ITTԝ;wZ Ϛ5 6l ߼y3(((***>>>11ׯ(C:ϟO2ihh8q祦pUVtQᚚ4޽{$//ð-;w*ѣGvYYY6}] ?X׋͚5+''kn\.… Ǐ:w`kk# p bm޼F-XYZZR(Ç{yy5_Huum(--dTJJ… MLL,,,6oޜ"Kic6 p8!!!vvvǎk2ARRڵkL&n߾C,l>PCC[{]vuOymUϟa`Æ 0l.x𡞞}sϞ=k׮]jܹsQt_422SRRRXp8\y  1hˍ0aӧkkkE]~^\.ڵk4ڵk}ILLl͚5"؈E+W3 ZS P#| FbVҥKw٤j "##/^,\=('4k,99voll:ujBB)SzBYn罽׬Y#߿WQQ l3_SSxggn(z꯿ZpԩSӧgX^r%"{~sz &<|PԵʕ+cƌy󦒒+jǟ`0^z5x`giƏ+"EgÆ VVVW\100u9]C${^0jԨ˗//\PBBfAovssR2djkk-`XNs8*4Q555l6[{.'++K$$%%D'펢P(ANN+ <qSN1bӦMg& {={+tVppʕ+o޼inn.wͿ֭[7nǏ*((h>666%%%4&>>Kq~3MMM~C\2iҤ+V888I@QVV{ҥ?N4ŋKנ I\\k֬9s;vM$<Onn˫WΝC>|H$ fX&M2;nĈ?4˨Q>[ԩS ,m/?~affÇ[nb"@PRRr ooo rŋ# #""BBBBQQ]p7oDEEEDDBx߿]v _%KxQͳqrr{ѣE]Qx<:¾]{JHHˣo[*ڨꙗ }ȑ#*!!!. ;y򤳳ڵk]]]1y<^```ccC<==---'N( Eh޽ .U \.?!!ŋVVVׯGx<7o~0##ŋX@wl={?~bE{$&&;w.>>>??_YYyС;wѣY6ܿN=ԩSOv nlltww QPPX~%Kjjjp֬Y}v\6^:ueee6m[3444=z333QWzF KU8ߡCnݺ{ntC===Λ7ӳ.UUUeeeťeeeŅ!O>:^D޾}`0&Os^vVGQQorru.\zaٳ͛x<^]]=;;[0##ׯ0+*ǏpŠ+DXƍO<|8x&Ml2!,, +~-F{4QLLӧ]ۛ?PWWݻw,Mzj4'%%>󮮮 eŊ<ڵk/^d0>>>V*++oذY]]-coݺBNN.''ƍkll0'USS۶mٳ9R\\|]v |CC[YYرΝ; ClrQK| |xٳg}ڿݻ}Giiѐ!C={(ԋc&6XQO߃k hAjjύ7f̘vھ/ ܾ|}v8 >`XΝ;۶m;|p\p׮]zh"&y޽vg8q"NG]Weff'&&ZZZr8`hh"##333ČllllmmmmmPzmBBB\\\BB߉DI욟hx'O.^XFFFua~W666.X`˖-FFFY>}?C=<>~֭M68qE2ޱcb;D~ꕗWrrrAAA}}=(//_^^.I6oތ,- ð$++C,,,VɆH IDAT/I رccbb._Ÿ>&&fرU={ׯ_'y0j3L&%$$H$kt q%ϟ?2dݺuk9@fϞ=g%%SN{)<@o<==w=cƌ˗/@2ٳ{-++C׮]vvvMFkii=|ȑp8==_vy=SAdAEEE )))GMM%LNNNNN~ׯL5:/b?Ϗ>}r-Z`}UUUW^}5k:X6w^3f`aaa\~yIZK H$RLL F5Aӣ>|QTT:vaÆؘ&@R]]ŋ7o<}|ҤI&M_?bĉN>s΂Ç_x1++Kԅ`k׮}9z(&&6}/*(((**k?GSS:DtpprG DC DR+**Y>ZBee%:,A'78% DVUU-..F>Н9~x333r~)))ΝuVCCüy6l`ii)Raa}}}LrmmmQW踘E}.>C 444rrrE::::::bVSS/_zyybiffBfff:::] CJJ x}}} #G:${~Ϝ9wYee3gΞ={ԨQ4m˃޽aCttѣeodddiiҥK oҦ8qA222ӧO[WNNtxԈ{1 /0F!--h ..߿p8Ν8q"Fu]/??0sss<'L!cVYY *0 LMM|lff?;yAdϞ=\.wڴitIII{` i1yb-Fkkk:{AAA.]z)F۰aʕ+D]@SSS~wW hb̘1)))'O h<]nBTWW 3#L0d6v>!kb}HQ(qƍ7=,..NMMMIIIMM <|0ͦR榦 8pԺ@b?F 2tPSSŋ:i{.iiիW^:===(((((G^^~ڴi&M=zkǏcbb߿K &LpũSvr~~~666MNQ-//1bD>WBA& ӧO ]َ;8^|y}SSS333SSSccc Y~v)))޽KJJ#;v찶FK+#IIAG#Mй g``laaw^##7n)]a^ HdX C^1 }KXX͛ Pj}ͣ._"ZZN455E]\tϯfĉAAASLV&Fk׮[n9sm-4-::z̙cƌwިQ0 edd "[mhЗ/x" Fh &uuu?~D䔔[ncFPP"p(JT% ӿ|l6@ M|@ JXƍc"xȑ***ҺqƼyD[M<977711QRRReo߲eѣGy<ޤI"##upp C3:X~ӧ&ڹsÇWp]\]]ϝ;;iiia|unImݻ:EW^Y[[_reɒ%Ѕx<:100ϝ;wٲe֢ @BCCׯ__YYE/x6l~… )O>u}k```cc믿:::>>>~~~?0h+WZ* iraaa7n܈1669sM:eX555G>uꔢyðԿÇYYYڣFZdh/P\\[r}rDc^^jL h4&FPSSSWWWWWWUUɤ?‚߿ideeQXhHqq1Jx"))`HHH 2l̼qFCCCzzzjjjVVSWW?RRR=_ׯG3;;&֮]z-[<~c]\\^jff#]bqqqZZ`2;;jVVVF'}P LЧUVVӥ]B >:K ݇o͹{nҥϞ=Xvm0 x ?~|?ON4iΜ9.]3٠ppp qqq222.)W\ 铅Œ%KD]@/˗u=zKIII~ Yti``gφ ֵ 9r/rq  222Ǐ_|yQ] OKq8p.[TT2߾}[RRRHHHqq1(cFP444TUUQ:YMMMMMMIIIIIIQQ>EhMcccYYQme(s,IKKkjjDMMMSS!hӧ8·^~ݻԠ *U[[%:p@hddd{ݻwύaL]tԩS'L@"D[ӧ----,,DRU۪oookkW^xe˖Pц~h4F5jJKK4óQK111UUU]]]~FYMM h s=ft6#?vh466mEZxrܹׯkiiȈ+`ˌ ???5*<<|ƌ7nuE4u˗0 <<R] ͛ׯ_⅊ʒ%Kܹcll,z; 4hϞ=֭p ,<ڵSyFSS RRRQL&ֶ8J\\#\.+*cՊa@@qd%%%NVVVVQQAUTTD"t6B(gp8H$@Mvv6Jfgg@ H]]]"(zA˪322|/_(ʚkkk5>>>))͛7JUU¢s{왽}AAAo _kɒ%mLbІSVV֤,SWW&Dgjhh t 8tb6Ftw6EE&ѭ(222544H$}0 [f͋/RSSE]io =ztҥbbb[^󡓳w^XR***ڵkו+W\\\N:Eٳg aɒ%'Oxyyy{{wy_U@#GRO<+<^ׯ111h/܀V\Ži4n: ôcTTTTTT̚bXeiiP@911 S#//O!Je@+***?G{EEEmmmQQQQ^^rxڀoqΘ1=GaVtÇwS;ꄭ SRRÿ_ZZa100vqq100000jrBرc¤x>~<(//پ}Y?3SSӸ8___ww;w9rdvvvG!"-?qĊ+̙cnn%TQQA0LJJH$ vmDmq82@x~HյweI#MIHHD,B'rrr$BJJJJIIȈCp8uuuUUU555L&N7662&kkk⪪*:|#) O7o׍idA٨f*$lllv?(ʨajIIɷo8NEEF)((ȷp8x?&CeeeP_𥫪B AkjjеQ? 󧬬0`X,r70ZJWWW8P(E?TRR0``$I^ŤR(h(8,Sׯ_⊋X55\.UUU=LABd2QC_~[/_𣷂GQܐBIKKT*UZZZZZD"%[W㡓t:߾?` *++Ag,"d.%%%ׯ*++CcΝ;խ+6`llllld X,ֻwP3gp\999+++,y***?L&ѣ֭k1aX^^vLܺu͍F=}߫ IDATtȑ.A^ۓ544@֖VVV+ȶxodtT>юp(/(##.8B]!t Qp8*++n tijkkTl6ͅ^dbIHH7ER***  vc>$%%ՕB.--MNN={ϟD"}ɓ'E[IJJ7.44ᠯ-^ +**W\hѢt'b\r=uu;vXW}]]RSSsΜ9>|8GQTtCWWWsssWWCIKK744^I&|.@!&&v ++I&u~4}Bedd:H$;"|3UI ȈnE[F|y|ERТ:V`xژ r%!!'ڒ6bhݱrBCKP ŌZtS(bRM.?Q[[^URRRGGGWWˋf@ HmLSUUU^^.zQ0;;]kІߜMSpڑwm H(jyإ4@~ xe˖=˗/?zh/O$ON~jbolccf~&q&͵µQ cݺu׮][~gË5ׯ_mz[ s\:n?T*hsڞrZJ5HJ]c%%Am2P(N(,,tpp?u`LL 300x3fa"7wж!AAAΝ;Ç:y 0gJy왿PPPyyȑ#oݺ5c Q숈/ZQ /^q͛>7.rϟ܇8YXXL:u]DFJJJA䚚af$M"F|̓}X]ό Hy[+ȏ7kjаyf</8L&y֭- |/#ԂmA*,,,**BQK\\\CCCOZ-3 Pbݢ[6Ω$$$ˈdeeg#Eft+LGX{Μ9mOtB (ի*{DK~1 SUUE---d ͛?L'--L||E8ѣER1SB 6:7!SrhC3 %1c!aNa߿9$˖-KKK6mڿKP&OUZZ#jEJQQ |…ϟrѲaÆ͙3gĉ&&&=R2?={XTTdbb ޏ$$$ܻw/(((??___Μ9sir]J 6mJJJ*((L=۷?ǎزeKkˬb2贳wƍ+VP^GIHHswwOMMkٳg .D}XXؑ#GDTfo !d8nԨQF:sLttt@@=<<&L0~q)((J>>|Y}}ʕ+ϟodd$yYYYyƍGYbj9IIIw꫊.o FF5k֊+ r6.mO$ϟmoozR*++d 2d SSS߼y͛ӧOs8 R[[kccKKKrUUa=\?ڼyٳgwڵo>;{3gnݺeaa!8<77733sر))),f2ӧTbّׯ_A ݻw Bĉ'NRSS######9ըQlll! 0 KOOy\\\LLL^^ҸqΟ??a @ӧ;wq;vlĉ?G1Kfڵ+W훺z'܁ 2Dmqxcc#Ad>}۶m>| A6jkkQ4t:aZZZZZZZZZ:::莆Z=̈́ >|m۶3g:::?IIAaaa K.Qwٲe}(/.. #99y˖-?˕@(XXXp8tx'##ӓfggϞ=;''Џ[{޼yMF=}TJJjĈ>|@322,--;eox wܩ1cFhh6111f͚GK7yxx0'OܿΝ;G4hȑ# m@А߿Dss˗;88XXXQr,+666444,,,++FM6m޽cƌ!Hqc\ɓSRR444z6@ssݴi߶m:( 2~H$0Qz t#F} wڵiӦulu!Mܜܼ(kGZZZ'++3cƌ+W_paԩmLocc`0V\yȑ[n5i+WHKKKJJ&%%ݸqcl6;===)))))˗gϞ'W^M>]FF&>>~= B+Vظq5k1bpҶluJIIɭ[]egggلa۷oy<Hzٳ;DkI! $$@`BB@{BQV[+mպZhR[m/"WAzل@!yFd (~~~so:\޽{+V@TפT}˗/SŋM8Mw…III$iذaW^mkkBE-ZR*O<]_1<<>>>>>...##dzxxxyy򐎎Ew_>((HOO[hԩfffWV lmm믩S/WP( fG$[ZZ._ѣ7xcÆ ~~~.jժ'OMڤ$`,,,,E"H$D}[sssNNNfffVVVFFEg2p`Vo߾}GϘ1c޼ys :] "..W yQII BNT*̮]֬YP(T*UMMJ~BJgrZV8܌644466v؀W 1odcccJ7; Ngp8BahhHRl`,^nɒ%Gv=99!eee/]+$$df-[tU嗸8OO4AiO0pQ֬uMXhѸqoշ Cp8dJBVVVBP__? )sIMMݸqϺu=<3##رcv={)S~ >ksIoܸ)d\.gX'N8q"S__'NuqFh4;w{FPiJe}}=ϧhJ_{':tСCS!Ed%&&&/f Z"$^[[[^^ojjB)VFR@t1B166g2l6dX,ccc8ɤtPtDZxqhhh7<O"ip^`466O4 DŪRiii/ "H'Oyٳ5r\QW*D055֭[D8lmm- ---BՋNhOΝ:u)֭[~+Vx`gV*m3Ǐ??lmm̌KOOOKK so񆹹vVΟ??82'O#z޽1c}}}7軆K.X?pe =}}}GGGGGG$##ӧ8pڵ2DP(gU _ B--- c$ֶoa_TT0UVVx{{>|gذa:VUUU> "8&&At:Fù[6gll, sssgmmpX,VxoEZZZrJ帐3CϸBhjjkn_J&&&fff&&&&&&ĄC:oɒ%_uBBX,Fx<^eeJP(,O^P444DRqYtAd˕H$:4iܹso>sL%CCCBMMMeeeyyyx">>,??_V#h4P(O;;;}u[;;͛7766nݺ544_oݺ?GFFvLFCOOiiiis=٩Kvqq 755-Y$**ۓ'OƑP]]=s̖;w.|rO6M{fSS׋i<嚾@SkjjҾH]YY)Ɉ9%%%eS',--|M\pzذa0555EGGGFFFFF&$$M2eӦMp ֖d2LV^^.Je2T*H$BnUUCH6V{{{}ǣh4FˢT*q(Y.kgtxJJ u rfff<߹r\eXXX7j~ DZ8T*{1 O>uqqIKKӽ_v…UVIx P( }za"jӧOO8%zzzݾ};555<<RAccEJKKݻgiicɂ)SDں['?~_DFFo.O*eee$;;jׇpY,dp,B0L<1 ]/Q\.͵ 9\.ˉ):bNHdiiO0ph<`h48|z3gٳgڴixlKRiiiiqq1X]XXXQQQ^^^^^.ɴQX,>CNNNSL鰘n? xN._WWGԮ2ӧ>at\!gffrq"xcmm_Z $iܹaaa{Fjyy9766F "q}}} @_1bDDDH$t/۷oٲeflWWOԔ(?~X;LPx<%DAeSRʯŋ|_|c'|>|r\qӧOmٲ}G2sWK.s p(9..ȑ#… =<x<<ܛB*ˉ+9i<M[ZZi3BmjjJ6LvwFDDL0ao@0UTT?ǸF5&xߵ-#۸"\.ZDEOQTX; ...!##ܦ&' D۶m IDATpF淡P(Qqq13--Ν;%%%- bnnND477s!֭[O/\a!CmsIOO_|ݻwSSSO8nݺin}իW;7ؼ[344yJJJJJJJsΉ'ZZZGr}oD"yAgܿˋNDg&ًe~ȑ#^:|hAa)H\WWܬT* T*됑Q0ɤP(,KOOfH$C"ڔd~C>Y/'3 ݋JOOh4NNNf:vԩS ^IKK t1///7779BiKKKWW7|S;$:^E&q*E 477]~]"(J>|0M>J޾}8JJy*"05 !ښ5rH [ZZܹ+V }FbX/*G\QQQVVVZZJ,))IMM-+++((hmmKhŔ---` H~YVZwޭ[.-ܹ?~Gaaa.\`}֬Y`~lԴ`;wxxx |||gKKKnj Fcll٫@ϒd3gT(ѶY%66חx'ҝ;wOW^r!!dffm}!iO]шhD]]GLCa0qW7b_mW!xQ!D)7&܌{6ifbdbkxX0bGL='}yyy111eeecƌ;wn``رc!5 333###333777773LAÇ[YYAUc{pƦSjZ"iIJJzD" pÇ6~] 1qĈ.Bfff2 zÊDbT*Riii>̙3_|Źs T*U( B//*J:gIIIJJJIIImm-^ ùd>oaaqFWSƗ@cmm}Νm۶ݾ}VVVWYzӗ,Yɓ?_3gN4 Fj*SSӠ~i6Jr?hDN.\rZZڅ (5%{yyq@gx{{EGG άRYYyabNrr2B` P 1bDDDČ3E3R(kjj /0q-HD9j`0vf6^)Rڗ_L- 梽22 %/R]~6;_8Lt:~ $x=!f ~qZ__Ȉdh4&~8.HKK%T*u7ӧ8swNNNӦM#`@!8 0qD䜜j==aÆ9;;\H$ϱc4M 2P( "e_TT{@ER)Jnn}ZZKh_}p5eBP$D'͍-))ijj\P(477ǩe.+x;w{w/{̙wFEEu!׃`j4͛&M*'N$T,J;}tYYB\2O!ӫ\\\=zgϞGFF:uT$ǘ1c,XPTTtE\ yĈ}>l&Lꆆc5ŋçMֵ-P(ŋ98ԤoooC^^^cƌx=uI ,ppp~yW|-FIHH@ Nޱ6mJNN޼ymW yBẺjbZPr!NiژL&NoSPFq\"a7QT} Wnnn~QA;R/ڔ~QD"1Dr-###LX,1622b, ONeeejX@ ~affvqEd1J7===UD"# @055d/^$ѣƍvڂ zy y cĈ:eeeӧOKKKˉP(',,,H| Λ7oŊgϞ={U<==333Ο??.[ZP(ƎwޮmȨGW_z{{1]2%;;;.. 9u3'\^68uԆ p-|MzڵkAAA!6>9gggk)jz'No7n؅?~݁$..NOOL&_3\tiӦM ҥKK,AB*dڧL}666\(bl6D`#HoUmjjmS\ 11IT gfZDFFFLLLtttLL̳gh4ѣ͛w//Wb͞6m1s}}}rrÇ߿~Lf'L0qI&5 m}`„ h4]]b)ά`0:LR"2}mذaDٝ,K$8q>t366666vtt|D&ဲT*---d8LtBq8C|>O2633X,Vpp?E}}}322ϟpu''.KR'NrJdd$Fp<;Aw]vǎ]uH$DD*111999)))66̙3d2yذannnnnnnnn ^Ymm?3gάY [̬;v,1'..N$ K$֭ݲeݻ~f6rbZ&IR7zzzD''Lfj@100000xkr}sbB&eggd2Y;i@ AMT޾};<<<<<\*∤?D$ cرcǎxӧwAѦM6o޼9sXYYwc &TTTdggD.rxU:*" "k') !b``H姟~˗w'Ӌ.//d2(//OMM MLܜ+|>-Zhĉ|ok׮/Rwa++O>~(..7nܵkfΜ KLLܺu;NڝAd.۵-$''ϟ?ҥNKzӧON)--MOOOKKKOO믿=РgccCtKIIYxqUU_ϙW#&9qqq^^^vԂgn޼EFFv2ZZZpgx<_RR"JKJJˉ!Bt:]; 8b333+^;8؏8paΌQܬݷ JӉ*, "hiiG1DxUEEE7n믿ZZZƍg͜9 <{֭[*))s΍7>׋b______///5gb Bhjj200`Xr3k"c| P ѣG#.--JWlqeeP ? e^+---%%%EEEeee8j6J/idddaaannnaa1f.JjhhؿT*{J:{yy9>QZZ?Ј B'x J/^x…x&9sٳgC&777߻wƍ?;Sz#Gw}}1cvmX|ؘ0LRU*0vA `ý;׭[~z\V0t:. :\^*)J=z++B .uLMMj7e Yb7|{ dggϜ93##c;~cH$Rhhh#kL&!=j555-Y!tҥ[ų= bggggg477\rZZӧBGdX,~iiiׯ_qF@@ݻ_K555jUP555#񷅆11CB B+a0/4M{CCC522¿, l6L&l===oddDRF!W^vС?.=z4c !pp8KqƍuwB!d!|DVB$ _J JIIrJHHH``X,/_woo„ ][!$J-,,X,BZt:g W*B{D `377'əӦM_^iݕ+W~w7o akkkkkc< N'iDB `aL&S;ljjJщ\.Ĥ׏s=ztׯ={Vw\KK˸5k\t)::9""ťغukBBÇqjp4V&uaݏ?8999::zR...oMuuuZZ%]t gQ2Qw<55BTܹswްaιdJRT+JBP(|\^[[T*qXT677X\.3F÷g:P:>&y%DYS2 Qí9f6mhhhhhd2  1l~=+==}ҥ%%%׮]#:0tBHMMݵk1'##!Dz)͟yPPК5k;{N*fee1uepkD{ k4 {aaa!rLiCZ81zӧO/Z=;lذA~:$hAssssss۽{wTTӧ?;v|G}Yԏ{mM0R_ )L&AUD9" u$Ȩ977=d7|3u?s֬YNNNN:ihh~x)L8 C#S({[Ǚ3g͛cy/899ٳl7oNQɳg|rO700066B9((u֋.jgĉ'N$攖Ƞ}}}{{{"bgg\1/ou%TUUUWW'Lne2|>_GVH0Ba2CHpuuF.??+АᘘDg=ztϞ=F j1c9,`t؝便-Yڵk~~~ "'''''';;;++ OrQsVBEOh`D"}zIIIppӧO8q-[ bܸq$uq:quf:Ad-++sqqimmvwwSLݶmۛoٗ)PEtP(ԱLCCCeeeEEEyyyEEJ!.JjD}e'=96vظ-[XqJ"q͟?_TΘ1~XzuaÆ?x…h~[\.W**QQQ}پ}f̘у-ȄBP(U*Uaa!%8p@Vl\\Gⴇhnnd_cΝUVVd2D*i<$GbT*U;jjj:bc544422b АbX,CCC.O#r9.5MV*555uuuD^$%%x:Do+QiOB盙A1Έ_jճgώ?nݺUhnniD$ IDATEat5kXYY=|Û`h4yyy)))8m%HBzzz666ƍ[r%NY[[N Rl3LƣBÆ ùd} 6 ###.]ᅬ1[âB>q ǎoϟ?)l6966 AdDSqB%*"d"lllrss|AZZ+BGuss;uK-mt:RbD@HaYYYRT&`lh 1=)=&<k׎9~Ү۞wVVִiӞ>}f͚~tۯl޼ycƌ9zh .Je2ٻcǎm BP|}}䴴_UPt8pmkkk^^ԩSuS-..H$ŸD"}Lv8x8jaa!5 cccccWZ8^ZZw1 333WVVV<U{m577޽ѣ&L߇ փONNnS>33ӧfffDh4ۿUN< =䔔|&)̙'@hx$hڅ򵥥ȑ#G1rH{{+_K,133 9"6lp!Qsssv>:_{.-Ê"D!|KKK@`aaZZZڲ~<>G};w~G4iҷ~Ḵ[vmpp{bbbQQQ-[|~ix䤤7O`O~.))ӧjF𞞞q`9s\pڑ#Gs>裓'O"iAdׯ<%=z}'NŠ;w<|27oj;lgAKHɓ'9;;;WUUO+V@O+W477OKKQc°aÒx]{6mt_O9|F3k֬[nYrxɗ5w7nݺuڵ_m[HHȇ~(˻O񱵵=s̝;w+++_;ٸqc\\\LLL8occhѢWm`'E,XЅ-ڮZZdaaaϟ?zΝ;?÷~{ԩnnnBFid333HdkkK&C/__%''444;jii_EH$GuuKۦRf͚={ǾvZN.|a*ɓhJ$s[n޽OqFBBBYYZC\ccc~~~LLLhhh``bŊiӦ999i__p8bx~iPPЍ7RRR=* @OOoҤIIII JEGbNssŋd  zc/"bcc=zt…Da__]v?RTL=ǧO裏&Md2? 6O2̵k;t@ cǎ66;s>qDRRT*t钳3Bhk:tUWGyM>}&u8aaauuu۷oGM|k+~m:G-^xĈLLL^:rF3gɓ'qIw}}ڿ|,X޼y?PTTD,6?qDЖ-[شiҊnѽb֭r22ӧOwaݍ7?^dff":Sj߾}秤 5M^^B護BfT3\wժU/#˫5kT*_Yccm/_<(( $TVVVVV>{ٳg7`0LLLLMMMLLLLL***ݻdɒ3g_m3wɓ'w]YY{1\.O2#ryvvvVVVfffs YD&qoi2, \]]tB\VVV^^^KK BD"H8|;ѣ͛7Obzo_ۊ+jkkMGGG;6;;޾ƥKV^=. O&ݼyƍ fӧO''G"h믿>yd~~~*11ݽjƌ8.?xQB(;;UV3 oϞ=mAF?~|Μ9+W;vŋqyь3TٳgmllX>ѣ7o_i0A1SS6s82^ʪJTXO?U(!!!y YVggg'&&Aj% BH__ذa"磏>rppϊG쌿0dX#Gj_@T*H?P(Ç{xxbOmGv~q\]]{{yB',--BRς'Oܸq|p ਨ(}}ӧ9rg4[^d:y{~4Bxzzzzz~*?ÃX,ֻxyyw3AOP(Jn@RBR*"uEz{nZ=o޼Ȩ\ Flycc\.ZEW}cccsrr_y޽ׯ{xxٳGSiI1\oM7w7@3'O2eʔ)S\]]_PK'OtaE.8\.*t:( Wtsss4Z0X,CCâÇ3Ԯ6lذcǎ j@x7VZoڵk/:I;vlLLԩS Ś5knݺG<{3g~'ǏsW֮MdJrcm&jjjBbfwxkիΝ|~R=֭[G裏z#HؘcljJBD"wwM69999::6MטZ BPΝKT(vxjjjRRҿP(xN,>z?}tΝ/_}wokӳ!TPP`ee \.D߽C}G/~~~/_70ܼy!>A/P(Ǐ?~ >Xv-Df?ydΜ9ݖWcgg駟9{7+#""Ν;zY }}:I߬,ѣGG]c'[m۶;v|׿,nݺuքOO?x?k78$$!cԒA!%%~ƍ555?x LLL&M4mڴzK$w"X?TWrryss3P( ⥫00Ad<=i ZTiӦ.oGP€l =j:00JΘ1Dǒyyy!==<3!!bۭ]{SSSiiijjjLL̍7~Ǐ۷oӦM+WZzŋB$8p@"t.\裏<<<7&&&ӦM۴iӹs^s~ʕ+b1b|||vNe2N-,,Μ9s֭[r[Ff{d2\BVTTwSx<H<_V:ujҤIffffff&M:}Z&VMcǎ7dzyy]r]郗lnn޸qWHHFQ(7oꫯ^6JsNwww 6k4'OYN̝;i6?5k.w^Zorqq!. ,YȖ?ё/[L&tﺵ_~  ~ٲe>T׶<:^dtkT=f/KIҥK|''۷Jlڍu}P]?cn0XڵĤj8 Ǐ/.. $L~k׮!tѣGܹ#Jkjj<<<^VwVl3sذaGh "X755}HgZA"\]][[[d_fwߎtE5jٳg3337oތ233Tݝ:u !bnݚe&"nѽAuٮ*w߽;W?zjoܸBiiibLL BXј|/]ŋzzzx4 4{yyj{ -DfΜYjww6Sm˽qㆎjjjpB2|ʕbkk1cA#j5N?vXjjjtttXXXHHH``={6lذb +++**A%~i4@ pvv0aܹsWXaÆ={EGGh'OI$Ҵi´oռTCCCLL_=| pʔ);w +,,fhngXKJi 5bX%FXP%Q'v{Ǯ# (M.,u>g>ޝsffYwfΜ[ݻcǎ͜9xD&gΜyرfQחtRe``e˖*UEbe 2n82ekkK\~j%bxΜ9 í" j'ڳѣG7D(Hm9ĩ2P~nܸ!;C5kB;vlW 6|6saHG)gϞ]ZT:~8244$R"sΕʪB)^… 뿋UܾUãx'+x lYǬx*V^^nii)y䂯߹O+^PUQ /]dzxx{NRyys׮] K3QGh'QTzTTk\`A^rEndzMkKKKq1" ԩS!ss2͙3GvM]uK,QM յbx޼yO>ml/__ۛJR>}lٲÇmj;T[[qĉ.CKK˭[6ȥKBOlKJJB_uu5Bյ7pB : Gn۶sH$}ݻg׀M0L&/_\A @Э[7D277mZ7ֽ{H$牖dbbB&=ZVbǦM444dWKKK,|E@@]޼ywP/w_~eРA!d``0iҤSNRFRRҔ)Sh4\cǒH$|Y;((UØ;w.N?w\Yv-D vdqr Ɯ9s߿>|8x`řlvpppLLLttENg"^oٲ%== @}-$sRSSr-`pӦM1ʕ+ >>~Ŋ}*yDDBHGGgϞ=gΜOWN8.\ ixxxVV֎;%uM!4k֬@ӧoooo[?B&88_sRŋTӪ7 IDATii)BΝ;Ru͚5M.rmO@=qttln< ܂aǎT*uʕSnz-Bi u:vիWzzzcTVVzxxx46ӿbԨQ-)&&/^}BsέBhҥ-M^Tv5~@cwE7R7o48͛77س_xocc#;t2wG6UȎa:l0ݳgB( ~aaaD˖-[d{hp\5=pÇ>ɩ-߷xdZĬmgTv+5ՊTpp٨ %;ccc%??t:}Ŋt0K:{lNLLL"}h4BL&ƌ3+Wdٙm .Le\!dhhK 7BH,^xРAt:Bxyymذ͛7@ rʜ9spE.}}3f<|PoԩS={D999z_~dȐ!oغtG۷Y̬m@iii:;;M:u…֭۹sǯ^ ݻ㟘<ؾ}{RR ={znݺ5v#4D"{ڹ,%[L&o߾}iii !!!_߿ehh۷o7l`ii9e;wϜ9xtAd[AMMͧOp)aaaak}Ν7o޵kט1cZD"Q-Py>'2B .31|,L8w!DPlH${pttl]A̿D"1b%I^^1=&bD" [Wtuu i4ZUU1rm]vssJNN޺utKpIfo)h~0T~ܘ<-^ *8Q_*J".@cH$Ҍ3޿?f̘e˖Y[[oܸ6CRHի;}7|3̨kkkf+W|Ǐܹeee|>$Ã㥧7wA")//or~---PUU\;LP(]RQ+c5?}$H\\\>C]] 6PJ[w޽{ll,n3f̸+W###}||$6K.YYYK,qvv^dI4:?J%n͘1#--ӧOӧOm`A'e˖M0=zX~Obbk ~\JJl >.**ׯn!NU믿 ޿9sT9hɓo޼yIu,#G4NBNNN%9c N%ZZ$%%!œl6D:|{xx =`b˗lܸqʕ޽[zu]FL_|!dmm }B7oT-2ϤUC%wocL |kRUf:ܟp[H$? б޽;55uƌ{177|Ch e̘1/_Tw_o{O;QSSs>} v V[[KPΞ=++fMM n9}4JuU֭[4mɒ%*4ҥKT D"ciGp2"\`Çy<Ç,Jߏp8!!!K.eׯ_0`'O222mfll,wٓ\B(,,@d2)3$00099999yzzzʟ'^x!v޽Bk.www<|0h*Tޡl2GV 64xLk\e6e_AijjFEE;RSSs///dnnlٲǏùJJJ{277 kD[nM8QKKKCCڵkN٭YK?~̘1R4$$gϞMΟza8ΡC4400hn0 d?UX,tV]XZZd\Ԕ`,X ##C}i-ӧϸqrssT*!D޼y#ۈ!Zx 55`ɍjٟoڴIWW֓vZޔ)SΟ?_)He˖8==ѣGjxVVVGIIi cǎ5jTs;wn~R 훜WMG۷op8\.7pB :Ç{FEE!T)S,--+++U|=Ba~H$gB;waxx8>y$Bi5Y߾}l/ѣGgff;fwJ.\hii٭[[*[[[[XXo~zvvvZZZÇ >Be'NXTTdH&L066vtt\b1 ~W,:ujܹ vĉϟ?W&#/((?~òe˄Bܝ$%;vxyynݺEDD3(k1m0lHFReYcw͡CZZZX,OOu%{;7ovXjܸq!dqa'?}RI|> ~۷Bd2[dIqqR?DrA===}}߽{xk-===pGDDt֍f3,,L\\cyye:kV2)X,|n>}o߮X,===OOŋkA&L@y{{3K$=z &N(P(\xmR(X;w\'''&>o޼sხkãCBoT{/-]Tj2\PmrZ'JJJZjp8V @HNN 0`J ZfMdddk &WBBB[򔔔˗tHuGk׮mRk֬qvvJ;w411ir~|rz…oY.<7_ -Xϟf-ZI$˗UmIIŋU|%BرcΟ?KR׮][ibk׮BZ`T* luV]]T*&Lسg#j!|d֬Y?8|رHE"=z$7Q-P@B8͓p(yGq|>\]Bp_أǏe`&&&-Ww46{o!diiaÆRu|JKKO>h{^bō7_Pիm۶ X,RRR"{uqqA'""B՚U YKKcǎ1Leh'Onnni%{k H$<>>^%nٲeΝ*x"hĉׯ_@\z77Ω`0Fz!ؤ"9RXXv[jh4ڵk/^x5"YzsRSSǍ'G ߿Oܷo+Wdgg +VH5kyҥ𬬬;vGvܙajjJ,!;ӧBf͊>}Vf3GI:<<U=zt˖-?0`Bh۶mMnVh1m7mڔ-^GVQ.\?uttXx''-[(턶vDDD``)S_5F%^zD"Z(iff1$$.\gԔ})HD~~>7m4U]5HHHضm[޽BCC###SSSW^r5r'N}:!!A,;X:ׯoܸkںu+F[vׯn߾xbuG͞;wn\\ܣG_UUZI,'''7k)}}}\GTUU2+U]]]FP(/᫵ShϓdsssT:lذ &gD2`=z> B--;wȶ߿۷rRT&l ֭411Ypa+};ςX8q|M-"/^+'ùݺuk2OO%~=!4pÇgee)#[5.m{G޽O[69-߂c`؛7ongGVQ}BѣJM$߻wo50yd???F/// F\I$RDDg>SeeeiiiVV֍7ـ߿C|z)>Fꎮ m|-}>Q!iiiGyWƇ<<<̙>#nܸqƍ}ʽ[RR2eʔȍ7.[D"o]|yR4""b̘1*ɓ?mBBBLMMUֳcǎM6t 2pKRR---Ux%;wgψu9zzz{n2NNN'fP~^HDH$Gუc;MkbbOP9Bd f֭[CQח[~\]]w-hhh(Jd'MTUUU =z4hРM6J]]]eeeuuuMMMuuuYYP **l6G988ٳgϞzڵ+ 2[Bm|N; j1x_~W^zUPPb;`oݝL{D,'$$Jdeeա{,yyy >|x}tt4ϧP(d2憇8p@*/Zc o?lذAE޼yӣGԒ={fddXXX(^z֬Y˗/kwss۸q#B*++K|JD::Ν,YbŊ#Gv5h3"hҤI7ol0 !w]v<{쯿"~e9rΝAAAǏOJJVml666uuu[l>}z5332q~*Bgo!KD2bĈ~̌bR(m-***%%ҥKw}ѥK׮]xA%#WpNd!#iǴ1rYMjr(X|n5 ]ߙ~mIOO/**?Dٝ;wZ_ŝsb\( ڪ*[YYYWWWQQ!|D"x_eQF `0444X,Fp8ZZZVVVd2YGGBp8b"ZZZL& $d1iҤYfu !TVVϟGEE?t:U/Im|EMRֵbbccccc߽{ݻ PNW\e\N eڴiH&/۷߿Et;;;'>K,gdd$$$lj<!p흝Gf155ݺuU;u𠠠`]]]ugg>4k\ |~hiiUWWohBn{gz000`0"B0))k׮͚5Ν[tӧU'|1bY\r…1pB'zxx;wW^ {ÞxUҥ B(???$$Dݶ {v* #>׮][x1|2/)S1kko_VmHHHHHHtt͛7m2r++k׮ЬZ0bdE*ؑUr4G駟h3ijgd2.RiNNNNd/_-7s.]RRR IDATRjU 'NdXgϞ%62*X#Ŀ\.{ETxAitP(^`eWr}}}qH$i׮]ýd%_YYI"ܦMڽ{wsssuG V!DvfBBٳgb1LܹץK*<+թi"p-Ν;;d^hѼy;aÆ?cڴik׮m%䜜Ο?߬EVqq1>M.`0LDRRT"tAc t$ܜ'$$h8&"kiiݻwС'On|jkk'Mt+W 43y~mٲe…_Va Jݶcdžꎥ|w.\?.]:{Ν;3,,Jv؁5jT=۷%cΜ9>>>fff'N@~~#F hGx[S9;wNOO?xɓ ?s]ؑmr(عsFFFΛ7ݽk׮t:=##GUN)--]~)Scm6^zҲ~#[[[@PPP`llX۷o޼Xc6]D8 .++OUUU|>,<\WW\.L&kkkST6MәL&Nԩla M&6;d)T* |^Jzxxxxx-8e-&&իaaabN۷W%V" 322RRRSRRBl6tuuڵ+VwPmm^zBɉ8YYYx|K#lffffffjj ʠTUUeeefee& G~wttYhI&ٳg۶mk׮5kNWwhrrrcmm="2ZZZUUUtz]] v{yP$t CMIIqttzˈ#6oެU;6666..NSSS=@URR2lذ[n8lT*ݺukhhQ= gVWWxE%~'OwҤI'OTUmŋcǎ]jկڂ2[▫W1BP:Μ9'fz]}+**\]]ܹsG~^dɶmd|>%;;a̙"N0^oT9AAAw}KSS%FM&]fͦMx⃿ ^x! fccc]]]qKYY+**>}*;svvǏӬ\ti̘1O0aB@G H B|bP'V f4MGGg&SL&Nl*i^JdX7̞=_U#ǧ|[QQp8v2lmm@岲RRRp1u'F$nnn&E(~~Y"OSSg$uԩSNeΝSreee99988;;<)++t x<Oٵ@EEŖ-[onjjiӦqƵS8WW׸8255]l… t'9dȐΝ;x]t9pB{oEEE⸹ t$gώJiii&LzjW~z QNN_SSskk?$Dݻ$Nŋ* ,;x`G|ƷIڵk'OwѣLc ˗/zjEEŀvڥ)** z#~7!۷GyOlmm)2=~XGGgĈ>|]RRR]DdśSQQqK. ]5;M&]ll֭[444,,,?uT77&] H$w޵WW=߿AAQNٳSN=z$;T*e2{駟_EzzznƌS MmmmII N4\ e377?~?d d p?~$錡%ELAdh:ӧ̬,<066&rmllD/v<;;;;;;777333777''OyTh``*bOh 9NjܹlQL7lذo>[[__ɫc2'O?~K9rÆ {``Guiv___##cǎ!^xQZZڡmC"2H~]v:tHҡرcŊ111*:?>h*++I V(:|>1/lb1P]RRR222pܧO\. 077'UU:Bd|۶m/_>ϫW>3cǎq8ÇI$ۿ_uvZ|ĉr@0xUVǟ9sĉf_}kʔ)j9ZXXO$ׯ]699mmmSRRƍ>|x%d!BaEE+///+**|X,.// VUU)NqkCPtttH$yr PT6rl6bKjf_555 `>_SSg-//r|PX^^]HA4b_CѴt:ܹ3N֦h?\܂T*3ljBFWQ*eddd7o233+**<ȨSNƸ7###JJKK[6P(,,,-,,.,,$411177<ijjgA %&&9hhh( G[[.ouܛU^^.@L|ٷʈc<2b#d``/[[}@<<<ݻwՅ :88o?ud?|ЬEqv6M\}R`}`4lB\A@BPSSÇR^^^] B9p׉'NCx_=.\'A544v=hРS>}4""̙3uXXؒ%KZܳD"9rӝp_O#Q9EUsdddddlOkv hW^8p̙3l6{Ŋo'ex<ȧO#FD޹sG_xo޽Y5T\\gԃ/斔_~j/Ny8MCC`7&P^EEg\,|DLB ?K/xL4bũ<!ԣGŋ$s7n:tSTk~Ĉ^=zt=Μ9kff?XZZgffΜ9tDd6lؿXXغu&O WÇ㏷o GGaÆѠAyyygϞ=qDttݻUF#&^^vήK.4D]ܹS$5(syyĉG5{l ZJ$egggggeeerrrZd2Y6+q8cnT4e>Oo&2Dnnn\\.!;555ܹN7R]]]VVMh CCCmOJww-***((- ZPPGOqR2U?xW%Q4_(--v͛7E"LoX2P]]ǒ.,,$qb$аsFFF%;u=(@"E 򭬬98ﶬL([8%Wqn.B~P>QC<0\!VCH$a+W&&& y*3Y,kpkC.\4tiӦmٲPQ9::nݺU;}}ϟTD&eh4$"LLLp>dggVZ~+W:tVЅ Fuq[G^x1}!Clڴ),,,88';;e?~gϞvvv!mm픔N!/={cǎ8p!C fdd[WW%=ׯ_~O끚js;wDEE1ѣGر}~ldE"ѩSBT*Vε?~tppPsPPPMMU3hH$OzzO222a%i׮]kqa/YM2rZWWWRRիɴFԢYgh,L&kkk+%x6'... [UUU?۷8kPnTM}xlMT\I$}IRmmm//CdTeee< +E~f(Sgc/DRrBx<¥B .U)D16svT3.d'$G8Ds3޼y3""b…ׯ_߻wzKKK9'+))AX,e/iHDd333rqqkuq8[N2eҤIo@{={^V6}[Zj...7o mnWUUU.]ڴi~iccGUǫf cӧO:{kf͚%Hu6lذÇ{zz6WZTUUݽ{7nr :uԈ#%D{N:5HP'";w|2bҤ$vss=z4622{MXVUU'$$\~=++K,#X,# E$WUUUWW||TUUa+***++kjjdgl .p\cccp 8uXGGGSSπGl}P/annx%HXR $ nݺuBD"988Ai\lVL2p8>k ٔ󌉿wd\.W__~v27P9*|I~ᇡC_~„ ǎ;tPN=B(99Yx6]TTZZZ VDDJKKڵ˗[o] ~5܆|لBٳ?3gH${5~x*zYf5۷oWUU7MIIix33y֭[d=zvss#ϟ?zÇkkk=<ʪy<^eeeuu5ȲJ +(3L6p 5.455 8XCC`hkkPTccccccӧOo޼QP ?gaaDRk:6p4NY&xdr8q@O18O AKވb˗׮]l?@D"Q=&#lmmB틎T*yݻw/^(l7|ӫW/`egg?yٳgO>fff>>>2dlBPQQLŋw`7 \x1 Sy_'褤$Xb\]],--! =h]tҥˠAʤ$8x۷憓===i4_0RĕUUUxx6<  Gr  1 6fX,.B`0tttlq{1+++SFF30 FTEE"W\\ >cr%%%8ui78h/ O|lMMM`2~kjjB.kjjBMM 555 UUUxb9Q}Gnff&bttt.@%4k֬/^ݻ/ 7WRRfA7VY6*"QQQ,+..nVJ` IDATDF9::nذa͚5Æ k0ttÆ Oy$33Fyzzoʕ{&* wD/Ս?3N.\X.\nR4111*****ٳg!.zj0LOOOOOOlٳݻP1'UVVLKWTTi'i\>Y,p8ZZZL&S[[嚚r\:״pj8m@3)O8p~a zt^snjBէyɸ+ϯ)"Dg^^Io69l6J2L:L&Jl2M\bh4\xȄβ)//Ų|>_""pMbU?#ʈNcNUUU555VAC2魭P) c8-=y?0cƌW^={ť-hY"rqq1Ūhrt:q)*"qqqӧO744-%r/~稨$ _xajj455:dmmjժӧ+YS޽{UUU#G$Zlmmkkk,--[+By!_x͛Ǐx<*ze޽DZ999!KKKyyyyu)sB'N2dngX%"{xxl޼f˽UVV63f|7G$='?Nwϯwޞ_}_6&_սϞ=tҶmB^^^>>>}Uk@5qpc9eeex”J;dL&dβ8Yv'F)^r:::$ Oi-N\jel7Mne,^%%^^>FK#xNtbhّN"tttBVVV$I6D \b==z?/Y[[+?>"2 C(D"&[P jw!vߪkP(G޽={.\تtՉ'ٳ,KHt9&&Fdk׮ٳSND B(%%G5j(2===:::&&&&&f!CCCWWWGGG;;;;;;333MIIINNNNNNJJ}uu5JuttXd ~ KFegg߻w̙3Dw.H+֊+(֭[[1/K^^͛7o޼uuu{tRoo/)4N{xxxxx" ={gϞ:uJ( |"X.oO0N!r\.022l6%f2WՋ~ݒH$yDqMM $Tc\?F]]ڵkvMj 3S#W'1N,677ɜZL\WخDKT&{8XvBvko2vZlYTTԉ'`YYY555J^֦R%%% 6·󪪪䞧Dd ݾ}!ԵkקOJv|ÇwҥWm`Ϟ=- ܽ{w;)FDD:ujъ~:!sܔVYYYYYY3,..~݋/?K1 ɐ*YYYDqwPcf31"[jSZRGbIRi_IiMZN**m(JҊJRv c u0zq=rez_O>2664hТEz#rDDB2e`=DrYXUUW^5 "߿ĉ6>[nĤHJJ?F ԦM^,}:\?b$Jaȑ#SSS]]] rYDН}Ad Ôʌ|~uuuAR$Q¤ctt)bso߾533aWJJԩSaaas@穪}vXXipwrrڻwozz  Cl~fff۫5J133Ǐ۷"8FF(h4bرǕ:52e 000022k yͩSףwGEEEt/K. ٳgO^^^LLLVC?~ҥK?՝9s=N_p… ccc#""6lذbŊѣGϞ={ٽ [UU% l6-!`‘b;FDʿPbXII`v‘b*JR) @Wj3)ñx+?~x 衔ݻm۶~w߾}7ѷFwJJJeeeM "~X$%%cN=JAdtݻw .߿ 2a ]z!ӧNؘlii)rZK$oݺu 77aÆEGG3f=~V~tttWۛIJJ7[d2}uvv` FРhZZZ諶6L/:\qqqQQJ*++رc p_"áC6ǁFeee}v6MR1 ٱcƍŋ\rEYYyƌgΜS 6nܨnݺ.hllvZppg>}t :kQQQHHѣGw1k,___љ%:DmmwSl6[F4y! C5w^:;;[1JAGGQXOOOSS53foÇhYQQ177uDp8moZGd)))A]%t5:͛'&&bfaa5=qĘ1c\5'-[l߾}ݿ֭[+))1a„ 6 ,/b29l+)*****񊋋}VXXעtiQQQZZʒWHTVVFaeEE־a,\ U>r(:ʬkkkب(++ zoS(1>^ZVVv֓H$iiւjjjƏ=rppxqDDĕ+Wo0>痝w!C(KPWWߺuM"""8`mmg]]6jjj*))amqx<^MMMAAAAAH$(((W,FEEׯ_E;X,D266h...***0:  8˗o߶Sŵ{%%T, jt:=''ȑ#\.]vv;;/YdԨQ***]v^9l6ݻ!!! .w9u5{{{;;_FEE{ĉD"ٳga;2 ò }xFXUUURRRRR"[RRR|ja222PDCT*LFи\܅Ꚛ6p*ᨱp0yyyD?ʾ} d ̖#׳ÇWTTl-a؈#?~VX1j((/(--m/_tsswE=0狻SUUuGq\>߬nU{V^ݷoߵkתԴX1FFFQ"ЋxfQctS04EEΛI24Ǯϟˎl. r;;"CHWW/_XXXTTTt`m{쉍]vmxxxל~ǏNZQQ0l0qƌ;; 6Ι3g̘1QQQfffrrr;jkkcʠ"H$黙袯ըk#ʿl/B222D"QVVVZZD"IJJxB!d2YJJ 1w+))aa\VUU+RYYYYY)''fy<a,WTTpJ/nhh@G@{o2:iii|#l"z{x"PRRj#<|𴴴/_vZ= صkSRR]QVZ1 [|acǎMII `(:vXܵjMMM9c;.((0 ꢜ޿444ddd]>dee_tҙ3g>|xxpCCC>~g{%%rݎRRRz|>@ @?w 02vT*رc'Ovvv2eJל~ԍ7ݍ㵴]{>}:J9::&%%999(**1ŽVVVV 4j]jkkQz[ x U8+zv^k0L&-lCjjj6j-%'H]KJJH$FAmѠ6 d**'''++N`Au6mwD>|x}}ƍ,XFC_AUUsbb]V\]HQQQ>}zhPJO ge˖͜9+&&fÆ VwQĉ|Y1Bv8V2@#!!q1 dɒwԑx|FFF;ȊD"}#2aD"Ŏ544ȂIz( GGGM__ iii]5Znk׮8q{Ӕ_xa˗/]\\?nܸcǎ3&n1mh",H$~cg |򺺺;;;yyyI Zd?ho߾;NRl6;00jLcAAg~@voak7n^vm~~޽{]}&3$c*j``(j:+((x+pAAAVx7nD _s{EEE>bdr{Ȳ B?Vw7AdHIIgggp833..&&&6m:|pN|>fW/+**PﺆjC .IKKʢeD |D"uɣl̟?ի{n@P-[)))ܹs'Om(łަ:**ŋ|"gϞUSSk?pы-rqqYn]``` ρbjjF999+++HaԩIIIh^xO*aݺuJJJ˗/p8\.wJo{dM2eƍ 28nժUnnnWW%: pJ`0PEMnkŊ,k׮]rL}}ϼaXyy9DjqjfZ숌 #2bciiaϻee}yzzΘ1cĈ]|vT\\۷fx**//O&) ja!hreeeQQZp8phH éh4MMMMMMMє:[<|ƌMMM]'Hɓ'߸qcǎ~~~hOdddv޽z∈|8ASSSLLLxx[PҮY?c{BF(ŋ|||ϟ?%@RPPp…D&64#۷ORRfSN[۷o_JJ˗/ŘB0lȸx"qqq{?~ׯߺuK8hr9`rNNNNNt IDAT۷-Պ+=/^[4 0,;;{޼y۷oDnڴ|ĉ?("o޼yMtttrrx'5kVNNߤIX Gmmmuuuee%ͮF˂w,xh 49` f{: l%Ms!RRRrrr$ HPeaٯǏQc6f22@A9cǚbO5kswwAAA 'd2]r{"IGG'77wܸqyyyL&ԩS<]r;55H$vq8/@ ł(櫣3tP.Ţݗl6 I}!!!!??_BFFF[[@WWW^8p`Æ #G<J=21޶m lٲ~5ڵk4MfAA=[ddʕ+ Z@ {{{w}mf̙aaa'OwEٱcNwwwuk544|p\)))ٻwի~/۩ ܰax+1 [hAYY٫WۇaXhhh\\L^`'?y;wN8pBA._驩aޞ.Fvggg?ar`gg]veeeo޼OLLt钍ͦM._loo$ ".}zjHHۇNϔ]v֮]DDD:IEEEX,VUUU(ppP¸fh8+dh@wkjj"łsmmm]]]}}}MMMkS`&###''GRdB_3nׯQ&02lddDѬ]\\@3sLw`0ژ  rkS m-,ňBAd#߼y ð4 /_uq%ǎ355?vŧw+((HKKKKKC㌌ ԇán߾}LѬ-np ߽bPo߾eeeedd<{ܹs¡$NG}ZXX'<]]]hѢ/W=<<|ݺuϟQQF:99GEE :jii1̺:Jhܸq222NO7ntrr hðSN=zuXlFCCw^YYuݽ{7.. "˖-w!ذaî]pSSor -Z$ð .W\iӦ^LPP fѩ ֬Yb TUCCÆ _<͛g̘a/_d2/큶 5r޷oZ3sL{{h ʕ+-[VSSD}$??٘fy\UUU*2Â,/DBb|A fԶJZ8WVV,ǏuF ZZZh#n5F7-7!p ~)޲۶m/n(YYYrԞ 2Hi͓)jNSRR.]*!!Ad m۶]v̙]|vzƏ?qjj*FL25RV655m Q:99x<^__B[[پӧO/))yf'!.^gΜpBpppPPӄ ]>7n\hh'Z Z8y(JLLL\h< ף^oB$80bĈ $%%EDD( f{h@P7o߾5 "9rd֭$iOtCCC4$Z1 pNNN'OTRR0 C?kOO>:u$..nĉO?tGGGӆ 233#՛IMM)==0oooooohǏq5dȐگ_Zyyy999yyyh9//X0oQTUUQ_xH"LCChgB~Uqqqii`{*JѴ4D:::ZZZt:.ƼL!(y7HHHtE> c޼y|>ۛBY槏`0ꊊnbYYYvD|CAH 0LKKT"hhh̚5͛7p%}'O%YXX.4h biiiǎ+((0LQQvȐ!oׯݻwE3ʽCCC/_~ ___]rrr۶mz0 +(( 2 QQQKHHhiiWeqvv5kѣG]={w4@hQWWeff ]d aƍ۶mۧOP3_ &sXXX~e@@@۷8Իo߾-[x)SܹS[[D"=}q^^ׯ×.]jiiijj i~<olllllf 0dP/N<HHHxCCe˖K!!!N Yn]MMM8n֭&&&^^^_|9<@hϫ=Zdcc3}H=~xN?zh힞w=z(DwQ@ ߯0o}SQQ!Hu֭[ҿ}&%%?>((Hexx8a&&&mPRR>}0l޽hɓ'ܺukh9::bGXYYݻw^^^⮥eϞ=w!ӧO> ǟ?a8N[[`9%tf~MHIIiiis<{ܹs(b+)))%hIk|~VVV1)ST==={{{5F5K(((L>'!--=tlX^^N&qQX;rكIKKKRRw]SScaa"z4558|pq@˗;w=yfxxx:JCYYLJJ{nJJ;1bĉǎǎ[ja͂Ȍ3 FP\\5ÇNNNuuu&M @>}q]]#[n6lMDD]d>|?FkШߢ" 2azzzYYYkBCCyVVV|t̘1W^ݽ{ڵk1 8pO<{ggc6u<~Ydɽ{.]b,--̤=zt锔9Q@g+,,|۷o߽{ݻ>[ &Md``ҫ0Zے355555md49?~8,,fc`nnnfffnnnnnnjjڳp\nNNpXzux]]]3Fc;vΙ3J;Gw:::ͮ{UUU9w7&V {GGdw }} 8;;?dN4\}D矴uѣ;wܾ}D"qȑG6l5.ʞz%%%%:i@aaSJJۙ3g>+DEEyzz8VVVMMM?n1[{xx<|p׮]˗/pt,0CPn߾=qD===KKKܹnϙ3Gx=:##CФױe˖/^Ge'f͚={|fN8֭[hGU˗:tHxw?^y)Sر= ?cǎ V2oߒH_gwKjcf7[۷ox<077߿Mhc&. 1p)JSLyųgZlo׶q'Ng ?~߾}yyymotD%%%jjj#Gљ4iҭ[~Ƞ;v, R{qttB*۷oswwW WiiitttLL̽{8ф F Laaallllll||tRq|׭[7qӧO+**"***tuu֯_/^QQ100Gt[FDD|}=|֬YMMMa]\ɡR dkko>]Ξ=pš7ҿSSs5l߿ONN-&iggpܹcbb"rhYhhҥK F)//O&I$DR D"5 $mb=x >>>!!ihhDAlll(kWnnϟ?ŋ7oTWWKKK8~̘1 Bc buuuhf9cMг|z޼y'N|=ܹԩS۷owwwGa˖-׮]{ʪ*2-++SVV9rd=O~ 1 ì^za۷o[ٳ][2,blׯ_?{l||o5n8 Æ 6l \rʕ#Ghhh8;;ϝ;J5"V\\ܬ[g3sȸxbPNNN Aۙ3gN0M!c6hР/_zyy. tݻwoٲE ˗/-____PP㔔\\\N>ahhwo> 333sppعs] 1tttttt\\\0 kjjJOOONN~cdQP(Դ#fff6-??0 :ƶ䱆L>Z@000Tn^^mqEEr2T[["X[[l%꾇B̨LSSS토#2.]V[[{̙˗s8k&%%|R1~M:Cfffhh]PP0lذYfA7ۿ;"""77wnnnrrr⮫FC"bڈo޼֭[NNN ƍ +K=Bbbk (?~ӧO"-Zg N@@@dddzz^߾}xQf͚/^n ٳw)w{7o޽{ 9[%p6q KX˗/ wpphmZbZ\n񮒒.+z4DMeѻ)iwSVVvk׮ݻwxvvvFRQQwuxl:ݍ\2sѣGvUXX(IHH\|M>pϟn:"`455YYY~"44jwsssuuc%t8>޽Cݹs`,[l֬Y::: sssss;w>yܹsk֬ٸqy,Y/꾯ZAAb~mN8,pu IDATNN7m4ii༼&勑۷+hjj:uԶm*++WXѧOq~!<֭[# $ZP[[ݎˢw׋ E,zjw~UCOK___///%%8BCCCTTѣGTĉM}Ν;ׯ_̙3}||  oH$ÇΝ-SRR7~T 66>w7Mc<==|>733{m;Kz5 RVVP(YYYcƌ166NKKsvvLKKA'Nϛ7/**Jܵ_yzz.]XE###s!WWW##UV͛7x1ÇGFF찰 lMM G/(edd555ϟm۶Ouu{LLgϞ-rzL4iܹfffϟwQzիW]Z~p_|y왇Gk)d |ܹ_}V^]RRrǏ,Yd̙m& cccuNgeegKEEE 0 j{cYYz.j$:4iR``󳳳&Ocqavcdž~ܵkב#G6lE&]\>}ڷo_XX֭[gϞݭ:r\ѣh;ݸqɩ3g,ZhڴigΜA7WƍmJm+--?~LLZ#FxIIImlc``uVѻV\ӧO>qŋggghXsݻkhh8{lWWWh :VSS#""y鉻[f &_Z|4b'@y5Z&MMM:::K,?rk{%%%ݹsF-X`ZZZQ%)))44422H$Ο?ժU?1kvn訤z5P(ӧL0ܹ3i$lAm۶-Y0IIIv A`ӦMxc6mT^^bn߾-UVtq()) >{lСڢrttdX7nܰځ/ZԩS}qΜ9_|ٽ{… ]1\.ZSS?#z2رcӦM=q|+VlذA,^zիW7n;vٳۧ&ƒPBJJ ð5kָFFF6jjjZ<HNN5k֨QΝ;7mڴN.[jաCl'99900޽{. WHHǏUnnn/ A9Ν;/\ncƌ3fѣGn߾}ҥ۷S1cƠP2\Wbcc_xlٲɓ'OՁVD"جA{466 2(\YY2&J6삂ʊ֎ЀaXffƍ ))gΚ5K/gW^!>}zXHKK/]tΜ9 , YU[[[WW'Ԑdv'Г xױcO5iNAAAccc{WTT,//'-D"REJJ-KHH8bAvmGGJ2lcc3bĈhѣG>Bܵ2zc:tHk֬ ?B yyy:::Ϟ=}f Ǐ?f2,AB \nkJJJp8H600صk״izStw+V~sZܢǏcv-PWWW[[fguuu555,e9NSSSCC#fy< 05H$III>XP([aT*KKKR(44J%222rrr EJJ ͠a0ѓ'Ohl:ݍ޿s/_ ߼ycee%5 0**##S\\v[`zzzeeeؼzJ]]]MM---[7mtٳgz q[pW^:ujg.Uk?+3&N:FEE8qBKK یpGd &DGG!66vĉ[oÇ ׬Ywر =Knn)S߿?x`qٽ~ҥGݽ{7Mus۷oBlK]]ѣG͂EEEOcƌK.}W^;v,)))//OUUoȐ!?~7nTTTGm#2jkk]tUVUTT888hjjiiiw^q_zѣGg̘'ArK Ϟ=swwg2aaaӦMScޯ_ߛb3qD  oKӓ ƺ]v'fֻw&O,##sMCCCq r… /\`ll,r@--->hѢvfϷGOMMm_x.mQ__իO&%%={0==[YY0|O>͛7oްX,קO{Ȑ! ڣX]]-xr7i$GG'$$L8q֭-UXAd??;vov 8yvv஥K9rD >>>!!!Ndee,%%U\\lbbR^^~Y=ٳ4wkn&11qĈK,9z`%x- -ZHHH~z푑n߾lg{0р8w3v3f̘wޡo)~MuuuEEEEEEL&d3U\\\ZZbd2YEZѬ-,--M&dddTc\WW'^@nᐃ2 %eޝC?6vN$iA]VQtҪFV6m*"E%[w.b0<9s׼y_uEEEmqq $veww!O D,{ƍ+V 9kkk544Ν;7HϦ&AAsssvٳg{{{GH@&..sN77Lׯť3:Ο?͛3f0:ҥK\r~E=WWWg@II {cccJO</''ǩS2rvv ؞>}cM ^{謲@իW?~|۶mc"Bں<--?7ottt$ngg.''AY\\,//O .,** ={6GFٳg7o8^//9s8!˗_?wOk||[Īzzzݻ)Ņ_2 DOO/55X[[GEE]pL&oݺ!44Ƙ?~Q^^h_%!LJ#ܺukժU4mOnZZ!јLf8__cǎUVVw@AAAjjjJx<_RR ;033P2V%$$DDD$%%EEEq80c_30/攗Rk666)))c-s asRDGGg ǐ͛7zzzw ˝;wgΜqy$@hkkr1 %"#ի ӧOӦMߴiϟ)~Ǝz---CCCl9}?vpp9sfXX//UWWm۶o ,\RP}d˗ܹPUU]zMKL }}m۶kQRR\d e!g7z{{8˗ch˗/Ϟ=W__dccgvvvhjjKDDD444<==MFP?.ZHDDŋ#7E~~jffl _|ׯ_srr( p߿WPPHMMӣ}+WFEE;wn#ϟ?}>}rʖk׮Y[[3:={ϕpqqwCBBnZ[[}ZCEEE***.]ڸq㯆"a݂|i>*""" t.jjjJKK)x|qq1ȨqxCHHQ#WWdF׀ uuu# D"e@AA᪫5B& ...ʹ݊ uuu, 'ɡ|tt O%4662> ~';cfff"H{H4&"x.JJJ/   WOOOqq1,۷Rxw`bbp6$)) 抈ZUCC,w]YY OCeee0GEBB^SQQQRRRRR:kȘrҥ;wSo:+Vttt<~x1/^xEFGGޙ… ֭nr @FFY_[d|,)OMM=}4VLD pM6={v N:(K!2liiI~ݻw=zٳgC"R~ׯwޥPo߾cǎiiiK.}/ W Ѩ-[Q?~899V400< ?GEE}VHr(ӧOfznJ_` 'O|K.vu..Çoٲ%//̙3h񥣣cʕ^zNcM69sc3g*w@s΍$"?~̌C{"'} 4baaMMM%%%_~MOO{zzzss3<7ݺuk۶m"$$4i$^^^~~~XUtinnnOCCCCCCccc:Bɮcaae6ttt$%%%$$~p$82fIII}QЁ|zzgaKz:::111!!!ׯdݻwƍ{ݿ/-` >>FNjuLLL߿ggg?x𠡡Ʀ97 4}ɐRRReee) ާXlj}inB&''Y]] |~ѣ_vppHJJIu2:^|nݺ;v?~|tfxxxl߾ݽ{nJPl 1C&"DFF~ƍI2vrss۷͛7:޽{i}_xpiY{{{aaassǏDd___ww.؍5䑤Pׯ_[ZZƝ"2*!!ҥKíL:88XXXDFFc_ff5//oRRAtR]]UV:ujͣEZZXXX|rժU񝝝aaa47lذ~z~~QXXx7## .bff`ffVSS')uuuବ<<<cqrrJ0 hii!$@ P~677ĖVB<`0&MҺ0ZPPIJJz x`Ǐ'$$XƯŋ¥gΜԤƆBBB222ϟ2eʪU/]oee5k֬!033۱c ,,z -##<ˏ]AAAQYY HgggH$kjjب+))IKKk:bccSUUUUUnlhh(,,svvgbbbdmmm555q?ׇ&%%U__1d:333-VDfcc$磀iWVV&--{ccc閖??*FGSyyyω' ˴iӖ.]2j_Rv@EE'*$$АIyVffP]]gee9;;ebbZpappIp(b0&&&X. }>"q8\uu5MO!W[[[ttՀ,nC(222$f~~/_Λ7ҘpvvX,6--M__wrR333,X`@Ɣ/^EDD0:d0ǎ;rȼy󂃃_9B'O,\իW\XXݻ_|푑˖->D222`0(7Q6n?b0?`0))?R>V H$644ImkkKnkknooG/t$ 'Isss<>iӰ gaaQQQ:vk_ԍx<򨳳3\JyѣG<J&444***>}jmm sʕ+ A<}Ɔرc%oٲҥKF99lnnnZhi2AGGڻwfϞ}1^AAAA&>$%%}ÑԄ#wAhW^^  {{{M6m4)SpssQRR1ùKVٿPPА N:::.Cgg',MM"5SG$$$ًSSSmmmkjjDEETUUO:uV 333FL|vvvW\XG_>z(666>>ѣGN_]:쿚ڧ?,7 p;H$ǏKIIqssR~-bDp'O:tgf"tcc#@{ `-ܚG[[;##cѢESN ?gŊhs]vߐzioo.];Y>|իWBNJ"RSSgϞMh̗/_622 tuua;|phh?R`0^^ޘ,,,"""@gggWWWGGǏ?777naeeEdW^[lYdd䐥,O>m۶͛7|||LD^loNNNQQ;;̬Y֬Ypvv>}:BX" %%Ν;0Ǐ߾};..N]]}nnn})ٳX,ӧg>{,777Qaŋ---Q2 ';is)t?w%s>۔ܔϷEظ  W[[[BBBRRSRR W^mdd4u!O8#CHJJJJJRvcrrrHHH u =wEDDp8~(qqqgKIIʆLD"2F٨f4C66ȸj*///?.,,FERSSFd)rrrzAzzhΫuww-p;x3cƌĝ;wR~SS_.##CWW099P4V/ 8>ܹscbbBBB~vׯ YC y$?ҥK[n,4ڑH$VV/[}Æ BBB:::pG-]vuu_Ν۲eoѣGvyqٹgϞs_⌎hۿ```^^SXX|>%vc>}ǏZZZ2lp ''gRR HJJ;w֣G dBUVV-Aq.`p 9'D\%9{oJ>tg?^1ß|||'$8SZX,b|||\\\h7  B "D$ՍMfhh.!]ZZڳg>?~)++L<  [3P988aj0_`vvAd.. . YѠ ۷ѣ݃'+((Q7nڴիW##?  {rrr𶁁Kq8\vvODܴiSDDcA&W޹sg/鳬K. ?s u+W&&&^|j*[CCo``e))[nNcx3dqq+WVZ5d?gg瘘&mmm 666<affc@XXX`` C<e˖/^8;;gee ffff͚ZYY}ƍo677srre;w(++;;;9s}C Dk.\#,hmmamm%K0:,??7JJJSNpnj3OKKƘQQQX,7#Da׮]AAA555Wp133?}e!#6mڴ/^ի挎A覧رcGz= COO@hjj"ކ?;;;;;;::::;;;;; Ÿ 9nL\J-*E wP2n988w8(A555$ UfJaDbQQL92>e>1b<<<<<<\\\|||X,_@@?ۣrlAA+--}Yll7o֭[̙!ÇؘP&&&### իWO:7===uuuuuu555%%%pH, a^/m̜9s|||H$ YLLLeeeCHokke4 ,3% )Tsss߾} (**D2]ss]\bkk$((&&++,vv- `׮]޽n?|[}61~?BW&&&t];;;F hjllܾ}{hhu;&&&Ɛyr7>`H#!>>~EEE~~~Cd@dxZ[[)>>ǏEC ~$?lǎx<~$hkk[[[i4iU\+[[[55'O-Qʪŋ-[`0WΟ?L4aaa_5k]p-nu]]߼ysڵoߦeW^ϝ;Y^^N$)30<AFǏ6nܸ{n Dq , ctD2.ښJ^ a1nww7) &{ڿ ===+^}T_͛իjQQQ^͘q/<<ήfppp,^xڵmmmcNooYBJJ ZН9 K2r*++/\qF>k֬)S?O+|RYYXXXϟ1LTT2Ggee YYYFSMMӓ'O6mti...FG4A֪9luuu"""_zeaa1w}̙3g#Rd7oܷo_}}ݻw9s]]]! ǏK.7nٲ-F "uܹXÇSP{{{EEEmmmUUUUU9%H';;;un٢'B;"H) i(1988p88NAAqʕ^kkk;;;+++ "ɟ>}wރ*++-,,mllXYY.㙘`F-;훛_x1H///_ 9ڴiӦLlhh8H-[Q7zzzx"##`mmUVV&))IKsAK^^D"*** \rС|MMMF8VVֻwxxx\x JYYׯ>}@?en޼9}ʥ@b\\9ghhh$%%GFF,Ȁ-Z4eʔG 2:DEE=ztͭ[^~}ƌj"pww:~8LJJbbb$SRR|\;;))):ŋ ;;{o߾]zѣGў={YBBחa"쮮NNN׮] :-C]vڵkUUUϏ533ctP2V񣬬 Lp"""e#tKލD"Ꚛϟ?UTT aaaIII)))YYYiii)))iiiiiiǐAA&T'O(**[n ~Gd1Ocꅌ`@x}}}o߾]ZZ9v011:uŋΝ[l= Iؘk׮ݻL&ʚ̘1cƌ&333WW?~ + IDATTii)- tvvZ[[{Rccc4k2؄qXTT5RRRׯ_155gt87d {N`K,piӚ ?~Po J_o߾upp;wnHHMPwڵk.\xA ݚ5k ׮]kffvݻweYӧϟ?>|PRRu?C~ZZڰGfffqㆡebcc69rܹs222O~ѣGqqqD"ѣGhmm߿Qaaapidd|rs)%%E2prr*(((((f䗔Prx<,C&''7ydEEEȠ|AA@OOFbbpʕ+Ξ=w={ݼysV򚚚ڵD"$$${͍gs̱2eH0KKKyfDd{)++%cxQEd)<<<?~`l`ԩS>|OLLDHKKۺu+qbiiitODoll6eVVڌt]]]@r9ww {$H$77ϟ9sf%_~Ǒ#GPӯ:{,}%vvvfͺpBllܹs)۷VDDdS$&&Μ9sܹO윞uփ h x!((ꚛѣȫWZZZZZZΞ=[JJ1"Hxg>|>w+WX[[ CIIINNϟ~w[ɓa%Z· |jjjQ͛7222Д)SG=|AAF***ZbEnnn``ZMϟ?8::Vq8܍7o߾_̚0}ׯo߾ߵk4>>h<8'&MDߑZZZUUUMMM/^H(\(**FEE-]t~jjj)))%%%ލ7oVRRrrr qBd2gϞ]hA@@"##׬Yא@ {ҥK[l111痕>|EEΝ;=zQZZ+WڵkҥZZZ( Ah!**:}kz{{߽{ӧOׯ__zpJJ333Ҳ޶mѣGoܸի/_ fAA p̙]]]iiic3 nŊ8NMMm޽D"O2djj*,,,,,ljjzU3D}+R|JNNGOO?~# `ll˫A=Bmmݻ{{{)ς#PE'HwqqqRRRrppHNNEQݽ{*sppX `FSSS-Zdcc:S+**_ƍ߿߷o_EE??={I$h4MA^WyyC755qss 6*"2;wӣܽz*WBB30aw33W2d̈́F\\\LMM>ӧO:!|||mmmtO>ihht6KKKnn(Fǂ0XNN>lj'z{{8ե6gDÙ_װ0brrr\>}4iҔ)S2D"ݸqCLL7 !21uvvݻI]]}˖-/_D"DƙSN-[ ó`/_0::=={KPPлw&###}?;477KKKS?L=K<4?+&EAdՀN00ܹd===IMMGQQ{ȡ._ϯ5x+W tx{۶m!'6ŋ(w>|`aa```ó~zJFo߿0:c ?~C'Npppprrֲ=***ʢKtQQQ1yo߾1:dL9qa^^xxxӋ2%**cW677tzzz111QUUemm   566>|ubxyy̙wȊ FE?v횣I߿Gki@ ;vlѢE xٸFLj z{{ #""|||lmmfff)))]]Yf͜9SGGf<)))YZZ?QQQ߿G+AefYXX̮_Λ7oJeeegΜN9t-333##ՕpeZ=z6>|8|VVVmmݻwG}LMM3gܲeːCzӧOwuu[4prr;v{nI,L/^P>*D^^ޠ !A---sAkQWW`)䏵i&:&QTTTnllddddi-S߾};jNHrrrhMGvv.*tvv2:@Aٳg`Xcccgg瀀[n8q\^^b:::G}a^^Zu  Ȉebbbaaaff:uj@@@f,{yFJ#]]]?ɓ9&&&>T\]]sG;vuO%Xed[[[a1r&2#>]>:u|sss77ಲ р/Wo@%%% ݻN<f$xga۷1 o>ׯtb"\9#iii}`ff&##SƆ7 YYYӦMsqqct,xӃbCCC 'Zx1;;{XX}{mGw驢k33L---37n pႣL:dddXZZ*((<}T@@ cNOOӧ _%%%FG4ijjDEE111'O^~v999GGGؒM߿D">>>ׯ_WQQϧO/r#>}saaaoo/QVVTSSSRRgccct=555}999eee^^)S߾;;;E:kjjzmllllllaa!''̙3MLLNO) DRTTӧ7odffb0###333ssSHϟ?H$X8YOO@KK -" BGR,5&&&fff2rJ!!$cc㒒iiiJ#PPP@NTPP,8!!,MMMʳ233utt`M,FW]]BڢH$Ra!#L 9#]]\ݘ`$'IV?qd,gg>P/\088xҤI4GC@@WoXzzzfgt  o޼ Ǝ˗/N߾};w| ܲ{)))SN<=&**ںx}{{{"}W^YXX#UId"aff...$"EFF_>00 ӧU FMM-33JD>@$ccc###3f%α)##ɉò UUU j~~>33sww7`֬YrrrnDd667oN `:  ǭvH$ǥkjjp8pCzF J0^@%F _/==ݰ`~ ++뀿局ŋ6lpss[f-dhhxܹ%HHHTVV9<`Z[[ S;;;ga~?Ckе[d"`gg/**?~\QQFTT ͛6lؠEA~Ǐ)-?~ޔnBw8gȓ'OkgggEELfff^~?>j LLLvrr*))r ʙW^jժ˗/Bdp<ž={ݛpUqqqFxΝ  5)**R7n߾ӗ.]ڻw/n薔eή//W8qbѢEb ߾}{oooXX؀ 5:::_䰦߿Êᤥedd$%%ddd$%% ~+---+++///)))+++++/J6&Giiiaaax<^FFf̙۶math0ʕ+W\ ΎݻwmLLL-[fgg'""B"~v֭VnnӧN:uCA!OcgggWWא4v0Uuwwwddddd$رc.]1G|zzgaǏ;&&&޾}חx ڰщ Y~ %88"i!''3777ٳ>}0LOOO[[770PTTܹsΝ;322tuu_xA{oS~X%ںu+ۂ ,XPSSs+W9::\CCt;YXXIb}bS1 S( 勋)w@FF_ŸІŋvvvZ2 VVVuuuwZAhGK}֯_/&&0}t?/1'""k̙#1 +Ϥ_xhiiWUU]f͡C^|`g6l ))lٲp^^ќ}< ںuD]f׹s`)S>}zݺu֯_hѢk׎,_~ebb꓈Ƿqsιoơ# %KOgdd4s555wY|y``#B&>>>,۷o߾}+)) /_,//ollX8><$.򯬬.))ZXXX`R<< ۷o￟?l7w'1BLww۷KKK;;;1\[[͛7D"L˿_xzzzxxXYY[nN!33D"&'''$$ܼyˋtƌӧOa+CA请UŎ?~i$}aanp+Qz=OKwww>eL&GGGG>X>w033idxyyfaaj i#++k7zNNy#|||C>k͚5'1Lwwњ5k&M0ŋYYY.]~+WLLL|2`ժU[n`Nlxcbb\\\544x|BBBHHHFF- IDATYDDߎ;aaa}vq2_mll2221ŋ###O>ݧdqq+WVZػxwf030ֲH ŖHQhS"BJh"iQJ,eNo3}!|3νјw~gPi ===MLLʮ_APQQ==pa $HIImݺu͏?>Ν;WXᡬtt;vTZZگeeei4ZMM~Dd4~EdFvCgg'+"x:bŊ8 |˗/[.Ƌ N:t,ijjqDEEEZUU;v@`eeU]]=I>| |͛Gn,um<Y[[[ oEE\)[WWXǓd2,)))&&&&&&** +..xhO_ƿ9{edddeedddJ&ѣ qŊ;w;!QW"{Vݻ|''^SN2~M}577߻w/222%%eܸq7o^|UWWz*''BXZZZYYM:]MKŲ9Xl}SiMeqxwCѪߏ8S__ŊNNNr'NҊSԤWZZhY~ٳg>+tkk넄^<~/nsN17n۶ЬGU̍ EGG掇ǥKxK,y󦿿.͛7&&&󏹹Hر#GTVV ss3gM0EFF[ i%%%uwwYkˋ8:::J08XNNnժUNNNڽʲRRR{.ZS?N$삃¢ϊt:… 7n˃ HSSsٲelNe3+++444''_QQqƌnnn4QQQ7nՀň WcMMWH$mmmIVMMMaUU\D?^|ŋ߿WUU6m]]][nmjj277?y򤄄y]]]۷o ;}(-H?~:mڴ[rg8pŋ֎<yyyCC÷oVWW l~!ή~:$"W^<8{7lz􌊊Hb+77WWWƍ...H2N:s  6Zs/>JJJg޻wochh\RRrO>)))l߾@ s1;;r6.X 3 l2oBL&CCCO{{܇!wjkksuuMHH}v0]]]Gݽ{ """OMzzz666#=˗njkkSWW'Y[[:thC{>TQQٴi{߿NtR@@L:u*֫ϟ?~zN p8\eR]ŵ~shfst,j1sAh_; ͝;w֮]1j&n~'HDAA.-U&.fΝǏy󦪪*ŽzCGGO MOOOk&rX/fZ*]]]8N͟?ҥpNgkjjN:ل`yzz?ӧ1?/_^h ׯ_:tɓ'7ovuuIIIśa̘1{bݏrRR+555d2˗I#߿722*));v۷]\\"""VX1 @"20JSSᕼxe:::LLL:::޽{ze.k ,u;w Xp{:TSSQURRǏk׮TQQӏ=k6JQQ1++d}wSSSIJJ5k e``kSee֭[սT*vҥEEEp^2":::bbb\»h VGGW^EGG[ZZ"0z\{CJ#ϟ?˗/Cw~{zzfggkjj[˗/^鐘}-,,իx<~ҥ+W&̅3A Q(Ɯ. -f>FQ(`0"""Zp3op8kFFF_<}:cn޼\jժHGGҒPZZ*%%4}? I/mx1r3IcFFb&&&˗/_p!Ν;gjj\9۷o700ɹu֕+WZZZn߾xb3kjjBBBΜ9cddtD#۷!!!ځ\8A\\ѣGokk{wwwXQTT4!!‚ј9a„BUU{999]pa͚5C{F(QUU%++y|:'W"3WM***BCCI$Rpp[n߿o└/_k׮~HJJ^ru'kooOJJR,֠x!8֎?'|}}lǥ`d2/..f$SJII}Ο?hѢYf Kƌsѕ+WZZZZYY!ΰ).. yf=s~ݻkkk BPlAФI&M`vhds2׀???% o͉}\ۛB?~|DG/)))99YHHVKK gdds̑uΝ;G460̩STTT6mTUUuر^媪̙CRSSS544lmm=<<6m_ H 7lذm6d! j,mbbښN%&&뛞yfGGG+++ /ѣ 6۷GOvڃ={6c c]'N(--.\HHH^z;Nzjxxx\\ܥK/s#߿y~SSS2o߾Cm'L@"JKKBBBvKv"aZhQyyݻ?~FGGN}΢7oBwI-h7Y<Ox_ōaSNAf͚ϟ߹shjjh($$kN2 ;=olwPx 4ݷt} """*F<ŀwZ2A&Lxgofffhhh"]ŋÇYSSsܹ۶mcVs^jjSCCCWZѹvڎ;o>w\p|}}_ma+pJSSDd~~~4k$ ^E ~k=CBSSs׮]-WTTpssC*}v> {%%%d -}ZXX߅3j+aPL> ?Bp`I&mWTTvppٴic:bн{Z[[EEEƍSUU---E:`h.\}:;;'N8eʔ.Ό>}z s&6Z[[#""LMMh{ZZZk=z$%%t,.mmm&L (,,Ȭ411 ׵ 6@dffpodn5kA̍|'?{IEE9OLLC?"ѣG{ -- /<#GX <ٳgoܸ1<}b^B͟?)FJDfCOڠ8.`˗/[n hĉ.\R#1R}}ٳgMLLP(wJJ FC:.%%%!!!ֱc}頸Ell{AQ(BwD”ܹS]]t{;wׯ_;v>UցCII (`ddp8\^^^[[R #4} Ӄt8/mooonnF:a ;ydee';ƼaDDDaaa[[[AA!***bS޼y۷o---/_ܱc? b0Ç>|844 [[= !--m˖-9`ܸq!JJJQWWP(iii2(((ܽ{3fllٳ߿Ϛ5p8ӧOfffrrr/^sg;ӧǏ?s挱Oh<|Gؠh49L޽{wppׯ#+Lzyee#GW\)%%ebbb%ikk{.111$$p8 >MMM!hp]vh4;;\*JѪ'Gd`< 'lO߼yX =|CwdCO>,8<畕ȌDϚ!!!nݒTTT\~}^^H (#..陒RRRKSS1c899|  ѣG^^^jjjJJJaaao޼)..>x𠎎r [[찰Huu˗/h4か UVVB H$655Al|X,A,_qgg's p9L<?~?vww]爉ݿ?55uH2_TTdii gj4??cVUUmذa;ZJ]]]PPPCCNvwwP(߽֗{dUUU!!3f8ppϞ=GII)((& @CCCCC# vĉKQo޼9s96qJJJz5&''666BUYY ? B-\֭[ ǿ}{ԩ ӣUjj~bb"\I8 F{yyHIIej誫]\\<==99ϟ?I$ү1~ J_))u%&&ߚO600PPPXnÇeڵvvv999r=: .AAǏ-111; QQQ̍W^ HKKk"""A\i8,,LKKKXXB*Ȏq1 Y8[lIHHx-A2Ѱt8,'}bcc32pର IDAT2.\8rC8;;?}7o~왎mRR(8pb```vvϟ7mT__奠iӦ陙!!!/_;wn||} APCCSLLѣ/^$Ht:T~ cwAGNZSSP||<ұDpkGDGG#PtwwϘ1CMMC߿_EE͝SRRP(ȱcF4aA2227uT>>>4=~M6j1MMM QJIIq۷oA222'N8~4sA$""e˖,aaaؼ@’?}tI:WXfMaaaaa_X{ = ."++{ԩSN: NV <07N47n HBBb3k4Cp'CˣH$3MIIqrr`0JJJ!!!W_qF]]]Y@@Ass/BBBϟ/)) AҥK\2 2p8/++kڴi|||~~~%6mںuTSSAYBhݻwY謹k.8wt:==="z.˗/} fԩz]WE:iiieff" Î=`<<<8L9ǏPwhxMFAAp>4mرAAAWWWt,#"::ŊHFVTT O:M===X,ƍ{HNN @ DFFsҤI[lan?xNÅWZ5 ^hta(jJ 톆HM xZcc9sµkא=---˖-CPGE$oFFFt:=!!F:>~@7oެhDvލBvލt,ʕ+ fÆ ^zA".\+_;r"Xʊ;;;Xlk 6P(--իWGFF X666X,688gx;KEEŬYXO}}= ut:F;wɠ:铫 6m鉉qrr355;jb_ګIJJJ]]}˖-? J PSSb>vY/ڀ`q`O8kMDMZZ ߞ={___,''bԔ_Y[[H$X%K:SPJMMMϟ6mkddaÆw\#PWWAʕ+dp@ս999Ç!## SNLJFARͅ m0sS)((x{{07kjj&&&RTxnʨQVV6qDj?JWWW``cV\F$ ?~twwv획 RaC%--e˖***:99Pw2zqvv6W9yƍ?Fg\vmժUGvС`##/jhh +?400000A… X۷?$::qƍG8ݻwo߾}]FFF[[ۘ1cN:iҤ'OѣGV"ɷnưaӣ]^^nݺ1c Qϟ|)ұć9O4ɓSLA:k.===e?]WWAH`8˗/?*..邂'Nv}7@3ggMKK2eׯ_UTTn522211 cCII֭[Yiaax%F,{UWWׯ_9;;ߺukhq!TTTz5N6-??F۷cccY@XlXXXttÇuuuv]]]v222"f!C4nܸӧO!( AJJJ}`FFFd2Çw`Kڐw?~;w Y{C{{ىtPseOA2|<cddd,Xpᔔ ;w2.,^844499n[[[]]]@2`0mmmT*uϞ=d2H /_R(7ox{{_~+V nܸajj:y䒒[n吳***̙t\04~ȑgϞVWW'$$5kL>~f͊G:!ڷoݻϞ=n:c饦9sfǎvXE&>봵~Oiio T*Ajjj...jjjjjjp8ii{=|pqqqoUYJVV… ׯ{ h4}k1 CYYALLLZZZ455333-[H`#jϞ=>|PPP@:HKKܼy+WD<oPP˗OtDAjjj)))?yz,qxbYYwaΘ1͛7fffO>G:A <| <<<7o޼k^ȑ#H|||--- UPPpGeffoذٳ#D̙3gΜ HP>~Ϫ/++ D"@[[ҥK>>v*"jU~̣@"20TTTtvv2[SSS$6rh[Mfee;0MWWW[[۽{_ܹsﷶF:(aN qㆳ355:88M \#q̙3=z@l~)Shnzȑ/Z X`ДΞ=m۶ϟ_xJ͘1c߾}F6D䠠K.]v„ Wظ H477777 Bdggggg߸qBU'OTx ]]]>|صkYo>ʵӧO>#IWb4nzzz===xmB͟?ѣGzzz{_iQFFFloo~S]]M}DL&H<]mmm?~WVV1^h1118xɒ%t8RRRJIIپ}+RSSO|844ƍtrrB:.tX,VNNNNN߭4|S`KKKXzH#""B"H$s???y!ѭJÓ{( Accccc#Jfo))) 555c 54P`377ELMMo޼iaa1Chjj􂂂'$--MkkkY@"zuEd EDDd̘1EEEgΜF?~8Z!2119r䈟EmwMHHرcǬYlmmfΜ _Fׯ?~m׮]g 333{{d6XL&߾}TKKΜ ALlmm3GGG<7ouqq)//G:hu]r֭[\~J`Plllrss:u֫W;wך_#ٳg;wYPSSY@ ٳgҥ[n]hÇGk52Kgg'\WW+9ז9y "H$>>, a`0H$ % Dx+Oo===T* .JR{zz:;;F:FP/T*A{eid"(**hkQ3oZ`6vؔϝ;رcþ27kjjIDfQ{ h79s|||>j*MMM]]]xǏO<9gMMMooe˖ׯ_Ϟ=秦:q䠠 ==ݻD"!0L^^Ç׮]cnt:Luuuō/9s挚ƍˏ;?A}}[[[aٳdɒ^塑VYYt%K w!r"[^^.//-KK]v۷o#ZEEE>cxS27EEE>|kjj"""qKKKsF8~ ߤliiikkSښT*#ZaaaRĢ""""""p;|$&BBB#1 .4iҖ-[͛ǺDӧOXWW?vGJJJ:::ZZZZZZN-Z 999?~@Pjjj۷o|c 7o޼y󊋋/^sM6999![縸#GtvvbXUUUmmmMMM0#^˗/pqee%Abbb:::κڜCO~%ׯ_xq֬YH0klCCAmmm}B*f+rF eu @ܚ5kddd\]]mmmݻ7)yyy}TEEEi*º"2 BѠ"2pee;wm>}3228aػw:;;?} %%+W9sf3gtuu]`HD::`p o޼yƍbKK˸8KK%P(www''еkמ8qƜ*dE`|“*,,{Ν;9S]\\T۷a֮.WW'O<|p`dP(77yٳgŊW\9wj+000---559O4)%%ŋXXX w%$$d-t-(! ٫~RQQ\`eddde999yyy2 GFRp~0\oO#񒒒xcyy 7"##KGG ,@:(F==tI&ō?<|pWWWҌeHD"3uEdQj[cc߿+((0􈊊6772V"""VZdcFܸqٳga…ӧO Dr[n|D"-^xɒ%C+l@÷l"!!q%ss x@ EIIiΝeD"㭬 xb``ϟ?v^^^?p,aQTTdiibz㘮ŋ?}2g>s̎;d3g̙3\=~6m:pp9\}||pl||ܹskjj⺺ F_MGGGO:p\YYǏښ?~1&sc^d2y̘1RRR&penll`[5UaBBB8D"  8pBBBÉ p8"҈4%%%߾}kQQQqq߿ && //?vXL&pF1~\VVxVPPPPP`TWVVC6Q366vaaa7o^AAӧO tCC+⺺߿W^]fMWWWww7csrra\SSٳx))z2.UTT+= |233ID^rennիx+!K,YdImm۷\LMMmlllmmԐ븸'O ޿ (g̙NNNΝY ۷oŋ~ʜ *˨,&&FPɇ ݝBD<\SRRLݮ111ӧE檪Ϝ9s߾}pC;vڴih4͛7p u֔)S-ZH /R%%%-^XUU5==]FFp******F555?~egg-̓tdiii))1cƈ3knjjDᦦƞ ¼C\a \W`0F0Hg +))+L$q8 ???DMNp}544߿dddDGGOkd2YJJJVVVRRػﰦ'$H+=E ( ֭V֢[G{ VE:A3@x.ϕ0\9'w$܏p8G TKYY7$??8//8??oF@Pؽ5z˗O>|۷oĸʻ(@yI&5*>>ť+G$o޼i ~kkk1 RCVWWK.9.H$D ɤ陙-{s1֭[ϟ?Karm۶GɻПXK.]Ǐ 7n\bi@@@@@']vڵD@`ll|O4Y,֙3g;l2kȑ_pEEEvڰa~zz-ȍ|>eBǧ}nv ù{nPPwll{t]]ɓo޼y ]z']]sΝ8qbŊvvvnj3:QvҤIǏ}dпН@1 c2!!!qqqJJJTCa^y<J?K~JP?-2xﮯJzN?/bbbtuu]o#G!HAAAO޼yr;͖=\YY)eYrcDwr-2DYY9??\CC *zҥaÆM2ڵk_.@3d2{֭7oܻwO?8d777WWWx׸޼ys,`mm?%f^`'O~F?~ܽ{͛Wfff_,|hhw"{ҙ3g8p`Μ9_N )da0{]dɒ%K<}۷kkk~{_ 1 %%%T'xyy۷O 4rׯ{yy={~)888//͛.W#(OyBccc[y򼼼oVTTTTTzkvb8ɼ2` P0ZՉD" x<0%RbvbXHJPT Lחf9c4KLӓ2JHKJJз|>ٿ$e:߂:+++3 G1耊 X,B!jEb1!c@ hv`06ۆgj̙SL/ і' 4Bh4%%%P( B(++t%%%+))<SF O0aojjE[R /@@h42d20<Q0l՜1< hbqLj 2 hJjjQTUUU:?"D"g33P_{4}E2+z_GK([BP$sfXdļY^#Ǐ׬Y3nܸŋGFFdyÈDÇDb```llѣ;qKKˣG655l6ðB)* ޢw}VGd]"ؕPrAdfffzըQ޽ٳD0رc'N]tŬY0 +**zǏ_xUTTaŲs把򮺧 W^|_~-D"˵_r3:MmSSS/_>q+Vlݺ>`w~UUUxCA#F䐐n7jhh,_/aFts>}ρ-<"''j!n6<'sI=E`3ڒ`\xAz>?tORT*UGGG艔UUUVCbX2+ bqeeeNNX,FUUUhn-~I<2ya|^aT*+=mF ޜhE//R见IOsB7|7Ǐgdd$}H߿Np8[X%tSSSO:UVVaD200033cm3jkk rsssss KJJ0 SPP000rvvv\.ĤҤ2eEHHӅ r{In466&YYYZ2HB IDATx5CߢbR_ޕzӧ쐐>nI0466Ι3/^% dׯ_[pO?h"ɬjSS̙3sssl, +dzVɛVnmK[VYl&tttZNL]LK^˒ uX` ҺqܹsGs΅ ʻ"@ryGG _-lYȊFAA(33k}}}_|6|.7PTTjxV( ~i4ꬌҽmQRR:t萝ŋ߽{աgW^=˗[MlKKJJ:Df0)))#aգG=<<,--;qիW=zBXXX$$$9rdڵ 3g\x--.#99aqqqcǎ =y$Nvϟjkkʻ@sxKi@߲tRMM9s攕sQS@ ?~ ^z%䦦NjbYAAAzGdrAo"ۙ>{իD;w~4hЕ+Wlm]=L&kii5Kd wa;C͚5+""ŋoKKK;+vuuMII.Bڡ۷GGG} ̙\SS3GGGy%+%*++Yw}WWWkbbرc~e..III֗/_Ԕw9@2}t`k׮ihhȻ"@_sN++?sѢE2rDvK2 ä1 P(R:"+**655566v_']ƭ6K0L&~Kmݣv-ZO>}ٰ0tSVVjkꈌ|8k&Mo-_\(|Q!!!ׯ_uĉ^p!66t:1ƕ%%%fvȮ2ɓ'w-ݻ> 2'$//o޼yRfbba؇з-;"L[[;%%C{ $K, ߶m\z;,X30,X ))իFFFh?sÇ[ZZFGGKVX,@b@ u)WUU۶m۽{!Ce̙.\8sLhhm||̙uV9@?ӧ/\pGZZZ_|9%%nܸ՞r-A>xE}iʔ)s̱9{l+˨*$$$666>>~ڴi~|KYYŋF@%y G syݸqǏ n5mnðvY;"d/wD&ɻ33Զ w^RRҘ1cz^KKK+>>믿>uꔂR//gφXUVa6vExccc?77@ J^+K ðϯ3w }v``llZRRĉ'N~ ǍsNw ߿O?tȑ_u̙_Hٱ쮷j&H)));>{qʔ)#FybϞ=ch4իWG҉z@uuX,555"H$UWW ªWSSSUU% kjjPԊZBQ 466ax\UUUQQD"h4 t:H$***)((()))++t FR) PRRRQQd2NLhL&3::z655lll D ݚ_ Ado:uk}}}1 ;{,q\.ɓcǎ]~͛]` ںuʕ+9ɓCBB455cbbdϓ_~ð\mmm%%%kZZZϞ=Ad \\\ݻ20/d̘1111z)**.]4$$dժU_3gonaaJF?… ]<ٙ3gI  ʊ:`0tuuud02:D"M:رc\nuuu^^^Lq8RPv;"C^喔V_ D$oݺf>>>G1c^x +VΞ3gٳg߽{LLLrss]+|M={ֱ&doo 77'NDy@?TSSZ__ҥV$jS-Zl2[[_^KKKɔ>WF[l)++⡆DDD_~sA;O <|а[hiiƺO>ŋ$| Ϋ.,,,,,,------)))***YFY511iE=h4 BZ{n>}BP[rAc2BL&]fX貎NI/mƌQQQO>uvv>b2\RR"eGWڬqK Lr">U[+4oaZEEEB6^"hʳgϖw9bǎoߎ;x𠹹y544mv:X___]]-Kȑ#+ϪFo߾~zȐ!髯zՃxɓǏ[.::zڵK,P(Ujw?WWWn)ҥKo޼\f͆ ͛7x`RZZ+ ܹ)dW177r劏sNy^2//(//#TҲf2[DMM VmzGwprvvviiiqq1STbٺw׭w COn7fi4ZFFv5UUUҏjG䦦&+Z:")**D03##ӦMzy̛7O]]}„ .0 ܼyѳfB'X2B]ssssrr$BsUe:Rߒp^7ߌ1Fw]+6lx׭~43gN:ѣ֭۵k׆ fϞ̹\n]]]vv6 i *%%ȈՑ#G~C8p`ذa~ɓBfRܽ{@:thLL㝜O.r EEE%dggggg PPP`2(j``0|p&b8,#nGh4Z˵%֢2ʔ奦|yMM I& ?366Ftuu;:ĉO8I 455lbxB^$III56nBi5#`^D"edd5ѣ7n܀ rK7n,//>}=<<]{Qhh3֮]lٲ}ɾ;"cƌ e=ZAA!11bΜ93(((::zʔ)~6m:r䈗W7VQQqܹ&MںuҥKwܹu֠nvb 2a<莢c2+:`˗O2%,,ӳզ>>>w܁2k-^xNNN.=.++۷޽<@p8FFFzzzi.@^d}P|@crrrNN(D]]]CCC###cccKKKssssssֳw !!![lyyEsܖiCmmb;TVVJ "ð:* ۺɩ1>>'KCvYXXtj yAEEC~W2H$Xf&E"&[Gdի~DDGx0L&'+]N:3rHɓ' :?rzH!߶m۞>}:yGo߾޽{mVVJX, ###gggj# MMMMMMfۛ h2ʬ?|0;;5յ@dN622eHAstt400x,AaB&YTT$}GUUJ)pK鈌az* 2}i[()){¢' ?p}cccyWBCClS ָq/^xbٟuttkjjLLL$ðA~Ϩ CRR.@ l߾L&/\P$\REץ?>88x֭_/^;::FFF~EZ]\]]b/$3_ϟ}ĉ(SS)Ss8;wJuӖ-[~Wy_|ի/_x< ( JN4 E-,,]/j*633ϸ={ 02lccckkkgggoooggd2T>R¸qbcc7m$}qvvvL&HmfymP(muDFb}Afffֵvvv/_LLL rds缼߿f]_)//󫨨wGFF޿?<<<%%EN:::hu SSSwD0lРAO@8k֬Gݾ}ںٵ755?>۲e\{捏ȑ#O8|޽7o.[z?N7r%r555R-O IDAT=z8##|'N>-$$$>>(--񩪪{nMLLϰ0vpJJJLLLvHD eV(xG[߿رc$&Lpx@FEEE~~~qqqT* PTT;wiӶm۶f͚/&MejjÇfϷ0tГ'OvKU%jjj={f``wҥٳg988=zԨQ?GхBaCCa((Jh{CCP(lP555UUU+`HAgbFWUUU JEd8xA~ 6Ȼ4'>|OD"55aÆ͘1eСZZZV;}[]]Ǐ?zȑ#6l  rsssqq[* -11q֬YRvC 7Dn#2z2w;"CgFFFd2Yzӧ=\^bjjz%ٳg;v fB xwJYb/^7oޫWZڢ# =<kY[[c Ç׮]upY{zz޾}{̘1cƌ|2q-AAA!!!AUUՍ7N6m͚5'O3f͛VLMMbq~~ni&| IOOwzӛ?󫪪ݻw .lٲF9rԨQ=Y$'bqEEP(ŕ@,WUU0 ՉD"}EbQ tLpKJJJ2No C$=,DhfUAAAMM }%(`0[T*JUWWRjjj***hqXzCCoذa馦.XCCӧOo޼Æ3f 6&ϡP(Ç>|8=zWVVZZZz{{h4V (L&s8ft:B)**rp 2B, D"(==666T*UII֭[D… Ǐhx0 ޽{&&&۷fŊG>d6^ZZd`08Λ7oƌ#.Jrw+W}/GGǤQFtd?~|nnDX377pBJJ?8iҤ_~¢ rSSӳg|}}4ׇ=~֭[666ͮUVVF310 oܸnݺ lOOO#FtT]]xeU,bWUU%AeeX,n+=*R(mmI\<}I"h$ 0}޽{***ƍ[fѣQ#O&uuP Þ?~ӧOGFF0 WWWkkk]]]6TLy!#GLLL\zaFFFqqq6X"){a&=:"b|d^ @/r;&e#GD͛7Mc]Gꫯh4ښ5k]x xI ZP+))3P4fs8]]]4i >}JY~={hͶX,oڵk*c@@AΝ;ו>,Ν;AAA X$}?s…Ç˻v?~-egkk-9;;:۷o߳gqu Bɯ_~G]Ԥ2|aÆ988t%TYYW\\\PPPPP2yyyEEEōhd6ҲeZT[[NRTTd226hhh[h#%%%,|;-BAoX,Nfl6[GGdz5֎;bbbO.ZN8Q__x̙cǢPo=ydǎiiid2Yr6222222Сl__EUUUɟ [\PP t:QƎ%[ 9332VMGGGXAdM>vΜ9d2モw9`*,,+((-((),,͕<,LMLL Fx>`n555UUUrEEdÇg MMMfQ:Y__嶼~i֭֭={}aX7o^paDDD[ dmlld-ss|Hw/~~~t:RNe`0_>qD//WvKu[n w-2QPP4iN?8qbѢEk֬\7)[[wKaEtr+WFFFΙ3I$[ ?~ѣǏf$>=&''ӧO999§Orss7ʏp-a2h#$A#ձa(_\\_XXXTTի[nD"|܊a6m/2uT"(r~(55_533_^?:哱XҬ^uGzU1XzjkkǏI,:1 #Æ ;}tccc||={nܸrMfkk/ srr޿'SRFFF&&&֖666..~~~ɣGnkP(,//\/d|RʑtdU;"555ޙ^ȠCKgdd8;;:FYY8!!mQDDDEEfϞ-r@?WXXYzz:zYAA54h/q8MMM9]___TT[XX999?E|1 p8fff\ fff}7 {̝;ɓ|͓'OZ}PPPhyJW\\- 755xNZQQWVVt̙3Guy) Zz]\EEŹsN>}Ϟ=[n?WZxNZ4hPNNNNN _4ҝ~+V|[3f̘1c0 C/%>;wP(TRR~G6Saa!,VII fV(s @鸞PZPUUɅ(js?~2QF@h_\S.4ZW\?jԨDoooK-p,˻v(((ddd޽?ٲeˢE yyyYYY>|z]BBݻQ UWW3;;;{{~' 33wJ"c%D...rd4CCzYzGdRd29== 2dF$KKK={0ҥKy<޼yh4ɓ]?x<ދ/^~gUUU̓,,,  FD"zmUUUyy߿ۤz lnVիW&L}Asa޽C 9rH,߽{GPʚm/..%&&&D"133ϟɓ{tad',X0nܸǏO4@rܹovʕ/w-z ߿˖-۷o_bŲeZ6o-a[QSSrO>rA䴴'mܸ DKL:kK>v@ PTTD=#{{{/.kQe(s˷leeeܡU(G g i6o{.333+++55SK.}fff.\ 7OӦMkټݻw4\rP(PKIIIGGÇ]/mذرcׯ_2dHH$8p@]]}ԩ88ރ꫰~M޵tF[zԩSu_6m eZ:ny)55iUaaq㜜>,ux1 J>|hllD----,--U3=>~W^}6===330rǎkjjb***.^L&:](CBJJJtt40D"GӠA dkk3 ð5k;z~c^*VI,QJю)iqUZHŒ6S7!tZY{߿ rgΜh̼߯:.AAF߿^ӧO.\زe G^xpttudGQQ[n9:::::ؓeڴiӦM>ߒH7'''''';;̙3Rμy>iiiVUTTQPPGDdlwu {)lFQAlڴi_~A]]=((&::m<==WXM4AhzMrrrjjjUU:u-AAAFx<^^^^^^k!H_~>Jx񢺺M>][[{SLa`ݵݻŋԠ8uԽ{ZRRRTke...`ۍ7ݯ_7DSp8OOOQQ-[߿&B)''gҥ .qƘ#))y9rdʕJJJGfΜÇAAUU{PԴdvvvEFFFFF;sD$߿ݻܗ/_bm )**dyyy)))fT#  à>|xQ__ۉUYY HN2eϺ ȸ",,,,,L2;9##ۍ)))4sL5[YYyxxDdAC'Nʒct8="'O@&CCC? PPPXv=aN77ϟ bbb.^j999sssl̫W )))jiiywgd__ANN ԩS-LF݅mpt޽7oޤ狉ihhMMM:rGGǡC={VWWp‹/ y(C>wuu 8ak.ss7n(&&&Obcc[JJJsrr222&L0gΜϛ7o޼y05ҒG\iYUU["2\ |MLLL&%ފȀ cܹsitx=ٳLLLؘimmϟX>:: ,lEDD/]ۆF8j|m3 :::ޛ7oEEE_|9{zzrrr.D7o޺u(SRRnŃ0z%?|Lw-t2ddd.]ѱ ߿%2228::.X`P}%`?wvv.^XHH;wrr,Y"++ ϩzF  ?~}z3Sx|QQrҥ']1!aQZZ*&&f``0UZV^= "L񣠠… 3իW&$$3(CA~~~LLL1G찰077+WΚ5 ۘSYYyٲeW\5  p8'//oggw…ԖF UTTDEE=zn1o޼{>K~p3?~0:AA***Ұ8^JJ8cTtRSSx{{c-gΜI۷Q{ Ah``lӭN`]ɵS^gDDDT;w_4:99`рOab<6tSDDDlٲEVVkaa7sxN8!))IC@@777U#U1..I&ўڵk-'Nr L#`Æ Gp^ʕ#ȘQTT$##9sև@ }ۻVC/_q.""IIImmmjjj&&&&&&,,,n|ubbbbbb~),,xb}}}XzH˗/_~}޼y;~3Θ1ce۶m?~TPPkBBs޽iiiiii?..nѢEՓ&M̸H\`A{{{JJ //?ijjFDDppp 2Ծ|'//=aF3 ===%$$\\\6mڄA|򥑑џOd`` %%C \]]qD"|ፆ )SL݀*   6111>>>>>`„ ,љ3gDd}=##yUnn.4000443g:O> $%% ¡C >U#cbb"k֬HKK7o^qq$CB??]]]~~lϟ>}:f )++cUUUs 铂(**Z^^/É`H$ƚHn}FM sFUU\8F}҂yy&~h }TEPP=ݏ??߿{0RWWW__l2 ,A||Qii),]y#wvvvmX!!f7'O>p\WNNn͚5wsbLLLbbbѳgVV,_|ѢE/_DGr 쌎sNTTTWWEDD#pYf͚5ťÇޒW޴i|ZZZΝ;!// 7F6k֬qww?{,G++|? VCaIII0̘1#%%H__?::ZHH *..z%a$cOOOXYMMm))) XJZZիؘ!(((((8w\ƖʪɰjWQQ<*&&&...%%%......)))!!!..NyAAdeeݻwEEERRR , FFF2[BBBw_AGGgŊVBoCٳ!cAA Ljlii걑nc3J?~c?.++w^S )))!!!^^^X[WUU5...88xƍ=vx{{O:+H('''EAD..L1mڴ߿?''GMM-::ѣt>ѐ?V C+//oΝ@cc#,KÀƤT<<ںn: &&&<ޮnݺϟL40evv7 {.\Я;#=ax{{xKKǏP/,, bٳgo۶MGGGBBΝ;o߾?Bl2c5(0ā׌ ͙3gΜ9>{4\ZIU|JKK^vvvaaalnHPPvEd[{c"2n4~]G:x`||xJxq/]D R"rwD"7nHNNqvvްaÄ gϞc iv۶medd޽{5c'0 k`a-w~vZ:䙗Ç߿III[n֬Y$)***$$$!!aƌNNNTCyi[T!))|K,[ɓO<',Ng"rvvg{wttƒ_~Ǐ?lhhz ,&&  oٲ#Nܛ &tttR2qG/jˏ9r UUiӦ1:Qܼ=&&с]]]СQ ;<LLLWW\ #JGG+++ LfaaYl1}faaQ^^~%lj ضmۮ]fllKc}޿ׯ\III222[nݴih L&>}Dp''tڵkm̭[)++gҥys=w,`?o޼7o 91ب9 BNNPPP@#yyyy~~~$ 19../^,Yd…/^btDH$RXXرcNJ7lp5qppl޼y477?rHBBٳg;;;]]]w56]|.^Pqq1'ONJJ@IssĉG$66;88ݻwoٲeAظx⊊ׯ_?ytRSS svv>{kVZ:}~ 5sյzjVV;wO#̙3{2Gݻw$ 0aQ#exAAKgg-[nܸk׮'N=e-,,N|cC))),,,K,kii޽ի.WWϟ `سgaŕGdee?HII1:>dggK.Q<++>rرЂc{{{N>]PP`ffvv2.<<|ڵ]hqqOOd Ex]]]T9Y\\\BBBX, b3BAdT#ɫVzillTVV.**Zvqe_'L655zYTTܹa-Ϊ鬬Quuut+E献}^xajj hhhPRR*))ھ}+W@DdJw|Ϟ=.\4~z\I{Lz^>Հ_Ѩc޽W^ 4Zkk+Ea^Eɵacc*13_Qr- xPRR"))9fgg7o,ZccCT:?|%ݟׯ =yUD.++ ;}uFi.),,ڵkYYYvZz5"#ݻSSS322zWnmmŞZbb… ˅{|s\\ao[622kSTTr LfbbRUU0# iӦ4:p855;wN6ٳg(yFDDlذ<,,@2dddر#33"22uȘ~ȑ(!!!OO N^^ީ*]ZZz` TWWFرc #? |||7nXWW{nFG XYYeffš,~QWW?qD]]uݺuvvvϟ??uꔦ,Y癔)Sttt@JJQ,*****[Z a9޽BΎ( 3i$xc`;*  2ݾ}qqqzzzO]phX|Μ9C?66~d2֭[AAA/^ QO:~zѣG׮];yEEEsrrΜ9s}BBB<==9kz"BόPpp3gV\YZZzW^;w&`>|jRl.<44e˖m۶M:&33ǧcK5E}_tO]o1:'Ӎdx<ҥKGGW^u>!OrrrR5rqqQp8xހ[%$$|l6311Hb=&033S033wb}+EAi`Y55'OΞ={׮]+W{PggϞ& /vuuELK.=|PDDkͣ}''}FF͛G&}RQQ1ZBBD"KҀۭTWW, 4&'e V111ފȀ ロҥKiquuUPP NNN8ٹqFff۷o3:uuu;wdbb;w۷o2Rttt\vmҤI!!!$7o<<=(??J555yyy檪 TUU9'QTTTNNN"kffFq 9ߒ}Έ¾Ytvvx#>iAAܤIm߾=55oaa}z^~UD@ON[ojVVV8Çd}e 555 }捰+VZN~"TTT#--z5k֌jjjV\y'00pڵbgg}իaKgg'x+zzzzgϞݷo_oeeea-\\\wIJJ]G Ș ''ުCg&H珌ܻwp9p .x{{{"H 띜nܸ~ .:>*&b|&M ptt\nYKgAA?~hjjZxXr嚚/^dl$cks@Fcc߿HKK3:JMM ·VS;vppp~Y}ʔ)߾}s^/vLL BF4iRP]]]MMMe߿׷Q=K O=A:999'O\lҥK 1*`YYhWWWMMM777dnn~i .. QQсo,,,9?I1Y޼y>y$>>Op'''C뙪jnnnoJHHPKU-[l˖-999pyoLLL4ֱ [y#ŋmmmCBBFu^52TUUW^IJJ2:m޼y*++ﳳܕ+WBCC/]N ֬Y2eʔ>ڰaԩS=<< bggggg!MMMTuuuuuu H$655555u_㹸a2=??AMXYYd2իWrrrW9qē'O͟?2;;ۛe<8>>T} Ui۶mё())s% ʀr7 gݺu666zzze[dɡC544 Y3gΜ9skSSSRRR\\\||988fϞ5w?AFJCCÀ##ɓ'3:nz#G߿Ϟ=#ʣGdr%$$(+"L;NJȍol=88APP0((Ƞ믿?X2|ĉǏݻ̙39҉FBEdPZZlٲGikk3$F^^ާO.]<77WAA!66윜? xp~~~///\s·ZZZ2*Aabba_~wuu%$$\SS_XXVHIIxG'NH{*wwwOOL]d 7/:ew]ʓft¥Ç߿III[n֬Y$)***$$$!!aƌNNNTCyi[T!))|K,χO<ɓ?D@w"rvvMkmmMq5o N[pqvvNOO?|Çfkk[UUKF  H|mjjjZZZZZZ]]>}YP)Yd ߿]^^޻w޿ЀԴ͛7o<111F9jkk)++3:+]pãk/--̬[۷ooٲ(֭[ oa(#߾}KHHѧZPP0::...UUUh%֐zիMMMCCCѡjp7G=~xѢEC=]"%;v|2*{ڟ<яMxԔ;w3?`۶m׮]zjzz:8.11QWWq$iC"B@@@XXˇtܹ\UU%,,l``=zWRRZ|c-\}mΜ9+4|bhh(**3z/"QYY[|||Eh۵kWfffZZڟ xXH[[K,zlkkSQQo2Juvv| s`ƍ ZZZ㔔ٳg\}2t4`BAd011͘1cƌXKyy9V}6!!~g>}!##ϧ!򥠠˗/EEEiii%%%|MKCg֬Yyyy=%!!A&1e" P]]Mc466&dr[[vicg瘙y#O>lhhzjժgϞ455\2\1_߿711>>NNN%!!PDD3;㯿D&$$S^gff޾}흓SVV}8qbMM =GwIWZcpkkk}}+''1MMMĂ)XիW|۷ӦMΩ_ii!`t87}}}aaALC uJJʟ >}ͱcDz/]˻i&{{V55gA AqeLy/U.,ޅaY}X.̐@x8P$aXg_ gxKYb,7PG<<@$}>Dю27of-D aٴPTZpe؛`?RRv|áO{)Q6 {K=u{K1uvнKM,,,nݺpB+wׯ_j* r$}pL^rutt^~) Wm$0BHp"x0:LVVvݺu AA;-˗*++FG,---EEE)**k~~>\8=ayyyw999]0wss|y\2sL~o߾u\TTիWzzz֭[[nqMHH&&&uuuigee$%%kkk JDFƑ/_(((䨨v9oooxر2/ ccc%%%Xs֭7 ۤuuuXmӧ, )++cUUU_'h۾}{jj*YXXM4 wj ىq8CpO>)((PNM;hyy9LB& NNN"kffy$ia@"VX@OOOHEEEAAǏ\vBʕ+XϟJuuuSNet8ȘU\\; qqݻw߿122 ?VVV޸q#00[XXݻwhUAF ,qJ(!ҙLVH7IHYrɎ*/2P&^wGeuw=N2r|FCCrss~ٳD"QAAaŮCD1wqMdOH ޥ[T (}łuEV]]Եk euvB.-;~+sd$|:߿sΕ+W -,,f̘.}AAAIIIک(a'.-**k%$$tuu9ɚjjjjjjpG}TRRRXXWqNNNII #%%#8s>|XvmXX΢\r׷$BbÇϘ1\~ݝ`vtttB^ȑ#TTTpKXXX\\ܫWBzzz\u z>x S(LAd[[ۢ|MMMOOϟ_dP("m x*W"WU\`yr_Lϭӥ5npZ⪐͉.'ޠ-3ʉOv\^^ b~9:•F2} i!l6[\\|ʔ)fpGGGGGw^xqׯ駟ƌ3f}}.~Q.#F1bDwRSSҚ.\LbŊ>ys6644pOccc 9222ꪪ***ʚ***jjj|l=A}}}iiiAAAqqq~~~II ~\PPPTTTRRBG ӉH:\᜜>}o߾[n8qםY,y#ZΈJ0`l$Yw|3<~LPzh111]]LL&455Aoo#F;99 {D}Asss@@-[yϟ?_bn0`ԩSQQQDBfffhիW~ŠAn޼)`#{ Co߾}Yf!##!c(Ç9;Nb~+:wջϯ711L&A:e*[̘1kݺuݶӞ@NN'N1b͛7NA)))ǏrJk_Apttܶm[ǷoTXX/444$$H?F5sL>5AR%dQQQGGǹsPBӓܹrʺ:CCCWWѣGt=LOOYbbݻw444\\\-Z4z*?:cmm7o*@BLMMy;Ϊxm~qii)gyyyq:`EP-[6iҤ@kk눈իWw uT*ݻwAd&gM(//nqkL&pbCAdz)cc6Ȓ&&&iii&M"Hqqq3B ))yڵ1c\xM#۷osnvI&=|ǎ ҥK;w0}ɓ'qwmnv…C=y$BӧO#{w}E***lll,--IJx`ss=(@甔'Ox񢱱QFFfȑ6mrqqBgnn.##Ad?lq-*......,,,++WXj2222222lRRRRTEE ?|PYYYYYY]]t:N~(**qMf}ν{wĉB{ŵ"QIJJrx?}"2΅˗'''TPPpmPBB¸q|C!/^|ǏO:U n@@]555VVVDKpp3l77'?>>>DQV\}vb-g {oq#Kݻi 6og.;v횧'g/jVt9رc]p#F|Çg̘ёYݻiii16,_ݰdN6͛W^3fz+WL6m$N(++{aA#˾ k׮yyyݽ{wȑ\ O:uotdt)ٳ'OeggH$SSS;;;{{{;;;333:.aVZZ'OZYY0H(]}}咒JL-WֶС NhT*UBBBJJJJKK#((YZZL&STŋ,KLLSLiqS7o$*KMM-v튊՚KP/FFFǏoې!Cbbbl6D5jt||ŋa# YfG[8pd eRRRO{Ѐf̘1oWud7o111_~GmܸqQQQo޼3J9rd`` Bh„ Ns玹I/_XdߊND###Ν٥iL2B(??_TTt̘1d2dwpS… v:tPN!#Ο??w\gz{{ {D;vܹsCBBoUrJ4hPrrrȪl6TCC!`0/^ .766GӧO߰aC 믿jkk[TTT'&&C^!蘒ukF .?lmm-&&SD7o^uu5T~ׯ733 ˗Ʃ˗XPPӧ7mu7iedd >X,((Dw8111➳.RSSmllݥ;EH$ݻfϞd2͛'^&""bƍ{쁉,] FGRPP@!>}DEE $:tСC/^rJ@jNKKKKK /655eddyիWo޼9vXvv6ҬbNlv~~Ǐ?}HGR,,,\\\,,,=eϞ=sqqXچ{@=|p???kkcǎu,+##cРA\444X,VQQN TDg\:UD㦦&"iiigff:;;F,,,B'N?~LLL```w?&Mw7ttt=^#66\i'VߓM4i666]\|o l6.ĘLfǷ󋋋`FN$iJJJ ,^rGz6nݺ;v;vlƌNɓ~#/R'NXdIHHɓg ':BO2444p%&矼fN$jiiikkhkkw^Aܼlۄ/t3f̘ ZZZ"""84/_ 2bbbbXXɓCCCfhh(&&; :BׯBHRH r_A/$0++͞iii˗!,,vvv&Lpppvڐ!C=!99#a mРA? ǏY, ђ?tPPeeecco]quv^z5kHHH,]t֭X,Vpp/]%Sjjj<+++{ :r/NϞ=;sիW;99u]~.aaad2|ǬO>}˗999 wpFYMM AHsrrrrr\Afcc.QEPnjaa1W^>}ʄ{w:D*(( Z[NADDDLLsD&mv=ydSSBA]?F==G9̙3QOfӧM&쁴 ҳ/^(//!Q\P8V~,))]/ l6?e777Ohď5]~MSS۷o,~֣Gf̘1zK.7soffz:=[!w:Mtuut}[ ]]}𡃻袊~bbJozŋ` JKKݿ|r޽.MY[[KJJ>~#AB&iӦNuuuw܉?׮]kff GSӇrHKKK_z+++ʵBM'-rrrt:]\\\VVx ..NRPSSS__`0*++qsq NRRRD]m--mU tׯ! @oDRϟ?w͛322lllVihh UUUMMMJ [}h4&Dx Adz###PVV 2BΝ;Ioܸۥ#usst钒G"*++=OUTTuƳŷoߚ[YYijj^te٭;OjjիmO?u6q%$$III {D@>|FRy!dggdɒ]f?nonn "yfرL&\QQ{wub9e˖1GB"݆4vq]~1X||)SFu…v_]?[;2iii'O0aBqqqWGaÆ 6l ˖-wqqqwwwuuh\O{.B_x?uhFh_ Ǒq'kBL&qD{kkkKJJ"@yqwFKK EBt:1Br[SRR}%''?>,,޾}/fΜIPf͚`0: wQVVo:q_&ȭmL&9c @abbÇ6訪Boo%K$$$L8%))y娨9s$''GGGwbpHUB S?DKlllll,g^رc#bbb:ԇ/_튍 o _zt0,""Y5]Ξ=ƍʝ~kȐ!=rqq9rdbbbksٿ?39;;Hs׮];dkk븸[G677|ڵkF9;;*ݸsBBOfSRRLMM###WZ՗~hq>;phWe#q= {}E{!D:^88*> \fqC\\Js.4ee刈u֝?~˖-!!!} LMM?HR=@L>]NNϏ`;wi1##v ׯ_ `AdhFb"25LLLigg+b***Η/_ rB"֬Y?k֬sεYѡޱcׯ---=^}Yf?vrvvh۶m#:TTT,Z)44tѢE訨ŋ{yyuKtE}}wsrrʼnD۷oBNҒdfffKSSSgՐxqu9SSӇ=zIII[xx {,999Ǐh9!!޽{O<_d[nݼysƍ֭7nرc߿OdgL&D_\r kkkkkk ?3\nxlVWW۷y\WWl8URREv!x|jB73ލ-BVVV\\\NNN^^^?cFt'{رc;vXdedd=:~ȇ @0pׯ;vԩϟ5cc㊊r\󘠢BPܘ"  2Fk-Lшǽ\ hZ}XXXaanY|yRRgVVVO>?uꔧG$d"""˖- _~JKK.]z}999//M6qeXΝ{QrrrFFƐ!C.])Վta… 2/\=((1((իoA'O/[C, 4D"rd2YAA7m4}sikk?|uԨQw100@WOLLtLI+8::;w?ٳg! R......;v_o{v(|'Nن2"Ns' 8g,'''...!!!%%KB%6QNDžꪪjjjjkkqRdddir_ )))ȁks#5jԨQ߿߹sgxx͛-[FL\@Gӵ=@qttr劇?|Сv122Beff:::r- |T*;1VSSC,BEdz1QQQAȶ"""iiiDUCCDWXy5k{DB6gΜ͛7o۶(gegg#޿9ZbݻŬgh&33S__%22Gff&Bȑ#Ǐ򥾾ׯ_/!!cKKKmEÔ>q֭|E,xxx444dffZ[[FeeDo{n(TUUܹ:lذ$333at2wH@T[[6b֯_?iҤD攚qiϟ[YYEFFqIIIii)g\ BK[ |222?Z7oJJJʲMee%JVUUUUUt+€<ڵk׮]sYjgz2ϟ? {AR۷mmmDFihhpVDDh-NRJReee"gYLL A^BdddSFF$55ƍoz g5kdffٳC7o^lٳMLL=δ{luuPK.qϜ9Z`… ˟>}ⵂǑBo߾}mLLLzz:B())i˖-D˗/?{LRR6 {;vݺuvQXXhggWUU~ӦM޽kjj"***B"-00}ѢECYYnnn?۷ussc08~?333Ésի7/]b"tuqqqCCCO"ϟ?BNN.!!a#FHHH]}FFFB~DrttLNN^tiY,֯GTgXfiiivvvBBqրD"HQRRXf>1BTTTMMMMMO_~ׯ/_`eee]]]]]]ny)w ~ƍ윝###G-*== {m۶+WN:[022j-bQAAs8䳗6qwi7o >&&b]]!C\|=رc߽{7mڴݻwܹp'NEDDlڴI4x4wwwˋ3믿'%%%%%!H$Ç~"BhȐ!C !aaaD#BHMM-+++44B eʕECCÇ%%%q^MM~=X]]׭[u{inn.//')))ʯ^277'ɸF 7ʕ+ JJJLbbĉnjsMatԿ;qDccX%8 Hseff-MMMvbrrr233322|\eD"Q(qqq999%%%555CCCSSS EEŞ@ #####3`~-777CbbbvvvUUXk'xxxxxx￑...k֬qww7>OOO/778 3/_;|vԬ466 E$$$'i4Zcc#LLL/n]UUUFFZ۷߿/՗퓔oݺ6sa ;w.\¢}7F֏=ʊIJJbbblll~m0 ,,,77hQWWʚK?^gs [ceeESSS9~'ODDDp6 "rCCógώ9dɒÇ)++:488ڵkl6{„ aqqqEEEjjӧ7o~xƍ8pZBBBWW+<<˟?f~ :ڵk? 8p X,_Y勰'NxMtt>Ȉ`qH"KIIX,>Yd 2pTzJ) BÇmfDEEɓ QOO?SWtOOϴɓ'4ѣ>>>l۶-;;֭[vvvNGM4;vP(>>>.]ڹs'g[[ۅ :TKK+77ɓ!莎K,SSS{۷كrss#,]t߾}[ne2;v qyEFF^re͚5-BEGG_zUBBb޼yFw>s挳sW B #G훕M7oÇu?11/_pVނdΛ7ٳG5k:D\\SLiɌ9s&xG /^𡩩I\\X[[wL&p߿ٳg7obccccaaAф8xbcc_~i&)S+ t4-''GtCCÐ@qRSSlNjxk L&`HJJnDZZ!`0Z5F "d>I9"DRFFƸqloo?p5{쁨YoahhtR__%Kl߾]XEDD̙3cƌq㆓G!!!!G A߿9sp=k̙FOmF[[[֭۴iShhhhh(>xpXNN.::z޼yQQQQQQB۷ONN^se˖ÇIuQQQyyyÇ544oܸA"ȍlǟnڴ;h72|1QQQ__߳gz{{ {D@ >>>Ϟ=qㆋ:ȧNuڿuŰ?pĉVVVp]v8mL־}6==իW_>}tEELtrrwpp066@ױ<|vvΝ;gϞnݺ+V,XnKBD"444=@ ;|޽{dVV9UUUBEEE![\QQQ r.Adfddn߾}uuuDԩS׮]{ƍI&uA'h:t… _|y9[X<*z%WWǏOx ''G^^^__s*ӸqFGG}y󦪪gŊ! mm={!lmm.]3̟ĉǎ1cF촤DQQnjJOO֖%QVDf0RRR? 77svdנd#G$$$; 6L0p@pttܿk777o޼9 @OOkUkOid|555WXakkkee|,驩?>qPTTtpppppprr?BTQQ!H7E_sssuu =jjjIhll X<""۫V_ngg'`Xee% RZd2/_;"&&&!!S(=̉L&wHم B7J իlٲxb0 $ ++]IIBE\;۷oڼ1e>ʩT*׷i")w0",Dioo/^8;;ZZZÆ ;{,{@??? . {DJLL… +W۸q/3ٳg9[8/4hРA=3у>p ?~駟@SYYɓ_|/HiRYY?~l2ׯ9!#DEE;xdVSS!WVV:t#D"ٳGTT4(()((H#z왇jrr:SSSӧOGɻի7n]USSӑvXuuuRRRBB­[rssi4ڠApQCCtB͛7H'd!Cƍ7n8[[[P?$F峊OڕfWVV*7h' gUUUjqUuuussC_qq6._Ե$]ZZſ<eeeyh4:۹?\^}… lrAdll(""Dn>~sxkLLL驧 2BٲeUUUmd z{{gϞ͘1c7n\jKv?2k.cce˖=yرc< )SGw箉Zbb B(==ۛBB)H@{=}tcH?SJJj chA||ԩSGqyhkkkjj>z rTT vl˗/oݺu֭Ǐ#͛2h QL&[[[[[[G%$$.2弟S\\Ik ~NZZz[b'B7omWUU-..ƏNkAdS.--mm4b1L n&&&ƈmmmSSS.]3s̮#B7om`` Au8p;ڂދdnڴi֭^^^s= q㆓tcccff%gf ϟߑN)..tҦ&\?!!!ۻw_@/䔜~USS+^X,֣GΜ9sbMMͱc.Y.H(((xzzzzz"-6m \Z UX7mbF h1ěj-N:[װŢ֊"UUU[\g2z>ZEm]jsz@7SVV޺uʕ+whѢ˗C=444 =N&"""0ЦvT6ރRCAqr7ϟ?s:䍰sFA2Ùx_s%rƠ99888*9ۜl5g1!ȢEv}ڵI& }}}#LUUL&Z "+(( [>Thhhh-: \)B(33ֶƎ{Y"^vvvӦM4hPttt@@Gԭ_x1k֬ϝ;wǎ a/_9sfVV֟)555͉8WÇ& r'@;ʕ+;GE֬YC&WXQSSaa "##׭[i&H^)22bq>m6tP{{%xEO>>|yyy!!!\6'\oi{ύ,Y˗/o߾}NNNf͚:ujkI/@JF²IYov+<*;ǛJ?f;p9Z w)hlP[Lv']p2)AbjJJJ͋:yo߯f/BQRRw2 ؞OҚ(gT A5׷o -nʸsⰟLs$/$?1005jԑ#G"ԔqŧZXXH,ɵDƧZ[§n"+@_zzzT*ÇËUTT8yA/bjjy3gڿOJ-$&4+ *VT{kEUWEuQ.(+R$x`7lذ}v"} oE%!Ƕ@mkWE[A|k^RT|WMM.o&Rl2ļE|yEa[%/\0 SUU_|ߌ39}qƉ{\QVV0LGFF}{fa@T>A9ukHԠhM)cT (k3[?^VVVJJ yhjj?y 4 <^CP&NxȒZZZ|%%%MMMhFV$AiAd4KO,D} L2ҥKbyyy%))i|:Dj33g΄-ӧzyy5559sABBB/^~eQʍޞ퀀uu3fֿ.ׯ/ZH__?**jȑ]4.$''7668pӦM...qW&''cfff6k֬ 7Wf=~7>llluI&vhyNP7)++^ѱk׮Kn޼Yؚvwꤩi۶mg#F8q+ÇAAA߾}閖[n+s8?۷111))) Zz='N$''{zzn޼YĿ 񪅏?gccckm>EoiKyii Κ5ɓэ7:mX*^ޜ.o$xCR(:1k˻NCj\ DGG'88xڵ0a߰a=.{(((HJJBBJJ }h|Gx^&g4?OX̴(%L3M4 fg#==]ċ-jjjD]]0#hTDzKEd, 2m``;rKk׮ݺukh?~9sfIIɹs&L ACCٳg***V\닊^/..n?2dߐ!C=Y\\aXYYǏGmԩ\.͛{F))w 񯭭277w^˝;w,Xu uÇܹs׮])Ǐg2T*f޽{ݺutޛ>iӦ+V=z&Mt]F.rݟwޛ7o:tV#عs+=*(bw<1+4gNnGGq)Q2FGw(**ݾ=na IDATKssƍ}}}E`o߾=}155$>>0__ߗ/_ ,ipH$g???_SS͛7ΨYYY3g  @EdЧgeerlvZZ o)SBCC!ܛ|a͚5'N={žN&%%r:tԩS>>>+W߿DsssXXX``/ѣѣG{Pp܄?a%_Z%%v? YxEݻwf}@O1iҤ;wL<188"w9s<|իӧOpfggr\\\.\r.\(QQ͛7?~\Œݻ eΜ9^^^ (1yy Ι3^t>}{葖F XĦ:88e=Gq&q tHHHL<ŋ0 @ @ee֮]g0,667D jS{ԩSѪ[+dee 0`{Ncbuf˗ VWW/]ص_;yÆ aCE5cǎ-G]|~A`8p@=:tðX///CCC4())'N0uڵϟ~ P OZhEy|CDzJ--ŋ{ KYZZJII-_|̙3/^vM62fqg ,,@ ^Q"44TRRe2BVUiiiKK֎rDDh".'Ne ۮH7nB4/ 8jȠw(//={رc544="ye```XXƲeϟO=(~qqqOz… -[#AANNNEE%++ .]}%_7Yf'ϟ?[ZZ~̬KKKyho߾:t[='NTWW pL4@ ,^B:oٲѣG h722-''G[[ K4RQQ)++KLL0`ޘ`ccRRRaJ|hDEE455IJJ#!"BbXhf -D"!pl-cccԒbjjJјL'O<<0 ry_umv )PK֑#G@opϝ;w_~ƍrrr&L ++{q͚+**Pm把fsEEEcccyy9ڭlhhmjjDhY32,###///))IReee "BP(B+((FFP*!}AMMJ``;''lM~.\e î_>sFt<+Mnݚ=44t́k֬Y𽔕= #H4FuTf ܭjhh@Xʊ tD"QQQQNNBɵ]P( t5 2lذ'OD0,33/VTTi4BQQ[~H$D]]"))؈wm8D}+bg3g믿[":{[=ztRRҥKridmm߿ݻw/_^bܹsG1k֬I&Vzz˗o޼cc{{{wȼ\rv\ϟ̙׍XʈDRCvڴi^nh z?7^t 0ߥݻwzzzB݈Dٳ'Owm۶-\)++IܣDbq, U\fXۼ,CYYYCCCdQnmCH^u8p򨦦FP222 ^XXQbDD&%%%1"CܻwO|lll,,,]A^ J#"ο۷CCC͛vڱcǺ=,ϟ??|022rrrh~ذa=q^ܹsx슊 KKKnl6TQQQ`rŊ[nmAwckk6a„{A#͛wΝ~m͚5PXVV˫g} ={&%%ՅJsyŋDɓ8ɓ3gFGGca˗Qt̙־u֑#G1 --̳g{}nWnٲe999/_0,==ð˗?{lŊ,RJJ*++.\c6G"S ?]1/AVVvӦMfڳgϝ;w#F{\gPVV.++( 6222&J~욚JTtbU_gUUUT3!_eA6@-x|77/~׮?mtwky2\QQ!)dr}}=+))vQLl:kVzGyxx0LoRWW_nƍ[ڷoɓ' +//=wܘ1crid>W^}رcmll]ŋy$|3"|Q˖- ?}$'')U r(c???/+^umv )|/_={ 5ɾG0`Gϯ_\@R[[f|mlԭTJ*T"o|oR4MZZZ,/jjj^zmv3f*ۻw+W:L&SYY9** _ԔWSSSPG׮]r9{lpH$cV! : RRREooo+Ќ3vޙC*iiiO>}˗/kkk---M6zh''[Hwy fԒ23d21 SVVns,]^^bŊ9s@ W277⅋Ç6k,ϟkii{8 onn%n022m߿XX؄ BCC-,,j]@ ?>$$$99@ ڢ;j£G\a٬Y-Zmmlln߾}T33iӦ[%P̽{[Wˉ'ddd+++Gq k}̘1aaaQQQ'N\v-龜_|mdd4tP}Lrϟ?gddHKK>v'jsmގ!?K]冄\}b@ 233 ܸq̙3:.qKYY̜Aq!hrEEN.//G-xcFF;TWWu4[AoB \\\={&Jjjjh[QQH$X,mjjrmw%%%1 #*"#q8YY3g̙3G'Nxŋ9DЍmڴ… nnn|p=zs\ ggg''!C{=^SSSBB۷o_~ݻBEEEWWѣG=7e+**v}U77b]SSU P({:n3Ƶk^z599YII}_JJG=ٳg޽f 5kAqss----))?XjUEEH;1uO>ZjpqZ -zКUV~zʕG @pn߾qƒUVmݺfx@_6lXAAU8Nyy9F%EwYYYyeEEE%%%%%%0!!!-b2{;vѣyyy|Y*믿.ZG !O>/P"2D. -[P.X`ݺuǏR:LM)++?~ʕ˖-377_|!# 4hЎ; _x7os8mm!C8::2\B~%P(͞K,qrr)gEEDD%%%M0e眜ee|)--e0-IIIN:}4{7/^9rȑO>m?۷5kֈ{8IMMۺukk?>lnn2aALL̙3gكꞮ\#Ǐ:t֭yI#G$FuС~ի5k//C eeeD@/F$i4w-[bX,rNN`2|ɭpuumll5j222x#PXXh``aRkzWdr}}=WD0VD1Z Zvvv111֭y@oemmݻ˗/_W4jjjj{7nX[[+--ݿ++++++kk@L.~''޽d2A|MMM͓WTT0!55ҲeV.--w\ҥK,XБӂ(::zȑO>UQQ &|-""b.]tm۶ǷD߿kG%%%WZ5cƌ'N8q_1cŋ Z[ 魥ye޽{ϟ[[[N:/??e˖m߾}…FP2q{rCCdX-))III[2KVVVVVV7JQ& gϞDr٦x#gWTT4 2*aYYYW'--]WWDbsswn`7o3B6mZˣǏt0{cnٲeܹ7oJOO766^|WX2o޼5kXXX^zh\З4~t_RPQQvpJ[[͛7...#Fx9[E֮]{:tD"{Dr劥0 p႐qqqw=Ç;w:ujt:Ry qIMM}Ç_~]__߿'N:NC=҈#/\m۶̚5 V? !999999TV2(˅ %%%%%% CQ(YEEEMMNFUUUkBpbccیrikkNm资Q [/ fbbB"E ";88Q"sҥ{B:ϒ\fʕ+G=wC}~|ynܸa``Y'}9;;n+((RRRLLL>$77WKKXVVa "ɓAAA4'=x#:UPPP啐pUoooqtk7oܴiڵf٭%cbb D\L~(mVDwy$ Td2YOO/99Y***͛7?22rԨQ7L=y$44ƍ7nܰaL'#uuup^^ TTTXJA^^zf0Ꚛ G.?qDCCC///˗/ܚIIIߏ[h40,%%ťCX,VMMfeٕ\+8v tt:=**uȐ!ϟ?l߾=88X={G###qtw>,//1cڵr?vvvD;;;;;;vX'Oܿȑ#\.G^T]]GGGļ{fN>}b_>JJJs]f̙3=*q1 2@GRTjk50 knnFqb<\PPPTT˗¢"<*##2Lӡrf7TF(*($L&Y, Adz333уau022rppx"a>>>SN ڵkɓ'wرb udf. je<<<|}}{⟼xNyy9JMM]lYˇdffb'0LCCݻo߾ZQF >0tذaÇ=|mۦLrehʕ+CF:::4-11Q`ܹ@Ѽ0 c2111T*᧟~0`AO- @SWW?$&&&&&|IGGq&lll^~j*]v}a$`YW𡠠D444455uttFș۶mp8£ ڑ|t:EJJJab^?7Z[ 2[97L$! @fnn3۟={VHs^bEЗIIIYg޽6l ڷoJƀn{4yy1c,\͛7W\A?YYY7o޼to.a>|xbZZoOPdee7osǡLAd "fmm-3ŹɑxؔFijjjjjm@Fspp(//ONNMWW777/`0xF+//hFM[a4aZTDnjj-@ 0336uر#//OSSS`iӦ/ׯ__ti`˖-۹sԩS]\\9_*toooSS & <޽{٣%߿0L`9==]GG# ?^J>|9rTNN͛7^6l%'Mf " }z+%%eeeF",-----QϚxMt㲞^MMMLLL333SSSSRRRSS?}700t. {ӧYСC 2T*J }]7nAAAǏ 777---55Ogee_t: %8LMM}b www??M6{DmسgԩS[MOO722_ kMl6D"mٲ#Ŀ`իhggg1sq8[>|w| \AAׯ]neeU[[ז###Q}Mߌ?ro߾.CCCQMt:4/###@)))⑙ʢPiLLLv6zOOOwwcǎmذٳAAABD2ʼrphrmgee` >K/T%*988ܻwOx---"coo7tSZZL&S__md2oc rsswnR`)((hhh$''{xx{{!F{CuAo#))zYf߿'NغuaaEΝ;gggrϣ?}ػwFDDG޼y*鞞ycǎA>d6,557HHHٳٳgbX?~DDDPPВ%K=ܽ{B(677'? ".v-7644ddddffP'OM`0PR tCuuuWWWcF ќgll kG^FRRrӦMSLYbϱcTTT=.%(JeeGW8?h?""";;VSS1rppg2JJJTWWmAd򱲲UTTvr2ZEd @fff,z{{˗/744%g>{hGݲeˑ#G֭[k׮u֭YF,`SLqvv{G$޽{Z3DWW7<<а|DK655EEE}Q=˧O߼ycgg')<<nBڣ LMMMMMY,^;e?~I$TSSTWWGZZZ `/,,+((/((928Fz8(-ׯǏ×/_nbbrEއB{|TDe\&.N}ϣGQ---h20 믿F-ηox[ a)**DAdVUUvfiii 2mnn&HD333}]]]bbμyS -IEEݺuǎ۽{S֯_tRstt㝝߿ݢ>|˗/[֮300hy(## <$ŋOE$M.N\ D"ٳ[M6yzz*++_x,𨉉Ijj*_cTTСC{m{4FZ]][TT[QQї/_?+))ot|wWԔ.))a2x:`04556 tIII1q :tΝ˗/rJPP:BУ))))))6|5-- EMLL,,,LMM***zzz*"h4III  "z|]kll$H$ lfff/_4-&&FHY__Ȑ!/^ 2N_bő#G6momݺu70&&fʔ)C~zjjjZ~  Z $|B$;RyǎM>^x(*"cFeddod2"2ad2*"㙛B555Q~)66vB͙3gٲe***4Rikk_oŊ7o^`D@ѣk׎;vΝw0 Ν;!,ʭcɺ222{ׯcվ3ލH$7nܸq۶m{5oYRRrw SLr{wppPVVxȈƿ/_XI&uShZͥ~Ϟ=‹(M<ŋDп[n%$$ܹsfff򋏏O Қ5kttt|||rrrnܸ!BzjllܶmnYYY222_|iy(99ť}Ϟu%?e@wh۷oO>+G^nݦM0 g(&<ԄC?P@a(/a*/##f͚8pnt;&Zccc RSSy.{޽ !vFYT歩DI\nYYϋ'GQY"hc|Ct£:::xFDEEE:Ө9 Y444BBBƍ/>|xܹ0sIII,MMMhwwf[nFȄm/!x $$$]4ߋi^JKK%Q2lccccchrRRR\\\||͛7H$) %Z[[us%&& `7SSSNm%%%&)ҭeuuurrr 2a=r|P%''ޞH$N0AHYf~ܼc}322~ϛ7oݺuP>t'xsȐ!<K1_Ӫ{fggXvr C___]]}g*KYQQyں:Dұl/z,v>DFFFFF *?|0J%H P~%deeQNQQ{2&&&u֖IIIqtt0,>>>//o?pJvVAt|>o pZ}oA5:Pxyy5jǎR IDAT- 9@E"Z+#dΙ7#]H )r/6rх*t ]B_OX볯xO|]:‹w !d¶vhOa TCХ4TFEEE ΀>T "<<{l 8Njj* %߿H$PAsduuu55?D "3TMѾ~*222 p)FbOt}777l fp _\ nX"h``,C'b 㚼 l{/!*E* Zm~]E=@Ed er7@#5Ε@HxҥK&LC?T@yjllדeFcLJ812+$$:k=`Ա&4N'[SMM UUUqgVzEm|x~AFcccmmmmmmUUUMMM/VSSSUUU[[ԄXw L~siRTaaa\Kq8z=_kjj@|YUUUB#Ob&-ހˠG]\\\\\Bqqqϟ?ipppcc4jbb«X&&&wﮯ0CMM-??E^^NLLC*Nȸ4;^# B\nbkk{#eddBCC! }.]tԩiӦIJJN6^|)S.\0cƌ>MY^]]+qWJJ NEnݺj*P^^TD/_eeeĚT*eeemmm;FKn0,J՚ݮYSSc8^RRRZZZZZZRR jjjt@ѣGcƌNvv6x߿cP&YGGGBB"11CDUU׬- < `TUUuLHHH*"777E 2DKOO~>>>33ׯ_/_G9rcBCCtIMM- ;;ҥK/_>z(Juss>>\nRRRRPPpttlnnsrrB1%KOyݣGΘ1c̙vvvu˗/9RUUUWW'&&FtU\\!DRB7㫫tqXPPAEdǏh;;۷u;7nxyo#=jժUV޽{7**jׯWQQqvvvvvvqqQVVu7'..`ÇӧO߸qGKJJ &ܺu+88!B"zDnmm=syIHH|@SRR޿3yyy D"h4KKK___TWW%fڛ_N}ŋs#Bd}}}SSS---#n^+TW><VLLׯ_0GGG^w #vp*++Ç>|qkjjN:lS(^wjnnz"apu",&&'Wi4.o666VTT c>>};S8 Pӽ~EOOh6,""UEd~~~2L,""0644p?% _\\9)))9{GB sL\e9sɓ{aII !qȑUUՔuu.++[p!^含ڄt?¼,|HOObQRRԤh~~~8 IAډrNNVCB8sɆ^䥲5dL&lb)))YYY<(Nw 4ABXXBXSSN''///>>q:̈#6o޼yߟ8q"88800p~~~ƍC}YKKTTTP "KHH䮂$!!!!} 2C455r<\266 2BɓqqqSz<00966=Nƌ=vX ӝ No޼yE|||bbϟB***Ɠ&Mڴi1F^tKNNNNNޞhMNN~}bbׯO8QWW'((HMLLlmmmmm R≉UUUNNNܬVUU _Fѣ#Glذ֭[Νu"#WWW+Y$''L F3446mNXjii}C4ښC̳W.f1j(###QQQUss3kc c6kpR4_`=z4]D4OJJũPր.a cc޽롡ցӦMd H$RZZZWAdYYY Ʋ2D*d2P(lAd\÷H1BsDF=ŋܬieeeff Ad0P(GGGGG;v>{ӧ'Oljjֶ600O9GGȩSΘ1#""[dG{'O$%%{mII DP(ƍ#ȓ&MѮJKKˆ<]]ݞvi}˗/ccctuumllV\9jԨQF vvvvvvx`|)11o߾ ޅ%}%K=_ݸq#T ryyW>~`0-,,/^)))999<DPa_GG'==G[ٝ>}IHHە.]?۷OZZ?~O>|ݺuUUUbbbVVVVVVjjj/gg瘘)SL:ƍ"'%%l޼Eng^p/_222zWR(OOO%??ˊGnnntt~*,,laao |||::::::3gD1̌/^\t)88N=y]/3f 1{ JJJo߾r功ߕ+WΝ;7j(^w{]SRR^zI&i4т H\\Ɔh}CKJJJJJLرcnjCӡ ! ##3*fee}ϕ+W>|P__RVVhd\ BW\ΝgϞ{;w{iiiVPQQX999ߖ"2PYHH&|||8#CAor :{5kzGB3f̘1cBL&3++ׯ_~ɁZZZL.bkkqƍ?>::q /kEdԩS;:r*""bĉ)0̂uuul 577?{Ν;iii...05E" /^ܿرcd2كM o IDAT~„ &L`V1zjŊܬ˗1^M"///++ lݺu80>\WWc<^|||]]-1ydX[[ի-[[ZZ،;< lɮK^xǧCt:NAAF[nÆ ?ыj ={aUUՎAR|JvDf2555]BB8| > """zbll,7Ad111ooիWC ~$IOOOOO!Ғo^vokk2115jIE0,=zqĉ=do߾uXʯؤI^* ߣ<~8<(ecc˗/?vӧO{]^Ƌ޽{Ϗ9O/:nll5t#V#}}w:;;O>ƍluԩ͛;nCCCSS(Ù3g^zUWWGܸqBL8hAiiio֭[ϿK"&L_Q0̙3gΜ9 ݻw7o URR={MMMy㳰vͯ_:tOYPPAd!zj[[[___33)SS#\zx+++cbbܹsΝB%%%www???WWW(Gh4B111w RVVvwwwsssuuR)|/dƖ>}6222''dRTCCCsss:nhhhbbybƌ/vvv>uꔷ7ܮfWQQ)((`mohhRYYYAvu\!!!8| > [[[{T\n\1=j(KKP"NXX /&''8q"77fhhhii!Ǐ3gիW}6߽{| 6xxxuuu!7SLc¸1~xɡP(JJJ^+**O8Q\\>^WW766"UBBš5kNo>k`0 _]!֭[/^f2cƌYz5. 30Dx{{{{{3̤$\LJ}ܹxBPPNt///RXX򟸸'N㙜tQ=~߸qcժUsmoo7oHȪŬ%MBeee]UDkjj:nNJ8| Ad===2ޣ ѣ>~eO??˗ :&&&&&&DKuuujj*~z޽B F744ӣh02lhwו+W8檪fϞmoocǎo9b}}=Dr(BÇaaa999 ۷/,,LDDd…˖-ui7e˖%Kl۶m . v#GlٲB 󗗗@AKDDرc^^^ ,sιsyݩ,$$?7$$$TTTRSSYf`0p 7Ad<8.) ZY@@WDƓC@HHH]]=##G[ r3gO>ثn04HJJ-UUUiii.]A嵴6 C~o-Zt̙R~~~OƘ/"6644ֶ?~ё0I&6 ۶m QTTܹsҥKahih]% n|||nnnnnn>}ڽ{5k~3gk0-[tÇGDRDB-rrr ؾ};Qd +D$)d2۷zJ[[{ӦM .TTTuBE-ZTXXx鰰#G] <=uTښ۷o>yСC---8leeehh>_͝;755Jr NOKK^З/_ ,oX*ZQQq+)N qjii Dȑ#{ B133]`7니̝;744tݺuC2HJJښhiiiSNN΍7>}nWSSPjѣ_>ydAA5ψ{)++X1FFFO2|;;;Ɯ=Fo߾򪮮>tТEѣGnݺyYfyzz>'NOϟfCC/^tu2_AA")))((XVVmkkc+"sD5ԡD'tNt+;;X_dɧO=z0 9rԩk֬9x[RRR cccO>xbMMcǎ*((M2e͚5{p³gϲYOzĉӧOܹ7899}:*,,bŊ˗/t&C̐ByE .3FKK+==oȥI$Djmm]fٳgB?#455M޼y?鉈hjjN<9..>ڂ ΝstΜ9JJJ7n5N … +V033hs}%7{%%%ފAAAmmm\zzogg'!!aaaq5nN7ًno޼ĄgswΝ'jii777ssʃ'߿3++\^sW;x`ccc@@"-- DZzǏʬo߾ jBBB~&surrR_~n񹻻?|˗>>>555n\ҥK=_SS˝;wݸqK]]]YYyԩ;w|)zGBB.\/fehhո2yy/_6RRRL&m+\-@P(b C7Ν;GPZ[[{յkH$Ryy9=zԩ=ߵ?Ąnذa666*** Flٲ'O޹s'%%Hs-^CDFF"Y7nhllԤou<իW---.]ꫮݻwV\ƫ>|#G%ę3g}6&H$ғ'OVX|9:ZZZFSSu+V]zusyuu:)7mĶտ|i9e[駟؎`,LJs?vԔc(((9r~EE҉׏5:۶mcmϮCmܸRy&Bq㳷 OBBˆ#n޼90=sgΜ7nq;8r䈽c aW2;d2CBBlmmqa:^e՚gΜa2555k׮صkW}kkk۴iӨQY[[[̙SZZJlxb&,,K~˗Ι3'..X!::z„ bbbfff[nmjj{w֜{~!X7GiTTTILLu_^EEŝ;wm!'' ~zII ;8$[ZZ .uttDL8Y9{AA[;gd#""BRRRwRRRIIŋ BlA!ׯ***}8w'PPP`?0o޼iӦg!իRRR>|u_ׯe/"#H$LwwwvJJ J7n\QQL>ud2 [ǻ&NH\'fW 9Tpx.52 >z~ì .`ښ5Yg?vX֖ٳgIYYBÇ@wϠ ֯---׭[d2>|ڽ{77TH.hkZ`AAAqi޽=#EEE%%%---222rrr򊊊rrrT*UZZZZZ?d2O ƬYtz<$$d߾}DKSSXxx8xgϞw=YY+WJC9RUUU%|r< >ԩS'tm;:++KOOXsg[,---mmm$ K=}NvZZA=wvp8Y%%" WƟyBuuu=z닊/O{{{bb#CrsskjjFtI ?oo`#))k ({1xIII4 Ju/aaa֡hD /ÙBu/\sss``}}}= ֕+Wf͚`0G]d2` O>}ٛ7oჲ@988<{lf֭믿Ƶk߿ɓ'^ܷoڵkq˃\\\ ;݄Kxs ޾}kaaѱ۷s΍7v޽{gܲ{nC^zwބ&tyy9B...FFFt:MUU'O.Z8~@Ys-7Oͷ?JVAAF۳g[qhI[[[||G=zРLj{ҥK.e+ӐI଻m }xAHUU yvӦMͯ^¡իW777khh?ɩ[ ssswDh9c6_~em---E TWWw &t dop IDATK477s Ad:G###{]}}ܐ?V\ T*J\YYY]lo2%$$$$$ qqqbeЖ9pÇCCCӧ߿tz Euuu;wΟ?!bbbl999ZZZ}N&&&~~]`'ΕN8qÆ Ν[`AaPDݻWGGy:"ܸq!߽{q[6ׯ_~}BBYtttn{EሊzZd=DW,O'kjjVbػwiWpyٳW\;X (ȑ#GwCIcc#wdVUUp<544 T׳ΤׯJKK/\PVVշs~}O0i.p)H$x_T؊;1 Ox8TpFgAUp1\Y`d25B888888lٲӧ111111aaa$n h'+W.(( 蔮nLLLW*))֊YYYݩdq]®Dϛ}tN޽G/t:]JJŋBk֬9zhTTԩS{S .....횭DRߨ kKwleMsJ-JII .s׮]%K8::Λ7}U_vdmIOO ZϜ9sʕ9stC\\E\NNN`<իb޼yfZrO?gccӯ<]_[!~Nܻw/??3^!j޽-̘1jjjyyyΝCegg{ϧLw^ӧ_~}߾};wq>UU՜cǎJWl]|yLL̊+*++MLL?ӓ'O&$$}ĺBOAӇGp-f*))yѶmx< ""***‰'OܹSAA}ĉ]Ms:M6M^^V8M7AAd99v2!L&,$$:4Z@@!A LRRB(55{{{t &8::t+w$;;͛7>{{QTT $[#*wb;WG8PPP~|^۷og2e˖eeeeeem۶Wonaaqĉ̆ aYLL… ^^^=paaa=ݻFц-\vuvt%%%޽;%%ӧO ECCcܸqZܹjժӧOI3tuuϟ믿vk˖- ^ٜ>}zŊ`C`effΜ9@8qbڵ555|ܤ$pcc7QU||ٳgߎdN:֭[ۙL ޽vĉ>\M6ܹ)BhժUb@﫣8iii^LL֭[WuWك_NO?κv{pbo|JgϞ=zܜ0UTTܽ{7**Ν;&L>} Bu]ZZNsW466]rb`0(ʹs缽qKrrqzzٶ4iғ'O: .lmmŋ ŋ~~~G 蒁ٳnڣsL>|`ddk.\S?:TWWlٲL&GCұcǂ*++8mdf'L00CL&3,,ٳ$,00?Lfhhq __ߥKm]sά,? 4LO |!Xbx/_r;;;^w`{7ndddHHHxxxxzzN0ADD׽G666t:ĉ7l***֭#-**RRRzYB/_fd̙_fMÈ3gtgf0W\Yhɓ'{~Ad4sLtʕmU__/%%uy*ý#G]6//O^^G۫tƔ`L2%!!!>>^YY[gϞC% ;%_YYbŊ 6AG߿?k֬<<2~ӻ[ZZddd,Y;bĈ}۷iҥ4:6 7olbbrӧOׇϞ=]w޽kjjs\})](.]4w~&0<1̽{nܸqĉORs͛7qKJJ222P\\?ܾ}{ڴiw{ Q^_\fff``͛7=<ϟ֭~O^&**j8i3o޼ȼ< sly`驩=JTT(66.X@TTcv*++LBP]8>>sΉxyyZȽLJ 'ciiid2YOOT*uƍWQQ??~422vPZZ*##>$ I2L"f͚S\\t!!M6}y֭.]ػ︦70ClE58*LѺ>=?Z?]*uѺZGk- #MC@z{NA뼏GHHȁ 333#hxϟӧۺuP(xXzUV{2EQE988(?pcvH (&)H @=4ŋo߾ݭ[ǏkzDmX,f2}{դGpdԎ;~ ռ͛7cbb<==_~}ܹ'O" jXx͛7֮][QQ1n8KK˘Çۍ؊W0vss{ECGd/Y,aaa!EQMtuui4lp$-bjkk)"SK5֌ٳkךڊfO0᧟~jJ#TQQ1psYXX4rɓ'?~U>3--ɩ9SNmX,m۶˗/O8Q(矛6m[` Ǝ{).r\2rHss󘘘#G|._aee߭[e˖]tI;@xb˖-}qtt\vmhh]#DFF>˕YXX8k֬Sݻd!#88Ç]vݽ{TWW7R  }o5=6,q^u;SQQm۶Ν;eff_>߿mI-[6r.\h?~s\]]_|Q 2EQfffEEETAdRO X,nZ# Lh+b/|||԰G۶mzzzMj8s~#GlRC_~eee׮]{?ӨQz矫 SSSYdnٲw'O{|.YIaرFСC\\ܔ)Sd'0LrD"Qd0#Fx FϞ=#"""""<==52m-s޽,s)555W^% W_ ;,Xf׬Y䁅F'gϞǍw[㶊\(:駟\xqǎgϞ7o^ttȑ#Т։Ds۷ԩSdȐ!ׯӧ⌍'L0a„۷~>|Q4=f=99͛7K,СEQ'O}1 HjjjkjjS,KGGɓ{uVnn;WZաCٳg}vUYYyՕ+WFDDΚ5rժU^JMMCCCutt4=R5)B̙3G!cո:ZШQ.^ѣ]>~XinYd„ gΜݴiҥK=<<:t0k֬3gTVVjz4O9sg\\MƏr5=Fxxx,[w۷Uy<ގ;׶Z b "GV\nII EQ"H1V;voF7n088K.( SEEEhh;w6g2̙3xw}}}@@|~AA+;;~aݺugϞ555522R82k֬iiiܼyG(x+Vw^aaɓ'1o|&߽{YfٳeۊlYy߾}[||= NJ,X@#jSYbӧO>W˻p7gXlkk۳g=zϏ<~k׮]d2}||z`U==cǎ;͛7q3fQvܙoaaQ ... EI9//ёrERU^^njj*߄fb*"7TϮ5 xyy~RWWI {=Ǐ[Ʋq#FXx?bŊxOCBBjkkdUꉈ9sYw^o8!zzzE ׯ_h4[nusswt;wN: HXǏKn&0k, ߿߿?..$}}};vz|TWWc<~X,p8Çԩھ}Ύ+t IDATӧO>=,,L?(fٳW^ӧOuo>鄕ѣGMQP(}vrrrrr+ 8yJc``![=zyCPhnn4sݻw҅hzMtҋ/رcfffcƌ9sfk.bΜ9P|8 >>><<8q?P[[K?]vX1ӽ{ݻ$33ŋϟ?˗R)Nwsssuuurrrtt$?\FH$1>h 777www'@Qvl͚5w޽}vS- 𾅄ܺu+22K.ǏZ'>#/srrRRRRRR>%ѲMXLfQQEQ*Qvv6EQ[n9xɡ(u@%k׮]fO?4~xjǎQQQ=zXh{*lRUUUʹׯ۷)׷!?lmm---?"!UTTdggegg}$c&ikkK$5N\.wС<oj Yw866vÆ fZ"r8NPPPPPlD"x˗/?ˤLDBΗ/RX-"㌌.[O[nG&@x_~…ׯ_`A+#{zzZ[[_r F{*AdJJJ LQP(ڵ+"Ǩ >yyyj4 JJJR/:hР5k ? 4gϞG%ZF۶m_~yfU711T|,lmm;v8eʔSPЌxcǎ͟?۷ ./?[x@ rrrH4//Ço߾ϗffffiiigggaaannnnnnjjjjjjfffaaajj~Ǡ=)))),,,,,,***,,,(( YYY$m,șZZZ$ߥK(?wh SSCW] x퉮,Y2wܴu!-GQTuuG6t8^hhlguu,I6ݻG֓J\@_VVV䧅~ A$I~~~^^^٪z$pLѬH~РA1SN{]j^zѢESLx6%&&[$ᘙ*%%ETD...( JAtQx//4PԴnΝF[/^800ʕ+{V\paȐ!5LlܸqرÇx<̬X,[[[.͛?~y-X\QQԬkhΝ{ܹaÆ}F3yzzzN%ujɳ{n)b٦fff$L\.W%TTZZZZRR"De>e-;vOHmv} Ξ=+9B>]DT4-... >{ u[UU͡榰Zlff/^-9L&Ғ$#bSS6Z@ {ۜTT)((=p#8;;oټys\\ҥKM_kjH~D"wvs#Ad,j ,_ "t!oooDڹs禶 Wu{Cڽ{Ibccw5Ǐ;KPK$ՃEEEdb߿sΓ'O&Sԫ[߈UVZի%---{{F|>???dL g JJJ|B dO.p􌌌l6100000`0ZD|>_( ²2@5)ߜNs\S|ajjtG_N[pTN tRTTT~N<宮#A[[ݽ'?B0+++??T Ǐ_p!''GEQ1 %˖#+LLLLMM\.~H1 7 o_VUUɚl[[[ [[[GIU [[[>KN>}ڵ[lٵk… gΜ)~H?U>͛zZZZYd ^fllJEdUL&S6QNWj+Dxggg]]'ODx֭+..611Q.\8hР4'"D2o޼͛7s`uV//˗/_3mmmrss)R=\ZZ*_z_p8FFFzzzd[GGGWWfkkkX,]622FFF d"1RV TWWDʪ*PXSSbqyyD")--%!c@PVVF|>9?ÑO*Gɍ>={<009}{ j֭[rrrxxx>y"xãޣ|>PVB>QkrBNɓ'M ")^hMMM<@hѢ۷-Gb Z7F(*ddE"P( BaAF"H|(]]]RƛD)p87Ě)'"iH$4!rEE qEvuC,4PXYAm677744p8l6WՁUUU5jԨ:|SStUTTdffR9;;_~=::GǏhsC!u+^SS#)+U޽K^)cKmO===SGGGh#yA,| {yy@ KKKɳz>OՋD"9AO###2 sYԸlڵ_|w}7iҤUV-_O?`Utz=g̘|)--M***/ "s\Y933S|YY ; X,& 2@c5r܎;&&&Dhϟ6mw}ײ5;䕕 :޽{ 2eʞ={f͚uF/mll󍌌T_D9 jժGyŋnnn(~1c-----f^I 'bȶ|dn3U### 7N744JQ…lC[[-P2}(P /deF$߬F2Jz,ݻǢE~e˖M8tUGG'11Q9lggb޼y\"ϩl6[,RkkkuuuL&퓗T*}y׮]ږ޼yo߾j`ׯ.]v'GZJ\\܁V^rzOpuu(*++UQIIInӽFٻwymݺIݷoߔ)S<==\tiƌ/&ftm _XXXL4I lڴ~z 0<6f̘%KL>_~ٺu9=ikkw511qܸq  }ZZZ -,,T+++ACÐ\Z2DԋҸ$@3999q8'O>))9ן9s͛՛ PM6 :4,,ѣ!LQꫯ֯_իzO022255ojEd rqq1wtt֭wءd@0zѣG/XڵkH!@{ءC懐FVVE}7nk׮ѣG=RVL}022ڸqcRRD"ٳ7|SSSޫgϞ7nܨAdD"hl6T"X,DhtzAdx<^bbb30wܚm۶5'N8w3g:99} ZTTz,mrSDQTtt֭[-[e˖wvN<_-]5ںܹF$ܜAYYY-3Dh  JHH j 2*"BAAAnZzܹ>%00ʇ߼ySo+ 2-++H$dMotuu Ad25d" nyzz>}T</99LYP˝yyymN;wnJJaPP/҂ܽ{WyX,V>daaQTTTWWG^ rIII#i4LGGG!LϨ ОP5h=z$%%5-ݿ N}KJJZۀz+"]333YXX&&&E5뭈,JUd2+"3LT5i*611Q;KJJjggÇZ-{M),, 9pǿ 믿~ɓ'h4.פ 2EQ cϞ=!!!QQQw)--߿rr  hS} i4$TDh͌ϝ;gcccM"2@B̙"@tkXvAdKKKdy===--f뭈LD|TD&Ad%J\8AdUy{{DepSN5++--ҥKNEFF)`0L&E6aX -**uVݛ~m֭[W\~ CHMMrfffj罼뗒0Pd24=hdww~m޼uU*޻wOa?NPnbaaA)r$lhhXoEdDzY,+l!j(QQ.]lvRR[3Ay˗/o$ T*-+++//JKKFMM 9]kjj066&dEQzzzr_}ĺ&OusssMɾs΅g2yyy*vB:::;|~ 6nݺಲӧO;995ymʦMΝf͚ٳg7rX,V2EQͿ8qbС}MHH ZP(=6ٳ˖-3gӧO7oެww Q8doo_oEd###mmm Iqq1@fD&WA*"mXܶ&O" *={HRԶZZZ]vMJJ?~|G׹s'N 󳳳򲲲޾}]QQp>N744422700SL`Gd۷d|>_F211i Bpڴi7|_7'4Aݺu߿e #;;[?ȕZZZ ;)j$^ɓȮ] 2رcʷF\\ܲe˶o>iҤϬ%spSf=:t3gtU#j"hz:lٲ#G>|ѣjҥݻw7DhfffM,IXrEd WAm2>>>j4xlGGG/]4**JT4@_˗d#??_"LMMI޾G$+W_.//'!o>}&y)ѱsqqquuuuuuqqqqqqttlǏccc ?5諯ӧ˗#IAjj*iJ]]] ή&o0`@.]N8d2G5xǏׯJ-^xڵ;v0a;OnNEdTzA><]]cǎܹsݺuZP(DM KJJ޽'$ `׮]n߾]o "WUUەrTM=zvyŊ̒%Kz!999))))))$sKRFnǎmmmmmme5F7~Zii)֜?(@r;vmjx_5kVNΝ;Pж ݻw׮]+DbX7olNƦޢ$p p߾}ǏF٬heVXO:w^smmG=O>9u6J(lMڵkCիy<^S{oV5o"2DrTAd6][[[YY)Htц*"kiiɶT#=z`0׮]m`:udɒEv< QeeeMwAf"aeX$oM7nصkP(:tk$[,NwE-_E+/2dHZZ٣pn޼9vw67|[BCC;w,K!S`0~w''?ƍ? "@#Je'Nbzgqt:O) ?5jTddd|||޽5=fD XYY]t)66vG0`@ J߿~{{{@P(‚$ .c ˺555RT$)W!" mTDh|||=z^[__ߤ "St.]$$$HXfffbbknܸɓ&h"5770?@DKNIIsrr(r:uٳgϞ=wﮯOɰa;x?"##m۶fGOOf߸qCt{{{ɓ'ahK:99M<977wϞ=f66ի2d  zoz}77Vր`ٳgOիG>P 2@sرiӦ믪Գzrzߗr%%%Tkjj( kkkR)F"g>>>'NPypppRRRK saaaqqq"C[!H?~xĬ,:zjӦM\.o…cƌ9}tIIIJJʖ-[&LrLMMCBB,Xpׯ_ڵ+ … F k֬[L+1qGƵ7mcc#{YPPޡC3g4[xxxrrrFFF׮]SRR~)ٳ >2EQt:]"Rݻ]tQ-,СC|IhhkֵBжh5k|SN?UoD533P>¢P)-//k"2h(LQTuu5ybHD\v 2@>\=zuVK' 44tҥ-!@Kp]]]/^,Hׯ]reaaaiu={LlVVV۷o'//]ζAdHTRRbkkK^XӧO;w ܵkWӯ@c{enn~eggg5z`0uuuj4LMM-((h˶EQ,k=zo|G-lٲe ,?~*aٳgϷ%%%%˕H$eee UD&@+ 2$@EdMKKKylll[pH_~7T ^/1118p`BBBqqqBB… w\$::zڵ޶mʕ+===gΜy̙67YJI._S|~߾}/_ΆAlHY,10!!8==Λ7oĉ3f̨j|`Rtݺu|IϞ=/]daa^?jWDNJJW}9zhh/4=&@]Zzutt^ZZ>>>ʟk뭈lnnNQT~~>yibbBQTqqH$?9DhX,Ad{Rppp~,Y҂}4Ueelll̙SSSjժׯ_nܸq)>X[[?СC}YrrrxxͬYn޼i EQ}sNEEE "O:5)))>>ѱIa0V?wڤR~~~HHW_}ucǎ>9A@&[@롣ѷo7ohz8MN߾}T*]`*{zz2L4Cff$\PP@^w%%%dMx,.դ rǧ9A޽{"9k֬ڵӧ[OU;vlϞ=.\`0C=|)AL&x˗/믿믭[Z[[Č7SNc1cܹsݺuvvvǎիW]]]rrrhhh# "kkk}ww>qDΝȑ#{^zΜ9XEEE]]]|lvCfuuuutt!{Æ c2׮]ҥK3{c0 J7.))i|wF"(/'S^^f2L$f=qD~hmm Advnٲ%66vڴi]vmdmmm77G >\~]MLL ,L*"XYYQU^^njj*; *Ad&Id?휏֭[nޫW7n|'-5o&,,FTxۗ6l+Rhm̙3gΜ۷7o ;vQ8?<...11!''ٳjmllN TZVVV{z7ȍ#SoL&9$yt:hABBB>} p| 4= x_bbb;w\oYR*Z` l6[GGÃ(䄎@ P~_>PrWd Ai|||ʌhp "S|rǏ<P Jϟ?yӧO;99͜9sȑ4U],X`Ǐ>}z4=wpssG]WW 64ҪFᾐǍ7~ٳg7`7n8pɓm6dȐw D%%%%%%BP( @PVV&BaEEEEE_^^ΔB6T%9HSV/ v.szɲ9 "`B>_ZZIԛnp8fs8cddGO.kll%z'Mtԩ^æR"Kw"c/--%7{侮LvW^^N+**|P(Dn 5.EQNNNdؘ4!4<RިlWȦ)#jnu|><17HdwtFFFl[>6gddfe|X( bjjz^z?ҰD"K.իׅ BBB?_Uioo/J]\\ɂEqܒRA!L*"kkkRY!휏EQ?xлw+W(C~7j|NEEE[lٹsgAA!C\h46nxСumذgϞs2dHKއ/(*###""bŊ 5Q(n TW駟:~BidI?#$pd! \oFV io󳳳IɶB$Sh;v,Zڵk-U/|`R>rb_֮m'Ʋ QDsYd@F&5x7ʒ#q>C  >?>88xРAOnw\BP*" оڵ "WTTgK(R"+I[ FS1d2#qAdvѣG "Ϙ1Cr{3-[ 6lX v zݻwS5v3f46GGGg̘1FJHHؼy͛7~YO?]dɫWtuu322>3SSF~-+++޼y-^f^o߾'OԩƍcbbZ-ځ¢^m vL&S!Xo زHXGVP $"䲍7o+|*3330ľԨyEEEӦM'OnݺRMWWJ">M*;wn-;hD"S+S\\,߄lͭ.#ǛsGQI*+F^^ӧOe;'5X,<ٶek. ɓ'3f̘9|>(ڽ/"$$޽{;wn4TO:::盙^X,67NBEdGn޻w着۷o<==G7 :_@(**Zf֭[?x0^􈈈 6̚5kڵK.ZbOOOOO#Gedd0ϟ?&"H>{ɓ'ۿA̟??666,wqM,' BH41tAbA슊b Ax#6,ذ *RmA((50 BK@烿d3ٙ`2]}tP_SAAA~~ׯ_}{^^ޏ?cD@CC_~r] D!|h N-Ῠj)4MUUUEEE]]G={TVVٳg=n۷o/YŋtrޫUUUG1|>ׯ_AL۹CRRsssb|8dɫ):/iĉD~=))X|N򩫫+++sF tCt:޽{/>zӨrAd{>}fRRRAd۰w"2BHZZ#nEW,H+bt:-A^z3&&}3fh5ϟAAAǏٽ{9s^7ȑkn۶mk׮ 7oB' B#F3gNqq~"ˆ߿_d Bp222'O3gG߾}|}}c IDAT(ۢڢDYYYVVVff&7//ﹹ߿'&4 ─Q O( kYeeeAAQj,LZZZMMMEEEMMM]]]KKK[[[KKKCC񉌌:uΝ;UTT:#Ѵw睊vo<#ǕZ=t:qR2N#%%%%%DA$ yyy߿g/L544|fiiyűcjhhYFWD*<m6EE&q-KD+//WHF ?Ǭ|8܅ONgX$uki`z=cƌ 6L:bujkkwRWWb ___1RWW߳g… ׮]xcǎmٲI_ƍ۰afcƌr… 7Ϝ9wVVVrҁ.B(+++++7֠_tOgϞh&6rÇ/X@UUuܹ8 Ls9&co6VRR"~EG%%%+"dd2JmX_H٭T'WZN}UCCukmj|gϞ 5kVt111K.]rG(""">>~ժUC6mΝ;8 7jjj޽c2Æ |rAd۱cGZZکS&OƟ Xb6lXpaHH]\\ۼ}622wܱLKKKKKKOOOOOӏ?rrr9s|mmm4BQUUUUUϟ?9"^"*zzzzzzzzzWZ]<|+''gÆ ~~~|LTD}qq//^ܑмڌ lXQQ;+..FWDfX\!Ad\~ < @tږ ߿߾};p[޽ΝO:i%%%˖- :thddG: KK\t0$$d޼yj蘖VYY٫W/WWYfSIIacD~uv؁"|NTٳgƌF8qƍ ۷o:tkFɟQ0JZZڧO233VII 1]\\tuuqXiii Կ,7CᬭvKO{nժUo=zݻw555oK+"_vJ=#S~~~jj*8sVV@ѣ{38fccþ(gffD~LḺcUuuu߷o_8hL4ÇVVVS^^.**~ܹs?~H4200gQQ{F55܆LfQQN+"WUUD7TD;VWWOMM(i;}}}UU՘v"#6nx}Ν; , Hƍpb1 ?7p#G zcoc\B&SRRz5f*ɵ(߿EEE͛׿OOσ :O>ztT˗q(vǎK~-)))111)))))˗/!QQQ\U__߆$ VH$.UaX_~%J:u*;;dؘt+W?x`Ȑ!qx|aÆA:HOOONNN.uO՝1coj,KT OOOt>EBꚘCP={=ŋIEE̊>lmm7nXPZZ5P]]ϟ5 pY^^^ZZ:''c4 WChD[驩mYàAbbbVZ^C"())l޼yܹrrr~'Yl'O|aYYYAS IHHlٲe̘13f۷SƏ/899UWWƎ?^RRի uuuuuu1118A&!$iҤI&L߰aad2"#Ly|}}ϟ߼y($IGGd̙&&&ZZZd2}!HNNN>$''\vL& MMM:󴴴t֭{?~Y(Jx\RRSNu۩IHHHLLLNNNLL|]UUJ544~~~&&&}UUUHp:ёXRWW$'':t(;;!&&&ܨ.Bۏ7.66> 2 Druu=|Ad 11O>qeִ+**" ]999URRXEb^*"Ad\YXU@֠QQQmYʕ+;*+9}͛7#''gĉiiiϟ:uL87$$8˗/9sL4)''GCC%yܹUV#IiD&ݧNzQ???|oţF?{իٛ᨜2Q^ 1 lgghggw1ʙLfxx%KMlRRR-D"H$zjSSS\`4{ȑ#%%%-,,6l~y닉iii=˞qW3g>~͛7 븎coo]UU7SXXxڴiOuBhÆ xIg1Ad<|36`2^ УGYf]tgϞ]d)'))C[Ν;ũJJJaaa?~#GrrrxY[nnӧ׬Yn:B$ITT%%%QQQ&MèO榬lee{n2/^ܹRB111 MKK+((5jTZZ=X@.\pm۶ z,!T^^Ad4j( i>GB#77acEEE rqq1 2F1,,,8kȈhaad„ Fj!5JP>|q]žWyy9 ,Yd2iȑL&/]6|;00Ѩ({KWlY,ŋ>̬H$RLL {]qrrrtuu~}qWPWW_z5^zц` !O, hy&WB6mZ]]~JAAAXXشiB:::7o`ЬG?622Ammm/_tssVWW߳g߿?&(((8pf퓐ÐI@@Dh#Fؽ{O=4@>~xʕ$ILLlȑ{MOO۶mL@Xƍ6mGׯH߿7BBB lٲ<<<4W\!Hq,___\#~[d =ggggZ|yKO"2ARRSS۲{{gϞ5,^t;h+:wP'uϞ=YYYw]^ȑ#ݓMJJJLL񑐐:vXG-Z())2;;ի666mN<SGGG[K}b[ٿrrϟ?ϟ?ߧO`;;7n|ݻwVbXk׮qO+:BϞ==zD:;vRRR:::#(ҮUTTfϞ}…?~nܸfD#,X $$DP( LvrrB̽{/_VWWO$rI^vEǑĉ\affV\\,,,sXӧO#_RRbccH<"#}||6mtW^}򥴴ݻ @KHHz銡nB[[{>|x]MM LVVVhUUUM pppˈ+ٳ7n͛?[wނZХQ(!C8q"???<}؋˗/_hnn~ӷm_߀̢O<9g }V#𕴴 BCCׯ_O2ElEׯ-XoC>߿:Oٿ@@̉'MC9]]C[n 8ydhh:iEW vZnqqfÝJ]# IDAT1>[^!$**JњmlTDDDLLU/FHTK;v,Zhx9C+2eeeL&EOii,dKD6ّ7{FAmmm A6;$״=FXػzo[p[ ATTTAX%uuu?򶢢bIII}}=#Ad111|vncAdb8"2ݍdjjj[>|۷o;Ö-[ ?xtZqqqǎ sǏOHHرcĉ\k.ӧO#GBg ӛ]~<<< ԳgϜg"222x^[zWWWIE|!rss9JJJL&_[FF$clEdb$,,:ÿt 284hN?tP`06o޼dɒvXCgϞ3XݰX cc㬬,buY,{88r[n5[k??۷#x;ו Ǿiڵk~X7o3}a``͛U5'addLJLLL\\\Ӌ=zرѣGÆ ۺu+~h͚57oLNNZ$??Z\\<""BWWWi Gh"GQ(onڴʕ+D۷o?7%HO񐙙Y\\`:wss;~xNٳ oݺTccw>8p`\\BHHHb |g]GSSS{E[ Ehr d4=nv+grĄ}&fSVy2{cbޘ}&fЅܼysر\k>~xyyy***xIUUXddرc[~(55ݸqs>}$!!qYfAW^%o|||޼y ?s?M0ի111IHHh(MLLLG׬Ysࠠt6݋@z|Ǐnjec$Ν;G9w{ z':7N4)$$ݻw_|tpp9s&Baaa>422?~/1fdwE;ի۽PJGG˗/3g7n\ff6B(..NLLҥKb7]NPEDD=z;“'O.^hiip%'''yyy%="vvv222YYYaaa[n]v-ljwƵ333w޽f͚GgϞ,UU+VeoZhG޽wf6=ݻw]_z[+ibc#"">,##i&[[[nܸ111188xڵ5m4qYYY-X@_RRRM\"zzzÆ @Pp&OKc L'߻woȑرylqqqg6|.dOOXc?~͎ٚaD޷nAA^R__/,,L"zޜUlkذ?|`hhp͍&6VEE%??WSw8/;oZZZN<|wKKKZ92###99ŅE@& 1 ʇ:88rɗIQQ0!!mNAA(99sRR.uҩNciVdf{:C.Zl0;~Enܸwuvv.//K@1 \oV陨]ZZ$52ҿ~EljDjb4~nQWh&:kE ~R8kKS())6^&,Y͛ׯ_s}xر7n$1BUUɓ-6nuѣmkk;t-[o***~~~~~~+ MOOG޽{۶muuuT*)::]6?oh1BINNnKRRR2&&T*5((hʔ)ޖ$"""*聴RӇ;I p:a„߿ z'/Yd۶m.\믐6Ѿa~B ߮<ш_[ı[d3f˖-={GG311yϟ###m_\xQNN.<<.\~EqJKKK@C>8p ׊BBB,KDDm***]gN8=<,, "?..ӧfoR0883o O!.\`ii n>lee5a„ϟw`YYTuh/l81?;%qg~3Q@3 /􌟋&%%On3^dggW__ǵc^8bu***o߾mR^^&RRR &&ZCADDxAdD" @bbbƕ8p]Ӭ ۷oW@޾}%QTh4۷o;:I&333-,,|||B t:=99YRRžK$ 0o޼Ǐ!C"""mpž={ܱcĉ\k.иq]>Я_?A3''ٳ|̘1;v?~kvٺm]ӛyQwww3hmv4K,)))155T*5++ӧ'OLLLvӧOw޽ubb0}ØLfPPԩS! d2LP('N:uСC(Ђ  Ж+) ֬Y={vi2D211!1bBٽ{7[WWG"._̇QTVVʀ}&豴RÉeKnܸ1) x L[[񔗗s!===ub욛yii)E$,Xh-&v&¦7篿xiKx6WƮYy֭[RRR Cc?ϟ?G/qDJIIԨB$ITTD" 0`?~hزr={| .Lѣ~2Ç7|hȑL&?=!!OvSg:ref{D?ld2oN&=@`?N"nܸѡvhhLEEEqqo222޿ɓK.9rdϞ=[n]fʕ+-Z4w\WWѣG;99YYYYXXKSSSVVDz ---//c```aaakk4a„)S,\kʕ6lغu=zҥ/^ǧ򥸸5fϞmoohPQQի$Jm\9**cpr֬Y ŷϜ9#""o YZZbĂz۷GUXX(//TWW8p?_zu@@wUUէO z,;JMM莌Ǎ?8p߿C*++gddddd***>|*tÇǗٳ+Iw`X6d޺u+,,ÇFFFǏx [WWrʛ7oVTT8::ݻWAA!x{{:cƌٲe .(ןpĉ'Odggٹ6{'^FS1cۏ5JOO_monÅMoNEEŦM"##*++<56}풓CBB޽{MMM3g4՝GEEw=}Ǐ;Tcc? o޼)Ϟ=MSSEEEnnnO>]vzK:~3g>~H"WX1dБ#GΝ;G3,X>ؼyszz)S-[ҩLK3qV>PVV_uΝM6ZJiƌKJJRUU.޽{qetQuuu03.uaYYY}}}YYׅB"dddh4F7DEEeee4 g5{O'O*))i}VVW|:;;[CCg^zRФGyhd2._ŋMk_pa6J522Z 2mcgذaJJJaaa4flڴ)88ӧOjjjߗ/_zKKKAtG˗/yuGw4d]]Ç:TQQǏ7oެ8qℷڵk/_`ffѣjrOOڵN zDX> IDAT8|޽+豀.d?~Ŋ/8hjj 0/ymۧU9sȈL&sAAA;v=z4x]K3?fgv VQQ9qLJKK=z<|BtD3f(//~zG]Zmm_8ɿQ]]]ZZJܨ7\DDDLLLFFFTTTLLLZZFq'nHIIh4|! bcc;}^~l۲eˉ'_|WQQ)** {K֯_?qq;wpt9~Zaa+W+dQQQ==&$е)(($%%P}iP?yUΞ=˷NIII!=MIKK#EEłO:uj…WRRb2 YxqII BHNNj))ŋ{{{X[[ z\ņ zjSSwޝ?K Ș={͛=(.1֭[@1s'k֬qww_bŸqbŊɓ'S!߻wC Ɔ btIHHn߾}ʕEYYlG]JRm*X,"\RRBܨjx#;;1WWWDEE%$$$#------))I,d###Ӗ-'//͛Ad EċB***$)//|QQqWFFDJJaRFQ(_~5 UFXX_ṾJ6l@266NNNnJ?}~#**}I&-\֖=P( БE}}aF|r*o߾CÝ9 4۷n 155]xi$%%=N"m.kf_2h WWVuG>}t̙n%))I"ݽ{ɓ'|(]֥KRSSCBBfϞ7{ [C뒺|{nd>xѣ7nܐY|' 0`ƍk׮urrhjiij0$++4sMM N$hrYYYUUQ]]ׯ|򥴴߭lBTƤddd؃222fg"#H7v|w]**''wfzBHVVLEE%"s-%I%$$:Df0mD 2mebbɓ6RJJѣG "#&L0tP7oސdt IXXO>IIISLX}/yiSVVѣ߿HSSiٲejjj2)11qѢE|WD{B(((_~aaafffŠW޴i֭[TjHѣG5*&&˖-;vӇ "***f}yO>y{{GGGX;qqq1JJJ J, :}tNN+**iӦYfihhaHNAAa… .ϿrJTT˽p"ښt:@'TRRr[nݽ{PUUy...T*UУk1ii鲲2nݺ)Sq ݖÇ~;wDEE]|y۶mCuvv9rd==L@ XD?޿kt:]У uu]v͙3eȑf"p%.....ҢgriiiII q;.NXh4Z1eRRRŠ߿?JHHq F3$ HHKل% UQZZ-ֽW]u׭u- 2eg8o~a~ʕ+'#_~3gxxxxzzzyy9883ZǏ?zÇ2LOO?((O> &M4ʕ+SNƚNPuZ3EEEW*M2\;! LViuB 8m׏RiFTDW|~tt4.*ldJAd Ad::nmm=a„Q(޺u%:::K.]xɓ(hO8N@@uN߮ڙ젠;whii :tժUʷ*'O>|0<<͛7AAA8_-zl׮]_^M_nݺ2GlʫW?~X #$ҥKz/_666\bE^3عsD"wf9`QQBh7gMMMMMM##4P)d߿|UL&%mmmK(522~SSS {"\gEd:Iy7|onn믿y6SLqFppŋwwիd?Y=i+'''y8DvrrRl2WW׵k.\!ٳܹsΝO…=ztR==='''Ǐ,,,s@ ccccbbbbbKJJtuu=<<ϟ߭[7WWW鱝rL&?hLf~~UxxxxxS~7dmm Hy*++/::::::##dgݺuWw7F(:t'Nh8yDhtX,ōiP(r?#srrrss_|I쩨 EPPP(:::x[$D"Mӿڳ544 ޿Oή#999xd*"!U﵃DEdHnC @3pttgYdIuuuKJRoޣGӧO5 ;;ɓ'/X`Ȑ!l6[iN[lIJJ_`#Μ9:~8Bhڴi3fܹsnnnddƍ񭍏#GGG#^| .DGG>M@#GFDDD{oocǎ|3oݮ]?{{{bݻ6m[lي+(HtrΝ͛7O-Ö! 2dPee###?iӦraoo*RKKKݽ-ILL㄄P=k֬nݺ_L~͚5kD"BH$B(//ɓ'Ϟ=޿BBBrttH$xgggbqڪL9}UEEɴwrr|-ҥ˻w^yKnݺmjjj?||7o8;;Z g͚5j(__{Єɓ'%%%~~~~ٳSNw^qqX,?ʕ+Ǎg``bT VUU~:**QQQΝZ[[[ZZZZZZYYYYYAdBqqqx͛7!sss''I&9;;;99-4-((HJ[[Wpb2**ɓ'+--%FFFVVVxgbb'PQRR[b?.bGG~-X}v;;s:tAd Бq8cbbp윏R)q5999""oŕd2l`` p@Y___$sܯgsss#HO>%NLMM 혞B(##C%, VEEEYYT`0pDjWDƅ)BhslI2899!DսuV B֭^j՚5kZצqɓ'_i68~:h bСC˗/Jaaa:/D:t}A#(JP,YRmvhccs=z ݻᅬ?^&͘1 {5sL\y|xy󦙙9BF=zm…l6{ѢEǎ?>qPT\E/ IJJ߲~!fnnN-,, wЎ&&&&&&@IOOGQ(SSSKK˞={vrrWݽ{7>>VBSS_~S{ɓ'Bt:sΖu桁$)))11Q9yÇ":~xkkk&@p86lСCr("FjL}e\%J322322R)1++ d2zzzDX@WW_vVî655D611 ' 2XJEdL&r+"xqcDEd" B}}~58$W^oVS Xrʟ~iҤI666-knܸ1iҤp>| .ݽ{… NNN˗/~K,III6GAmٲO>xO@@n߾}=ڞ{9rȅ [q߿Rݛjmm}QFuڕdر?rTyO~~>_,7mۆ?h4ccc|333bCOO>6<%%%111)) gPFff&n  ---˄w ;wL:P(999D׷o^vm֭ŜJ `*hp7p@{x%PoP޽{O0!00gϞ* Ld2ox<Oyf8#˙)))>|Uri4X,ơdCCCX/bH$jtӧ%''8Pi&6Dp8*L&];LP( "!Dstt$^YgΜ9-y P( DdqxDL)))(F344455544tuu! N&_G)))ʁcGzǏ'0ڜج\rƍ_|\br\n}IjT܌}vZZ^!Dq42MgϞMMMKKKR)f^A:+"7>PDrtttRӏӻw//P(={tӣFjG_?1tk׮ijjGM5lذgϞmذJ1̙36mRn6c ooo##\Wm YfEFFnݺ!"Ccڸ]~=88x=zwիMM1 O<3fL@@eZѓR\.Wٖ-[:t̙իWX;vH$իW\;ܶH$oooUUU111nnn市r}w'RSSsƌ^^^"H$ B(=L&IMM%>|K#dX,y)D2x`'RuJMM]pҥK--- 7驜P(ĺRT*J&t?j'ڛeff&V'JKKqK:Nzxx7#X,^f̙3[[/;\.x1 B꼵H'ܹs'555;;a0D(Y9lll 7cJJJ~~~tRR9NAAr3 2GR\3 IDATKɓM djj:eʔK6K>ה)Sׯ_*׾fffW^m p ܹs'BTe yw775t&..]eB>yں&<|p>>>NjRwttBGFFFǎXKKP(v wuumn>>>kf  YOD"@USSE&߿e .\}Ŕ&''+o6:::ffffffxG]!%%ݻ=zP_]]d2>ٳSLhup޽Hkjhh,Zh͚5t:)...**J555N#ikkkii}R[[~VQQQ^^^~~>qY{_ bLMMuFdb10-׮]wNも"@B uZ^^^_ŋUilL&S#wEFFB]277ѨQw6dGݰaÜ9sؓHN&Bw7n\'xf1˗'O۷ﯿxaÆ۷t۾ ++8zfJ500x񢣣#s޽T*F8pXߤa8AĔq6B%#Ç_.7pL.KP|>L RT#,!"d2q9^eFl\5s8*ʾ,\RRR ,M8:*** #FΚ T&EEE%%%*ioep|rHKK _Q~ 5[tieeeHH Ad_D"4qaar1]x%&AD,1S6pL<~SGk<GbCKK oH$\BFaΛP΍ \פL3b|X'<VwIeĐbsxx BBBΜ9iӦ/.!;r7arnݔwVUU$&&۷gϞMLL_4kkk[~vF"<~333 nVVVMMmD} "WYKK 2J%HDEd"q9::6KȨsηoVWD"m޼u׮]AAAjx<ޕ+W.\8iҤwnذFz٤I222._<`vF* tٳjT/x֭EEE|Ϳ|رӧ߽{Bh۬K.9rƍ vEd2"< @+$ .]lٲ~AOO[PPs@Fܐd8FLE&jnjWU v7'Ȉ(;)HK4 UN\)c͡R澾Ϊ޾}{T 011H$6666M"9rD%a211yqU,WTTkkk; 2&&&ʇb2UUUUD6J%MjAfx̙/L_֭[[t 888̝;wɒ%Ç744TW7WE6l=uw߾}}Uw@VYYot&&&J8Cb6nhkkQgKUlI$ݻSRR#GZj-6GPܿȑ#gΜ)**ݻcllLDo)JMM`wôG=zΜ.D:t' ZCԮ@@BT_vf.//oĉ#GD"5\_P,C#."\7mb BQfL@ &DU;xDaM1]8}9sΝ;WXQ_Y7&@" [L~!>JwhX6崜2AMs~S O-+++ T^N _?C|tΝ_lOR9La܀0LMMMb2 8ԅJvԩSN'vɯ^x TN'~sqpp(,,LJJ233SojjL*RT9U&ZZZ2L"Ԯb0u.GTDFQT! WWWzD899d|||4xiР={BعsN2 tXl0E/|=EEErs_RR8/B— (Ԧ WcyN?7p%K d2L^"Z:rʉ'5r@.Kb';QAAAyy9^\& ȫMi|S).|>_CCfs8 .W8?x<%6xMEEEEEE8F d2y2 +++6 ?#BxFIC;px\8_ALx#SHHinK+/7M,̢ܠF\.dlb1 A؉d|>kCE&0 D"HFzŋ/^ٳgOjj*BHSS޾K.]tm$)&&v,33"B666rEs8x0@9Lш$@f@"D~𡿿 X9sfȑh/^<~O?tܹɓ')h rss-Z={>Z=?D7Ǐ__KJR~u rUV3FrΝѣ!SΜ9sȑK. Bx<6,-----/)))--ť}e%.Ɇ/qP`N*G4d DSXؚhD9ꄄ R-L&lcXDg-رc׮]|3g4^2 !TYY-Js>RcSDuuttpnXKKdbJ+ELM'>7<_muBBBqqqYY<O( BXLlc |'dŅ ;8áh|>3/Sp\[(jjihh_D$30 LVXX(JPĆ1555yhiiI$ҳ핆3G&ƾx=ڻwoee%rvvƹdWWW++VFr&&&CUojjJLLAdPHRRU2+ uDEв0p\SSh/ llln߾ 2BgϞ~Y`?~РAk``]VZJJJm۶vZqرcǪGtuƍ__K#dG8ellDZ}%tVRg|坩D[0~;DbX___WW__Wb\^TT|6:XNu rG-sN7E_Uw)ߊx)ƒsmmmmmm bӺ@}|~ݻwGGGGFFFFF޹sgΝUUUͭG^^^g9D@CC#))!D&utt233U d2é)**"]N2LR'Qc 21::Yջw[n5ˡbӦMK,پ}:.aÆ'.ZhޫW2SEEŞ={~Yf-Zu~d N| A3쵵CCC}}}ǎt9s >o߾_ӭ[ddΝ;OL&djkk}+++kWx;555666???''Gft@  bX, P1h ww~ҥKi/kjjZn𹪫ӓSRRRiZZ~LOO%~1o ...8p,  |V2[PdZ޽ JD{>K600D&&&mJR{RTT$%??_yB9gl6[SSt266x>aeT.t|BDzzzDbP$5,7hd2oYYY>| իWYYY8 G MLLLLLLMMMMM񶑑0\uX%R\Mʅ18y9Uy6}cBpԨQ7o x۷o?p@ii1c9ȥ 3228o߾>}>|xȐ!,o<u=~xΜ9+Wl`&&TD+//OMMMIIIMMMJJ)))>|VB$ pS"/o!a BnP(p>>++ ;J7oLKK+**"ij!!!ՆBqVKP$''(>>>>>>-- g NZXX)Tmta}J؉D<=<<<99$VYZZ}mXEEyyJ IDATy*b)QZ`vq&m577oi$x.J333cbbNC(BJb1Qb}L__̘1cƌAd_UUp1T*~{ 2cMM͋/<==x(mmmGG۷o=L&9"HVZb v~[nӧ̙3n}vΝ8qbpp%B(::رc˗/:u%Ktuu?0~5khh|7oXYY5*"*/_|%9 mmmKKKkkk???333?S!DP빷M`vbbb||gN<dKKKKKK;;;;;;8 H1Ml(gB CKKK[[ [XX|>+X:8b^_x<ĄCT&*#dhllahh?|>РA B=x ,,ȑ#/{2dW XZZRW^߿O\ű*sAᏢU"CEdԔ4= mqtʕ ,:tZDҥKONg㳆Z8s̏?o߾ݻ7c['Nl޼瞞Ν8p`c+''gذa7nݻ7:ujiiѣGgٵZ@aaỏ޿7:::;nݺ=g. %չsΝ;׾Ç8LdJKKBt:̬SN?211\~2dk߿2D"AEduINN~]uu5N777߿ppP(TwgAۣGegg+>w+++)D"111QWϿXUUUIbJxC%?Ҳb*Ͷꬬ>$''Ç111W\IOOLkddN611Յy zիWg͚շo߱c:kTNҸ8555xX,JUB""HqD"TWك 2Drvv~igӦMR˓sM׆ijj}'+"cT*uϞ=so^xQmTUUՉ'V\ݻ>}}佔K Ξ=; @__: hy;w\hhH$j%Ūs.y%''x"666..۷߿_zH$CCΝ;[ZZ0SN8FfeZErGKwJ=zt|J511MNf$iܹVjxƅ\.gXs]v433S_QaaavvvvvvnnnNNNNNP+gBt:]9@,:w\;X7 uP(xbM "333%%*@?>}tffB@QT===<Gx6p_~ͽ|';:>a„S5#"rRTP( Ⲳ2+"r&\Bh42ɊT*qoLx#{rUٳ'JwިQMA&iӦ ;@mx<^ppYn޼yѠ?~tA+?~ӧ&L8qb#,k޼yK,?~;6mT3@KzBHOO*k5rdpp0uĮ]YY׭[:zӧO,)R'rvvoܸIh޼y3gΜ`O6fu4L&}. \200qpp>|8Hv ebX,RޙN8|0'% kiUƏhѢ]v\f2 WR^^gffRT{{{ÇK$''vpio~xOnnnTTTlllLL̽{vY]]+{yyyxxhhh4ˣTqVVr8'''++ o9$P(BP[[SN@%X/FmHh=d~*++Ҕ('$$9s&112LssshrNLMMq `ҤI&M>uԟJ$\C>Y[[_rEeB(%%b1^@y@5008d2U5d2Dth;whqrrsNk"#~_uĈ:uRww:>}g׮]ϟ?vرclaFѳgO>vĉw1clٲ/YD"޽{gϞ6cƌիW"B$1!ɓ'H)S}\OOk׮g֭+wVN EEE?~u233544\]]]Lzzz =jYY]p]> 1h4r |,???AIllŋpe---Z̾ DZΏ??ڵk+V;w)SR޼ysuurؘD""}!TZ_`0 Lp+"#L& Mє+"CٹիW)6I}ᅴ~xsM:͛ B,k„ &LJ'Otqqٻw1cX,VNUUU#FtR#&X,TD >HKKhյvvv"H 3 {^xի[&''S(;;;777www777DY'!A1cƚ5kN8F.CEfP(BCC_cN(ƍCeddS P($͵@</榼H'Ž~8Wplll(666&&&P022ڸqWZ5o޼={8p@x8 2Bؘ" &Jhiid 2)((Pn`0p޽D~3N:PTDf9;y?,l:9s,Yo122Rww@cbb2mڴiӦUTT<|׮]ۺu+ڵ+XmF>bsĉPA$JjBB%߾}{ӦM4 'ݡRWسg'OtڵP˔߸qtRiiiϞ=???"ptzzz~Ehh?s+W 2d̘1&/`0lmmmmmw}˗^JHH8tP\\BhFFFvvv%.v Ԃ[nƌ-Zr9x<ׯ (%%oH$ 2BAd\^gE+"CB...Dݻw k=AdЊ+.^ի-Z:|ҥKaaa۷o_lFsqq޽'5jjj^~Ddkk믿pλW^ׯ;wŋ8`hh.--~矉 ^H${jժǏ۷ׯljQFuU/r<$$dΝT*uҥ3gduօ:99ڵkܸqp ޽{+V:tgݑ`TUUUUUר(pBXXӧO Ӱahnj w޽|r.۫W}>\OOO=mwnggo߾2Ҳ{զ={lǎΝ+(( 5j^~Μ9Oᅦ>sLXp.ۥK.]{rrr^|k|_eff"X,5%;:::::뫯kv5gΜG~I7oި422RB ''oWd* P%g"2^GQ(d2G i5 &yĉ]n߾}֬Yh Bo޼y/_qFIڪ㣣"##sss544\]]G奭2%K1bĉVVV{PPWΟ?___111xOcN}╰*++ 0d2O<>iҤgφ<gΜٸqo˗>tвed2Y``?, h_ZZwm۶%%%?>22RCIKK0a€~Ͻ/^QQAdBO>=y3gLLLzԧO@%&L0aPLLLhhhXXآEG9fxhF$iԩK,ٴifr|ڜǏo۶-""qҥF%RYf͚zԩÇ7|Yž={ٓؓ꣗/_hP($08*hii9|)S9rs#+QQQ*;.]D\L (WDd"(##C ɬF^rVCCD"QԶDٹ8>>Y痛wqMdO 5AAi"]XDʂn{/, .`AWDJig6A }'LΜ$uD<88xŊ b3k֬'N\vmر_|ٽ{ȑ#l?իWxҁMYYY\\llldddo޼)++Zm۶1cƈ6L044 \dq%D/^ܺu+8qqqAN㣴V93&&ӧO7ogϞ-X0 @CC000ƍEEEVܻw5==}ͤ+**ve``lggtiH!Y,֙3gZqNs*..޽{׽¦Nڅ" E=O; mg6nh`` ))yޓUVV:thСt:3-bjjdɒ;w]vMCCcŊƍy& ۻƍM> A?y򤑑Nllg/^R]3u t%K?---cc㰰*'nܸqUVV^zɓ&L]d̙3%%%EMAAaԨQaaaӦMTNN{Uܾ};88fZj@:>뇺N Wްa֭[w9j(Q @QQQqqqqqqwkkk_zc0IIIW\&&&VVVVVV]v-ZJ'~򥨨<\KK!??_CC!K'***~ edd***HHHOAd+ë֞H@c2)))'On{k eСQQQ!!!moM FXX͎;;;PUUUUU1b[SSիoYY IDAT?sE55aժUk֬ jll>>ɂF߼yCrz?~Ad%%&+"8bȕ;B<VM(".͓՚Ӽyjjj$$$զPoӦM˗/:t <q b`0l|ؖs]/mwҥ9s0ptR'Bϯ !OS%q=aÆիW9rWW5zhii˗//\g9jԨ 1bDyy۷GuN !{֭_~ɓ'w&?-flllll}ѣÇ766D̚: gΜ9sRWWgXo޼!!555mggRVVn2*"REr:8LEdXCC5vgyyy#>~Jk`kkۯ_ZQ***^~s[;99YZZ)++79WVVGWWWX,aæMwޫWuuu~:Z\\Vrrr3 |ruu$лwZٺDFF޽FZZ!$&&xÇGGG0|gϞ^xIz}q~_ml'55!*NϞ=B;mRTTf -[/q/q8}РA :r৻qk +VׯN'sȑ#utt-,,֮]WѧOΞ=wڣGoQ|5EbݻwXZZ^vBaaITTT BBBϝ;7w\sssIII}}ɓ'?~X7ϨuW ^ fθr^xamm---}Q 0`\P\\HԑpXLLʕ+555ͬ;555kkaK>.ׇɓ' ^5tF$,--233EWyyyTTԆ \]]t:2((ڵkEEE`3g--yXZZ.Ygw"+>|Gŋi4ڕ+W(Jcc#NPPxXXO !|?d.b:uJ΋hwPgٲejM$%%/_.OLḼ[.sȑomJ7N"uΝ/^dee}Eԯȑ#%%%[}}֬Yl6ӧO -x!uGCCԩS%%%8{U&Mdnn!)))33aÆikkS(fRȩT*Zr7oR.]j{S7@p<<<"""Dݗ*//!y͛G"q8Ç~F%` roܜD(By=+x? eeeZZZΝK <5 yz3ZѦ _ o4gm:@&?p ׯ_c@5QFt11#FUUUY]]mjjjiiYQQ! Y`E4Nt{ͿWMn-2ssssse8Λ7oN|v횽aA>Eo#<>eee Eݑ.b߿0>>!3]D 4FIJJN:5<<yAqqN^Y`E4nlذU;#BL&â֭[K.C͚5+,,g8m VZeddijsǏB7yɓ'[믆;vi|]hZjE ޽{^ZXݸqJ'T8СC;iȝ8qdzxxTVV gϞ?T[[9s ddd tG8Μ9sL!f ?۷ƍk?>M|}}{E98 `ggpݻw_r%>>>778ŋTѣGf~~>B@ƾәfaaڹs'd۶m|BK.%?k…|Upk7o&/#O8A^"jp 6ryÛ6~]'LNBBB5i4ڔ)SZ:a},qwC^^U;#ZSPPu/t>_ `0(J~-Z3!C\ѣGl½{*++weee9B^!77}+\PP|2Bɓ'Fly6Jb 333I]nڂ*j~|߾}jMVV:**CXm #G<<|wW^uss#NJ|!777///''ӧ7n(((  UUUuuu5556(k@gSȧN:w[Z8@T>}za{xx0L(PTTm'E3344$mhh@566YUUZf qss۲etaa!1|+pEEE6> sƩ/_߼y3**Ç7o433_[tRy9BxvvvgΜi5}9 `ժUxIdd N8!ڎufe2uuu'Ou떞Ҵ]ZݍgQLd ڵkbbbbbbG8|#FG?'1c<|MC{NSSXQ\\\[[)++G}!Y᣻ b IIj4K]]BN > ߿VWٳZk'S+((PQQINN:aaa ֭[[Of1m4AZ>}عsN^^Ϙ1\xϏ`7<o|ZJnwP59R( :;;oذ={JaΝ>~!FuwJTTѣ#""lmmyV vss^#L&<Z; aaaFFF999!!!hʕ+ґNNN+o޼JJJgϞuwwoK fl6>5ɯ7ş>}***JMM닋:***QSYQQQ^^^NN&hogΜt钫ǵU"2s1b=ztCC[DDz*ǏONN޹s'Nwwwzݻ+L:5...44!:s̱cCܹs###͛WRRbffַo_{ĉ''ׯo߾}ҥ/ݻE=o鶛[JJΝ;Əƍ]v񬣡yooor}PFF(H( \o#%&&N>kK6bĈ{ر-++k2۽t cf2dŋO:j*" >ƸqP+tF$/_^PP@RPP2eʔ)S#""ܹs 6lذ &3EG]N9s7o,={9PSSCꢦ*"#z|[^^\BB"r]]]3kkkBbbbPMM͍7 ZiiÇ v &&m6QwUUU5JNNӧ=zj+,ȶ~jP8pJPqqq TjO:!''qJuM*++"""v2k֬1cyJJJۏ=;00p;wEPUUU[nm߾=$$`رcǎmTէOss . 4+a*))ܒ@1 \쉌Jt|"NS[8mp3b v֭;}'ٽ{ƍTlxSRRX ] IDAT,<<<>|iaa넄/" VZZӧ !))Μ9Sn1i$3g&Ԅ<|w/x;\beeo>[[[Q>|sBGVTT{,BhĈÇ'ƪϚ5+==ѣG<%,\n]QQQVV_hhh_~ǀ#ssu֕eWN(2 Nڂ`\r}ذaG{S<{agm@F}Ν80<<ɂo޼s 0 !!ں0 eeeeee///'rɕeeevyyyYYYIIǏ+**:UUUMy`2d]iii&)++/lu_b;vaxxx{Pi5wv2-hy\111\JG_=222"D1L\=Q_\\\|ԩӧO0`Q"00pԩw޿͛Ncggѝeذa+W433KMMp™3gB*mQSSsÇY[[_p2 Oq8rQyy"|bKKKWD#1nP# .ٳ!CAcccUUը]v///77 &;N`\xٳ077Yyyy\.Wh͝,,,^ʳػwexBk֬a0G9sf'//Z,bzII +WUUdqXÇDv?{ dXD@dKJJ2-w=ܼys鶶׮]ko'"@.9Kؒ8/X\+H#g[%*"R&fT'DCEK7 0A͛7o[G&//~+V9sf׮]Ǐ7001cƤIufΠ0ݲaddddd$yuwɝS }$8ΣGΟ?ҥJWWG 8Pf͚"eeRb_ ?wʕ prrZlSWO<`qܨ-[_K|r…y!&&FX #YXXXXXlݺӧ/_>s֭[O>ydr5jTDDĶmۚ_M]]]\\<##<^T]]Ν;6.6WTTD~7z&9333KIIb]`ǎA(\z{ٲeͳusswqIyyy^z!>}$I䎬IKK_v-00000Ϸo9r$y̠?cڴio~1 Dp(֖~9B(33}ˈGyyycc#9A=8H8?8sD43y "Á*k@$JJJΜ93u.3ȑY>·Zio!|hG%魯'&ё ~o::-9xPBT*gϞxkEbr#]=<==g͚܊cbb\wޕ+W:tذaÆ #_t?aiCGGGGGǎܢu>t߿ӧ dh=z311qԨQp K@NNNNNN)))-rqq4iرc'AݺuҥKbbbӧOp႙ۓN>nݺ M>}ʔ)]޽{tttY /TWWÓu+))!ȅy 85GR[TIN/ssp!6׸aÆ vd2/]diit҃;޽{SN]pMBnrpp8~x3Ad|ӧO8p8].D+Zpoȑ&M5j4+//?/]t]3lذǏwż>C7nvZXXX@@˧L2gQeddOSMd2qN766r\ enn}*^KFEEu 2BW^>}˸qD@///ww~ /177?yILMM,X't",ȶX,VAAP"555III=z|!;;"GEE~qŊK.j^pEf2]FED={&=Dxϳ g#-O, !b4DΆ!"b6m"!DG4B̯MaĤ< ݵkfΜٿ/vDHw/TDr:qEMկ c9z Dr+6gAah ,95JHHf2<;tgO܉&_ i{ B-]8|ȑFEE$޿ɓĄZYYoܸ-[***\.|߶Ś6mڴiӾ|rK.M:BGCCÓ'O"##\ǏN'CFFf̙3gåK8`oo% %""A={ǓGnnFSPP(**" //˄y222M~pxb0Q*"xYXX466|VXmDEE ٳg5_S;vȇY'Nܲeŋ|nAAϟٜlZZp.|ww[[ٳg500>Hy6B|N\!뺦hI,@_e{OIbV+k2r:̟m<);Nxy' qу|.?25?K} I_W0+&9x`GLJJzibb'֬YCP lllocccddߍt Ϟ=KHHHHHHLL,**355󳶶644:/BE=~_~$&&&22̙3k׮eXC qqq2d!y_x%K : ikk$%%9r$ 00ps̱u~500yw{uYuuuPnn)BHII"2>!_RRb*++%%%ɗ0h4Br555<' DzMNNbyJJJj|ΧO***,KFF&77UVV毈HAd#&&s5V\\êd2!2JCQ(~I& vd2/]dee|Qw b'N={Çw [YYM2%&&?JR\\ VDfXMVk'Νurr:{,;yիW)e1Nwٳg옘ccc!Wʚ8q4 a^QRBM %[XXmЙUVV&''۷oB}۾} ;m -6re˖ݽ{ĉAAAgGjjj}l>eeeۗfB 3/h4Dg _LE[ ?ycgAdڝEllBAdg̘1l0777Qw 2ǎ;p@3'n._ܿׯ[Q*eee;"2}^lƍk߱cdž bcclWm3~f…G?֭[>'J{{{/p@ijjjjjWRSS_xzԩ,|JEEE @Kqܜwedd#cYLIO__*"'*jbbbbb*..~1%_zׯ7pV |/^|ٳL#))iee5a„)**@AX낂Çoذaٲe˖-322ucWP( :9sSp+<&ӣ."2S( ssC q=ggSN 3mڴ̚5+))IGGGt4.f͚M6XbÆ ?<طo͛7>|䇊lllBWDrz~fڴi.] _|9cƌwxǏr֭ӧOkhh_]]jjW^^ޕ+Wܗ"/4tccccccׯ_y>#CAA'utt455%?fgggeeeeez*## +ijjBAOOݻ ;Iؘ <~xvv6Bb}100ݻ RYYYo߾}MZZ}mjj:i$ׇ?Blڵk/^|ر?~„ +V033uJJJWJJJII)33P]]=//毗'!!!..+"Ad.[]]M XJtVт 2ܼBV:tu޿/Ç?~,%%%:Nuuݻw/_z>3o߾MKKKKK{/اOccɓ'0NX,ŋ,Xp͛7[XX\V]C\.7..n̘1ͬE^NDS[NND_+++ P CCC d!X,fpႥExx43 "''g111-:P(G511ټyڵkDYAAIhiiiT*zzz3fdee},k׮^t:]YYf+++>>\PPPXXWTT[TTWPPPPP8]qqq6*--ţ~dJJJ,yyy]|-G]]Oӧ&MR>lE-O-- 6xzz!ZDEPBfuu5kܹ͗QaXOvss7o^DDѣGt!\.ѣ={LNN655ux_0)|~xfgg9섄B9"@@%|ӧU\\\XXHjBbbbʪl6gϞTVVVWWWWWdقoF؄444$&&>x **jƌ ...Æ 'O}}}O~SPP#466fee}633L\Wgl6QSxu?233q fèQuttzEc޸qwemm-8::6ݻwK>}T[[d2KKK#KKKBT*UFFg "7SYLL }L 8 #XXX7>|ŋp[NNN˗/ti޽g rrr'bz)'''##brrrxĕPmm-qLPQQA7z&aRPPPUU'y1|N\OOGBCC,X0hР ./]C R\\RVVF)((Qe?G)D񮮮}LFFSRRnݺpBvM8%$$yĉ+}}}WZrJ?;] mѺ:"񓟟OMII3ʘ&+**r h>ZRRRRRRMIIɧoșcUqqqOKEEgĠ 0`Ŋ111w޽|֭[ k%BGGJB$khzի_b\\@!DP𷄆WEEEQQf+~IΠ /vG%X,eiiI./?''''''{ڵwww߸qc+*ٵѣG+**L[GG"2QPP@}'LdWX,kkk 033;wOI65jԜ9s "6;Һu'Na;мwߤ5JUQQQSSSSS311qvv1_ UUUœ?Ǐrrrz$LYY̙3o߾iӦLARokk{iӦ 2,//_WWWYY7 M tuup8uTPPP>}RRRZz,++:~x???{xxٳ ,&&dӧ===~M D"QBBB10 :K.<uD"T* [+++_~W^C?U<<\# 6Jkccccc3˗/_|yϞ=,kC QSI3P !%\R_]"{dG ǣhL&fh4Ϣ+VjH-++.//J"19 [R8- \]]񲾾@ *,@ףG[n8q.]bU.m&laazАL&Z񲳳2˕H$d2Y4ōq2prrb0qqq* "# tڵ͛7p-?gϞ˖-۾}4͛78s޽C B.]ೋ SS~W֦}6..СCZZZ.qssߡ:qļy,,,\beeͮ[ԩSBPKK G rm= G>})S®]6m4sΝnݺ}|ɓ'Ǎlgg~ٳgi˗/߷oۓ'O7X,n U։`0 ?ГH$8\YYYUUUZZ*䃰eeeUUUb8++ Ǥ*++kjjbqmm|PH$Qe]8ffN37YMMB8=R%$FUTT/&q ! _ďnf0VVVZ>b f:::L&vFݱcGjj*N$ϙ3g̙^^^C2d Q("wpd277X,---m-(( DT $4 Na]nhj0?_8#??4d2'mllS<O> !Qd2yĉG޳gƍ:aÆ3gL.Ν;ͬ`nnN&ZZZzzzyyy!Y!Ј\VV#I(**b2---gg'OLrJQQDիW޽{չsg YAqq9sΞ=xutnx7o駟|>YBk>nSrǏ1cԩS+WnٲeԨQaaa*m~~~7o^dɮ]뫪Zk׮͛7~1cF[*Dt8T%0^H[BbGD{b1>`V)yCi## 񗲸!m!j_ʧYp… +**_;Y[[zzzjz}d2LѧQ3?0>ExJ*,,i烕FS'i‚D"gxa>,E/^~qeea޽WZխ[7{{V>d[[[[[۱ck^zuΝ۷oGDDTUU.ш .̚5SNVF-[W\!:Fde.T҈|ܹS˗:uoYN_fȑ#̙7cƌM6>l򲳳̙sҥI&|ƗVHKK :f5rȑ#Gdϟ?>$$" GMAːdG9 8 O=`,]t„ ˗/2dȞ={Էݻ⤤$WWצֱT"FdB.Df0 2FçFOmDnm[=N'&&FԈ;88 >\NSІ$&&nڴwޝ:uSIIɁ|#*?bĈvbVVV999BHWWWFd@%A7000 Ν;Ǐ hO%ݛ뛔b{|~+--mÆ mwNR.Q?pݬ~Ç>>>?c;fH$2L :脄8998Z^^^=jfȆyyyxFdήpR DRs%{{{ssnV#\\\;wpM|@CCᄅ>..n>>>|Вx<ѣ:p¤ _ Tŋ˗/x/߬BaUUՅ <T;~^gFvuD7a„Η333;zk󽼼.]d!4@򂂂<<>^zX,njsssX,6622*,, zzz AdAdSVVfDFd< MT ~~~#9rҥK,XI'5x1cx<رcdrddH$ 200BH$ѣGrnnn?ׯ?ow֭[pp3?>x`RoTD*)/33sРAzԍBCC;feeiӦf^"4H,oڴ͛Gy󦻻>|(6&Lջwo''͛7jztPee%y+Yfh%V\S:gU__L=33а?| >nD-rd+Fd&h#r]]T*%H hDVjAdZdzgT^bnVS֯_?`QF^c+..޺uóBBB޽{w̙CtM4h׈=z}}}풖6t޽{;88mݺdu؍SRRB<OJ`PNN'_~uuu_^r Z e֬Y7o 533[f YfUWWڵs[n]tiJJĉI$ǥDKHHHFFFlll޽7olffy;,A"hv${-GC٬?~ BD"eee!pMOOXfST\re2FoDaFCɽ555IIIݬ/LUYM!ɧO644o4+11q̙!!!GNMM~pp0-۶m{]LL ޲eK3TVV/Ω/_>zP(l1+8z(B*D622P(Dήrի[[P[[{ŴiӶlҵkÇh$IHHP(\zE.]qL&DDDٿ]d2'Lt }uXwP(k֬~rråK|$ӳ 2N޽#Ad<7ϯwD"q\Dp8!**Df02L[[Fd{*J4"C8[[[r8=zĨv`0N>ݻyiz,!===]]]3229ҹsgM |>2{ٳg޽h̘17nPXv߾};w_P__OPƍwqTfR ;;[}7Ϟ=ӻ}A`թS[~w֬YG/O-I*K.}޽{SSS CP]]]xx?`%KhiiizyҤI޽sR&&&7"1 ;ޖcjjz/^svvv;~ P?aĉݺu{={yχBDTlժUo޼u떛ۊ+B ܹ页I8LR_xq۶m;v1buMJKK{6L!dhh{Rx<<7EMoD҂Fdyzz!4hРdoYvرbŊ3ghz,T*ppp7oޠAN>ݻwoM >}z||+**z1|}Ι3_~IIIN: rPPХK`-?HOO7oP( Q!|5k֘Ι3/=={"jE"?#77_MJJݻw׮][VVѵ%8L @;@"-Zѣ$ww/_~<<<L8p`LLj7믾#FHOOX:ǏsٮaaaA|||nݺuĉnݺ͚5 Zj%d2LF :4;;X<,,,^)*8zhݯ_jRSS l2ssիWjz\y߿_|݁&Oի͛7hz\-Çt:4힇GXX۷o׮]ɓx{{;v ~'T* DOOիÆ طom%))[޿c!nDF|\Dx%%%!fRi}}=fAd2,JΓFd'ӋSbcc rI!CdSZZܣG2 .899izP@d~o;w>""Ư\\\LLL^z|<++ nknfffׯ_'H*F\755 JNNhMfiiٳgڵSXXةS'MpҥKSSS\Onnn/@@Sb1n#k[]bǏ_vyVX[pvvNLLlVP(JsrrkFdBhDFq8z!\PT#L&k%)e@E"ݟrq"Fd\'h#B"<==ш,\]]cbbTր={611qܹK{SZZ:mڴ!C 8͛73f'ߨDjۑ/ì]>|e"8pׯBB d3g޸qȑ#]vU[1{씔>ׯk׮aaa tOOaÆQ( cɁ 2qgΝٳOdddZp#r]]hƏ…gN8\]]3' jiiɷI$X7"`qyy9f 2~ j 2-˗QF ^!WWד'O=ztKyҥ?jzDmFO97ɓ''O9s ӶZ|o߾Su%?[700 +V8}4J3gΘ1cT:ֈL&9֭[񞞞 ,055]dIZZ@+|rP8}t{{۷o>\'~999BB :ݻ111l6{ر...4tї.]oqXډJPLLLByyy!==>ȯ[ZZK8FZZZD(S*&$$|~~~_n2Е+W߿_ciN8ѳgOSSgϞjz8m`0W\믿u02HFddnn0}m޼zogff.Zĉꈈ$-622ŋ֭+((8~Z@#2ڐH^z-[ьӆ rmm-^ 0ƍ7nܘ6mL&S.vvvIIIM   B߿G5"JRSC 2D8h#2"ST\@$sss}}8oۛ_*r1o޼˗ϟ??**Jcid2ڵk'N8o޼7nISѽ{wt|=R:277$~wނ 뼼H0:UVeffFDDH$QFZJ>׮]Ç&+,,ܶmq8ŋ;页"2 @ɓ'_| M]4"q#22222_~Q.M5"#|>J6ӈD!SYYI"'' hiiHj$׈ Adyzz#LR-*6l7oرc_鱴IRt6l߶mBQ [###e˖+ ߧO@ s%LJ'N7o;ɴ?~V۹s7<'D"H+VybfǮ]7XZZlkX\\ӻt颣caa1t+H*PP(}||5J"s”aaaAsrrFs֭ݻwwEmCǍw/^;vϞ=VVVCxbW'O$>>>0R__@SS-[5*55ʕ+C !"AHHHnnի:dllMAe۶m۰aógϔYٹ5B|}Lח"Gx< "3 2+4"H$--ވ OOSNc~~~K,ƝǍw@J2e/]4p@u﮼[n/_*',tmŋCCCSSSSSSO<)4bŊ7'O\re o ן3g>}hkk#[333333/_|֭>} %Ie c2ѣO<})׿}6񣝝B(==][XX3RiPPLOO:u7n >|IN튊(T*JΜ9GT.11q޽bx/_ׯ255@,ks9uƍlٲ݊X,xD7oӧOÏ~윝]TTުDF#|~]]]ii)KJJBb)kjjH$B#2BF)4"0D4cݺu%%%đHU 4{W[nUTӧ}}} t=(ST*0a¥K.]_CCC322駀gφȯfg̘1ydLvȑ^r}]?qBh֬Ys̱C\\ܯ*ÇCBBƌz7on۶ 3g6l0yd&pLϟ߿?[~}޽uLJ\!ԧO~ݝedd?~|+Wqۏ><@&wޭ5nܸ'N=Z{AAA#å*D̔H$vwސ!Cn߾{nu ͢&M4iRNNٳg>e ]]]~+W\jUGHpJ$ƠJ" *++񻂊 Ȕeee 1bOF N˯d2, 1pp>XMp~-++%KL2^Q();;!h>՛Xd3 R픖 > Ng0DRl6BHKKb5d2 ;vegg &^RCSJh d2y߾}nnnN?~|+ MR/ "##HD#rYYBNAdmmmhD|2OOOL~vvvv111;ѹvW_}5`;wtI#j~蘘\.\Zdɂ B .Yt)BDDBh֬Y[n׸"v υ Ҏ97jԨQFɯO?-Z!daabŊ7oxSpe˖)+w^ЯJ7N FGG -[o߾xlkD=/|TPc\-lllܫWة~qq1QbÔiiiJF*FEEXb֬Y{9w04do>r;uD&pڮ_*? חbX\ZZZ^^.+++qX,X0K|ڊjRfj[!=Mxd2Q(x8~|8֌Gs<d2L6rL]+"""::fԨQw߾}?wM>FHkjjb1>s0qFOO"<̳2A.>#>tuud2eXh:::SBfϞ=cƌ'N]nƌ4=45o-ćWڵkPPիquWS ={hY("޽{舯yzzz"kkk|BaAd>/2h4R8| Ad@sTDF 4ڵk6mR[xզ!vȑ;v8qRtА!Ck.}%BhҤI|jmlld?ܽ{ .;eBmL믿77|[__ 77/:99iD$79c%%%8;ww+W;TEC[ Mc)O~~>^h4p? 0L"̊ VVV fݽ?---M =oPo߾4buuuR~@C&G=bĈ'OY&""bK.UߧI"H0G ŷ~e˖'6DrttoN:Xl\d2 FQQ<!pd2N///'n䣍ȸk xxx9rD[믵^:ydul>{̙3> S7T__?qDGGǖɶzK/^_Ν;Ǐ߲e q%^lll~1>>իJodZZZ?b}tGnnnׯ_?|ԩS]Wn߾+|>P 6}={n߾~RI$RZZݭJMMUyyyVVV'Oܷov>Չ'9Ս;666vűD"y}nnn^^qB_Lc,K__ĕ|JUgʊ\B<%%sxL&a;W666622200/W~ӧϜ90mڴquE5|ڽ{wMdyyy999xXPP@R[L&Hgmm MfٟqGZ/$%%zEP@ Ϥ^iii޽{֭^|ٳ$`DFdmZII ѷUWW' ̍$!'p#n"fo{aĜ}ax9%~۳ JU@LOz<a$ċ巏'6l,?6sk…OY>""[srr d2Dkhk>"31{ IDAT>k͚5>|SuttDFٳ#GdٻvpZ~-99999(#G|۩Tj@@ٳgw!„ ܹ8q"Bرc8;a„nkΜ9>>>BݻwǎC)9/ٻ۷o'Nܹs_>o}:uߪy5=6 ???;;;???++ /eggtx |>NkX,³}5ω L xǔh:&&?ϝ;_]z;6m߶D"A#2eHRL$b.ɕ_&qH*Z}}=Fp5"tttN^( c2 2leegD`9gT QoBmn4kt/a(?~lP<,}&Dd^l6Jh|/.KR\nSl4 "I*kjjJJJtuu "YZZZJahDnsAdR3ԤH  8P߷o?\XXء*9s($8; 'ܹ^^^윑A\3wܽ{r2l111 wo.]O9]aɒ%۶m#nUxR7{nk:Mr 64+.^8l0+/_qFbS}$(ZRUU}߾}_~m۶ر#77799m...>|Z?>88&d B&i4QPP!N;dTB,gee4'rJeeL&+..H$x5ajjjaaaiiiaaA,jrmH$ԩӕ+W鱴:uuuٙo߾%>/f0&&&&&&FFFxB[-PmHWEDD|!doo?iҤ3g*ӵɓCݺu+33ƦW^}=zoذ`DEE*ultfnn޷oߠ ttoGGǑ#G.Z>_PXv3֮]knn%%%zzz "]K.^Bu@AAWrssG3 9---MMMqɉP&igggggG\#MQQQ|mJJJLLLvv6g,NNf}kkkMDSRRc#gggOMMMG" ]]]Z___PPwedd_p7&8lnnnmmmkk ?`޽/^|Ν;tD"#]> D,))d IĆ_|(|}" WBKB+E&-# v_0-Jb8©eee?233FwcdX,bdr\6y<˺xa7ƌ`g63]˗/ "$%%?⧕|;;;>/?;4Bo!! h'OԱePtDFM2p8}y2lϞ=SNɓ D"͞={ٟqwwwwwnm~]6!Fo"Æ S(EVTdE oWXaƆ8L$1իs 2$77!K/// zׯ_]q~DjIIhU|>wrrR6++K6111** GH$P(ҥK.]lmmqx捖rSSS#ׯSSS4 }FFFcZP(######q_6 (⣥ >ZZZZm+̙3Z.=8҇Kq!L00be_ĝ /JshkkI_w_\K!`4CG>(ɧE"X,%%%xT$eggZY|=>5p(fefAsp~رf.]qC&&&Y>OPBzzz+gU*J"D 4 "h4"D(Ce]/\믿ie2ٳdrO!߾} l´L~zŰlll޿_QQbJJJx<~׌7ܹs3\]]gϞPݻwR)D2334iA3ZZZVVVVVV WWW8oɓ'O74 \]],--;f͛7fff8hXZZ LBÇ>[[[뫫q"0={vY|?>};>bо#66W^nݴip1d[QSS#J+**4nŹ:ŗz Bqs'?w&8‹q̗Bq_q 7_? _H3PrIIIEE^nfÛ7oD"H$*..ƹOԩSae>G 'OL0и#Gnذ!11U]&W$vԩԩS6663g ⅚ڔ'$$$$$ǿ|N;::?$:&,eaaWtɓ'7l ɸ\FޒҬ5= U•XBBBzz:B:99 <Wv>Qh]vڵ"H8ڵk4G)*:gΜoذ!88x޽޽ǥ,D*//ܹhp70K"1\^^#¸K_0NKS~#¸9HL7*&S-@kfqrS _~#"VL&@ 0`WZ:W7d\"l``@4"+uuuD!.+UUU #//Oa/ 5ךA$HON'e|@ @&G|9''a B(''ad2iQWWRGGZM 5ŨA#2In }ZYYũ#LPaM6FEE 8PL8~xMAqE"\\\պ=z<|!$xx޽{uuu666=z|>_c=`GTٳ'O,]T$qܞ={ۻ{GcV7oތ5Jӣ4O>}gggh4www//'8::B<*F\SPP3\@~ȑ+VP('''oo=zV4ƍ/^ >uի[8\RRAd4KN}qpSD(&rD0 7Sb`|ۅ\.LƵuXWW r\|/An @bX,P(l~5X\PP_XXXTT_PPPXXS}А盘 B###fllLkuÆ ۽{7o!ҭ[7 TjNN5D#rmmmYY_/-D""I$OIFïFd|"O6c5"Haaa%%%#F0`G,--5=AYZZfeeuݻw߶mB(''(???77_!:Esppx][?Hfff^zƍe0Vѣ|@ ց!Lի߽{k׮%Ɏz߿oVWW^vݻO> ={\dIrZ/$cǎUTTݻ׃ rtth:thlO?EFF۷k׮Tw"@cu_`N vfn`!FFa!qa,n~I_/3La|I4 MDp H_)DJ/*6ņk{YkYZ֎u+ **łtAH$!DH}iל 2i9cdd```hhhddԦsly{VVV 7o4MWWW  2uLQQ* EvDƿw:?i׮]2ǧks]ENN'N|СCɮEo@PSSKJJ.u떛ݻ:`l6k׮x5 ȴN#uaaa>|PQQZlY.BܹsΝL***]xݻw֫W/kkka<_:GDDTTTzyy-^{ICSSߪ*m۶-YC75."#_B.~hѡpBRC5$e4$If @[Էoh;;;33.]Kg|:~xXX }O݂ʍ!`F&8ồE838X-8TUU͜ZwPfddedddeeD2M622jhK ƀBCC "STssK ꈌ'99Jvv6B^DGdDnC)ZZZZZZ"=RSS˗/_|%22Ce, tP">Ad&) ={ g׮]ކd1\.7&&&,,͛ 0a56ݻw666T*B'&&ٳ/^훃C``k[ vDRNNNNNN+W,))wޝ;wٳj*77cBG@jV8+2c*N 7.N 'bI/,,+cW#2p =^LfC700 I~U ?1 ׯK.p¬Ynڪ0O<>Bٳd@s˛lۑӧ?}1..ڵk(MMMccc333KKK sssI>jh7p߿?l0+XYY]pA"CCׯ_:::UUU*** "EEEAw"s8yyyI:" 2tDHdv5**jѲwժUβhNx?OYEEbedd]I}X{[n*l#3gV\)0!66VE6Yvvvppcrrr{#Fɛ_#^RцJ'am褈J:;;;;;ٳgw6k֬_~u![^0sԩÇ~dԩ;w&.CFsUUՑ#G9;wի/_>|s纻] $H pn.[RRrI> nӴ¡XppFVpZᶲEEE555 ݓ:ܜ@Fg? ׯ];|pPPІ .]:iҤđ۷~XVVvر;wfggm޼_~Csktooooos9rgϞ˗/0`| @*W@V[)Nʈ@ Vp*W *^p[YwH69+&} 2p/[lذaRWW'}gI^^ʊfmmmZZZRRRJJJrrrrr͛7srrB 666]tҥ c>ťᤧ ,200KAdsssMM͂UUUG***8,DhuuuVDGd"ɓ') ]v,o[h4ډ'njzzz]̹ܺu*h}!+..vqq,,,TUU EdҥK<oI5{{:ȺT UTTlذa׮]7o6mZC7[ Ѳ[*:p~q۶m ,شiӮ]FEvi';;;??֖s'NX2((hѢEFFFTă53gΜ9[nѣǾ}.]RXg-&ldWG@g\Ib)6g`D4ÇMfccsaoPۼh  FΝn7ZZZx<KcXdUL XvmBBR.%%Ed6Jm  2B?+>k ܪ߽{'M|ȑl]]]Y߶0˗/2իdW$[#FرcǻwZ].I4b^1a„V֊]|ɩ_5D>pN5k"{{{ J>/))ٷoɓ :thݺuV=z#?.9"۷oBR "9::kiiI~VVرcfDQQQ[[[&':JqHnРAӧO6lٳlBbL_3(WWWWWWbNyyybbb|||BBoݺERq6MpvvVQQDf‹ B_~KrssB+wD"2n#2RSSQS^^>,,lɲa0W\0aGAvE2ԭ[7CCǏڵZ?zd&pΜ9x%%%UUUiii"WLHH۷:v7dWڸsM:Gϟ^%K. И? ֮]R__?s~|iFFv׮]W\ٽ{w19ΪUn޼Y\\w^MMMIJ˛?~DD?r .DFF><))yݺuktF+%v=tM6 ؉ݻw_t+W$q[H3jTduuuk֬ TWWoV.<<|޽ Crǎ_]]]ܹy;}}}V522رc0a°ab|WwR>}ĉ iCxÇ;w.!!!Թs )?t EvpDXxqddĉC(--BBrD GI*VRR".Wov++SNğ4pսyS,XpΝ .L aMhLʊ9p@FFBHOOť[nݻwwvvntz=?~<{l+744Deff>mm<DF1L"L|؎"` "wD- ĉe7ȑ#e7~[rW*zkd]L3FWWsοqW_={477 |9^*ɓaرc%ߤ9v옜\FFF +==!o8qd i&Æ k g;w͛7Z-̘1xI2P(=XA=qYYYvԉ9sM~m,6Cc\rVonpyM LΝ;W` ͜9SxCMM$GdffvʪZx'N8pT$~rrrT*o߾N*--X3%%EKKk%%%R5).,8pAq\șxR)B̟xx͟7iy ?˳%{58tمݽ{oZx7/%Hrrӧq@WSSsȑdW?O>E%%%\~zssswbrĈ8cCCC5͛׫W/[h^W=ͿT*ǻ~:B3g6h[ ͛7)}!t=ߦmٲ!|r 0D 2 1$cǎ9cI!DFF"nFmڴIAA!--evsN== 6| raa!Jׯ]FR{p'O*((dggXCgΜ_9ёŋfff _lݺ!h=(ܠk…G+wpp_ſkɏ]`rΝĜ{!5hjTdٛ7o9߬G!tqJ6֡Ct$e(((>}Z*  A2_vڅzTvJ^z!fϞ-gϞ%K\` SDC4<4ᨛD]HQj̙GxD[[;,,%oQ+//ذajCWW700NMM%&\z9:^[[+_~AAAqKǏ+))zj#ǎ]\\OAd@qs-Pyyyff&B _NNBݻۅ… e7~~*lٲ6݋T:nݺq8k& ~ݻwիWׯgy:ӧ ړ7o(((d#ӧ{xxx C)**6Q(m۶ xbbbihh ޼y?366!'!L<)8[l=j<5|zx/_P(m:Uqq1c(E~/ >UUU-;rrrN:͝;7<<7nE/_D/7nUV1X};xRP=ƎUTT Ο?D8;;!4iҤpK* В;uտb7nعsofϞ>̙3-a`nn.J忷 )((xxxxxxlذɓ'ofX >sTUU)ƍ6b2/FM B;w$VرcGӎI13E~RVVщ9k7+Ï`5]Ǐ#m|||ȭ!((O>R)NhL&sڴiO<+--ZxK |˜9ski.]?s…޽{nݺ!߿O_5QK?׳eUUĉ. ׯuttZ>u}^M{nRݻw߳gOvvCPCMMͽ{ ߹s1yY999.[PBBŋ4x<I,e0OR8BxB(++!Dn凴{QQQ]MM-44Tvh&O|SN=roƌ .\Ǐ'''WUU%%%9s!ʇw`;H~8#G6sGRoGΞ=!4gΜ;w>x //͛77߻w/eDEEbd**'''//?zׯ9rgϞ4MYYyvϡmĉB'O?GJJJ=z4p@/:|e޾}ۥK+"РABӧOwAdw3Zw˳% (..޼y3ٵqrrի֭[yRH?}hC;w\~tժUާN*--~]]] Ɠ'O*%%Ex^nn.qIMMMSZZoi[TTDZ__ +++ST#2BHAAFAAȸ2m ; xO">~(]xyyn!**JKK%''Z~Ĉl6;**Z߳gOffݻuuuڜ;TYY jjjJ8~n矔򈈈UV)++#~ZΦt:.jy֭Z޽{)ڵk񤻻;BHwAp8 bXe4 Ksn޼)tʕYffll,IGRX<]|a,IF+/..Ɓ?´iB?zbLg޼y=L;mt$e6?իWS(HrԭJeeI(ʉ'ȭJ^rEZZZZR(EEE ҭ[6+Tիm+\!CDr8,`РA"i^b"xuDZ*h|@$~{:[x̞={*))=Zr[lh2ڋHWEE `0GN|k&Efh>>>#""BYYYx2::!B[nk><ܼ_~Rmm}ihh8p|`rr2GFsȐ!?ޖ&a;vqiQZZ*]fff&&&>n7 Iv-̔a1 TWW:؎;B c*ZJrko??#Fg2EEE4MQQ4:NAAB]H1bė/_- ϩ WWWիΝ;7gѢE溺yyy;f %Kp8񣣣gϞmmmdoo?gΜ/JFʳutt,--.]{[XX豋y0f?e˖YXXXF~bvI5miw7vX[[[hѢ8I 377WSSqٵDݲTtҥs[nMOOdC)((8::>{LZ5$.2@ :mnn{b)`'''6l`ff&JQQW^HJfkkj2USS3ydviR wvޭfׯ_% ҿ 277'Z)ƍJJJk֬l6Xjiiass7򏟒~B(>>!$Ͻ[TTtqq .ڍ:DEEt-<~xƌt:Gp{): pڵ[n? snn+1`̟?ΝW\h8' |{ }aʕԩ… . |ׯ[ly䉭ȑ#qpqqp8o߾^djjpeY,VVVTTTTRRG좢"bMUUUPII ފVTTKjjjg0Z:)J}}}3@-66Vv]@jkkݻoٳgk׮8uꔥ%Jnܸ9pرckjj?fZZFkPQ(Ç?82iҤ +E***>}^D$:", $Kqю8L&dQQQۅ^XXvh_~ܹK.mYR''AM>@_ܹjjjdUp?8ҢP(T*U... O<~#''7r 6|! @[[o߾۷oo;/'Vw̑s֭o߾FEEY[h=dpn…˗/ԼrJVV.P/_h4ggg xСk׮^x١ěyА<@Q7:~~؛&33>|F/\0z踸%Khii] @=z>}b͑f! @{BRnjǏ?xԩSMEd!ԩS'IDGd6-NNUU舌/ "ȵ! h , 2B~v.Z#G$ UUk׮>}ʕ+]tz*֨|宮L&366vܹŋRg=?~ɹpႄ;99EFFJTi[`˗/}vmm?tovv65Dr޽;~Q455A\ree+KKKeee M^^… OppW޽{wŊ:t?~ee%۶mkl[nϞ=+((޽{JJJsJMMUWWo-f4m„ IIIӦM ٳgBB⒔T\\,G;" "WWW# ȸ#rMM@GdHn @+& 2B/66Vӭ[ϟ?]7.!!dWZ wҥΝ;=ztϞ=O>m 755577YWW'''r###tĉ-Z_Iݽ5 'OtRnn'OƌS???===33'>|Ǜ7oڵ)S. ,<}ZVVFv233CBB,YңG|ݿ?\fϋBCC̙cjjJv?ŋ#c``0uK.yf׮]&&&ׯ_:tuPPЉ'5eeeO>ݾ}aôf͚sʕ_&%%ٳLJd],=rpp)6ar"͘1c„ ]]]]CC×/_ /9;; '#rii)BHYYGUUUAdC;"S6m&_l+ဝ0 ooyh혾~DDĸq㼼;/]#..ʕ+W6119r-[LLLȮ wƍ_5jԩSɮ\.F>Bh„ ZZZ7o3g$<{z,ʖ) ___\y]]ݛ7o={ŋo ؘhjjj߾}͛ot{{ݻϛ7Ȉ2ٳgÃBFΘ1!TXXŋ/^<˥666]vgH---]riiiTݻo~uرGV֭#X ,&L1bĉ'hT&h Ʈ]\]]N}iyyFrrryccr?_OOH]]uDf]GUTTpGdAdR`)J AdZ---cc㨨(B~~~&M***bٲK{tڵ׏?>%%eڵ 좤B9rdӦM]v;we444. իWѣGz""AW^e2 ___OR\BP1cƭ[ƌFxm1̏N;99999 Nbccbccϟ?aÆzܹIII߿qĺ:%%%[[[{{]*((]4=|sd" 4hB妦y&66͛7;w+BHWW!tbeeeaaFv</###999!!__YYIӭfΜiooooo=?e~YfImjjZ;v4h/^hɓqgϟ?w҅B(++ 555KKKkjjlvuuuuu5M*QYY6"3LqJw4Duqss.LPn߾. n:uuEeee߿_0sN2%88x֭7nܹseAQ__C''0oooٳ#FU7cdd_!:Եke˖ߍܳgHWL*--튊 ܺ5..˗Ǐǯnlw,O 󓒒jkkB^jJ%^za߾}ɮB&T*>9q߅޽.n033*jڽd|LIILBUUm666T*233={vQQʕ+%0--ԔFAGd~=zt钯׭['~e;;ϟ?711P(>}j(lcc-Wq_"BHUU舌b0DYQQwD") sss?x<쪪;$$1|SS1cƤ\x6b2˖-;wٳg>1w\???Fvu@:=zo߾٧O? /reeeSL!f9rd^^^޽kkk n5w޽{wY4̙3T̙3\ի%$55̌JBʀo߾d___ggg1kxw޹WTTYYYx"o , H "!FtDnCh @VPPGSSSoŊ=A <822rW\!}ɜ>}#""g͚5eʔvsSttΝ;bf͚eddDvQ86W__PPPQF-^xԩ]tץ5gϞ_ѣtnT*-B%&&^vx18Ld|ZYYY>}""t/_> ! ¢cǎ?U7}!r>>իW}ݷoɮH۷oߌܹ߿رc /mǏϟ?ܹ>8::8p 00\pQF h4+++E6mz1c"##?OY$&iooooo??ӧgϞ;w.//lhhg``344Օ'Pϥ4333++ׯ߾}lt:GE{E$T*G<|cX3kjjӉfzzzLLիWq@YAAA;|%Ζb:.OYYY߾}'LCNNyyy = … ,X7j(+UVVB J=vXΝ><{VP(]v}"cc? %l6Nl$D.--E߃ 6tD4mTTرceGGǐ"7۷WZ5s}1 :l޼?xٙ3g̙3i$Ǐ1ɭVZZڙ3g._ )S޼y\pLMMc ˮƍq㆘a߾}#""$OEAAJ8]^^w8τ}mvvvNNnttt555555ttt455Lfh{ rss8m\YYd08> }mcc㠠?cʔ)bٽ~Z7oK!(F^^@TVV 2"nDM%ekk3f$%%]tI[[dJٳgϞ۷ozٳg'OpB__{{{][߾}޽{<`X#G ӧO[ۿxbw.ribb"۽zZl_.믿@_ X.]tExQmm-ьcbbp.Xdae6N :?<_#tg"9RVV&tƉ:(..]bمa򖖖"#(hzzz~~~nnnqq1&B%kkkĹSWW'v $\"m/(//'6]]=zS%|gU@[m۶QF=zA:uibbb EW>vӧĉ\.W Bcllgutt;%kjj#lvaa!1T[[Bt r}}=Ñ#"rrrDG6:nnnGu o͚5^rss^~*&Lpuu>|x.].]Էo_+j!***'O>>l62۳wFDDTUUu5 ݝ`]]s]x`ܹ"EEE 5Ăf͚+QDD[ zG].iii"_+((())*++?㛤0Lyyy%''cyjjjT*UMMFt|ϔ+--]zuZOחr8򚚚ʪꊊڲ.s粲2Tx|}$R:tl55?| ңGlll H999======UVVgU~NHH 拼`ZAAdԔLb0 SQQoB6tj[⚚ciii}}=qz?K28m6tTTTkkk[YY5,o20[n?>44T O t: B.]h4ڛ7or :uB}I8CLjiiEB4?r=UUUw2#2YգdDF :4$$d2FKK[tɓ###٣HvQ-MSSsرcǎr111aaa'Nx666={twwի>@BuuuqqqO>}ivv޽{ .P?|p}}}Eׯ_ׯ-,,{2 @ a- ***qֺ[{Z7h֭AUA-"F?ΧO@ $w07uߧlݺ6((Ν;k֭['AP(=====l@4$2$xx2??'Úm[?!p!y!Cijj?!pD ئMp3BUWWCBHI$n٠6,1LYT.AQRwޅvȊOCCCCCа5,qNRڪ*w_c-Oѭ[7A7H$ޠf}YROhyR\*~***L&N3 #·J ο 7c}ٰaæM477GA; Ջ/ƍ'9nll.9pJJJ~]]W^!X,V Ad 2B:lGd%: 2 D"'$$,ZC'=z={?mB&w=s̄Ϸܢ SQQqsssss[n]AAѣCD"cc{yy{ N?~N!oyIBBŽ;%-[6p@UmM=444333 nnnR0`ѢEuuu]ڨT*Jmgs+**M#1#0ϗ 666yoF޽ssSMRh$F>M~]VVVrr]%FfR7p`"Yjܲv:~ AHh't[Nο5bɻ f̘1k֬...Rd2 ''Gj\___,uMlvLL BH[[Fp"b"aD&:"C^{>{lGҿ]]k׮t\_1cƸN8SNwErp&OH}@Rq4Y#r宋PR6m*//s#G r177ۼyinݺue 0#F /^x9N^xonnsVVVVVV][nn۷o322RRRp7466vuu2eKϞ=-,,]i),,xj>_UU|~MMMee%gkkk{5s `⬼&^VXdCr݌>g) L?EIJ!ĢLD4yh;A#xmmmuuu &E K.=y$ 9997 /0hWYYx<^yy9qTVVWWWWWWյp5#H8 `E̛> jvVpJI 5xtˉg|Udnh4Je2ݺu֭b27+AAA'NT)++F8 raa!fbmm2-L&"􊊊fM;"HJ"p$.!!a:ѣZ?ډL&oܸu̙^^^ϟwQJ/bqff&%'''|[~͛7Ǎ@ "Eݻ&BʍWm|N ,PWW6m{/Dkk!4W,HnۭUTTmD)&CE֖W@u;!؈?Ш=,])QMMMqqqaaaɿ E*,\.WKKF1 MMM555Y2`0h4҅p̺5\WWWYY{ɔvt:dJf:::ryK1cƱcǞ={&\6(_|.9njj%1&ȺB[[YKn C(2) kggAaÆhș3gvD_@WW׉'8p]"HCP}}322rrrZZZFFFFFFؘ뷲9nǩcAA?~n555?>XI^8tP^z]zC~OHw=o޼ ֭h|]Zmmm[פR.\hE:t'JKKӱcΝ+ul T]WW'BH$XrZAL&KU---۸ uuuX dfd8\UU󫫫q࠶'%8Č6N_x/ @ 2o]`Y,Vff&BqA>U_'@PXX JJJ$Sjjjl6flkkkd2jjjjjj_;N'`&"di2_c``__. yyyYZZ7D677WWWTظiGddGdPqq1Œ "kiib!X 2^хL&Dj[c0rAdW\\\GϢ9x"w433+VL6 ,]J4^r߾}>Z6'xiIx)EO]B,}"h wC"$N|YYYYUU$gϞ~~~VVVRfYYY=z(((ѣ_U}޽+Vٲe6mGE"Q}}=FkTjxxx``_ddd޽BCEݻwo„ 2zkcǎ?tD,22Xޅ䠦&77/(((,,˓JKnC4255i9v+D"Z%6n/Jd%.ST"r9r E(]vEFFO{+&C쬬삂|\SXXHd l60tcoZЀQoeRR$ZZZ8za/* D?~ŋw^W^IH8dGdPqqvCCL& P(TQQT:"d3<<\ ttKѣG/^M sT*u>>>fzӧֶ2l:]sssD88??㕗x~XTT[TT1qbD"q lTZ|[TTTX,j<JWqqqJJʽ{rss͖L'U###cc6)]ΦM,X0wL333"˻@ȀX,~mNNd877'T*u]t Rڠָurrrvv6'NOOT5)|ok;;ۿ|Rj!#M/((qDOO!T^^4LD" NMMd2tDWMMMrrrme( ￿sN```N0/^̘1o߾֭[zuGgͿ6jjjmzTEE_tF! L޷o ޿w744KVUU5JGGܹsilܩAdZDDGcǎ__'N)Pcc?uH`hh>ׯ333G)B">deefՉ8ljÝ\.WOO>ulaZ5y"IZPPPTT$ \.]լY~5k0 b92"ʫ2==͛7_No]]]L fff\.kh5P(-$ʲCwvv6^GUUȨsOOO}}ׯ*|)A_]]:":Ŋ ^޽#"" i8Ndd䯿bŊ[n:uXA'jΝkjj:qDWvᾪUUUvZ`1XSS3jԨ„---qkb "H;w\|yUUՐ!C6mիfo)'OhxE%BUTTیw޽sǧd2^G3q0&)h +++++fDEEE8s#999xuuu333N%,5n۶mԨQNNNăWYYŋd>Ƨ})СC.\hccckk/z"KCj۷o޼IOOuLḺc5t:'w]πRPQQ1bח-[^L&L&ɑژ_쒒PyyqGdQWW4MEEN]]#P( =hѢ wӐHŋL:uϞ=f͒wQ@i 6,666 -""/W644#|>ԨQ^>^CϧRy/qFy楥iiiݾ}ȠM>}dN"##}}}=!)77ݻwo%{!T@UUUޅTTT8𐺋8͛7o $sTf̘Gp@>N({.99ſB ܜBȻ^ F=ʹ8}mmm8::vu&L(--셇 Œ gggqAd}}K]]ݒ "k#i4Z]]Ƨ:"dr"dÝ0ј1cVX SbbU̙sƍ#Gʻ(}u4aaas!|NNNMR[[f?|ΩgΜ0fHt%Ksҷo_~eͶZQUU533h#GȻB奦x"555%%8Kfaaaee5hР9sФ h]wkkk@?qݏ?up_4:.A۬] "CGd@%%%֒H$ o>-,,H$+_CCCCCCb?z666}QKPnݺ5uTd˗/ȦKKKel6Jjjj6NDY]]wDTBB ^^^k׮񣑑QNdmmmccsu"w>6bĈӧw#FwQ@9DEE}wcǎ]~ƍ],ٳX#33sĈ|>ÇM o߾m}􉍍ׯ_LḼ[}B 7o^ppѣG+BCCje.QUUUZZZJJJJJ "\w}3p}$q.E?vXMM Nt lee ) KKK۷WZ ȕP(L~> IDATKK{{D"/`0]&;x/7o^xɓgձX,OOOKvwwԔo͠Mt_4LR---_z%5nbb*5/ B!%DkX ZAd2G"HEE=Ͻs2ÃL&M0=zիWwf :4995k֮]LJJ8q5888==jjj.J7mڴ~z8q"ù>~'ndll, 2B޽{#G\zƍhMMׯ_#--NQ,ʷ6>^^^o/'O}|7{ݻ UdɓBIII?NHH8~ڵkUUU݇:t>}@1bƍBRMƑRB_aiii!mmmuuu_DP(xcS DK|||'njsT'' 4KWWҥK/:|04adI& <ʕ+zzz򮨽VZedd,p --O=MMMeU~XXXtti111f̌]n_* ]hhh~u]]cOxٳ w͛.rӧOr B}SQQс `bbakkk׮ݿժU**F_SSٳg>}tԩW\𰲲Zf͓'OBkE"|544^r!->qѩnuu5gXeee6DwDhAd>!RBP(JuDV!Bjll e矏?v\҆ 2jԨӧCkdw ܾ}/'OZ5:Z>}V\yݲ۷oX۷///OevYyyyÆ ? 200 r/^,,,HmWTToKJJZL"uuuvDF544d#2@LLL:a.___qܹN y}GGGq :F?~իWϝ;_#,-Z***ѣGa"gϞIII*IUUuС7o_v=::̙3111k׮x t;v @OOH ǎ+BPJ_^r˗ǎò7n,]I~DR*W'"b6olgg8I˪72kVok2d۶mO>-**:{ի>\YY)!4mڴw@h<ɓEEEcƌQSSwut WAMQww]vegg9sۻ&--۷)))-,,444lll 6o޼;v\xٳgaV sxMijjr܌ A2r["ZZZ$REEECCJ~6LPB  }! 򊏏h4K:a.zÇOMM4iR~~+D"XܹsgΜ9rRI GVJNN4hP@]]]e!Kt%'HSLIOO߰aÁ,--CCC 䄅͙3ӫW,\bgg BP& 'N򲷷?uԄ ={m۶+_E;7]vmݺuo޼Q',X+I6?~3g.]p/^rN 꺎B}}}Fz*ɬw]DǏ bbbL+v1yPYԃѹ?Smmmrr_NNNUUUjjŋ.\hoo{ԩo͍b 4h޼yw$dQOOMﲶ "#rsslvqq1q!T\\,D&8L`0dtD.AGdlxzzʼnNkܸq?鄹@iii} %Һ2Ɛ!C^b]?~ܳg> ѣS==+Vdddٳ̙3֧Nrpp_x={]RSSSPPѣ[egg:@[[kΜ9ǏkkkD߿tttӻ>DԠddRT#" 2Bc2 //Nk ʕ+0h+ooϟ_~qqq(:WWxmm}޿_4#66sҤIs]ARTp{X޽{߼y]L&~*,,_]fbbKfϞ=;uٳׯ_*h';;{̘1.pi[[uedd\tiȑp Mdd$BhܹL&S޵Vll,Bhڴi4M޵tp”( 2|p__ߴ4yץ˵퀀kiiAWM۶mfXIII{p8T_LzT9s^~=mڴŋHm5k֬;wFFFfffVUU=}t޽ ߼yӴivԀbbb.cmm4 2BHGGb XNAduuvDq?eY5(=zhhhtNTMMmȑP@ eŊ\._~ӦMkI>>|a/rw_CC#!!ᔖo_C|O$ihh޽{+EFFDfhӦM{իWKJJӯ_ׯ+5#,[W^?vvv8q\bjjګW/y+++?~i ͛0sssy%'OrVZX,>x𠷷7fGigxU_PfWWWůnݺ5rHsssѫWӧfͲ033oSM<#={._,I&q8{{+Ve%ojE"ٳg,XгgOMMM)S<~O =5ә ,ޚ }g!D&I~t D"޾};**m޽◩eXK*"rƶm7nƍwy=GGN(FT5x"@b {{{.;e L UG=)31ݻw?|0--o߾-o٫Wof֭7nϿvڤIjjj;pnݺ988̚5^y{{xqkk?J ?D---ŗDI$R;"dQ]ȼgϞ9s]pAEE%77s_F$;vLGGŋ.(:Ha4wFSYYzj kk .DOguuuX?D",~9BǟR$ݼys!wwÇWVVʪ 8OPK.w(wޙ>|P޵ 333PDne/[9իTVVH5|Y_vdɒނG_|?tZJ57-^/Na BL:U Ȼ哟+ioo/Ǫd2Bf/_X$?[n|5]ࠆ9Q fnn^UUתIS\Ĥ=)--}ƍ BB:::FڲeKtttMM 2Bցq4955Ur0<<\UUU*i?bx .ݔ|}}q" {>vN-^!ŋ۷o777k׮E|R6ϼ)/rJΙFSS@9ON"FyݹsՇ VQQ!|~hh.ڷo_CCX,nhhpwwhT*̙3b_T /SSuֵ~'O̞=`hhh|7ߗa0(F{{ѣG4hBo?JNg.¤M_vfB}ϫ/]ԷoϾ­{wi ޲5޽9gy|)))Ȓ%K X w8L7/^38@&oyTZpPj7}56mU; kkk___Y111qcǎǿ ؼysBB\H1c`mmʕ+W$cccB999/nj3e#fAAAAAAbxҤIk֬qqq9{n322BϞ=۵k_٦_:P$>?~<(P˗x4IZ=TUa O5/x˚x"Dw!JѣG!"*$/]D"TN*Lﺭ6l؀O666,\P%tbM_ Z9>B~ƍ?Ǐ?tлw:b:%jժf[رCr$++ !'9JHH_Κ5kD"]tl8e>{lSS>}~ʕ+!? {e[lAeYğ[ w%%%zzz7n9rd'Lwo@GG'O:|/\PUUUE;z謬˗/{{{wtBرc?sQQђ%KBBBtuu{}||7o޼ejjx~!f͚?ݻw${F]~}>|044|رcN*-->/00+]zuܸqZZZ<|01بD 7n!/_D&6k,YHHHllǏϟ?b^xڹs}><|J.aÆА'Ouk~dy<]]] "P(K.͝;G"MaÆ}޽w]@>xo=|i:h"X| 6Ϛ5k͚5&&&x3f$b0 'nٲ%11w2Ah4ZTT̙3[nvډ'f͚E":ngzoT IDAT%"lٲѣ .6|͛] Jޅ\-y!$BB555׋D[Bb/ WΓd|{uoa...=ȸzݻw^aÆ/HV䷼N_p:Uvvo Z7|t> *&&&66!T^^.:|qvPN ULpBUUw޽{^vmuu"""ݫ?zaÆwhw"ӧ>>>֯_P(?~ jy|CO(ɬD1 @@ڪWAd*jhh ۊOEZˋjaÆ]tsҭ[_~%..J8pN():~+W~K,鈎=.rppyrRk Bo޼A:880 t6U[\jRBA}q ^kke˖EEE#>D#P&/xut>AЬ'ORTC"Y-h$СCϞ=CʐxbP̃f~ZɤhhhxꕵuHG믿fee%%%-^8%%eرcǎ=yd׸e\.Yƭ3229$۫XfZZZUUU!lؾiGAA陘ioƍ㿒@>|붶/4E"6nxÇO0AM;%&&>_~ ߏիWͮ]v}lYMM{8l޼yǏ߱wJNOeŇݘ1cB{_v-ԩSBZ|/^xl2|eϟZ`AXX+++W|r};wfdddddlڴiϞ=m3F@@t߾} !tᢢVsww?~xzz:ͩSB޽e=e^'OV\YQQ1{lydԤ]#paYYYsѣ֭[[r 85F`` jk%bȑ#&L쮮ׯiNNή]jkkΝk``0s̿#)DAkk|PTGd͖ "WVV Bmm튊 bBt:S?Yir_xM @vRSSBO>x<ڧ.J<$$FYYY]tI#===vٳgFB֭[-lk߷sF)AAAǏ'xZZZvKJJdeCIΝ D999o1tY==ŋ˻&33D" #F0̘y^<L<>M0DÆ kz:iȑ"5S|ڵk?X,vի%wʛN*Ur'̙3!NlhѢ/y}[=jvvﰭ/x˚n)5"P(\z5n'Z|!5;}\JdGdi366ްaCjjjӍ}L&?A.\@˒Q@HD"Ν;w2,O<@J|(WE$X{vڌF@ܹsUUU>|(r"z#MLL=$$$ 2!4hРϞuvvٳg]]ݗM)8pȢELMM0`ԩSef On۶mȐ! EUUW^6lxi+ADCݸq#,..wE͎;lvcc @9cǒ 6(EEE&M׷ ill:}+8Я_?~:tM4{{)StNϞ=>PbtW-R󃂂mmm-[!6x˗/m:gٳ;88hjj.Xܹs|?=j oYӽt޿?x`v1yעBCC k֬KI"AdMMoΝ;-?_UTTl٢g'ᠦ 466[[[s8ɓ'Krmk՞bѣG,ky.''g۶mNNN!]vʻ(YzB(''Gr竨\xQrp۶mRoX]RRB(==}Y[n!x<^DDqׯ_GPSN;w!$B׮]@D@i1bɝ6݉'(JYYY:N||ӻ; <_MM̙3mz`jjjPP˕+WZ&~L&۷_Z'[Ir$--D"]rEVS1zYeyyy'N4iBᄏ|riiiZv)UUջw͛7˻mΜ9e"BCC]!4l0ye˖d.y]^~g+++FԙEs޽q]|?N&:6  rP֋'@uV 2qN|`2}BB`|;;;!%KHN~B(77GܹCo߾-[:~رK.>^ҥK2|G' 9I$ (>}Ɔeee. (qʕ%KƍşXDǏs%%%?-?իؿjtuu%G&NvZP()ƌqN>?,**JHH9sfjj lŋ/\PXX9)555+W1c˗]mۧON0AޅLH$9BH(N2uuu t,__G-\ԩS3fȻ.E,_?>>/^3fL(?eƌ111V /pPPbcc{qƝ;wT*U}khhhvvs*++ fkk#MMMgϞIgffJ566Il"bBeeeݺu$Bx_rT*L&#BA׷v"4rrr>~9u_c˟~i޽?3m>e˖ɓ'Kבɓ={{/^?~g#3f̙3gԩ2tuuq`[}ѣ2ŅX5Өxxx[.66'&&N:533sΜ9`„ NKKkMĮ]O~׫˻"…  w!(--->?}ٳutt&NsyIH-wuΝ;֭[~_SSӯ_Q/"|hiiyܹիWEFF~7uuu{2dŲ?~-[nݺ%ՋHD**͜_铚:~ѣ .J6·]ࠦk6lQZZڒ%K cǎw۷oǎuV.;a„w*oJgϞIIIR߿122BI$t:J1̊ b7E"D]*/ Q?D@iӇL&uڌƍx6#hT*5888''gʕv211پ}{mmbȑ#^v~yxx&%%7v*))>|;pbق}5udFB (PQQQ@A".붭uYh֪Uk8j+Q2H`ۀ Ouھ+WѣIJg'O,..>|P^^>w\GG)S޽ݻ05_~…~3LÇ(@7477755FmmÇ{""" 0{ԨQ\… N =MMM̙3"##Jݻ=z4|>aň#,Yr񂂂ɓ'oٲ%44T$999Mr B:az-RT3fLQQѵk:dodX󕕕IIISN-..7oP( ^`ѣG=ҥKRi9vZrn'++o@D6Ffq3L??oF|M]8p`bbbQQZNMM]lY}t+u1QAA]~mL6hР/Θ1#;;m7J,5jٲe'N(,,,**JHH|ќ9s<<<֬Y?@t;eV5x׃? &_zԩΝ7i>>0.]>|pѢE.]:qH$ðb~T駟ZJo򲷷OHHx]u,:x3gTTTܽ{?Ǐ/%IDDė_~*Jcz}֬YtpcvT:`cATܻwիwkkkSSS͛{~[nniN>=w\777iӦ=yd̙KKK[lK@H$G^~}RRJJMM]|9Z۾;rFymllc6밶^n]AA7|sΝT:cƌ/Y]~7n\NNN|||PPH$>3|YfYYYX0 +loo___WX&DϼLMMJ@ hnnVըJ2 FpH$ȵL&x_u տbqUU6ݻwgK.ݻ%KF pLLLƌsڵk}}]=z믿޺uk޽;vzb1J,]4111666))Gf͚WO!nnnnnnSL0ƍ)))۷o_t)DrttӧOgP=i🚛ΝfRRR="^Ço=x455T*Zj5VU*x[VT*jhK N[XX?͛7n\pH$ ۷/^ :)((HIIvڍ7._\WW1| 6 8th4Ϝ9s0 +,,LNNrʕ+WnZ__߳giӦuhdއ?Jڵ_~۶m1c+hћ6m+0lҧW^y_]ZZaK1ꐻԸhcecРh*++аJRTxhHѠ־f\.<p\X, Qx<>hKHt:1<<|ڴi577_.\شiSii)Bqssuuuo覴Z7RRRRRR^ZTTD&N:dKKKÌD&`pΝsΝ;wn۶m:~3OAC(K.]tiFFơC;:0tАooogϞ={_U^^.Ɇ_ :%ݝզM,YwmڴiӦM .5k4ОSo+Xp?~wJ|f|>ĄD.--T*|3 $s8zDF? Ad@dzd "=zo޽{=z4##c͚5˗/_x;~}sm۶1̙3!!!xzzDDDぁmﹱq„ O>MMMr3Vd۷o?Q&y~͚5~xEDDٳ`K+ Cwsrr޽{޽ǏYN;99E_+BjժwEP'OaXsssnnnzzzFFFZZڱc֭[@ќ]]]{, xuuuyAfffAAa<ãgϞzߓǏ?ðSN:ukhh 9r䫼Nj؏JC0Yfa}ʕ?sӦMMMMVVV>>>^^^zիmW &PQQqΝk׮eff666d/Z8Q׭[7qEM4駟~Zn]=ؘwۧ7H$555L&$MMM+**t:$~cPP(D0kjj "õKAd|駙9U``Ç!۾}+jSN;w^۷7o믿Rɓ'Ϛ5 -NT?:tΝNϛ7#;tA*:u zw1cL:rkB8:::::3466>~ݻwMHHXn]MM FѣD"qrrrtttvvvrr}mNϟojjo{yy^Z,{DGd2|x kJFQ[TFQ(^)njjjO&bPafB\.x<u 6fQŎl8o޼}M""""PO]]]fffFFU}X,b.Gp =zgQ;//0SSS꣏>Bv׽Ϛ5k֬Y )))[xypppXXXDDKο"333 Þ>}D0N?ytRc\\aj:%%%99?S~~>a<gϞzի! ]aXCCÃ1{hѢcrwwߒ,XaÆ.rG ݁%%%ĸ^YT677 j5AdT d]5 {r/^4Xðc._\ә젠H$k֬Yxݻ-[;O>!BUUաCvؑf͚S>vʕ+'N|Vl^z0`@YYY]3><}/^LӍ;*2ܳg;w; qD.** "梶iccJĊ\.mpH$1Pxh 2BA}՝t3T*?))飏>2A͛wܹQF젠p8f͚>}c͚5k̘1M۷_:,,6lK\V$H+V6mZVVݻ O`w-Yd۶mmL$vvvm/NR t~訨"XL&;888880 S*<~vv ~GVaBdGGG{{{{{{;;;kkk jjj3gNHHšCN8ozJMMC=אJB`H1TjZ/RRV`Dd21Jb Tub B@!cG5F&QxG\|9??eii(leeeiiiii)HX,1^ ӧ%%%%%%xG@_Fafjj~iccb& HK.---O81{>h111]?O>mW*" 8p@tÇiiiJIIٻw/ˋhEVggg[[[\.Ja-h[EEE^^^nnÇQ8++ ...sի;@pppZZƍWXqm۶߸C cz,--drQQq3 RF3Z+**<O`Ã&&& 1at:*" $((~0G 2P(111111.]y33w}oYcV=x?x5{{>lʔ)w;uT''!C$$$D"y-^xڴi<$RÃ/_r#Fp8ǎ9sˍ& }||>xz(F(lP(4+?vȸyI F[н߿r*++kjjjjjJeMMMmm-jԠĵQSS2ǵQUU|&FP'x(^,"z~O2L&\JPIII/are2H$JbX"H$H:E"FJKK KKKJJJ EQQީЖ4M&Y[[%QbI$M4iҤIgϞO>ChLf1hVfffzt5 ٙYRRjfee8p 77VVVr\.ڢt2u s4%%%yI$gggWWѣG8;xãh ,9sf```llƍѬB8vΎD"Ad*jaa2r!,*"CܬWdBj^Ed":ˀ.]f`Ƙ1c6l؀O-VRRs;v[.44tڴi0蚚.^gϞG֎=/:th^r%,,ɓnnn&M7nܪU:(/J*3b r%㮙H&H-233h4\.KC 444) aY B'Xϊ2L,ɖt:h,Ba EaaaII X\\\TTJ_H$Dbgg'|.{Ν;e˖Hӧ B,&iiiCL*{,,,T*U]]N'VD&r\Za&MLLt:[*NUDF/>2/2p .젠보-Zt۷GDDX[[qqqݛÇ{ݷo_nn_|1aRѣGrrرc֯_l2وIt2lmm۞|o8vSN:EEEm`mJR4 Y[[cb@̙33:aB%AKtV\Դh"??I&y{{w0sΕ;uQabbT~XFV V dr\.d29cb1L6 j0L% j@ XzCìAEE^zEBQH4''ڵk.֋P(<, mb@ 0ȫV___Rx-/////ǿAx<12(LB$0"wdkkwy'66}ZT[o5}t''~mmmڹ9hР9sE p8Ǐ1cƋ`T'O4]FsVVVRT&b9/#HOO߼y}vJnݺ{ܯ/ˍ=V:Ĩ01*?LCZ(-ް`2|>bf2(ax? ^?mTUUœO>EaӜ<{_iCH$^F 7ZOtK577 hG˒Q~NkZe2{ua?~(w}w̙ѓ'O 2L[[G췶&HYYYD^W$ ?juܚg EqqqyyyNNNYYB@IA- ǔ|>xgx<FD)JJRj5NPˉkU1 =z@5R) B#.LMM:;;OF2$ݻwk;rxDʤR)D VDh4A"2o;LP0 {^!tKAAA6AǍdɒm۶1 tr//.\wӧϘ1#***...88؈r_Weee'N8zLfttի ~>|p7OBbkkΊȈsjja|||Ξ=2#GC`й}W JJJ/*^#xAI$SzzzeT=zzj~>OP <<߸q!]quuwG.={#J=x555EAdJokjjl61WD!*" -((hEEER`7oޙ3gƌcL&2dȐ![l9v޽{ &Ə kǏ;vŋt:};w=z >[ZZ_Ν{C XN.'%%SlllGPIzƠU}ojjR(EEEO<)...((… v?ܹs 0'O{뭷=FGWwPMTC(T*ѥ#tNբ+C cz˥hxQR{{{*0 !|@Db/,'?, z)++{^EgPyJV1 i&d2 a0eA{M >> ++{3qwyE{E=z_[s\2m111d2?DӶj^_tZimlllxGMU:9O7Z}U<`p8cbbǬPEQՠD {1cFRRҾ}cCgff{rZ# T^ޞDF1Fp?a Cӡ82q r}}=. J^tɐKKˁ:tȠ8N\\\\\\AAݻ۷aL6f̘tnfGu#GrӧOtɓ'|رcbqdddHHȉ',--2*[[ۼ* mjjz鰰aÆ󉡡L&3!!a/5^d2ZG 4a ˍWbŊŋ:ujϞ=ǎ;{l˕J[ijjȠz_kN71Ch4ssm&LbIZ?@sbI[j tY'~r&4N3x3oĉ;v숍5A=666O>h4T*H$333 7ZzAOWl6"zi4"2Ad@'p8{NJJ2peq͛7OjX[[/[lٲeiii-[F9x`0 ݻw{ @bj^]z"; zE]C BtftXڏL&o @'鑑eee4sH<=z0Ç#s܆"WNP,XRi4* ڀ=H>7H^McD@[r\Tޒf(fkJ^8%y\]]\駟N8ի:<ͭ $1 +((pssC=(RTT?B/u: c"2aĀJb2"UD&H I$R;+ pY4&&f֬YN7n ^zի>|xرcǎt:=00pÇ7Jt zO>TVV:99EDD5*((N{tXyy듓*_vm„ /D0bz!.g…bxƌ^^^&Mz^ 0eoo/IEEJV(eee(Ĥ F333333Cd%==6ўܨZF!`tRT1ZYU+B1*E xmx{{o߾}{o6oM,D ZPe2^Yk\uЉ^eJ[V(JSd O$-,,mX,2pW^۷BBB:@RT(\.W(ۈb RTTD|"1, QEd T*X,Ad|䪪*yUMnjj+AdD"._n7>PTW$SRRN>޽5 (((00?àQSSSVVVjjjrrC?iҤ`PXZ S*.\zf e͎qaa֭[ P(ֹ#F\|922Ϟ=#Fhcc5bĈx"o۶MV]whjjST*Uyyg***vAA͛7Q^ gff|P(D6^ɋ-2@t?J3[ޢ$1TqsssEPat- 4޾eUCqac69s̚5~aOE;{ZZ^'p :W( B6T*BX,BmH$Pu@J BE(]ZZjv8/_&H$J---% j[L&<qrrJII2eJhh%K-[IӖ\]]ݻG 2JhD>LMM 2Zt0Aj5^QEd&aXMMM 2"r+u)D255ussKJJ2p922rڴi &M2qkB^ZP\t)))ҥK?sCCT* ݻW]Ķ<@Rh4Av1<44TP?̙3&666///>>` [[[z9s9r̙ׯG[5vظRH0saÆʔ)4i+NSD' Q|(L (bgb`80336l)Jt E[ޢ-oQ탢@0h(ߢJmQD*FՈ FC&:t7nl޼y…W~w̙{/o֭z(lkk!Gjrss߆U'''+++Hdee%-,,̌87`05;`aaaii'O EvvK j5D"-,_8ΡCUVر3pss$1 J%%%bEdbYRǯhUUU1 4"2 0ŋ>(6lؑ#G :X,;vرc1 jW\Ae˖jлwoooo/////ݻwݻd2ťo߾WׯG=zp^xdd(;uͫ1 xw5jԇ~xĉov̘1n9zhqٳgqYYY[lP(7oG/&&&RT*g㺺:HnJUj455o2~f|.fl@ p8 _~%&&*0a0ZUƞ `% x1>n{$ -wr0j[ Zc趓r5קOݻwoذ᧟~)S|gg__ŋd27iY,t5EEE?~O>E ?񉉉H$tnD@*RhMM ^躠 //gΜ#>x.Y"E+VgeeeK...O&ܺu#JAzD&xx𠡡㹹3$$W^ݮsFFưalllN>mjjBׯ~~u q蠠GEE5oU8=z ܭ)ŋ{jz׮]Ǐ5tEh(x#''oWUUkQ BHp8\.Gme<JDY(nt۶m]gQ tuuuUUUCl0!rss7o… M0!<ƨp;h4Z"OCZ0z= {F饍!( a4-...66ԩSV Xr!C^bo\.E/L&l6J ioxCѠPޗ@Aab 7n5ͦxcfmmݞ|>Lo&?]@{K.XbС , {/7|}}SSS:j]A0nܸvΝjdggԾ@&Nft(B/l'%J&]]rrrxxx;d4M/XXX%%%gʈAd@в"2al6"+"D&HD€.^hFGG( Xl:!T;MVWWWWWWWVVVWWԴ6FB!J0X,enn>I Μ93v_}yT*u˖-NNNqAA?Im)077c1֭7o޺u/^|C>ST;;=666AdKKt}#1 d2UD&Ɛx< "sR"rmm-{D"2h< |/_f͚nڴwyFs玷7) _JJJMtƍ˗/''''''RTOOO??9s] @wSRRRSS/]uֆ++? }If?~|ĉQQQǏ }}:99eggwE"{,--JKKR)133#(LP|~ИT*DGUWU!DF 0\~?M2 w^"Zڹs~8sM6uWQQQVVV챎BPrss;vz$駟nذa)S,--<_vA*++[x3>}k׮ǏqLBq=L\h4 (H-yGbdmӐx%ZӧW^999ca.Ȋ(}DP}hb|DhP#uĿk|^.V/z "z^^`FWz Xz0zyb.ku577'g>#K@m' / ԩSׯ_OW_W={JO&bB ܸqcgϞ;wJ~~~sem@C"0 qFJJʕ+Wꫢ"33!C :4$$/l%ciiaXII DP(B1 355}^Ed..r8<;L䖗ۺ&"н988HҤ$1 {wؑfC:Ֆ-[fϞ=cƌH!#>>>W{D % E@E"Z]Tt]Ƶ]T]숊 +b H $swn '3̜ ( IDATߜ &FFFZYYu544K~b###77_6mDlmm]]]ɮ@0AAA_%£#m©mˢvY'VXH4& ՝'1>ڇ±}O氩T*FCGA4&KhN[0۾6k LMM7nhdd}v]S(۷o }#FqAFGGƾbm۶m̘1dW@wx2###66666v͚5\.w8{DBBB'tuu=uԬYem &*..\f222yyy, _Q&:"#ZZZDDD뉅Ųf|:"zMRR{~Ν۾}{oxk8p_ݰawݖfJJʴi=<}zҤI_BF[z 2sL777__7n:uJYYs/\ghqGdSSSBϝ;`nn>k֬1cƌ1Bp+A 4hŊ---)))8|qqqqiӦ7FHAP8%''7nܸ^NUUUII #L&SBBMYNN"~jb2xQAP;"766#r "Ns{?!!ӧ 6]NNNAAA;1k׮yzzΚ5k֭ݸfuun\aglذAGGWXp̙sΝ2RƏoڵd@7ȸܹs.;oڴӧ?~?~trrjmm! 2NA۷s*((ٳ099y֭B? ++m۶?wϟ2eܹs9(J``yܒz=:::7osTTTQTTlDsIIJb&ItDqG䆆qq}%pAd<ϟ=<<޿k򬭭={?iҤۮѣG8c//nY-LiӦ%$$9rh̘1l6{ũ=_'ׯ_/++3{z@hhمt?==s.Yd…a666.]"s\33G`}Oii޽{uuu߼ym۶܄e˖˓]wbbbNNΖ-[^~쬫o>-"zB9vشin%***myyy(**Α-//ω r8`0Bm:"hOvDf`` ++611144 MӧO͛߿off55577m^uqqsg333a6}}}544Ο?w޼C]f-pI׮]{u/@WW7000?? ظq#@R#""455q޷tttȪ|bNAdiiid2!.+D;D 2X[['&&RYf.((e111cccSSw}˪TTTDEE:u+Wܡ!""2~ .?~\DDd劊.(( VJJJpp޽{CCC߽{wA+ۄ]w7`w2L;;tBBB𤴴555I(qO8gϞׯ_/^b]߹sB$xXK}~;zNtt4E? K.nذ+ޮ#MYEEoYAA}x!Df0i4tD"666#G MYl٦MBBB 444޽+!!1z6gDXXXII3f|1&&擯JKKϙ3'22"::pǎ***[n{Pkkߺu봵. QSSsҥ9s]@QVVNLL444;vlgϾ~:D&$$Pkܼrʉ'7.##[TTh͛EXO **bŊ gguשݻÏ?{ {⯶4#hYVV!$###8\]]-!!& 2d&&&uH<kll\`Ç>u^5IHHаyWGUUUp'ihhٝ:u<}#F_7VǏ_^VVvdW@rJKKم&y5;;;5&Oz*Ie[[ 61555M6-88ٳ!!!dW 3fp86l A|>?((Ɔfl'Nti.BP(6m211aGGG;;;kjj2aÆm۶xե'N~*o-RB,4""Bp >|>}oKK .U Dy<^xxСC%$$ttt<<|ƍs;"+**VWW f9dddx<QFF!sxWLL }#2>Ad t#G&%%-Z>uիW_v R/ EEEnnno޼vpp O:uQ_>jԨXrAAAIg^dIyy0a„ ZZZcbb8 ..nii9vرcxUZZiӦիW̙3W\hd@ qrr<Џp\Z]]y<ёmjjB㫀MMM!m'9aaa|!$--X,aaai9)QT oǏBBBhW\uvvNNNCIIIM8رcsE#lll>|hccCrݤZb}Ν#F]˧qÇggg#_zrNNN&%%%%%EFFFEEuU,]4((LMMMMMzjJJ nSgffFsƍxKM6ڵ ?n޼633EdddtT+8$&)"Uy/͆đ!&xtxBٷoݻw8c){NEEQUUmDFkii9, 2>Ц#2˕hii;"S(QQQDƧ& Q')f٣G>w޽;uTMMgϞ)++]g]xq&L~ѣeeQ[g/_ŋK,DDDF=z={ٳWAAaرcƌp8߯~oƍ,kͣFrpppuu%"MAAA||.REEE]]]}}}UUUmmm}}}uuuMMM}}=˭ `nnn !8%..[e0!*J\blOLLNG& UUU$H 6<4B]JJ q&h4&--MtFcX F1L&I;@ii阘KKӧGGG˖-5jԣǦ #Ǐ_rצBxDA%%uֹ]ti݂ ;v,&&d.X`ܹ|>?$$?y':߿… ܹsu ));wZ[[s8;wرɓ'wC/C :Sy"{SmٲݻYက\b/_nQ>}OO+G-^xҥ (//;B ؼys ڦlܽ[y'OtwwOJJzWo%#_fExI?~}~ljNunA G0W0EMF WtǯW޿ȊvvvxJAd>#񼶶JAd:.,,\]]MRmPT"""n6 @?acc痓[Xpa'{Ȳ{͛7Ϝ93((w{ *z٩SN<9&&ܼKoWRR"#2ɜ4iҙ3gDh//V===[[[[[[%%~ppsNb+**cEEEaaaeee]]]]]nyIII&%%%%%%---⹴s!!pnݺ5|СC!;;;CCãG 2OOO8q׉?CM4B:rК5k|||BVjjj% C-^x޽x B( ,,AUVmذč{L>f;88\zǂ?ّNVFg[n! M6ݽ{˗H/ ʕ+߿ 100ptt2eʔ)S:{#22r]WR )ܬ ȂShX[:C``Ľm||Hhhh-,,!OJe?'cU"ۼ|+kYfɯ^[(&q30BHUUѣGĤB)..&wqTTTHJJxxUUU_!$&&@ڌ6&,,, =jĈIII'OtҮ+V>|H'&&vҥI&9;;uŭxʞ7{l''7o|zG1bĈ-[|ݻ Ǐxĉd[[[55*_xW1bڵk. nN<9k,87 x<^yyyyyyYYYJKKq(H6|Bi4ZV>lvg\n}}}MMMuu5)`6¿l6[NNNgtuu:4o< ӧ#,XiӦIIIZXX8;;:t>j*0a1U0+ٳ5w܀"Am/^@-\p…O222B111!^g*o[$~X[[[PII jщ'v)ʷlRRRP(g{Tx/uu_uK1d?~Ç;fObXm}N1e/Jjsssy<N]yWc__nJeX41٭)&LPWW?vX@@@ߥ{bRYYY#2J "KHH@QQQ JIII/L&1{3qG67f"CGd@333KJJ5kVod{1cddd 4=|pʔ)RRR>T*zѣG;;;?z\p GHHȖ-[\WWWWWWPuuuRRRbbbBBBXXXKK }͇wmذÇoNHHRd@w 677%@oWYYÇ>1vP(JJJl6[KKMZ\\\܏z-!!!,\]]]ZZ*fhvΝ!l`:YII °!tСzxx$&&2LooXZZ"Fu~D7n\}}m3&&&o*3RSS:^*^rrrhh={!!!ooȐ!\!˟٥ʻ|ITTի񜨨6ˈ677|]5pk׮]ɓ'CuV糿n }-Ő%::Ԕ*qIHHwUUUԄihh &O666ffffmM.7f`NX,%!!ǟa0pkA̞=/66vܸq|ӧIP~~>q뚂1j&++K#ȒDw޸#2F\߿$""=j׮]eeeѣGܹ˗/ϛ7ٳW]X,Vttȑ#'Lp]:ŷ(**"QH1{l{YY׎ IDATY} XǏ?!TSSѣ?x 00OHHHOOGa``  /XښNمzܜܼ\.hDYN#//[!p0B[[e FD>==ÇD'Q';"""W\rJSN9rƍ***ׯolluQQѬÇh4'''wwqǏq>;M<955_DDҥK\`̙ǎC1cϜ933g.[,66ۻdT*5;;;11188ɓ'{_BΝ;x`*;S˓'OEEE'O|ʕYFEE%++f͒>133[tjnn3gBQ1i$}-Őڵkqqqn"%))#ʦ"d}}}ii)L'ۯ\TT`HIIt |@KIIv%%%Y,Ajaaq.qWc_9//"˷,Dd2; "=6AdaaDB_TWW'--O?R Ξ=)$$DJ+++WEE~BsWVV?x"RbbbD `HKK N~ݥ7 `Ŋ#Gܺu3l%$$366677ǧAI6662 SSSsssSSӡCjjjU[vڤICBB_|Oo9rdÇ޽{333srrrrr9ʪD4S]]]EE}gq?''?/--h4AMMm{1 _@@555?~855ȉ']w1lذTbB x<77'޽{А:;r9˖-;rB_NNN111mՙ 2#~~~'|>...37nܸk.bU_;JYUUehhOp'Nh:zݻĝW,--MLL>L_zokkKJRRR_TTW\\_XXXXX_]]fy!!!|d2Y,1v>mk.h5\.7d 7QVVVVVp8GVV{wOYYYNruu~y>=zYƲe֭[}ξ}vYTTɃ睊ɓ'HII :ttuuGlPSSc``mƌsiӦ](88BWAA x=>FR544 AԔ7}ǹws "l-MLLtuu#""rssxСk׮FFF"FpɮGϝ;k"\ݻ...H|c^zӛ={… ;BF"#={gϞL111uuu;;9sx;wL>,//ohhhhh8x`###}}}JNNrvv^d׭[iӦ2qqG]6  \.BiaÆݸqCAAG =dҥAAA$Aڻ߫]؏9#hlllM#W*++' [M>QkoD#źxb'WSS[|9^[[x2((hƍ?~$sǏB˗/OOO5jԅ ^xXb_xooo?pC[荈:tTDDDZZo޼zݮ]I)`~~~gϞ={ g߾}[\\P(8⢢D7&Bx ܭ(++۷?s DTTT]]􌍍:iڵNJ+vM~\]]ٳfmm펇ceee=V')))۟9sEEE ̙KKK{Ijj'O.\(**jhh]x_VVP__~z ϯ|UTTd򊊊l6xf$svvNHH3fLg "+)) 4f|~ii###o&G%%% 2D j_?*$""8 Ad@Offf$lqΜ9AdEIIIKIIy;kbbn:'o#2![~ѣG B222C 9r$>Luٳ9Ǐ DAGy捋Ç> ~UޤI,Yre˖]K;1<-- _qIOOtONN藬H;~ϟ>}:o 1~t:]HHH0,**\\\,Y2j(aaa%iiÇ/wٲeŋ/^L+@wԚ==oܸ2U=ѣ'NUWW'(t:N\7BKK PƭKKK?|PZZZXXɓFN$r' V4hPttt$i!$DrGTEEdmm-5bUWW#DGz]N}2 @3jԨϓXٳ=<<9h㥧'%%?IIIyyyBBB&&&}q՝:u*SVVC>?o.,,lll}e''0IIIr1.\0331cƭ[>y$b0SL [Adee9x/^6YVVVOOOOOOWWW___WWWCCu_~ڵwjjǏd+藂zmcry\\ܝ;wkkkƏuV333CvMF[[[322p(knnVUUwppk%/^,&&&&&###94񣉉Iǝ?xעh[-Jmnn_`ޢ}ǏoӦM4iR_㵵-vf'DeeeddܹbŊ 8LⲲ2S.))Ӊ8CU(((())++++((p8EEEEEEYYWrrrڹDXII \ +by>_QQ!///%%rY,BV|#ree%NI "777Ad\CqNz!"ر'Nd2ϝ;j*R ͻwn޼s޽*fff6gKKK x,rrrx233399999޽{|>ƍÆ qvv3f̏c]feev_t ,} ٵt'GGGbNEEEff/^|ŋ;wdggx<555---}}}---##zn͚5sȐ!V222~,999qqqdh ߹sΝ;>|PTT 8p 'Bӧqqq ,hll400ummmq~UUUu)"As[drQQQKJJ266644600vBh޼yǎtzSSرc/^$7o}vxxŋǍ7m4 ]9}BMM͵kΟ?#**9v?l:A,btLyy9(!55֭[555x1*,//OMbx---!JJJpOJJJTTT08q*"\__`0 DXJmhh Hfp @caa!..?sLR :u3g #kjjJLLy7޼yClmm7mdee5l0*JvpΜ9{%%%ŝ:uJDDyzzzdW>}]__…m^e0A? pԩk 6l0bNyyW222222^z񄄄8pv'rƍ .k׮e[nu :uJAAad=ztȷo2 [[ 6888]_޽{qqqqqqpww2e 1okjj*++NmAzJv2"I[>ٳ–,Yѣ}FEE~ZWWw…VVV=233mZZZv7ou?~kkk^>w\!!Ç;::;vذa0 xǏ 2BhǎKKK̙33f̀.##ԩSϟvww?~y:2őTޫ>455ٳ'44477쓎Ԝ>}ҥK|>7|;Fcܕ߽{ʕ+7n񱴴5kִi:9R/.tWnw:ukԩ 444?Yy޼yݲ ؀͛7ɍ3QQQ[@TXXx혘r6moo?|WWW<#\O¼ܼԼ_SS 7oޖ-[ȮdƂ3 xիWB EEEeZZZ|r_zǎG駟{s޽{/// vĉ+))LСCɮ ޙv횟Utɓ'W\Ivߋ{CCCtttxxʕ+WZ5aqqqYYٺj IDAT暚f.ʥEEE Np84dRTIII111:`0mBR~LOOoҥ6l2eիWw%##}vAvϟ>}dɒF}}}ss#GAdx/_ܿ/^hVVV֭3f1$N}u\\\~~~SSBHHHHYYYKKK?.3fǎ-P(jjjDFIJJ ljXF8C>`0( 3Ad ݶmrrrI)B̜9ѣ۷ozP###O>',,|#ȮN)--MIIyAJJÇ\.477wwwQm쬬̤f`@gW?5jD!ptD "#dee8jLRJY,BYBbbb 2q 2tDcȑ s!//_͛'O&dff;v,<҅ӣ y<BHXXxYYYNRUUUQQQWWp8pIEEE'O$~D6l(//_dɊ+455ɮOy&BhѢEx>!99!4gΜ˽ccc+''믿]ߕ;VXp_~ĉǎUWW_t7dF3f̘1{ݿ?<<|Ν!55 87oޤ=}4--yyy!mmm ݻw[ZZ * p86]\2(?y$""hii 4HWWWGGGGGN!zKII%''w&kbRIIM933SpyDd2;"KHH|YLLuD#IASS$Ͽ}ĉx˗ggg'&&.]R}8x5k֨Yd/^yf<R֒Z7ydiiihhhyzzn۶IIIyyyuuuƍkkkjkk8aee*..fee5cƌu:tիmnUO aXdǏs̱}h tƌG__Æ 6fmllN8޴ Bnڴ_3â555LaömHt '޿Kq ejj!>L>]QQQOO׷x^_"|n</<<{С:::)))_PßSdI]Zaw!$""B_6׫P(c޾};&&СCt1A]Bܸq!4eOiiiXXa˖-;s ,h.]g2?x;wɮ(ܹejjd2 <<<._L.\xwޝ>}zҥFFFBЫ yyyر#44޽{uuu/^~֭[-,,O<9cƌ!C0 cǮXñ? !!#G;`H]]l6}Gr\JJbAd r Doll #2dvvvN" ,\[ veee'Nɓ. |= beeeeeu .۷o>>>'OF2{lyyyAzϝaT*u'O%UUUk֬P(iii¼2^qqqq8eee%%%aކ8qbΜ9{)gdffkkkz\.wW^N;99s"##:5yժUs.]DL^z5%%533#^ɹqF||Mg*o[6mڴk.ussy󦓓B,77!TRRQSS=ŕ$|۷oϞ=ūT^gV(CNÛƎɓݻw/> w"""`ooܹӧO2ҥK&M5k֞={Ν+$ͼ+MOOg"""*++B l`0Hm5k"{$@H $ WQTE;E EXlkW\ Xv M@ ~pΝ Q\&̕$u߿߿l6{?… ̌;8addddd$R$wCӧgϞ!%%%Ĥwbٳgvvv]]^r%$7 "Bl6竪g-啔r]]]Î^bJVWW;PGd$tFqqq>X@ `2$Ǐ/_|oߒ]hyuuuQQQ&LP(;qDuu5E}-[(((ܸqǏG]/!!i̛7{p"k^;v@xCeee>>--#G\r!"ϝ;GRb1F;s 1rϞ=zB/^|+oDsD #442͛ׯ_?rk߬p ]huo߾]`JݻٳgDtKBqqqd^^^dWܽ{B\vlW"%%ݻgΜ_|)S$[hjj0`ҤI߼y#G"""?~Çyi44}tKKKk)dXTرcΝڵ7oTUU]8Ԥ[lquu522S0A:t(::J[@|fGw<W⾸K*мn: loo>i$OOOb@޽s-Є b˗B,K@PBBwVG77 'G5ɓ'Æ # vfl2w.2 \뽼:$uuu׮] wYډͅZ@ 011߿={^~ݫW]]]]^^^VVVnnnvvǏsrr>~G̷bX<p8Wr8+tbzzzNrww'.| 6]H PSS+--}^ݻ#nݺ|rox¢[n_LNN644$Vrܼ<| _B)++ ;wL8f+'v6Gb|aaan Wp߿ׯߗv84/_.Y?$;::hhh4}e3RU߹&^,i kZرc}Ihh.'''###===D"CWmmm|>VWW֭[ѥ ;;/?%{WϞ={}@琛/_~}533:tСCDvrǎkzX}}RHHȜ9sBqqqcƌS3'>>~РAx|``#G222BnnneeeUUUx2QF㨨ɓ'WTT{>>>xѣGmvy[[gϮXӧCmcR.ZBCCɭ QTWW:t( 槟~SUU%(tuu,XiӦE:uƆ캾|xxP(vڳgUyΜ9k׮zٵt6m .o2BH^^^GGGGGgsrrsssq:977755#T*rZZZxAKKKKKhjj™SPxx+مЅTUUӇBȄ_E2E5YQQڼys}}J~~>֒#H"poE7|Y6nff'222&&&...22|˖-߰)R 6rd?-w郯acggz >}gϞ۷o7oe;&?}D3jkksKˡڸ\.˵kjj޾}W^=||>_AAtذa8lhh؁k[ZZ޼yarrrzzzė2|v"$x gZff***@YY}Ҋn&mW!EEj>n @ecce˖Hp{7Lx+V]s366vZ||uƏ?k֬hii]O>}ڵ 6tˋ {{0"?aaa+V:t(3D4{ݻwҀ""O%&&_!4559&㌲6Э[7əjAW gϞ`0ȮNK(J}hqqqӦM#󨨨+W5ׯ_`hhѣs+{FFF߼_ <ܹs;wHHWU{E^𚨨(1T*F |W}]f͚5kx֭f-g"l.6]DOمt~{}򥣣c||ٳgN:a„/:ujٲed>[ZZOS8ZXXC`>SUUUSS#%l6Ng0l6F4IWPQQ!|P(D|>痕I.H7>=`',)T*t֬YxMNNNBBBBBǏϜ9SYYIRkii9rA-hذaL&={lD@1L!DVWWUUU4fp7Ad|NK~d "~g贆Knc͑{hJJJV\>~Hr΀ve111۪U FI,XPXXvvRSzzzϞ=ɮc򲷷}mbbbRCCCCCK 񂂂\Ʌ܂/!ʸiCCCC!^ SSS.lEEEӦMspp+}|||||fΜٰc+((\rfϞѣ`ٳ8<{o%K.]ZRRbnnnjjDkkk׮^!tŠk(KRNNNÇ'OΙ3:52dŖzzzgϞE6-g"l.6]]ii)ۀ\XX… /\x`OOO//ݻwA&Ccl6[CN$XFFdĶэ0LDpRbh4ܭfS(A) F_otR |~}}=liiX,abX\ZZ+++%%%Wʪ$c4RJ{%&ǒmAx<7ydH$JLL|ٳgbccO<5jԸqƎۻwo6tf'ѣGzz:^f2***ij qb.bJKKLfYY1@#2T"`TUU#;n"53W\! ;;hr_r-ooo ?O2wmf;"K@@k׮}؜:uJWWm=544bbbVZ,Zhdұm۶m׮]߶mڵkɮ-H.** hvvvwܑʼn'FEErN[mڴi׮]7nppp\aÆݻwjnz_*Ur%711&>u ^>>>?7lуjժ}5\/f7okxBjM}ߴiS$oރ~ 0 55=**jڴiG%:ЎD2N6VR IDATTTTUU8,pw~*pGBjjjsYNNb4Y+++D̚b BӧOϞ={ݻw={VSSӽ{cǎ7nܸq'df͚رܹsx&ۦM!C7n޽7oLLLO>rm۶۷8Y\\|9V\\+33y 6իSN}Ҟ>}:s7n۷hQD3;xΝ; mxww+W;vlڴi'NZ["3)5_nnn>} srrj]?>~xĈwܙ9sf#""zEvihݺuᙙT*Z:7o 8000ʕ+ӧp0 D49??GC< aIIIII@ -EEEhr -PWWĉrYOOsSԚ6=q>7n\n[Aܻwb988ĉ&X|e 8mˀ4֣G1cƸGEE;w.66OjSVpMKJyvpp700 .Fܹ3222''ז1ӧO߿?##o߾#G=z,ǹ?=ym޴ "ڡ>x{{?zرc#%66vҤIW5k֠AD"ܹs嗇9@gVQQQ]][7|7'qSDe)򊊊Q ڗ/_-[,999::ZAAAIIKLL\re~?G8$ggW:;;h9D37O$$$ 82B![f͆ H,Htqqyɓ'g̘f rG7$$/ @^^ ݿ۷oϟ[XX }Zô>}DDD8::[IUWW7rH*nݺgϞuK %)(( .ct:]M6\.Wkՠ عsǏq@+YhQhhhMMzVUUER.i'N0a۷ɮt|>׮]իׅ L~y>|ƦnРAB011: |~\\ݻwݻkя?8i$KK˶}Ԧ=x*77W[[!4sLH,,,y&~X]]M"""LkccU[[[7VTTٳ'Oo߮/Mݾ}׶Lv})TKK+66 21c/~z!񎎎d:%%'OZZZ.Z9<$Έ^>}>}y FFFϋ3fLHHѣG_|yfH!3`0x<^#+++q(хwk%fK5TVUUeX$WvbD,|xժUdW@G'5rpp@}/^ܿ?ͶwrrRQQiz u֢" &u!%\.?$p8CEEE"Fɉb@RVVs(1j|M\$tHDlJrZ @k 2B3fLll5kȭw{5g͚eeeuEzlWSSsttܹs<߅ $?,/>~z}||.9СC[RR $g+hvxv7o|MMMԳĦ4}ÇSRR>}d``n:?~G8 ~oʔ)nݒ%!EE={yxxڵk…料?٥_72dɮKPRRRRRՕe0ϗ +K>LKK+---R%R/lݎ޽pمv555jMII BV2=*--x~ꊊz>OlJ(D"M5`mm|}}= 3gܹsMMM[OM딓pFGGGGGKtuum=Anjv? y&۰a X,uuuVp8^^^3g\b?L6nhcc~ݺu:uԩSb/<==/^loo:qn4dX?a„&gff6 !rsrrg555 $khhAdܺ TTT(:/PUUN JAd<]䬭}}}#) 0xӧOCtW\5kÇ[u_CMOOG奤Kvvvw!<|ÇQQQfW\D<_Uƍwލ\\\;;ڻ#Gd ,>ϟ?~'Og!CfdddddܼyGF2IYE6lسgƌs]mazΝ;}}}CCC;&yیˊ+7m{bX,^|IV4bX^z2|t2VZZW}Are߈P2P&"ˍTRRj  :t(s\@q/᪪J"%b7=\[[OnH6% %%%Fє|"TPPxt:]QQQYYYNNl6BQQQRt:`PT fJ166quuUSS?zhxx8}tWWW ϟ?&MڰaߦÇ/_|raaQf̘Wl 7771g;;۷oS)S|uP( M6]vӧOg0ӦM4iR+Mӭ[=z4DRZZZYYY!8MpDYYY!TYYD ӧOE555!tDںӧ8H|[nVҕ?sŊNAAA<履~rqqr޽{%߹sdΟ?C,NZ`AǍ!.\x>}8p@rLhh޽{Me˖{۷eok.K1_׮];q޹sQ\nll;݋CG^zlvzzsٳiӦ8Y,5ݽ{wVVV={l=~DYHQVVXlСC'Ocǎ6i8gΜЍ7µ+ٝ={6...$$dWF4T^^.Yifɕiii+^FX,f+++t2)+))5/ŧK.@GDzV81Ldq h 0 qam0%LlAF8 8 Ldl>8 LdY," +((0LF=\.WjX,VQQ4h񔖖۷nz݋/] _^L`Z+++++cj QVVVtttLL̽{LMM.]:{޽{]hԩSէMemm`/]%K,YwՈӧ3ӧI%R0==="񪫫pMSSS( <"X, R[[ "+))!*++\.BDt:]$; Ad@{߫WX҃ȳg^fWXAn%]۷===W^6{@Z VWW]py… sssСCΟ?l2"""555</B!=5fd&LV⑲Ozi6=m4_, 7$<,`ٰp!`1_F 7l>G/ouPt:HLRZKK˥KN<ѻBrrr666666'N{nddDFikkkkkknn}GEEE\\)))T*u˖-sqq߿?Ձf7.%%޽{&&&_tɓgϞMvitZK.]tiAACCC?nhh7k֬{Gmvd߸!TXXH$R444B8RSSD񕕕 EQQ`H%;"99"7.66v۶m䖡AdRyfڴisٽ{w4-- !dooOqttϦ $СCD\ [lϏAQ(GGǐɶǝKvSLi&ƿy!?+b[Н;w&NeoB2IYE?~lٰIpƦ ĈN VXfsss .* |JPXRR|>xP(l4sbQTUUU`2T*b|޼ydΝ;nj3o<*ZSS3jԨ/^]]cǎ͙3gÆ 9ӵB˗666M 숌qLtDƃTTTdhH"O'St:N7숌#F8sG=zdggGn%Ç733 ry9~j3ϟ?Zr%^su=:w\@@2<<!ddd$^f͚5k$&&8֭[2og@ Ettthhܹsoڿ>}𚐐IE033;peˬǎfmB~~W{)sBBÇ,Y~z77skM6/<<|͚5Ρtɒ%3g<~8LvE|5eeeeee;ddagggjjz-#uuu%%%xFoS~Z>xob,2.K=52>Ew$"뉛.!}ۂ _ʲC]EEE<3w.͙3gΜ9-A bfffffOddd<{~٥Kp:gЧO_x7o 2gȐ!oMjjc֬YSSScnnٓ*uaÆ5k믻w6226mچ LMMmիW󫫫999)q]q"<8km)CH8gq"",1q}hOHw7/zѷ~B"΋"U/uuuĉoF{ʲ;-~sHХѣGoPr\\ܱcMMMLMM ڸo޼IJJz͛7o߿߾8N޽/_"p8uVT*uŊ? 222z#F]]Juww3gok.ssy޽7'Z_nz^}}}NN\q\#2BHSSS#dGRUUU-bvDff 7...11BPqqÇɮKCURRk0 ==Xdɒcǎb;;;wHĉ=iUV۷xVmNr,{ot#!Yr 6mڵkWbƍ+7lذ{nbSɦE[JMM566޵kתUx 2d̘1߹dH...;v [0i+33ѣG6;ǯ^z߾}W^urr""Ț5k\viNqoZ+"Zİo{=ɞ]&~Ͻ>wledK7/o7EI\ _v7$q | =3DZIO7 {r=<{!BT*U}m|_R IDATO,~:))իW_~}mm-Jׯa <zTx^UUU%%%X\RRSXXHe8Ғ|H|<˵kB'O>rHɮ._~ٸqcUUՎ;.\V\Ę5 !4mڴ+Wg#F߿?ɟJၿ6ZYYIڋ544bbbݻiOOOSSS''={X8=^RR~ի ٳʕ+[p.ÅvH RWWwvv>}4Ɲ;woRd2={s=BuVppSRRBnnn޲HHH8}322;rѣGv2ڵ`DFF׭cǎS$%%h=z3!4iҤׯ;w.66fwԻwe˖۷oٲeu4ۯZFFF{ٶm~̙Yf1LWWEYXX| 0dȐSNAQBpŶB]H$ )dNVxGF2ٳDgJ"smD'KPU""qDM25Dj ai6aLM2I&0ɨ&ӓ J+I*_HŤb这Z}R^dxk_mXm2Z-,E_N6%nMA_R7P(zիqzuuurrrRRׯ߾}VRR%ݻO>zzzАfUUUc?ᨫV/611155mzAHT\\'}AIIIvvMSjjj\.WMMq\x %ڿW^paΜ97nܸyٳnݪOv]t!rrr͛:u]V\믿;w&755=~8rQT*ߡrijG#2ܠfюJJJ ,tz)Jmm->-uҾ}t ϟ'%(cǎMLL477'F^^a_ZONNѣGH,^IIԩSnnn-',,,<<<77wĈ...;qĪUgS iGs:,XMv-H&$zd-I%{/o6G CH4 U$È-a\k4\ېT#`ÿ_YnhO@ $cX{$o0+U?(MIo ogggK(_| ׽{=ztLOOe?!dffffffdddfffeeeff#8N޽+otBaLxI|h_)BOnnnbߟxUVq\yǏ?>sL+!!aɆM իעE֮]ڳgɓ'Sξ}6~[CC×/_0`˖-}h.\h4ZhhhaaիٳzjKKAYYYTWWrUUN:ujxxq\E?mǧ@Gd&iaaq޽D۷oXXXPPٵtrYYYw޽qمM:̙3ӫ Z|}ݽ{;n߾lٲKӧOwppٳ駰UVxׯ8tʕ8H!.NNNB$tm I+6ta{Ԓ&l;Ն \4n6*UkΧaQM˥4(&,@nKQ/%[o#RMterrr8[ݻw/م. npttttttF->??wJOO|%'e2:::iiiq\.pJ#<ZIUUUAAǏ rrrsrr]T*UGGGOOgϞ8y;ZwVUU5)=z(''GrZ:2"ՕZ[[PTI&_~Ĉd@W`0:4tйs憄4-NӧOobdD NSSS ]a٥L&355bX(pcH$!999jD_∉t3"UL0aiiiMvfnnndҙݻwFVd,[[[yy3fUûw8ΗwoAt:}ɓ'O矯_~/\K3KYtcǢ'L5w;vxQ>}6oLv9'NXYY]c2dj"ǙÝϞ=c~~>TPP֭MMM5ݺu]uuuO>Waadʖb4->|8ōy<͓rZFx<ظBaL9777!!!777++Kuڗba[N2!!a!!!3g\hѱcFikk7a1k,MMMggg /4Q|^jzޝ;w2ėP,QTeeef ):%;"4M$Adb$J Ad@;2|p ] xG rJHHXlU|;yq1 cc㄄rȭQ,=;;~t钇ǪUN:cƌQF5pȑ'N 2{n[['$$h4+_p!Zgq$DoP"}*++]UUUɌ{߾}qqW 0  P0{Kt:H'7)Ҥ011IHH6lϏVVV޿ĉb 777%%%+?~իWMLL֬Y`SSӧO7=F#2"D555|>qCMM,$TTT8L+**pH:"K_nABQQʪB/;vlBB—N$ .tQI,]]ݕ+W\255ŋ.]:qℶرcԧ|ѢEM\"LMMߟhSgΜsww'@ץnddĘ m-T.**aӴ4"{ZQQ! E*`0l6`0X,2PUUe2 rM,VVVVVV PX^^^^^. AYY~T(Jf֤R:::ƍզRޮ`71[fIIIØ2#tzݔwuu5{͸q_6uTggs8pgƍ ,Xd n۶mӦMfffM411(//g2_WTTTYYNӉHxZB"fAd@x=F+..Adbf*$Dތ?~֭uuun rYYY>}ȭh{_&wM8B{޸qƍSRR""""""N<:iҤ)SI]9suu]reHH֭[I*صkWZZرc/^Lv9)X|ԩӧX,k(+++++w޽ّR]re啔TVV B>_QQQ]]TUU 2JUQQ?L&)//ɱX,axT<|8Now|~}}=BH ~HBD֖uuueeeg}}=u>'Bwl*))!֐ `2L&SIIIYYbX=zHuّCMMMMM jH$7)yѣGYYYs })r\n#wP:::^ruub0<8~xXXخ]~ɓ'/Zh8Yڵk>>>qqqM 366Æ ===PvvBҒ "QSS "5 "+++#}t:]$ qɎDGǏMHH:t(ٵ p­[ݻ;K7Ms1 ə#XYYYnnYH144444ܰaCQQ͛7/_V[[;|pWWW CQQɓ7n=`w=bĈ䰰08)蔔~B(**r8|Nv8>Kt-++ |>_(VVVmuuuEEEMM YXX$ז!f0}n)ɚqP4aKp\Ʊlܑ`=%;R VF(6)$$$DEEeffi4z*ui4-""b'ND ppp2eJPPmۮ_~庹-]g-KAAСCׯ_wtt0}}}DYGG!G?rDYSSBl6Nܗ[]]MD%;"㙣h4ZUUFC "kkk퍑nttt{"#6o|̙K]K'O}uZ'gb*K444+++ccc/_e___###WWiӦ-\p߾}7npvv&XrTWW{yy<|֭[c=]ʑ#GF.DuF-A / `SF=8 nhE I!!`0M!B[=|*))iS.))INN#8zzz}c V^ԯ_~]vݺu Ǐ矃N:o޼1c@/.ho 7tz#Gtrrھ}{Adyyy&I>~jkke*b>}D VSS+..FᛁFDsǟ$;"WUUT*S("kk7]BX3f=ztɒ%pűX,yyB ]TaaY{䉮.n3>)))M4\%B${W6b70<-Iqg}:WmfffL8Kc "S(mm휜P[[ݻwijݺueT455;" qGdDRGdAd@4~y vrEN>cKKKklTы/ONv-m'ڿ4mMƛt…贴=zL0aժUZZZ-RdpBÇRSSs˗/&LS]fѣ'Hpq7e h 4x/M% bgBBBTTTVVPVQQQCCK1e66/˗/^tҔ)Slllf/_|Cmݺu8Gmmmgeee%%%Mġ/q[z& Dz]]]@$BCDE4oG%jkk] fIKK/nle&wqM]iR)M@APQT\u"뺊ذEA׵X׊kE JB/!y?ܯ * xM&SN@?VPP3eeeDf*|c6jeaa1dȐ{D>sLp8xZUU5**xHYYPYYl6>+#FXAd|G["qHQ%cqssknn~Q;d;!=X[[Ĉׯ_~{+,,ɱW111˗/޿{PP%Bh…UUUnnn#FpwwuKMMM3f*)) f T[[{%Knplll\7Q驜\PPP\\"ddd>Sp8ZZZ4[ٳM6mڴ),,LNNn ,Xjmeeo߾]v8qbڴit:}SLquu1֎(++SVVVKҊQZa8fq(^LLNi"ۍ+++DffiPr;TTT`oљm.`bbv般RRRB<$DfĴHYNNFZZL&222 EJJJ#rSStDH455޽{߽{WB!?????ݻwsɥ?:2{quuٳgnnӧOnݚ1۷oJ%ȕ+W\\\IJҮ5*o߾<*}ƍׯ>|N;::zwX```zzzCCÑ#GqLv;wAGԣMMMeeecʙ?.,,$!OŔ544: FQ(;88x{{խ_gǎZt&0a„ JJJ.]taÆN8qĈΟjKpuu:tƍhR򒒒r>Ox<|z:,gE:"#h4D52#2>&DH"ww[n4iҲeNSH$:ӧOsΥKn޼u֒;wXbܹg骪s̙3gNnn}̙`5f6?u/,,LLL|k|}}ƌENP]]W\\_b͌ IDAT\\ZbI 8KjjjJL+++***xT*Ntdy.[QQÇ2!%%%o޼)2 𭦦&$JO}wܹ{˗h4}WeohhQTT;p@<>,B~ t:Ns8OoS.((HNN.((())!2222D:Y$x⦦&___۷o[n˗/L`,]tҥ'NXpabb3gH$øq&LN,N&[ZZӗ,Y"z/艨***Ƿuuux*pMMMpTUUJJJY3X,m16(iii%%%DgA999MMMMVWWa5IhlljllFUWWx&|Ax1^I*Q6GP(!%%ERU FT"Ҳ222 `HKKX>,tӧ޽ѣGm-##vQPPUUUB%%%DY#2媪ryyy+b0<OFFZ cRRR! Pt:=""Gܵٳgݻ7""]ܵt7GxI]7V]]mgg***z--^8$$vܹ/ [ZZ뻹mذ,̌nu햖 Wnnn2sLOOϤ֍:8}Gݻw֭R}qssssssqq}b˖-</+avvvvvv.NcʸrEEEQQ񭄴4BO<߿ҥKV\y˗ϙ3gͳfrrrxb~]rSRRΞ=x˗/^_~#G;vAxx8BrssϟrE͝;UZZ-8|L4{SQQp8nnnVUUΡdll|> 棢N>]ZZcX::::::zzz:*++wS_UUŷXUUUEE1r\ncccMMMMMMCC&b0cG~Bl6D")((~ ?qYdfKK .577FUUU---D<3Narrr4bl6b#@҆nݛ_f!dbb~444|AQQ"D D`2Bt:] KKKx 2F/;7s  ^nnnOw!⢦vq o޽×ugcǎtۋ>Ǫ~~~/_ܽ{Ǐ;::: AɎ,#mHHH;\۷o{zz޻wU,^__Kߊ@ 055uvv>tPG QQQjjjup83x򥃃ҥK7m$r<==cǎM6MܵoGcaKK@ _֓H$@Q(';w6lPZZ?޻w˗DaRRR`„ W_^^Qjj*N&!444p(S8ɝjkkp^8v_XX`0B?obzqqqii)1]YY:K"Zgmq`iii&)'''##bDL&SZZ"Bp̺u熆)mU`Xʪ***G|C!!!ҭ]bEXXXBB§V捅Ebb@ >qɓB/^8qbSSDGGۯ_ eȑ8SGGǼ;nCFFp8|@h4===##aeddݡ@əhY.]sƦ4Mx<^qqq­Ћ뉅eddTTTTTTTUUUTT q(V/b2b|ROFFFFFFAAV'\.4=!MRp8GOƍhѢ{ >&&&{miiT I$pGd>_YYDGdr"kjjp:nllhf#2D! OLLݻkcǎ]buVqݨ޽_:tf233B#F 5J8nݺ{ݻw!D"Fuю_9WDR(5k3; BH wEGGo޼׷Gyq`1pMwww*;L>= ĉ/i4ƍ>|g677s8Hvvv611)7oNOOg0W^?5Bccg͚5QRR:~ypO333") QQQEEE#8 Hzyy-_|T*$!!իW_NHHx]ss3L144}zmiiٻwo<;x9nd^TT_\\\\\L43f2ZZZ***8ꪦ39f55.ԄQe||}|2d kiiHHHxޟdq^]/q4955599911LzԧO333YYYq s>{,..CSNqƧ]hQLLӧOB۶m;|pff&~H[[{…Xzzz~ܹs߿eXwNJJڳg?ӧO+W/^3##>\~{G{%*leeկ_}A;111tuuussvZd>|<`X^^^[nAd,66رcنƍ_|yGegg+**O2eƌT*ѣל9sա7nL4iСgϞ&MoܨQttt: #3gD۷oҥ!}}}GGG'''++/ᨮnĈ|>B?ӧO8pam߾Jv1LP(!>믈|2bC 4h tT`XC 2dBŅ߹sĉK$GqqgϞ>}:ߧOׇoL@x \ bll] |od2vUUU߿sΩS6lؠ9~'p@򵴴$%%=y322H$Ô)S,--͙L)++</%%/^9{lCC"Iѱo߾=7HR~4mUUUSN5ja9r 88ɓ'fff&Lx?~M>~~~ϟ?/))p¬Yz)d-OOIIڳgܦMLMMutt.] q0hР|l6[YY9--S+ raa!B"D:"슊 |ʕ+K&C @ttt~|rʔ)W^300Xzׯ] /GGGDfXrrr!:d2?DVPPrx yyy\UUҍtD 2 𨬬w!?.B~׮];}׍=%@)//5kV~,Y"i:|ػwo qww'z*"н 0Z܅STTu-[$%%?^>I&ihh\u8R q>ٹsZ[[M<9::O۬Ad&ܼpB===[[?ŋSWWw۶mE|r}t^xF---#9(ĉ=<<]Ǎp.]qҥ?#F:~dd2f˖-ϟ?駟N:էO++cǎAg.I ++۷o~ 2B 2BHUU<!biH_XNuG䎟'KT ~z9eʔ7Q.\w!? 6۷"00pذaY]]ݾ}v!##4qDqWԆF]]ӧoٲEܵ|_[nk2jjjYYYyիW_zRQQӧe>}zER;t ## .;Vܵ@7񪫫mKKKUU\./SYY Ï"jkkB&U1^SkllRlƪP(xZAAOS(;:.##hL*d2L&X,|uu:SqqKKKŋ---]רݻ7;w.Æ Yƍ( /3o޼?[;xVVV4 z7ܑʉ-w;UV|q-M\UUENNМ9sOb/m… CBB>z;_Q `"3]__C9 %66600ҥKZ7o$|*"e?P􉗊ӧOwڵdb}-Xo߾]UV\\ܸq㤥###|+MMMǏ߽{wZZߨQ$C6~JbPzvq霜zɩ >}zȑKKKO>WWWw]?sΥߴiɓ'[' 8 ~Ϟ=۷o{zzVWW3 PLLCvvΜ9>d||Ν;Nz)|rnݺRcǎ-\pW\ILLӧOzzg!#,,LޙG!//4mڴ+Wzzz:99m޼GP:e˖\Rb8uTee… ]wG'+V_/uÇf !VTTDW‚y<F355577ݻV'> }O.$KEEE]]]}}=˭ruuu8555#"L&_,''c222t:!$%%Gh|L---߈F9t:N+((tYYY<,Ng0xY&)+++iSxjjjCcǎtۅ8tPxx8|||ɓ'=z֭#G̚5;p͛}||rlv``444"""6m}ҥKlvVVӧmGl":G۷o?~|nnutvvn'NM6wQۓq?D"9:::::ر㯿?9j*EEEqWrvv 9600xNGdPyy9]YY-//_WWG"D:"pwDf2i4Zss3B!:"w#2?k׮3P.xƍ w-?M6]x wE444Ο?|MMM;&Z:CIIWqQȑ#矯BCCÛ7o^~>WPPbnnNt @q=z[Nt?UTT^dxte2t:`tqa|%0oo@WVV"***-ATVV 8N}tbfDD˗/B111AAAyyyDCQQF:Gݻ-Z$zaa!B:>>^xAAAK.N[v6>f̘WYXXyxxtG+v#SNm=:66Wi>RA˗۶m?_]IRRRGzrK <ѣG w-@ZfO<DGGϞ=;99yΜ9ƵFidtDnFrH=?˗?em&ɓk׮mjj ={vWjkklSD⅝]ff^+.Y111!__̻w"222 bccB%%%jjj...gȈ -[8q",,gϞnnnw5114iRϞ=O2eӦMǏOOO{pRY2A]uu'&M$ZDm߾}͹]⌡[ _fMll# AXMMͩS333MvZqw2dH|||+Ž~K2KYY~TUUk"ljjjfffjjjccceey ^n]nn.D]///////+++Tx|CFRE·,NO%4WWWTUU2Nc\.Wd;L&) شi_EA*++SRR𜔔^z!ee򄄄޽{kzJYYdbFQQB!v$\MMMxxgbo{$/--UVV[xRRR$ WzƟi̙SbQF=zkG+v<\QQjiiE䖖[?P Bڵk?RVVJٳgAA'|||?2]̙3W^-B~:Drrr_nj<o׮]k׮urrڷoY$-2qD8zdee뛛?V2qC믿7qi,-- gsuums]v"矄Pee;wBMMMҡFZ|ydddAAeB{ݶm[\\F2RW=|L&D <~8q/dC q޽{MLL͛FZZ!O|%m6p'Zp5!Z>i ""66mBgddo޼ycZZZh4bqiii 6m6??@$y,Rq!]uQTD:Y%vYYYYYٛ7o,peFD444544Ԥ@R+oDKDUzڵ|>k֭=z`0_[HT)ޯɓ{=|044rݺuod2c/l9c_?P B˫wޯ_ a2ړ'Ont ͗%ᗄ@ xӧOg̘/ϲ" O81<<|Ν ,H ,TPP5jT``СCgϞ}-B~xk޼y3))8e ۻw۷oʌ~wiii˗/8\UUU UVׯ㕷lnpp ޼ycddjժ1c[())!vyf|F/c"|pDާ>q>'O<{,%%ESSv<ͫW*++ w^}c%\*AKKWәX,֞={Ə?eGG;wH~n!::Zd|ff槂^i!EROKh4,wH^^dVUUG FMM nllĿ !&lBy啤 2yxxI;d>>>!!!Z0u'$iȑ#G<|p@@ŋǍtRAW^ccz))p ҳgOq=]H0`@HH'MTTT/&Ɇcǎs꒓߾}|l>DFF滖ׯ,X@܅~\999yyy%%%%%%8sLH$*QQQi[j)d2YQQQQQ# WUU &m'&&FDDƢp:תV=LF q߿ƍ_xѷo_1/ݸqcxεkׄ011yӧwARRRBAAAxѣG/cG߸qht ekjjUe˖-[omm}_.::ѣGK_'$i͚5Ǐ 3K.DGGڵkQQQlHFX`2z :<.Zh; :.2b}7?".Ç###F/yiӦ=zݻ gџV]]mgg***zmuu`ذaĜ7nt<p?Cxqqqqqq׮]퐄o޼ܑ[իWoٲOƎ;֭[Æ CUUU7''!T\\;Shg/ !LKKKKK;w\Ǜ(xܹsѯ_f0_q}1_~/^>|-,,MMM+:'/A__ZxRuuuUUR>O&I$el6} "уx2Z'D揄vH$DR2>:tʕ+$ܹsׯ=Zܵm۶r'N߿j'O3f 夦={̙3n𨯯7m۶`77aÆIf͛7;99 8P܅t6gddHrX?r劵XF%x䤤={!LMM𭙙ٿi@BBB<== ]x999999999YYY999ķ_t:/kiis8ܩ !@oîQ7MQQQII ѣ)/=f̘#G:;;KII 2dРAӧOt=:...((J;ҥK"#_O2LJBM:!t)!2eWwΜ9wޝ;wnEEFz]]]\sΥK"Ο?/WH_ǎ$%%5zW޽[d-->|xԩ?&}srrѣGNNΩSBrEOO@֣GK.ڵU}+oر&&&Uo D3f0330,V]]3O>rEohh.:AYg9992I$ZfY^^^$GR[z#HHKK7z 8/j6L&Sd^NJ>6Ϝ92iҤӧGGGJr !p/_\o߾]xC3~G >~!ggKZ[[쬬ӧOo۶- Ç\DG?~|Ǐ]n݃vڅ999N/_,Sk~ٳgB~~~/_lF֎`h>88x͚5+^D-ɔíkkk+**:ZZZZ[[PߖCccWeff~j-*((A䖖>3v%%% "X,.kjj*D  V⊝D&u*ߕ@ _bkiWYYӧO]/>q+Wx:HٳSL6mq8߻wO>qtt1bѣRyk}vȑ#]Kgkii1445jTѣG D߿/_466{ݻwIIIqrZZZss3DѣGFFFp=lmmoݺ!T IDAT5tPq>JJJ222233qϭB*MM=zL---h.>_RR38KKK2t:"hkk|Գg:͋/d2ϗ---=== ˗[%TWW0̙pBFgU~*/y6#^^^3WZ[[y]O*<嚛Μ9ȑ#t:w@-X`߾}_6%K>nE Y!#fm|>}!(*VJJرc]B555njjjt|4lٲrJb[DDH#777/_"bbbװ")~r:GݻxuuB5iPP-?1c\zCKK볇Wx㭏~j_ze1b?{&''yǗq.+++l$ׅ%%u _tG?Z555L&ƍÇ捅ERRƆ 688իcƌb2OF=zťHOOb1BIIi޼yvvvzzzǏ0`ΝCsۓ 2! ݻ'B +ZW9sݻrrrG;vKB޽{Ν{!͞0a”)SߑJJJnܸV]]?bĈ#Gw*21 Fvڸqcnn~egnnrCCCG%r@ssszzzrrrjjjjjjJJJjj*P@FF'zcx0aBjjj@kB222B4MWW@WWHkkks8qkpKJOO/(( p8\2cs$%%wI$%sm۶v ,xںu+N'C9s5119sfG@Kv͛LiiiN6O>|>ƍO033=zED6[ﺝ=~TEEE ,xrqq1bQJJ ^:00044g.;D;vXdddvvq>{?+Wlْjbb2a„ŋTWґhA䘘&Ӻ]n}p=婪5yyyjjjw11 Ę 铺:s%Dﰘ^^7o888 Z9?\ܞB0Rb VwS~mоagddl޼X899ݼySTTtbKEEeǏ-2spphii*2\vvv7իW=Z[[{ ?ݝBHHH{pB>>>5,X@DDo߾ZdIWWWhh( 55uڴi?6mG}} =t萎o|rܹcǎΛ7/<<||'ߛA¼yܚ&`jzzz~~~=bw-Ho<<<+VXbEuuupp{7c ) kAh𰰰0޾d]7,qƍ7l0tuu߿888{A+;#~^+%%%OM6XsEgϞe~;700պxm5ýPЁ#; &u ͛7{8u԰"p}| fNV RT##~i…_|277š'!^ªyC5<{xȑx,_uu5DiɃ k~/Sv_PPw7ba!H~ ¼6:%%%111..Ν;mmm$tڴi#ܽ{J%%]*%%hb>k̈́`|, 7x<v؄Fߜ9gO͘?cuuu666o޼2qekkuVYf egg`0 6R>400;wbVVQ ^/]Oaao.//dOC|%sͳgX7bfwR  d;jhhc07o8AY!k X{y{h4c5*ŰJQQQD"ĉ޽kllLJJڿ?''MLLDEE?}4eOvEEEnpTee%CTT3-C°= "sqq1Ad@x< X-J#&&&...쮥?Ӂ^ZAoذaÆ T*5>>ׯ^577066u?-***޿fΜyرs1*455555_VVs?~\EEnټc14Ç>}{uڏfaaag+Wܻw`0DEE444\]]544EDD])@xk<`eggggggeeBUUU2uvvwvvvtt `;;;yxx233׬Y"((###3viI=k,,,\]]n < .TVVfo1Õ۷o.]֭[0 s>psswtt,[lҥ6SPP055=}9s&kErqq{zz222"##޿ޮ0o`ԩzzz$5N8t:=???###======99>;B|Uii)߬Y,X`gg'//;?|Q٥겷"##nUU ~{Rh/_|b0 ***pdh4AAA~@ #33mkkfFGa2\J233i4,l?fee.0uza</""𫰰0~W{s8 l(V իW^}H$.Ze֬Y 2,?VRRW\پ}ɓ'==='f;Bxӧ JMqWZ>G 2 ##'++Ϛ5MWW˗݋\hΝ;{j=_+ʪ*X!!!GGGKK竪 ZZZN<ZQQ DNMMuVtttII̙3]]]hEKKٳX}K1l`ӧxbwuuƆ}YLLlܹϷE gggNNǏ9sիW߹s`S˗wvv_O:;;wuuNAAA 2wss{ICC //k׮_O BCCeee0޾y?AHHh,ɷBAdAݸqÇ]KvbؑTWW1===##@$uuudUU0fddqVVVGG'']IMM#""mllGhii;ƢI`[ZZyإܼa{*z쬨(_mmm࿀"HhI!88xʕAAAqRRRQQQ5550hnnCSC|||WW&ݠÊ*(^C*3D00hAAAI0  z0|ѡfkk;9sN Z~[PϦM~7;;ׯ_=:~ܹ#G8;;߹sWZ^^L$`̘1cɒ%/f&4ݻw XWկ^.=Z|yWW׎;UUUpKRX,V\\Ν;̋D#GBCC###^LNN·.]PSSC$Ϝ9ܼs[nQ( dk? ><<|d>\f kAFHtvvvvv7+++/^t...yyyeeeeeeeeeEE&lZPPPPP皚i֮]zmmm111/^3g/۲eܖ-[ƺl߾}'OdGyyyT*̙3(}[Ijs""""""XXXX݈?AߟٳAAAFY{{{@@ŋ+++P:dB \zuŊC;/_6|p… ?xڵk999SLp85Uba[ nnn~~~,+$$Ě*fcB Q[[sUFhఱ Z\AAAAA᧟~tuu/_Iyxxddd444Onjj:uT AoQRRfeeedddeeh4---== L6M__] L|JJJիWϞ={رK._|ٓMw"s`9PD>z(00b…w'AAA77777ϟ߹sFBBu͚5zzz.pꦧ~|||FNN^٫#2</ 𴶶S]#2< 2 Ӭ_O49sfttY] 2񩫫 J䆆斖F2J0\e~~~Ybn]qqq~~~/UUUkkksss4+ +V\pavIbuufrr;lmmg\dd899潾F&ʘ )))pHa.LﯠBAAM;p@]]ƍmۦ׏ipss'F֭[lq111oooMMMvf___???Lʽ`k{z{{WǛt:ǎkoo 2e*dAdeeuuutttsGVV̙3Gwo$!!xKZXX|mSdX =zѣ IIUVm۶MVVu ǻ޹s3f矗-[Ӌb]###A&/{D2ݡ B0gmkkSPPD+1 twwݣGۤAwޏ?͘1pbbbbbb.[}}ԩS߿Y>>>333333SSSv~;w [`8 ٹh"..UUӧOàUͥ1?0^)$UJJJJJ ~Xuuun AAA&_˗/?}8/ -,,t^0ׯ?x ''jժu <.ܬȑ#aaa4 ~999qqq455z} <"s./YYYd97E:0ߩ{u{zz򤥥 =<}fQ&7npkײAAAFGaaSQKKӋUUU999̮< s߿ˡ۱cǕ+WXlڴ~cLMMMMM}هI5###wKJJJJJ¢gΜ9tDСCN)))...f###xְ:77yp].]vO|PB}bɕ~.D"vD7 Z[[1 kGd DhAdx  kb ===+V`w!/^`0~eeewttt萿HHH|||qq1|,2AtvvLJ5;y=y$&&dW HqTTT^^ڵkпkMM sggWww7իW?ӷ=tzNNݻw=<zhPPлw QzAF&''IBBmaa{'O42,X1cÇcbb~߿_d '>>>---h ˺fϞ=_ph߾}py۶mͬ6l`0^*...))uA]]ׯ_OZu1 --}A@JJ L)))iX0 AMGG˗'d❝JJJ˖-pkAdٳgS(d^^޽*)))?5558NWW?L(nذ˗/rrrc,333ggg .,,<!?ʚr9 @ $I\\s D".wN`bbĮAA]$i۶m d',,ؘ[7X]]]FF^bbb e!0 ӧO̕$ ˄T^^7o888G{ Z9shxsS(fNy``%}ϟ:u{yFF͛;99ݼyW4|ɓ'RRRn?~<888''nxb..{[NNNn͚5x{{3gDFs=z(Fo6lp??SNAFSBBӧGe#Ngggx)))?P( FAAAGGVQQT&<iӦ>|XPPpy;;]v͝;w C`D"HdmKgg'~$]YYYRR^YYYVV@  KIIH$2Hæ\  |xjLL̒%K](PRRJMM}]gϞnw=۷o444F<~DDD``5k //|߼ysXwġPTTLKK{Ν;ᚗ/_hV>> w޽{zj ?GQ9C7Í>[݅ YjUaa!Nܺuk@@ٳgMMM߼yckkCtvv&'''$$$%%UVV M6@WZZZPP@ ;w:th<û8D" zIKK lFo,5''<<dp=-`0^xȺp6~ӳ|rpm%%Ǐ/Yd)f㸸䪪*,;}iӦYXXKII]IIi"7'7r-~Tjkk+ hf:"f p{>^`ue & 4lݷ43^ w*77-lS'###/////222yod,TUU⒒fD" ^F :vXWW׾}<<<]W1 AA .^wҥ~ߑ׋FDDX[[9rFSRR |/rɓ' Lbkk+ cg.\XQQ!%%5+W(((|Ν;oLL xW\yMkkKHHs  ] Nfgg);; ,, jjj:X~ɨ=uԑ#GBCC^x1o޼o-Lvuuu555 ۷45+zIDDdd3"  2.]k.###{{G>СCzv ;AAAGGӧOF Z}!*,,frssy{455-Zs^C0J#66VHHryyyp'OVTTC "޺u+::DEE|̙C9΃>G "x=n]/dܹa&--=o޼G+BP333i4Lϑ&)f"a~Ĭ  QwwwEEL°)D&a3QNNN"H"H$4HAN˫˫****+++**{ C"D~|||.]D"~ץKN&&&s./]رc}7f0!00pŊ?^lJ,**RRRJNN6mݻׯ設wpp_DFFؔKKKϙ3$)66ٳg߿#BGG' ,,`̟??88xҥqDF _bbYVV&7 wZ&ƍŋAtwwdJ䌌 xIXXXII50/-ZTVVs?|{{{CCßWwAzYfMrrrff&4mGG***YC D굒H$VsAAAuƍ72 ӣM"Μ93O cdӦM< _QQ!:gϞ/LMMw޽gϞ!>bbb޽{EI$N! H Feeeqq1LdMVVVIeqqq111111 111qqqqqq48kkk=2\P(d^C )sf\VVV^^^NNRh*++O8qM}}W~]L&GDD0IKK̮]v[++ZQQFaaa)s''N ,XP__dyyyccpwޅQ̙|ƍgϞ;;;wZjȡؘH$xbnz;v9^ze˖}M2KIIIIIi̕]j2e 36{n#Iddum۶qFcccUUAgEL7X;Wqss?ܑ`Þʬ_ ٱ ^^^BBB+sX=rAA~0(***++h?Ӟ={8/LȠlmm:ûw֬YLrʍ7p8ƍQxb򪩩7o^jjǏ׭[wqIIɁݻwޥYZZٳlOA0Y1PEEEuuueeemmmaa!Bf=C0 OG@@@HH|@jhhhjjjjjjnnf]hlllllֶ3H 'bbbʰ5lq!E"]e˖m۶:uje555_zźFNNkĚ8886lss8777asrrvttx@gg'!, РhpoT*u4@AdAaoo OOOv2;w^t) `߾}A჋ʕ+O>ZFe]5ݻEEE ʲvMVWWJOnnn~ܹ333]\\ϟk׮└ A'H˗/1[RԾ1庺ڦFiNcccĔWm  #Ad5 F,++۲eΝ;̼̙æ2뵗}}pFDD6FXXX݈~'³6jPh4ZxxxPPPXXc㚺 2Y qP"ZպwjYwCkX'ZZG., H lCBqmE#soN$7H$˖-:u*"nΝ;7d(Hbq\\\.'O.\P(TjvqΝ;Cn=pl...&-...,,h4(-95K绺x<&r9l6b &p\.x!.K]\\ <F1:j\{x_NgZ+** BnL&ѨjM&`fYӕfFc6u:d(t|I(HWE"BMk9h\/^?Zj̙tzCl2//\+J/_\S{XkrJ| VoyC`pqqclfX 2X, b6D]ÇbgY?m۶͚5)d"===::{xFG(v֭[nVNTM߲ejE&nE <޽{mڴymsrr ТE#G8qbΝ?CHHKwׁF9pU^ix`x{{?f4q.|hrqqqFF(pj +Wq)--ZZ_v5&RզԴ!9tҥKx<ΝX_ҥK7osNYYYl2*~Ff]z5.....{|͈#5xr>#L&Ӿ}&O\ +]vÇ1\L&-ENPGufsYYYYYdR(Uog bK*jqjcXnnnnnnL&Y5a;cHPttt^V^hѢ#GٳcǎB#{d2Je۫}D"rE䒒^(>y/㰇V%*" hh4 f3,...t:bPЈ׏`9sfܸqs̝;w׮]1cxz~С"|JojNiiiJJJrrrJJ?sݻwl6آEO Z>ѠAZh}CPˇ FP~‰';ڷo:u;l6SV+;ėTl6\\\r<rB)| /La|@ĸV`Wy5 _W{=dׯl>rΝ;۶m۳g~ٳ { +ٳgϞuGnj NRp?oժ̙3G쮁fM0ᆪVuذa7o&|eZo޼oܸa2BCC{lٲ޽{;L_byD=/!\W\ψK/;h8B(\`|>J:N3hz\͛'L0{ș3g_gN|}}yO8>xΝjNh1c|Dr~ԩS;v`2]vׯ_~ڶmf<`|sΝ;?DFFΚ5kС͚5sv@t;vLKKh!!!ҥ ~(77̙3gϞMHHh4{ꫯ)S8/{I߾} ^4 l6]vڵ#X,BӧOjF re2B=z+vڵiӦZ>Ϝ9s֭[Y֭[׮]kJǷ~gϞ7j?t8뉼rYYC^977hVZZZVVVk5E9“X,@'uhp~qn՘pqzf(8S0Q~[51. B&U51,h4ZaT 4kl޼yEt:ǎB.\HHHضmے%KD"Q޽۶m۹s:ZS&FRݸqIII׮]hٲe7oܳgO777gwԥ-[R(9sYb%$$$$$( @зo?&xmM4wޙ>}zϞ=Le'*婩]TJPJeǎ6DvBxxxTDvqqquu'|>^(++ãL``Xx=2Bd2ݢsp+"Cи 4hٗ/_g2L\rl6< N!_t)$$itzXXXXXyeeeJR(ɏ=zɓ'333v;NdAAA~~~vڵkVT3eŇ_{uO?t:tx/Fbl޼y"}ip|%6(+++--8L.LDz}^^.LDkڧP(@b\]]|>p8.bpb|3W_ &RjNZ0.9<50JsS "Ht:N H |mȈӌT*f W"==uFk֭j_~7o޴Z۷3fLttt۶mcǎM>ٳ_}UTTSz"˿.JbT(zzzB0==hFy<^aa!zDFq8DƧpEd&i+++G r6 gJ[oAdҥK:/hRy…Pgwrqq "ktS7̓F1L__-Z [lI\ٺu֭[{oȑayW^±cǔJEݑE IDAT`ĶxoNg0L&V-//7LFt.((jF\՚L&Pq ǡ2o~FW%4i+** s>hxƇP(jϧR0>y~^_ f̘1vX// BiӦM6mϟξu۷oݺO?z.ۡCN:l2<!ꊃv\l66D4:toAdOOٳgoܸ?=%@OxNR_$&&4(<`s8?AhhhXX\. Ɨxmܸ1**M6h4^ч'`oz=.HRpf֦d#9-\=݈ZD G>)g"EqB!Dl#W'DH=M"R`#GBB:!?Q-\Tm̷j88001gGz.N@P TTTe˖ZzvIɓ%%%&&歷ڿ?B1ׯ=z4Qf)ʴǏ<~8/sbx 5t;# |"h8^23HÙ6"nD h=yd27r-QfD:<#i9gAC?N!4zYCOЈT8=S#R*a /DrZUJq)‹]"Rޗ,Ab4wƗ-im-ħH|QTK|?*g?`0ڶm۶m[bf!(Ϝ93LT*eOIR8=^CFT*lRRJeVVV~~>FDݺu#2/ZCbI$DQmX5VN+KTDvqq&h***rv_O ̙3g˖-3f̀*MX~~~zzznnn^^^NNN^^J!A!</((H p\G1K "z=9߬P(ȡgaT-Jq:Y*i;w.Xop$ԧ-[w۶m J۷oͿ{fX>OO}mTj@@@@@@~f933׉駟psɘL&cIڼysDDDTT;`0pfqq="G>'9GDq=BA~"`ar04R˙aı(wr2!F8qbyyyddd^^Gzz:xբoܸw~0`~F\~mL&999!!!UAdOOOZZZǔ"4 DNNN ݡ"X,F3L"P͍( Ad@SЯ_?'ON0};v޽{ ?~1Q$2JŸD˖-pÉݶX,999* U*͛7srr+>>>4CCCL:`0L4رc{:uZ|yݯ]ֵkWN2d۱cp+W_7o|&L&Yf͚5sXh$// Zh F-==>z;@p7H$rv_wj*#Mu9DV!KTCjUhֿ{f4F=U6٪`a_D"!oBN;dR|&a NۣG"JR*W^=rBr"H"bHWD7f077r j5\/ Wh׮N}?* 1l6T)+Dfb>>>Ul6>|x׮]իv޽޽{k4HP(gbh@hhǏ?hР[ƾҧ=qW&!JeMAd|^Ad܆k4+"rqq1HTDv"3 \"{6 x>77~;vBym6{l5:ݻ"2nnnaaaaaar<,,_ 4t:_'O=~8%%ʕ+YYYۻymڴi۶m6mZheff2$???!!e0Puֽ{78qw?===BgΜٰa˜&B0""aJɤV=zqqqٸ9L Qؾ}{PPСC+DӉ4@Sk!Ck+dHuӉRyTmj![-r5gMbqknO\bX,wa}EE[nN:A***JHK!}||peHH$84ė0L\#<<FH'III'OTTI&WS}=XnժUcƌ9|p֭Cw= P(LKK2b;v?{W !fJeE"͛7B8qQ\\Dx >[fAdR8l2Pۊ :nX hRbbbf̘˜S ,سgƍׯ_쾀HOOǙc?_~~~ڵ8pT* =K~~~i4+++ EjjjFFFff_uA^ҢE6Ok׮_9w=<<"ҥKxŋgdd\z500!;vѣGO<Οbxqtt4"''\>9!!)%&K4&#??믿޾};4Fl6!Z5`0jj,␨B}6KDш\P(} X;"666,,lĈiii+rPjj*?BJ5bqAAzD.))AOkh8*++]vׯQ(v͘1]vڵ{E \.7fKOO;w:uB4k֬k׮ݻwڵkpp\Ueee˖!C| |oݮ]_%$$"Vرc|]' K#\;9))W\\\RCf͚A ,{ ex -ZxF@%%%%8lP(` oHP2l6U  l6s86\.͆sMn/---/////teeeFQzXVV𣥥F9:ɆC*7<<ںM,ڠgLa6KJJƔ EbbZ&fX5ŔqF4f;!!aҥQV\p2##c4'NLL4ho߼y: ___6nݺ52LRUۘ"X,S\\ *"|`Z 2"`cz=4͸"2D]\\"2ҥK!4cƌm۶]v @:8||rooݻXSN͛7o,ŶJ {Jx#GFnOn8ÇG}Сc:'fc999G%c]ryu|\B7&XVRArƍ#G # ((( 00000/={,\p82-+**Vk4rѨj CEEE{xl60 ot:ŷ4 #xT*㋂x'B p{r,VjxmYYYee%Bh4f7eXz=Z:l6Vic|NhUf\.uuup8|>;T&G/^ɔH$$<<FjL9///)))//ORP(),H ?~|Сn֬CN]ZZj۶m~ZJLLѣީS?J IOO'd*"D"`08 nt8 BTDvuu5 Eii)2t:Nf܆\ 2鈉***؆}sΝ9sfHHwީSN:ZV=z={亿RT*۷/Bl6߾}ʕ+W^Oz@ ׯ߀~\6mZ@@7j:m f7ݻRҥK#GD/֭;zh=Np8**>???###==]Pdff>|0>>^Vv%BdP0 ԕbL>F`D=[DE^Ng4 V58z[QQa0*++qˢ/mnF4sM͘Lij"Y{Us46J_q0DzqEj6MԨ&W~B"DDDTh4V)GBՊ[X,t2߫TTT'|ם:u}vX,CMOOܲe_i{ʕGEE8qW^u ߍ7m)BPZMAdV?i4CEdPj"2Bd+"AdB!m w}wW\q5dSL?}y .N QQQM}P{L& kZܹsӧOOx!v}qqqJr̙+V&LG0ooooo]:SF㳳&oD%$jb۶mSLi %| hBbxH2BLTgLpUD)@f~&8[EBl6B f񩶚h41eBGٳXb>>>b V믿^|f۳gϤIjG}t%{-XU uS˝40h\]]O:5iҤ9rdذau󐐐/w}}}sssm8\\\ד=> gm"*" 2>rDFm nhD`AP[[>~x# "]ߟ;wnddĕW\1۷7n[o'7Fرcǎ-[VZZz3g߿ʕbx#Fӧϫ87k,.{…޽{4wÇ۷r՛6m?Zjcg. ˆ,*JP8/7   |=(r ?Ο?¨T*<9F#N&rrrbW5ŔC&ϝ;p´ٳg'2Y~۷G<Yh4x,N3z`0Rz1LF=-o"`^5b bl6zd2q|}رc_D"9rѣ۶m['OT*MvիWϟ? O?ݽ{~;x`˼y-Z޽:T[l6+('%%+-- p@فL&3Ͷm۶{}h l6͖H$FjLYV?zHVX,N f:th\\\.FOJѣT*y&T}MX,⧴ZNӒb=1\yB!9}||0R(%K/WVV* LYT~x<>@ +======m:A&MjΝ[' tEEE^^^!DRա-L&/,,Dyzz޽{ iiix3|GIr8ݎt|Df2D` RJBEd@zꤤ:8/EPVZկ_/i:,ӧ>|IyAAPڴiӦM%K/qqq[nd#G8qbXXrǎk׮toQ=ubݫW޾}ѣ b 0@*:oNd2 (3E億TWA倀:_>(:bj,Hf̘Q^^nَ;Q_B6%Mp'hB!m޼C5v ł=L& tՔNS(XI-dOOOHE^"δ6(͛i4.7s`PFF"dB"Y,Ad.kB6 !L&Q8|&}ǏoDAdP߾}GݺurnsvG^֭[۶m{rYPݻ{#GN4iԩx<h\""""""6mtŽ{6L*N>}VPƞ|X&8p`̘1H-͖H$ne4SSS{<==g̘III'OT*D1&^SMeT'\BYYYvvvVVV"B|>HD"???XUbX3qd)/,,)((xqbbbnnN#Z[@@@@@9ʲe&L@?+*N""<==|o6&&J*((8tЮ]/_3uԨ˷o߾aooǏ4 j nJN!S(G iӦmݺu֬Y/lv-ZhQ!FP(p BP(N_1eD"j߫יZNMMMKKKKKācȜ@ N: >\*?5npE͛Wh$ ]T3gdggdK rO&oΜ9cǎe0Æ / J$72XCbjx ^oX?C\neef+//p8x lP(D!drjEdbdv/gϞ'NXpbV^ݬY{Λ7}i4*++:g2dݻwwv@}ќ9s={}k׮ܹ3n`Z:r҅ .] ݻccclYqs]r׽?]:4}t' zɤV2IIIYYYĶ8L(z;wΝ;svGQ*{o r>}PډBm6ҥKB8OPfaĜr1WPXSLE;_~׻wΚ5˫~z[XXX```^q̔O@fGճp?c!š5k&2.@&fݺuFqԨQnnnoK'$$͛]DRSEdHTPPDv]8x<Q!TVVF F3L&fY,"Hq*" 6`Ǐ7 ԩS7l0i$l6?駟fgg?~Rٝ͞4iҤI>A^޽{+V(((]x1ΰСC+V?>y}BBŠ+֭[Gb7o"Rz^,Ad:!LL+++MULL󽽽ݗjժG^z˖-KeZ<~'~'plٲmۺ'!!9:wYfmڴaHff# `E}?Ø1c귧z5Q.((PTJRReee)ʿx%BbKRL&e2T*˗K.9#/pر'NhΝ;ݻwĈ R)oV]%c5&4M՘FyZ.((IŪ),HR)+|8p ::zҥ/Ct߻wÇ %00M6cǎmٲ%S 0%JRoI18CGYfnp8mڴ䈈( 4R /2dȵk^b'!!!EEEZIHHFX,8s\RR"ȸ /..Fq\ш*++CX,*& 2@P[,ɓ'Ovv_^@ XjռyNr_9M۷gΜ_Ƿl=ѭ[K.O˗/p͛7l۷o֬Y7ntH!t>|B 9rG#2X,;td2)JP~ի*}Cɾ2 /HR8Z{k׮޽{Ϟ=NVV֙3g/^XQQ|aÆdӧw}jEzgpQ\RRR5P(sssq5 ϨFĔ>S'N_x|P9ٔJkׯ߻wbxVZo~ĉZjժ˭_:d2aÆ5:<{~G^O۶mۥK7xk׮P h\ ƯګWÇ߸q%,#222ڷoH$նl6'Op>r!B `0L&2L&SmD`A\]]iӦ}/~u)V'ݻcǎ7n܀J9r<?~|֭|C4_|̙37lذh"z>nܸڃKlĉ΂:b4UWT**'''77799ܹsfqwwd______ \0~O>.4ŋ F^>󘘘:SR>}JݻYLD"H$60UcyyyIII'OTTx*g𨩦oNNn'7LII?~e-Z4ygm6[RRҵk׮_~\:ަMȐ ׵k׮]v7nܸyfbb޽{-_.]tҵkCIp8qqqڵ[v5k^tP( ryyNx-===B8+"s\:N Ad\`00 x{SL!j7"4mΕ+Wz4)S*++?qD! t:}ڴiÆ [tرc 7(5kŋ/^Ж-[Ϝ9_͛74hкu x{{{{{wرCF,P(BRR^mLD" $;bÆ ;v|뭷l6_~Vk- իWWQCw„ v}bw^ol6͖H$U5ͅnFFƵkqK Vu8V,XO4ižϟOHHp“'O$Iddܹs###۷oFBcǎE'%%ݸq?ܴiZx7Fk֬Y` psqqw% BHV?#qB"pc.kXh4p8 h4Ad&IJ hvɓ'G쾼޼yo~TUiy}7ƍ۱cG=$wDR/SN;vlVm6qzx^\v튍]x :s̒%KV^=,[SN n 6T!ɤV2׮]SM6OZIII;UTT\rݻgX.\اOWiii/^R֭{utL&S*>HYY)7fvj ;wٳMx^zZo߾rռյ{ݻwwSRRΟ? PH߿??ܹsMggge"ܬY3fT*=<<BH(V[!Pdt:r6E_K= 2e>}/1ڽ{wxx_~9m4gř޽;|pV7l0gw1i)ju!))iɒ%SLp-͒%K6mڴqE90f͚ :466O @׬Yf͚͞=bܸq8b [8BF-^xL&{m 'T>P($*"Dv"]]] Ū"2>Ҷ8L h:gpݗ2cƌ˗1j7LG4iRddիW_ml; 0zN::u r Gmݺu߾}UT| ?ҥK_ޥKW_=#ɓ\RCRi0pWWWL&6_~ȑ#yK.]zʕ+ cǎ3fܹ̈31psNBBBh˖- Y~~~Օ4 OMPRAϟNw֭[nV*((w}Č9p>hFbŊ;w1;w2B6,**Byxx}y+V׮]i&gիWZj7nlU*Ν;ܹcǺv^l6̙3ÇUL:5++֭[,Yo7n<~x]w4}ZPiiiNNJ弼>>-ZXt);#/G^^^^^^=z/v{mvnَ=:ss8>ƍ>.]x:;v셞BP(mWe 2SH;s̀\nDDĪU_5i$\ػϸ&o'@@B'Rl vYAb[wDW@EQ{ X֊\ E!RC= y^g; DDB/L&'$uM+pWt! ON0ҫݩy…gϞ~z)G]pA___]n۷4aɒ%[l _~\__˖->|HBB©S-[fggg`` ---!!`0Mu֓'O޾};..}åK֯_ׯ4iիW~̒Wz{{[ZZ ˃>}p<==U9B IDATCd2EEE $%%edd6lУRȼ'ez{7ңAa;w~gaa򊏏[\tinn/_pB6M"6m;P~~gF)..nfffll|yВ%KrrrT7jkkedd_^ZZG4Th=***/))!Ą]suuzk 24uT ̛7/"""""bܸqݰବ,n:WWk׮8qDdd$J]dɢE\O>}SNaaa!OOAyСCcΜ98{-[|"HΧOƯ3!d2vӧOﰘvƧ .]tRrx!-- !9uoB{R]]>|877wƌ_ITUU5{7oܻw2Bhɒ%/^PTTVDEEn݊/70`5552,*****͕Mޣ]mƔM8+>:>>͛7o޼yEVV1c֭[7|p^n0%%ݻFB͛7Ochh`%K577o޼Ν;)))DBGy}iiٛ7o?lbb?rH+o-su]z599Y__ߟ+Mqq1Q'']v>'Eޝ7痞\rٳg 1b;iƍweeq9Bhg_J[ZZ:]L7SQQx񢃃ҥKwDd1[ZZ6ĝ$B|jۿ)))$`|iob"[Z}u?l޶ټͭfXD_0`4 #-uTpdh˗"#x |y鵵bbb$IVV"H$_T]]FbE_ 2FGG;88Nڴiӎ;Ν!r߯XbժU?k.̞={Ç>y$<<|˖-P&~nͭ XRۂ6?~<7op8NNN{Аa2jjjoQ]]?r޼yѝ TXXP\\#SS69r$,,Ν;߲-ww}޽ȑ#<X pBYY 11|KJJ2 555:xojjjЗ+ؕ8! D4+ |KvfMBHVVy#rEDG7㯸Y`- Žy&...##XYY-_|ᖖ+W.ZVSS3lذ,PQQҙ\.!22Xo9z(OOO߾}[nx#{ܹc[pƍwލ\]]޽VWW[[[ LfZZtP;Oz`b>|pe1xQ˗_x(##Ӊ}t3777mmm{{{---<(}'^_fNTTxKhg1hX .0p@3CxN$hrrrw*pf͚uٜMMMߜ <mX,ít:˭i%999""ޞŽABB_DK@Gd@ߧmaaqDF]ʕ+[]Kkii?ў={yӺo߾Xz5^÷ cbbBCCG<vpa͋9qBh/ ӢY[[{zzjhh\x!)`y߲uuuϟ?} i͛7Bk֬Sil???bKBkBAAA.]=]ȑ#~~~_ @_UYYu fL2BQTTTSSSRRdL݌ᔕw , OTJ"po-|N4԰XjN r |%++^IL1QsssFFFbb">Gt333;;aÆYYYn2d;;ϟ"!Yf iNLOOk;9XVV`jjJ<ݻwCUTT,))pJjj!RMM։3JxAZZ622rԩmfA+G{F-KJJp!,..N"p%7hΓ'$$,_xggӧO+(([ :^^^cǎ^ 8LĔ*++%e:2,o !!!eee8ۡ)STVV_UVVɓ',X`ӦMT*޽{SLAM6M^^ ,HIIog̘qs熇8;;;99 6!KKKmm7n466N0˗l6kLK={{޷ou'N͛7 eT*իW^^^=srrڳgb$޽{'NtB ,Xti)dP\\l===;;Yf X޷l}׮] %<<@w1s}%''IBBBKKkر .D9::޺u+444::hƌVAd= Q(m۶-Yd۶mZZZ_@pe˖-^8$$yl.Xdz S!ww}8pOd@;~8~P(㗰X‚ b۷x8#))K1 555eS"z||dQQ,((K󎗖VPPPTTTRRRPPRPPh9.K{/111ypt2_Kd"tL&dEEE555UUUUUU555.M>}JIIKMMinnP(cq ˗/߿:zh ===aW$|G1Q'/2Bz$M{Аa2xp"(sնّsyr33gϞ}!<<ÇO< 777߲eK7i="&&Wp'XSS×NebO' h{+"L|zo3GGGOOϘmdnvv6ڍ`&'''..^ZZ 2#2LJ655s8/uDƗ577vDtWWא~K^AEEe֭~~~nnnE=vŋU˗I"-[lٲN<ŗR^ȩTj`````BfffxDDD"=U{]ΝqƓ'OڵKصJ[nݾ}͛lݻw߽{$%%}}}7l MAQ[[{˗ EP(_P^^S˟>}*,,y| 0=.GeeeNNN^^^qqq~~~qqqaaaaa!i'`0tuueIII~;SvuuuII omvRRRttt~~>o?%%%t2޿311qpp055577766K@vŋMLLdr}}) A?x`.=o߾ uCCØ}+ϟ?2dH;tШ3g,^49}WU[N|||DDĪU𚈈1MMM222؄ڵk׮]oaaq=>K[YY 艨T*J0`V(/u IDAT%BɼYYY7y~ád"L,x%\OghhhFFF D$HYYYD0DF4T*X,iiiD "9GAd@>}m^xacc#ZO:jժ;w.gwwwa2O?]tiΝ}wr7o޼k׮۷oڴo޼y1jԨQɲeС|:u^0III`0TRRRRRRTTL&,))IOOIII "Ȩ*+++)))++gQTT ] rrrsrrrrrrssrrrjjj B5779NTSSSVVƭqS___PP@4&EEED'Q555MMM MMMMMM---|SII[D8%%\.L&yxxk׮}ȑ#]Qb GG;w8:: /1c۷o<(&&zڵÇ7o^LL̉'BG]xg͛._<**jŊ8Ş3gO0ƍE]r%88*- RS||g̘qɓWVV|X[[{zzjhh\x!)3ӧOG_>|x=a}l?x}xL +Sijjjw8rppwkP(&xE#Qx%oGd0%8Q ;w'Ro ƍwQabbbƌsܹ o/ϰWOgeeonn.ZzOO3g?~|ҥLII9r ;9tP@@ϟX$՝3gNPPk&8\ZZZ\\LDA{i䗮 WRRS0+**B/4_qqǏ?|ӧB}'Offfܹ433SWWÇoߞ6m||={ ƿkkkw\x{zxS^ f v-s=~;~xaW# s" ~޼y߿v%s111iɠk-[l߾}@zsUTT%L^SYYYx qE/6yQSStB~~~ffG!2=h SSSGGG ,labX58%gee߹sZ`񂬬lTp322q877!$**mlljbb2dCCCjggwe{{{a*+//G999ٳGT1t޽'N\t!CC ,]>رc̙KNN铄رc.\rttuVhhhttь3VZV[ׯ>}Jќƌs) b̮](JxxxAAAmm;$..?xqvvݬY#GP(۷oԌ7ȑ#8Չ}t{؄UVV0,TSSD4͛7nIKKkhh 0`jjj***{ڊ<}T ZZZąR ለ STT,--ELy;"6T*_߃ bqFBB#2L}pGd.r{9V iii} ZSScdddkk&Z:he˖ ;OWXX윕uMvF899%''zJ Łnӧ~UczzzpsX,V"̻&??̌8r;aeAӅM$'''&&&'''%%SV&*D3&&&C 6fiiio`````0h ||'%%TUUER]WOf===Ϟ=}v3}gHHXƗNUUUG_~waGcc# ?? //da$IEE aeuuu(5jԑ#G?~!?L0dN,_<===:::((h!777n /^_͙3>|ȑUUU4ƍ...?s]];x񢌌̝;wNrqq^zכzn[w>~xylll7B?t޽{as˟YPPfa[Q( `0܌g*---++ׅ7KJJJKKkjjx(ۚT]Ԥ$%%%%%qYYBHMMg544zrz )))SSS渍8ՑH$]]]cccSSSAÇ?~Ç222>|{e{yyر/_;99eggl%%%"DŽ |||^{n777J^'O޸qyrr˗/^ZxꏚBCC\˗̙#t+ y x¼<'??0==ɓ'"dV+0h=zH|BȊ111 h4,''fY,JevDnnnFIJJȈ#xKK #2B~aΜ9AAAG4~ XbܸqANNN؅~F ʕ+/4iRXXX-U=zwOyk֬پ}5kD諚nݺh"===aҗtxEcc#WdT.///))IOO TL*}QX,V||ׯ_~ի?r\cc!C̚5kȐ!&&&®~DBBbȐ!C !pϟ?$''GDD߿QRRq(**Bhjj陛Ϛ5K___OOOGG޽{:::wEpaӦMK.ݶmŋ,tdި(5?9p'w̙3;vh. @B&q{QPPcdW0`@k}?GYKKVFF )((MY,ŢP(4 G-YYBbbbd2D"566DƳ`577ise~M6?'Ov-]СCC ;yk B"&mݺu+W|xݹ]ŋ]@! D7e^?~$y?}[#R4N@r>~i]]Nvttܺuk|QQQ}}}}}}dٳg?{mbbbRRӧO'L0qDyyy@JJJ޾}anY)))YLLL 2~Ad@??_Ç\>+޽{ǎ®H$ %KC"P]Zߓ 2/]>}wrrrMVRRC++dzlWWWqqWKܺO``իg6cǎEA;dቮtuub*Ǐrqq1_pYRR.EEžqu@+<<Ç222cƌٰaĉ]HJJ1bĈׯgXϞ={Ç9"**:jԨ3g2vϞ=311G5BvQc]]ݭ[-Z$""2l0{{ɓ'[ZZLyMdddddׯBgϞuvvvuR+p(MKK}vii)BHNN?FFF}ovQF]pI􅶶6"wEEBHNNNFFFVVD"UWWD2L&:"KII rcc#Ln'Lwα 2+ijj6ի}&gϞիWO>R|kk눈aWOtttXX޽v!=ׯ_|)Մ˖-KNNnAAA۷obt .@;di Brڸ7wo߾,W鲲t:]NNNVVVXsYYY9s#z;;Uޣfbz>i_mm ]Ӗ> ԟ ( *++{͛7mllϟ?g|ҥKQQQB.PFF{>>>pL O?)Z@'-----=`A744Z.(( RRR8,''GшeYYYޕ-lKN:`0pBu3ӱw@@:gϞvߋ̙3gΜp0///3fxyy >\Aqqq!.[UU$@@ /p޽{e˖5662dĈ#G1b!tJIMM}Ell˗/SRR(u&MdffeeǏ?XZSS4hhׯ_ D~^VQQi#n)MӉJ|~JGR8L&"9T#25kM&ZD 511YzDž]W1b3g>,Zڽ{7ܷo_"755]xqΜ9.`+W}BBBF1v >x⾪]]]aGXXXXXXXϟ_zFVVvذa#F>|#p8z/^|ŋ^R#F9sfPPMW| uuuuuɓ'\.7++۷qqqo޼ٳgOyy9D'r222-C$iС_p1!EEED "WUU!****J'8_ "I#2QWW5jԟg!%%'N̘1~v9ټyƍ䁞//Ǐtgg;wp\ٳXYYyyy1u{{.^d2}||]HPTT4s̤p'''h"[⻖'8YYYooCta7aٻvZx1Cp8eޯDӧOD1S|eFѨBL4iҤId|^&,, 411155533355E@ܜݻ\GhcccddO^H$WWWӧOo޼m۶UUUXYYYYYؘw}rL&ŢP(***UUU |x!xȼ9YJJFBBVBB8 Μ9sbae_~%11QEEE_~ٵk]K6lXVVBz`>|p gddN8q۶mmpU⢮.Z˗T*˗QŎG}-oo#GlٲEصMΞ=d2]MDDN$DX7\YYɻIH$F%ɸ2qG[{Q*[7npss۳g+/Gq'N!dhh`%Kk;577o޼Ν;)))MMM9rNNNׯǽoBo޼9~xLLLnnȑ#Ht+ zjrr FvڅÌ>9|G wRh_;?#JpK'|_uÿ֟)sQH$ɓ'O___+++W {}}}U\\ -QQQ33333˗#^|xڵ@#))iddCp6tt9NLLLIIihh8pҥK̆ދB;{l#Kyfmm-J9rѣ.%%%?kkrsss;֍ 2NAdiiiqqJ<@VV"_*%%d2%$$FGdR \EEE׌3]KW377722 v-_>>>}Z;w0uֹcXEEE%%% *++{͡Cp_ A>ԫ3d̅ -Z1cƠnov޽cǎ̙3˗/2eʅ dee|Tmm1clvLLL4s֭AAAYYY>zF}}S?~\صЗXUo5UUU$%%t:B X7[_>}4a.{ѣG Ϋ155ŗc+V8vupp{ԩS#"" G1ѣG̞!!!| 2:F"?~lgg'HՑ7{n1wupp@UWWw-_Gȼxj}||x;)=DDD9w v{ouٳg?p~#2|ٳgG;Vu_uuuɉ 8 2226000f333###iiia )---IIIO>[[[;vر666 %gggkkkv*))EEEM8?իW|͛W__%Kݿ!se˖!h4څ L'//Sׯlvjj3gΝpΜ9:| ?~;33_t=UUUk׮ sƌsܹ~IԩS?ӿۗZݼy!fooo#޼y333FFF...|97a„ 6XZZ~ݻAAA}j;S^^ᛘ> 11qǎ<믿>}z֭yIss9sccc{` !jժ~- @صIAP( Ng0?nLܬyyy13bZ?Nmi4^)'''###--z@kƍSTTv9$888++~'NRK,Yh=ӧ޽{)www7t]v-Zh7nh;w=ZMM-::zǎ񁁁PF ݻwo@@'O [Μ98{-[|ɓ']Q:w\222jEÇN'Biii/^Mhiiiii}%???;;;++++++;;yyy!QQQmmm}}1cƸ|ա*t+BEEE111ݻdaƍ0lذw3< PVVF1ÔB򉉉x%Fk#2é륤X, r$$$yG[wDo{H(s]vm}}}%lll֮]rJ[[A ^paذa[ndӧO~XD޲e |ÇB$ n?!deeeee%**i&bc֮]p|J}}ܹs---7l ZdΚ5+111<<|ڴi_+V<~ɓ':::ߩo$''rC\"ިq?s@Jv8Vk555|9溺ϟ?l9p:FIKKKIIQTYYYÔ7 8۷ooll)d$.]Byxx߿177G]twqM]OHB 2 QAāuVp[U *YZEU[kkպp׏ $ q~m!0?xnn= D;w.yW޼ysXX2j(PػwӧO7m4۳gl[ΈEDD̛7!|/^xI̙STTpBp%UV^oxդ^z:t(44tҤI:tww*^{WB0ﴶJRO>!ww]|9%%!(p4S"H$zXƉ)))8aӧiӦM6=$$$$$!t˗/޽{͚5` ۷oMڴieHMME999nnnJ!U*r5 nt(..ƫ4ct:Ph4 BH3 ^wptt,,,P(D`0TuW;J}]`_*J"۷oܸqK+((h׮X,pBNw?zQݗjׯׯ_7k f۷oK.]vMׯZ믿R [RV֛F1$$wwŋ}`8uT֭ܭ[Ο?ÇU Zݸq ._}v9o޼oB@PT8 0㤲Z5MnnNh4x#9\18 3f08`0|>ff LjD2k֬Jj ++-[$ѣ֭[ BQ)p/^4oޜ(Hr9uOd2p4Ļ2H.[ @ G( I߼yӴiNj>X9Ǐg̘qmb+)3*WEdh[WZE |VZs˖-H]Ӗ-[>{ WA}}qqqiiimVLT*D2 J)f "###555333%%y{{{{{{xxn1qqqϟ?wܝ;w(Jn݆ gd>ʕ+O82ѣ?ۤI0ˡ;w\RPڵkYYY^z5m?qűwޝ3gΙ3gz=iӦ[i?铕5w3g 80g^)S\v[nUT |>GAd&yС:_~ɒ%7oބyzzvݩ>>>>P(| oE"H$m[ RT*cDĜp\"4/ǫRm۶m۶U*ŋe˖͞=?6lXhhO5uk׮-sT+"#bqAvvvqqZ6T*7lVōBb,t+";88j'"˘1cƌ!ݗ*֦M͛7ϙ3'88w6lؐ0xNe 6Ç6mrpp>|ɓ'͊Ck.<BOիWoݺuΜ9.\  L&d5J!,Rɓ'+VXbEy++ܹsgرZӎ̙u]v-Z}߯T*###@p8NePPPhz}nnnnn^h48Vz}^^^NN^jYYYz^Rz{ҎOXfggg:ph4ϧhN;;;(3vppx4:::[ ~w~ ڵk#FcߪJ7o~Ç_x!ۺu/8p ,,^~ڴi޷o_z^3ڢqqqqgΜ7oނ[,Q*)TϨJX%ojt5Ν;SLwGTP/a^zUXXhj%>V322233 */_޸q`|`|Mn$zvWPP@h4FՒ*h8- E"Q˖-q"\(bHrP|#F/]tԩ7.]{'N [AAAF۷T*r n[ "FJFF<ؙf8l6۝p}  sQQp0 2x`ggǏϚ5}z3gμzq*S&QԣGٳ:uw*eΜ9OHH3gΜ9sB3f`ߟ>Gq9giƵJSbivqqqttb&rT*?BGllCCC o͞={٣GX9ر7oܽ{7Bw3cǎyg̘qůJRj*00N'$$\~qqqz6l؀j;vl̞t{Ȑ!qqq6mrtt6lةS6olL&3nܸrUjp%*U ] -YDVC2222%Ud6aaa/_Ljjm۶^^^/^CI0z:t($$ݩB1{X.m!Çzjbbb&M:wܭ[PA.3={\~=11gر'O ھzS`0L>~:xm|Rd" F!Dd",H|Jںuŋuv R:w`0_^g֬Y *v~aѢE߿ 2BViZgb;bV\7VmZlƷ`f-֌3-fΈqʫ_~.\@h48`#G0`̘1o>sL]"l2!!BLV&ix[gJZbڵkK3L!C-[n:PeXZW5M-Sݻ.[fϞ8xo 6nhe5Y f[th\bETT޽{- mM<955?yɓ'T*]uXaa!N$K]˨.1Ix0BHR!CZ]^j!<[_+k|M{&bfk.ᨱ;bhN}zѳg>z! :T Z{РAǎ9rd_~5υj4f̘}y}z<_~ҥKTTTZN?~|#FXfͲej[vB&o!/iӦM6=̑m ܧJeHHȣGΜ9ӿE=<<<<<ڶmkJJIIIJJJIIG%''WWWL֨Q#T*Hb%I&+VlcǪT;wԭ2BΟ?frssww(C~~ڵk'O )dVa"3P g 8LܗMHH0LZ \Z'3*.04eTQf:|3P(^Ζx:(:t2 gΜ9sd ٳ˗,Yd2ӊcw5O^gB;wnG~7o>~SVre͚5!!!ׯY||<ѣ_|OO>}˗/ 6l޼y8lc{F[p{͞=<oȐ!ݻw߻w'ڵk~\ߐXI'VϨX7\jt5:u͛77i${wnDE˗/ɓ'cpؑ[+b\ IDATQرc+Ww_Eppw}7;ݱFΝ{+A9r$FuV@@@|̌BHIIINN1ԗ/_^z5%%%//(e2݅BatoԨQzŋ={AfΜyWzyyUmjܹsm۶qƨ({2߿_V/[}V\"KL3N-ޙKĚ Bgq}X(^e+%'nl&RD.2N?#pAhD)2^{!(ʚ5k~ᇨ)St! 2}ӧWV~AAAG)!*:df*疧rҺjķƍ[lw}wߕ+)XWϨˊouGϷh4k׮ݶm[ƍ֭[ۻGu\.ǣͯ^JOO㏏=\IP9;; * Pϴif߾}6lصkז-[ӧ/XU>l2OJDVT9RċE;\.X_#Sqy>^_b`0T "F9rdLLL} "#p1c>z^_'̘1m۶Gnժݻm/j 2FGG:t޽\u B\c&" J"5B]nݺxݻĈD˗Ν 9""믿;wX,ww(U~~uLrttĉ^Ut͊W8>$8٬hr9q@P4BHB ^](LĚT4&VӉ8(qLZDLŤ0LDoErQ@<!D H='[9W4B(%%J#[T*NN4VG۷[jٳ9&&!f~z"))i{ׯ>}]mP1rZ^|j !Ad(>tٳg޽{ƍ?üy-[F S.((H&$$[H$4-55!$M&Bd2PT*) %*"V%.FrEd...,,lSLٴi+=z4a„on޼y̙B͛7o޼GU*UZZN'rBOP( ⶢ#D"P(bP(xKNN̙3cbb.\vZm}ȑ WS%-[DGGoٲ}TSK.wGy8[q:~F#rx8BGX&)GBX^ψT4[&H6=7*[H]c5"SrJ 1@N֢&t1F ķįo˲xb # 4BWJ2Wh4ݻ߾}{W;vlXXX =?rWGuŋ/t%44XormՆ>*Ǐ?tPllD"Ylٴiȟy@e|"ۭ[7ޚ9ϟ1cƦMuM8\iѢJ} H$" @ BM&Vuuu^M+" 2nCEd@֮];??#G 2BOVXvZ{w|\ovٳgߛ7o>|;j5klٲm۶qqqM6wl|~@@@FGeBP(޾}䛩8Ѳe˔ 777P(p[ N_͛5jԨ|d2,YhѢ d2{wGEEM:!L&dD$"#$ Q$9HN+҈0Y,HՊiJ,M$M0GqqqrMa=Md,_k&77JAr@ߒeT2Kf$6cޓ*ald67Yrss#" Sgq\\jk嗏?`2;q5eʔ~СC[liӦȑ#CCCuիW7l͛˖-[)@a4oܸqĉ'NdffL&Qh{^/,,dʕ+_5sAd&|򰰰e˖M<~ۻw+9;;;>~>T*5 "[T*B|>"#j5DjxrEd<[fEd ѣqU|+Ν;۷/}ݗj4~3ghѢ}NN:5<<<00p_+Vx ֯_/ ݣݻӧ'$$ڵkԩ%W}F|$|v=<<$ N_lٌ3.\cӦM @=bP`Ghd0aaaݺu~NR{ݻw]v]r?{֭[ FΝ۷oVZARk׮]p… /_tttС̙3^%u@$^,NKK'?](\µE"j ]d2=ڱcGTͰڷopEEGG <~xIҧOH$*1{D "B,KպP(A"h4\h4RTrEd\a788ȑ#;ڰaëW v=TjTDN=ztHÇGGGC?׬YsCۻGv`47l_޿\MIJ*ٛ7ozhJRT( "!!2&)ʸnkTmܸo=p}{={h4%Kػ#:NΝ;O2ec˗/_tiK,D={lժU탃q@=|Ν{=x֭[E۰aCݫ=ʌ2**>>lKdY W YS(?322!LP"2ڱcG``ٳcbbʼ\j޼۷oz=`Rq[,DYYY' 2b9994ɉN :gt/ Ad*ZTT'|ZY.6 2&;vʕ;v "h4ڑ#Gڷo?t7nTr^(JhhСCٳv3gFDDٻk&\$]x& pL933333_|P(J%A WPHK4maaaUR*2^%yyyM4iҤI&ɓ'/_ݲeKff&Fo߾}ڷo߼ysO3TLnnܹsΝw殮QԦMvaҤIzUe,l{Yѐ2_ꊃ׮]t&L@QT'''"P{{{9r̘1'O>PTT-ZXM&L& RZ(L4qEdj!6m4ZX,t"͂P ;wnKG˟~ݩ8:>sI&m߾ƍ7sLAU\\|۷ƶmܹsw&&&f֬YB͛:tzaôZ?c%P(BP(yLbp?===--ME#EP򘇇D"1"J+͘1c׬Ysȑ <pAZpB{wPk׮ʏIPϟJLL{{޽/?E͛7g2U @P*Ϟ={ţG=ӳ]vgn׮]pp1GbŎ@2HOO'srr[f N֭(ر۶y<^UvTo«ŋsPR ++K [斝m2|>t 5yyyDɩ!`0 DFYVD 2 TڳgÇ 2BYf1bDǎݝJaX3g<|wݳgϙ3g:ʴJr߾};wLKK:thlll=)ə7oGc>_ &<~͛=<<J% ngeeݻwq[OhDe +E"^FFZjԨQAAAU2uօIR{+44!T\\+Jvڎ;J>>>~~~i4ϟ?{縁_d-Z 2d͚5ڵH$i]UMQϟ/]h񞞞;wǓw2 %dYmj^޽WZjժ!Clٲt/ nNMMA۷o[ZTTCTDFxi$Hdށ{;=2eʔFٻStӧkÇ;JbŊSN_Ur@1LTZf SDrff&9޻wa8ꊿϗJӦM"6:;;Wd۶mS+WwG0qDdJHHxgϞx͋/BEÆ kѢE˖-+_Tb!~bY eVb6k[qJ(s HrYx/lٲ3gX- Eb1j!u:]AA4jq#;;bTN+"2B)**³"2 аaf̘qرsڻ/5aժU_oܸQjңG=z$''ܹs+VӧϘ1c>3{*>>>z/ڴicǎѣG7?͛3|۷W|[[{Vb<==GWW^eee^zb0D^H-DX7@PT*Utt̙3'@}BP7nܸq!C-ŋϞ={W~x|;bs}'B߿'2ǸX\\ruuq]哕7ny@ P(%̕9cQfrGͶxJFN.h}݀]f|jPl6ᤦ"bqVVQ@qCRI$G... Ų"r~~>TD[999 >~j Ad*z֭[isݺu~۷cbb꫉'s9$kĜ8qŋ~~~&M=zt/2L{]x19w\7o޶mªve};xJR,=$4H$RH$ FcDD;E[jժU+bhLII!(ϟ?;Nb0^^^5j􁧧' JJJJJJJJLLLJJJNNNJJJHHF!$>&Mt҅;ǠN;x Bo߾@TV$\bfT3ц߿="##oܸQ>=TJFR4E% DrjfB8REh4Z~~ȅD0aOn3:hh2337o޼pB777{JO>=55񉉉ɷnJJJ*((;D2(d2wwwwwwDRC"Լ,\.Ӊ1@Truu۶m;lذF1ñoAuÛBɓ'vTpiQf[sNNNi d %@{y.]aaw/t/JSRRBb!aDr4-;;; !xo߾;+"F CAAN/-\TTDеk׏>觟~ڸqRCl;wz!CzV\\qoƍ:uƒ_paر'O^vmN۷o=VXq%{t|c\NIIQTQ*lP&7mTlذb-X%JRin̶gdd III7n8rHff&͖J"C,K$D"FH{z}fffjjjfffZZ\.HKK#6!GGGTݺuk<{XǯAYbEf^xa [UD e[,#0L[ 0p88xzѾ}]vDnڴϭe2Q!aJxDidYEdV>I8lYAQQTDP(ƍ۵kWtt4&e IDATsuiԨQNc%2,"""""BT^pܹs۶m8pz۷.\믿^8rwB ͋/LK.Yj< 6lΝ;]5kt566gϞUr@'''L&lٙQ/_q!2CAZ+%%eWbXܮ];qpݻwSSS322p@ p# f[E5@f0H R/BAܸEq\D"=<<:t HDwįz'N0@PȕW(30Bk%l֎'dgg֣e6D"Qù/ ӦM ߲eP(|`x{{zARB\.dZB bT*Fl\ٹ\YR 2N 2BG}ȅyj |Ԩ'~7.\8pRs>'N/""bӦMNcƌ3fh|sΟ?AХKΝ;wڵQFiWTTѣ[nݼy͛r{۶m1٠mݺuʕ7vZ^߿.]bbblVK.}􉌌uV͟9Pn4:ʸa-[x+|Gn40EEE͚5ڎN[MőPw<˗Fd|]mFW#ԴB*hmRT*3M4wǙc'''{4P'(ӧOn?Cq\_ŪڀݻwpW*O8i^^QH*geei||<=%` ɉbx}f@FƌԦM{nQQQ6l|rϞ=, t7oބ+.lڵZ:}g}f( |n8CHOO/**Shg'1 gggCӹ\.r9H4|>15k׮޿kiAdV+K;L&C rJJ>TOh48Ǐqqq)((0EEENNN:ť`'2 ŬYmAd@Mp8C=tP "#pyQe˖ٻ;GӃ&ŹǏOȇH$jҤI&M|?hҤIOMM};o L&y-[:t(j=gϞ3gNFFƺuf̘QM)L^?lذ7oVxP >|+ BRݝګ\2- U4i2a{w@hr6qChG5cw9fhYX ě@ܳuЇ{!hQ "??M6]n(HF *"t@T(30 l%lƃxK-0Q2"V'yyy "##CAFFB x90.gH$`lggg&r͢l6`Թh rrrzV\PPh$"m'Ņ z捕 P(驩!X}|>d2aV% Hj...88tnnnEEE"2 2˄ 9|ܹ&iT*uɒ%NBP||||||>sEǿ}^z5)) mp82C&{zzJ$L&>RRRryrrrzzzJJJZZZZZވ?:0L۷3pZ&խ`m0k֬3gΌ?>::F/sΕ+W>j:Kjժ-[8qbȑK=KwĉGiVPT"[fq%$$ܹs`]]]͂t:fI6•9% ٖҎlcֵԘJKKr\T;3LP( E"P(lҤ Śr mĿ+tNuqpwwWץ СC7oիf͚=nݺt*Nq q^~HBx<"d2 euZ;ӧL&;|5k7odZ`Jwwj5Ѽy͛7twRSSq7==ÇjK{@l6B dRĩ'xEHZ1FtppŞmڴOR/dVAAAtt}||\ҳg;h4i/r̙vU߉(5kքBQd=\\\VU*+n[^~M Og0|>㑿Z~s*O-\2((" WZfBB`gUe.QfF Y@IfJPQQQjjjbbbBBBBBBbb\.OMM lT*d2D" 8*BeNT cei0p9LRP(2..o$~G*bqƕΝ;DSN-]! ҤIoZ?L&# h4E 2""2B\Nsrr2P*J3&&&f 66|VtR@0i${w)1é8BGkZJj!Ɓf{zzCbX" zM:wܢE/^Qs9vӧ{U'曀#G?}h4777W8%g-S2,2peKx>Rϙ3gN> :=z488!77xˍPrQf>_'Qf[sNNNi*e&7E:d2%&&{.));NMMſ+t:d@D+麻KR([tT*JKۡ033355U.ָǏ"ȋۻqM4!/6xӧO[BM6}#HR"\\\% ;<...4 WDA䜜LCX,^d2FciM&cǮ_͛V{VBM:Uׇۻ;)׳j59g0{3:# .Qj/^իF?7n\g\|]~~U.??iӦ-]4$$&'......euxJҔ%ah4g,1r:KP_N~^7L&o~رP|>.HF&55ÎQf%++ d2m)\Zz @i47o޼~իWo>G999if뇓oVىzlllbb"FѼ5kִiS??????owz`РAf?~ӥR?D 2B(;;"S(:jDu\ d*"2 -[l۶r!j*ooSzj˖-^@_t)44͚5[.::y Ncbbvر`{FZYIKK_vuu- cdqd2XXl֭[w]b !$rF@s*eFėĖVXe.! hHHH@-Z|M6m֬Yƍ_P֭[SSSq4͛7/^ؾ}{ZZB ((( V̮X,VN.\`D3 =\vuuhAd777Pvv6EiZP 2!*"S(rEdלFYA3f̂ ;nِ!C\2hР:u YFDEET*\c޳gODD͛;i]hѢ]vmڴow_@r{ݺu kKvٌYX2\.7,&W% P(˿W^VdFQ.kڹ)&(sm2k4XJ2mX*Ԥ۷oߎt '((h„ 8CPS HRT'[J%u֞={z=FرcΝ;vجY3'Zwkǎۛ4iz D"),,T*"[^Vx<*VB!Ux\`L&rEd>t:p8<q?`l6fx76 s޾};l0Z۪U+;,))) @ 2^p4/W5 2rٖFzz:9cL*e&7x<@?~-?~=Biڴi}5~ÿRYEQ@)6DȊ`o"Uv{NJ k ,XA !$sH^'b2s99M0ʊfL}}OIҷo߾|2!!ɓ'ǎ+--ѣ%3L ~ʀRRR:v<7z!C* wDP(\.7??!pX#P(DX,XD}d2Yr9D"UT 2@OާODAN:ݺukv횹+@H$nذӧO ,Xt^}pԩɓ'/Zրm֬Ycu3gμ~ȑ#yPM3*|>JII Btttp4 /y<!^ h4N81k, ǏWK)ϟg TJ-r@ã(3;5ʌ].?2F̩:6QOp8:::uܛ7oO5kagg:@lhii988888999>Yf rqqdKKK *CPϟAd]]]lA!==C& 2[%%%q舌r! U:uڵkׯ_O]F022zAPP_*,,ܼy;$… ϟo``Pe]FFF8_ajwD&y<@l9W"b v~~>J#\. P)S BJ[f̙O<9qℑ^ ;v,X_# vqq9~8>|]]]qtƌ3f̐剉gΜٴi1cƌC6...|>ŋ*ssO.Wׯ_B?."#1x)),,DX,2,BT*FⴱD"&P Ad###ww{BY;uٽ{ӧO;::"Ϗؾ}L&m֬{СC vhY{Frtt\|իW] hݛO݅45J _BP$H$*?Lr\.xxbq8d2L&^ O{h|ݻ#bbbF]߾}[yE.|M[WWgrՙR= # b52+OxU$QT|>!!aԩf͚0a¼yvZV ֭[v:waN>րRd2.444!!!&&á ,;v,TwM޽{KKK|bbbRٶDGo߾_AOO/)) !pX9fB!BdH$!`0o~DUe2Y-/AdM>#55\ݵh[[ۇz{{޽{„ _H$ܹsӦMEEEs ɓ'Æ 8pC \'H$Һu\\\oWWWu5k ,HB!%BXGLDcX8'X,l6ifhrh XvP={nݺƻ277߻w{ esp<4`lTfQQq]PdٳzǏgXd߾}7o~}޽:4|pMT$_w4JF4TD~#G|DsDoذÇ_hO``kH$Gk!Zuݻw{YP<:R#2 2Bl#2BwDFtV ҢEYFݵhcc%KL4֭[ʟhm)J͛xbhiiƤo߾ |I< e˖PC4Yf5ku_p{*|>?++Kyfet+[5Pܻwoٟ>}ڸqo-|===|ɝ;w>|nF+++P(_~MNN^~fvl}CBBTVP(QQQǎKNNFYZZN4iƌ|;x5T|ׯ_E׮]ۺukrro,,,<<<g׮]铡ezQ˫TBqɤ$ e˖yzz{!_iiiᭈ=(O\.8q=zm˖-wT͓\VV| .~mݺVqK!$j\L}r&Lի׍7LMM]Td2=zT>|*6]~jhh=X\.G7B!ɔJ*Ad@GdSL={oqM'N۸qY]RPPe˖H@0k֬3ghZիWC ;u) +W7`0 ~d2z*++SI0 hzD'欬5/))*\.`2t:,KGGOt6futtDC+@edd,XҥKC}捅ů8J۶mҔhkkC 4Nq8Ϟ=ӧ7oӧP(tppHOOGegg'''+#Bpss~:1޽{݋tR۷oW3wHgϞ={vǏC1dddddd\|թ[|yhh(Gu777PaaǏB_~MII"ElB?~\%\;wǏ?~˗,ԧ=z$$$;;;?}H5!=zxLss{V"ggg2o߾@y<^ 2,K".{L&+wD&hiiWqY32"@S=: СC-Rw-M6K.3g7md``ׯ_m۶k.X<}􀀀6mڨ(UIIIp -TVZuѝ;w.YDݵ 55uL&SݵPOj3ʥ?(((TY$*{ZGGw\D-K"رcl6ĉ޿b^x}Zr[6n܈?~$z* reTcǎ!fϞ=wv?7oFFDDg \)r&}ajSL=k֬'OӞ={JKK=m۶|>???#!}}*:"Ad&IPwDH$EEEx+"J-"S( `0|||v@&] 2dӭvPyÇxE͝;m 77vڝ;w֏-Z,X 44tG?ljj:sLu@Ckt n\@sVV\TZkep8 6o @=S(111˗/ꋱ8C!nMV.]&Lo*ϗJk׮3gJs!/^ H$'ѣGBgްaiDDѣGDXt)7ݼy3vX?ǂׯ_iӦE}x)))?+jff|[ny/U>T*^QUwΝKMM=x`ΝY?AAA?{ꤘr\]]=<<(m.]]Zc֣GDs'''b&^YEGG AI$"I$fwD.++#" #77B D"!od "4c3IჅիWZիWoذawnժ+)K.=x!((]c0x٠A:vxe.r>onn>wܐu4ZbbݱcƎZ?(@3())ťxD 6EEE%%%2M{fXt:fl6[GGOhkks8\xL&bikks\:r5 %T* OLVXXr@^">⏯!G޼ÉbbL՟*KPp$7HBϽ!Rl6od}5:uʕ+SSS.\ONJdggC]>f9r48III/^Qw-Uo߾s޾}۱cG9~^^yy׮]+cZKKKbfͳr| B'LH$~СC+L ~X9]~xDb\}}}TE"p%ݻ:Tv硊xb޼y>$>|={5kV͓\l칪e1վ}{sss&ŋO.Dr׮]:tFi ʕ+}}}b`}|M #nݺ5lذ^zֲg$Y<ošo޼kݺu3f 4ø{HTRR" PXRR"BX, bqAAAe; & h4`0L&fh4.s\.W[[b1LV7(?O#> yDžD"Yc `6MRBG;BHKKb!h4Z۶m+$YLSN? @7bCH$y<q&aU8$ 2L<&C +V\|ǏҥKSNd"  999zۮS!ܗ?Ǩ?,=<<֮]kbbb~Z#0BQQOAUK.o``rW{*`0 ?"`0T*Y[[!$TJP40eΜ9ƍ3t)IIIG޾}MK^^ݻwؑm6u Z,\p׮]!!!;vPw-@Cݺuի׮]D4M EWW6Qf͌4U&*UpO*48x( 4?²l:b𴱱1'~جDq!4P(p#{'a:uϷ5`ggwe? "gee! ޿_~==|p83l6[(ʊuuu FIIr "L&+l9 ,#F022ڳgϪU]K80}iӦYXX͚5KE&ѣGϟ700iOō1رcBV:l2 _ (P íj䘕' TVO7v9\۷rss1nKR*AUsssbU`0t:6\T*?WW*pYe4b W3V~X4k@__g:)۷oWZuN:>}zȑGt!99xP( T̬Gk׮uuuhȑ#={i&*:jԨӧO+0a„GEE!&N:|0O0ǝ7o^\\ lmmi4Zzzݻwۗؿgnذaɒ%'Nlٲ*#VlM6iii9ܹs7oVYUVݻ'NSΝۻwo?>|!Z=?#F@?.P(VVVVVVӦMC={ɓ'w {vvvӧ-nY ~nƍEEEʣWǏQ5k/e/Uq_s:^\\%HTX,&:"C IDAT#VGdhٓZ$>pAOO-[@oiWnٲ͛'On(c2^p{QN:uhXOO'O۫KxqP(H⥥@,D"H$Hp|H.B!qL6ã\.n g9E8Ќq\ Z@YQQї/_~LL&YfD渐.mjr9Z%,reF]722222j޼q͍ށ&KLL ;}tvVZi>}_8p@%Ν;  ?]KBr^p޼y;wDBpss~ʆCtRu2x+V_f ŋ=<--M6ǎ}r!C/_Y,tuh 0Yf=zh޽!@e]{̙3gΜ_.]=Z"2YeW?8be466>y$rܹ6mslvXXXXX[nݺuR6mڴiSE?{j_Ld6l>> #GL:uBd29""_~ׯ_G&VRRb $N:i,HbP(J2P,E2@*EEEP*|cikk3 &I8}f2^r) #ɸ3MqfIPorrr>|Դ,TR-[411133ҥ fjժjh&Ѯ]vUT.=gddܹsǏx:nff377g2>DDD|ǎ.J%B(%%& l4;tf͚9sh@ |r[[ۤǏ>|!/X %%eÆ ~~~u[P(NNNNNN|>͛/_^v%KlllF1bĈ]A";;k׮)155EwҥMBٸALnnJYWW"(l6DfBD"1 HDtD.++֖Jl6|Gd-tD}ʕ+O:5i$uHp8qc„ k׮mժE*^p!22͛FFFgϞ9s& ZjUppk2eJPP7 7*@ut:RT(~bgqLX,2, q g\KKK  ϗ忮3Bkb~>PW^z B͛7ܹٳq; J_Xs޽{ͣG[TTD"ڶmkmmmeeecccmmݮ]&0?x`TTTrrr^80jԨҒ"D"Ȕ?m۶w^bŎ;VZ5f̘ׯ> I_o߾}#V$7?}eff1ݻ}QoI${ݻw Ȉ=xƍ9z^z5k۷/_$!336!d2YOOŽp8H$d2 !|)^#2Jq 2J5M2aܵk떣Gn޼`nn>gΜ5k0s!͛-[}BѬY3++N:=SNl ݩSN:s ׯ_'%%]tiÆ bXGG¢QޭW(w9xɓ'BǏ?v옭9Fd2PE011Ynʕ+9~CCC//1cݻSnļxxҤI&&&Kߓ'ON8qɭ[L6mҤIrB|LSS*!cc_" DNMMEq\\.8bQ(rFb?e2? 3g&%%YYYf :vXPPЂ bccw߲e'lRuՁS={ÇWw9Zh:mڴ$$$DOO_݅ 8\xt Wyk6Ͽt@dׯ_qBB«WRiVMֽ{w+++cccu ?p/m6 ϑd/^/\.JoW?|pCw寿2eJ%R:v옞.}&ZhNϜ9s̙߿?qDLLΝ;[l֧O4YrqqqgΜIHH5jTxx_$HNNNNNN7oO!.[,00pԨQ3gtvvn\b666*Ad33۷oWqVVB 77Wer_<}!A rGd---B;"kiiH$XoGd \\\ڷocǎ]vFHKKk֬YÇ^xqLL{@ٿݻҜ7޷o߾|gϞЈu 55u;v쀱DtzJp%IQQn, qf7@ 8:>;љL&FùdMƭxܪ}{9!B3%B˛7o߽{HWW}ժU͛7WwCP,,,,,,Fd)))8|ƍ7oXƇzM6mL2nܸ:ZܹT*}]ii)B@o~Ŋ+VHJJt֭[i4Z^ 0pnݺAd|1......>>۷o-[}رLGGǭ[;~Ī4]vFFFzzz0+7nܸq tc=[jb9B0!!Ǐ?~ [ݽ{wooѣF6o|騨(^z-\pĈ B˗ sp?V266FtDF^-ZtDV(EEE8!D&+L"pGdL&fZ`,kڴi;w\x&m4˗L0aʕm۶Uw]d2ٵk8p*ݳgOuKdff8P"ܻwM6.ԄVhhѣ_TP˗;995J݅?1N~XRR!j"СCO{L&#bRˍ"˳!-*L&cLVǧ@~m۶A> 6ӡ˖-ѣO:uhbҤISNm,> 4^vHڸqcϞ=ϟ??|pu~9B8h gggusb\\.!YP|n}oMsNOOWC>t͞3np8Ck F w5n&]ۓ HQQ˗/^rL2eʔ$w5|˗/_e<= 2BtDfXRw@tDFUPs[[[u҄dooѣG_reڵ={tqqYt)hDrÇ_|YP 6ҥKC i ~;?mF:7zW\IHHx䉺 uBk*W)"LteƭQ*6ahD'M"-[Aʚ:#3F䛉3&'@u7M #vڵuVPYڶm[VGYti^^ޜ9s|}}B'Q]r!4k,#&Og",X072d;w ŋ/nܸq ?vtt 5jTթS'D ͡۷o߾}2Ç/_|mJJǏ:3 M?LMM[hєςHOO"h4Z۶m;voccӹsg<6w̙+VDFF5K9;w޽{rcc337oeO]]ݪBt:j舌7 T*SPw@Sjccm۶{&D"߹sgڵ[tȑ#cP(=:rɓ'{uѣG7$$$**?ã(55h-jr srrڷoH$κ|W\.B!OQ#ryynGdh?&Y#2^n)rV#@cǎiӦ ki}/`z#w&7e'4%D*+ /&ƫj}h) *Y,nMTeprhb|WWJj 㫯4iCZ}zi1P(۷OII!L2e˖-CS8… }.1SNݼy3ނm۶ ;v옘kwNP]ֳgO}*G:aY-[;ٳDvڥM<_~AȺta6l5wTe//q«S'~Zrʅ ~7{ճ3T*t˗/_bzѿw$IqqqӊJ8l4J(ZZZ B@D+pb8333777333'''+++''';;;++ _ǠP('T.[͛7cǎqFN.3g-ׯ_zuVVVuOiݺ### N:hTTԅ ߿֭[7nܸD"w`ɒ%=zrŋ>|rʟ~ !Գgϙ3gzyyX⧟~~>W]v\oIOO믿֬YõiiiA?^%}kB?chhS~~{֭[s[H7g7"5Oxe\C@shh.]Ս,..sNBB7nܸT*[j_,__~fff YvSfMLL 4ut:UVZpVNIIIKKƉշo޼y3;;[à@ Ie ssssss@ -,,,,,&(///777//O"e H𯒘fH{yy|H$jժIDGgϞ ;)Qaa!nVNS(*R#2M͞0a–-[f͚huk֬Yxl׾}I&9P(6lدڭ[ӢdԨQcdžJv9XXX̜9sʕ?ccֺuT*Utt4مqɨe2FySucT*,))qqqo߾m)dɓ'B3f̘>}:B(<`~G^'N4R~NNN 7 :t:{ lڴi@77ϟZ6--ťVVVb!dnn^e|>sLB022B "5 bAdkAd7C2337ߐ] ۷oo޼رc,kذaGիWL9Z}֭ӧOǿ|珃[ldC}ӧ}}}./R_yMegg;;;/\V@]͵Jh4Jչs#F 2D$Y[[O6yfbb"J_|I|Ka_?~}ij=zԱc*c w}왛:''Z-^ .\{h-u?zDbD"B= +|W^Vw硆?~xɷn"ܹlժ#;skV]GO6i={on޼p޽ѹsN:uƆ2 Tjjjd2[uT4//Oo^^X,.((JQZ,fyb8p8t:`~\.FX,CCÆTBxz2JZ8$JJJ Eii\.S)ryII >QOqĄt^Ը 5>nsA/[l˖-+WꫯȮjaaa߿y&^777x_|QW^]rk׮~~~k֬}?1bDyyy>}:tquC @T*DR?ޫW䴴@~addR\hѻwTd!Clڴ ȍ8w={DQF5;@W/gϞkӦM```@@/\$MOO8pB~;zf,X0iҤiӦoYpeXXمZ(bjWR1"((?TZZ*kh覐B* Uӹý`F|r;;;ccϙG$ZB~^>;С͛7_~wׯyzz|>HwX׉>\srrzѦMLȑ#ufaaAviMN5گ@"% BGGm8[ZZZXXXXXXRR\9g7S>OP lvu ~KVpVX,"fc8]]4333[vmXXXttt``ƍɮ"77}fff'55 !!!dnnWQFP(\.HbchhT:"z%MݻMv-"@ɓ{ܹsɒ%G6l\Nj/.^xk׮T*??s8;;]ZcѣAYXXܾ}~cƌٰaCTTԩSȮԙOo{dAd?o֭[Wb\\\_<#8::>x̙3xKnnn _jq޽m~q;vxҥ]v3/_D]|ܹVpppx3g"""3gT`0pO͠yDp 2Q(Ճ"fb1BHV"L&BDRC=of MI۵kσƩ}k׮]r ~ȩSv-00pȐ!i/_|ҥ/oǎ}W_}|Oh"bŊ_zo߾dܹs===; \*o6lX͹#F5TueС\v-:tɓ'׭[Waw~ב#G ωOhh]ZZۏ>Gu:9k5sJI&ڒ]K3qԩA;vŋdN OZ//[nDǗ-[6zhNϟ? "T7"//Oј{uR{Uꈬ{3f+J舜0 VV jAd&eϞ=4͛7dUXXxѣGX{TTԝ;w4 ٥F.;wn޼yT*d{ҥwUdWH=zf&L Z`аa.377߼y3:iҤ޽{W7ի!DtRgg fW^]`AvZÇi4p'jO'OZٳ;uD<==i4.o߾xZ wk?z_|݁&&$$䧟~ڴiSll,ٵ7n:uСC˖-}۷o>}.%$$\~ƍ{>}.\ػwo< FظqԩSccci4r,Y[n'O "Y4ͬYػwokh'N|w}􉎎;w.)p8w 믿x<^@@bB9w8sۨQ&LP]SO/WZbD"Q޽qA:ujW\qww:thDDIJeGǻ{nXXzc;;;bҥKlv\\\VVVaa'۵kRSSw޳gOlwTW;^+ |ݻ &$$$ܹsرd|8qں{ί^"└(J[Th4L****++SD<]xzn}X,CC۹\.Fc2(>OP l6дАb}+Лq)Sxxx_1|iӦ ,^t!DbffF-&ZRIRUEEEƙ!CCC\^#ZVL&x`[P IDAT04J[ZjբERSSq]%%%]tׯ_/**á޽{D"ŏ?~ϟh4ooo??={d+P*G>yڵkONv9d#FwӧOL&ٵOwСQF=|]vdjm6c KK+V|dWHm۶?Z@!ɖ.]qFvؑ슚N:l۶m͚5dW\.Ob JKKryiiB(***--JxT*3AѸ\nb*odF=PTf |>222p8,X,>C<q\bʗP YwϞ=[& &{OڳgOhhhqqqbKKk׮ÇUppp\~M??Lw&$$¬+Wwʕ۷oζEqqqLL̵kܹrss+,, vss8q[|}}| hz&Mlٲ_u޼yd>GDDDYYYbb˗/_|AJeeeչsΝ;wLJ]RiRRÇ߿/^T*@7t:Jnnn``Ϝ9r֬Y~9s] DQQQG2ɡP(^pw}t𐐐/LJJ:tо}Bcƌ!.Lm޼yǎL&sժU&M;VjjÇ/^rvv*** -\yyy^^^^^D"@7pUNS("[!kaas&&&DTJq{<A ؊)Z7íqcf\!f\TTTRRB䰫;9|>_7-,,,--e<9Z,+V3k՟?'suu=wjkk[RR*cQTYH LLLRH$*,,Tf-\ncccllgR266Fi4JBvD EVeeet>4=\.wܸq6lMٳgϞ=-Z$ܹsΝM6͟?Bmѱ\P*ϟ?OJJJJJ>}r:u8p`LLOi=zW_QԄ,L(Θ1cҥOeX~\nݔ)S͛wߍ3\Kf9 K*L\G~kFٳw?mll&N؄r4!yBNNNZ۷۷'.PJe\X,H$ LR?VZIsjbp묲]tAAt"ۘfN4Z>CCCO8sN[[ۆ/533SPp8P(DeddTDhb!T9RiZ\.r9Bʢ"V*t:KKKL&8f0 ERAEDDĦM<8vXku~_~%^MMMܹ+Wdee! eJH$b8--ݻwi:Bݽo߾m۶m۶H$Ru^IKs̙WWxkkkܹsw?oݺZ@~ɒ%ӧOrwwߖ-[e˖{_o vpp :P]z 4(** .OhnܸqѣGJ$=z޽{፤FSZZm۶'>ӭ[R_ rSVRSSrss333sssrrrp_ [ͭp_q{@H$H$o޼}vnnD"!|\P(ZD666 HfWXѳg &xyy ?] B^^^!СCD"155ED2wDod29B@t#RxDGd!Ad/Wos%D"7|We2˗/?ŋ/_:u*33S&Gi4P(433311&&&x6*JP๥d2ҁ}`ooҪU<-ԦM"""s lEƏ?yk9Y|9ɜ7oم@ݰ\h… ^zUV͙3ӳW^_~e^ߕعW^zj#In P/]t常8R󝝝*R8dW?~ī_&*RRRBFFFyy9BF ܰϏH AJ1eeeb8##ȗϞ=j!`0lmm׈xt?}tԩ 7o?ܐ988W^ 2Qx D!Ɗ0>_PP@4BP(8dll;"t#ryyyj5~g7ND4Uɮ4׹sΝ;n,..!~r\ ZTFrL&Аbq8KKKOOOb*(@`nn@5J5eʔ_5&&&&&*=z˖-g>wٵZxƍW^ _*ڿo۶իqqqgϞݰa޽;.tТ]~ŋ/^|9ڵkΝ\.BQ(ݻ|BP(JLLLts|>oī .\Y/2x`;;;bsrr2%h4ԗzիW!d``S0L[[&d':a{߾};55 RBh*|M4ŋGi)L-ՍbАfW" L&hFFF8dllT*j!F#:"#t:~KSsGd"@k߾}߾}׬YAϒ:R9jԨM6M:r@ER׬Yӻw7,ĈDP Bq!zʕ˗/[nܹO>]t[4h޽{7oyxx 0`սz266JDT|P7otWsssqLS XͫynHӛ7o={VwCJJ IT*>}S"s+055uqqiӦMpښh4_qw޽z~=Bbh2>zȑ#"a...AdPXCٳg!SSSC-EF\."kڢ"### Df0ArPf̘Ν ]rMNZZZPPׯO>=p@]^ٿ$ܻwG-H$;vرcZ'O\__^,hmvҥk׮]tqssn4QOLLLLLsNVVJuuuڵرcgeel6611U DXsvv6O|K2*չ+WifE"MVI-Yjj'OǏ?~͛7xviGG6m 8pmڴquu577'XX[[[[[nH$ O8rܾ}:o߾]v"PY6mƍpc911XȨnX,FU l6c"K/,,Ad<644DtJLPg85Ov!66dtN=zKRR4y`͚5m۶ݱctmfΜLv!(J:tJMMsݻwܹsBp;wl%%%={ѣGӧOjOXX7n|`p4`.((HNNn =ztGRT"(--M6@KNNu?ɓ'OHRP(l߾!CpյA3`aaaaa>YRxM>~x˖-YYY!>s]t֭[pܣGnڴiƌ^ڱcŪ#:;;ĪP({nu"VD~\.YP <9 !T#ZFUuDjDGd";{@>~%K8::] 4MTTԪUFm6 qԩ111!!!<r@MׯC@)jŋ8|_~EPh4GGvڵm۶]v...0 dO>MJJz)^Ezxx,^ښJk7` ]$bKKZpQ߿}͛7o߾C=<<ڵkijjJvE!!!xK~~G'O߿Z͕wU߱'TgڴiC ڵk|||vsqq)((H$!PXsG߫T*33WxĤ4B&pwDj#2"tD.++c2Z`+B* 'P_>o޼[^Z#Fum۶M8r@3{._|ŊdRf͚أGkƅF!V&%%=yĉ+V=o\]]ܜt6;YYYYJJʛ^zٳth׮СC=<<ڷoobbBvi ׮] E"B(55#1T*.]tZ[[w%<_\\\ZZ*9Dh4t:SVV3eee ߊVq" 2i300 [bETTTK@oݻw'O4h&/\022rNNNdgϞ7oޜ}L6Gh{ݻworܹsVb~~~_|_|ѡCx4 #Fq;;;6իnݺ!B!B(++ +aeXljjz |L&x2 !r% lhhVu;"3 D.--JKK Vŷ# *,,lݺuׯ_hٵɓ'޽{sL4i֭G!Pqƹ] 4=L&ӓآh222tϟ nbD"]VD"Q적h RSSRRRrrru~~~DԔ̇ 9}tǎ .[O\' +-uzϟ?y++c Z?<~իW%_|cǎ[YYS~Ĉl_~ٳA~  9qĤI 6L<AR[nkjkkȨ2KK$33Wx,((rD۷!6M ZL4 B툌8LtDzedd4mڴuEFFJVAAO:57~,4mժU 6m倊6m$JϟOv!LPT-۷̷o&''߼y3--‰dQ644$P7srrrrr1PXXǘ?N: :UV8vr-9X}.4 1cFbb"L֨H$+VDFFkP( {Ya{nn.vƍbb  Z >nLXEl_" P(ر#N=l6{Ŋ5U> BJ2Z l6VBC7n믿,--'M4y |qpppyy}p:{nbֶ 2BH H$OOOﭭtzAAc 2),,422B!69T#243flڴ~>}:ٵ#==}̘17ngϞ W^@sss7nܜ9s 6]~76Mv-*b2 x=zGBqH499ݻxOш\ bYw#4? bb\.W738L3p@.2dРA5wijSµ}JR(5ӉUcnZG;fggWVV&J֮]ۣGzjdRk*oT/Q*SVVjժ)S'nƌ3f̸~͛Ǎdɒs熄a: ϝ;tQ:٭c~~\.w6 Œ[XXb333P~~nBx<Y&:" rqq1H Fj5"CGdwfff&LXfͤIt;H!ϛ7ѣGnnndWŋ>|x"Q# IDATdB(<:ML&)(((..V*2l###&idd`0O:pOrO*p0_vޕd!TXX*JܚjZ.?]&1*((?UfllfqWCCC###D"Q׺;3[|ӧo++H$\m**"55u[nD \uaЖ-[;Gv9תU;vDEE4hܸq6ly#g8pСAޞJ{?Ǧ %??£|>_&D""D\ZZj) (tzYY:Р~+V3"+..nҤIW^޽;!<<|ǎ ,رcٵh*jƌC իٵhL@ zyrR,**dJGoʊHDR]ep3Bb7|Hf9l46JuttD0el6Qۑ^ ŋ?իtx@V-,,껶FB.=) JU رrӧ 2hΞ=8qbԩSǏOLL<-EJΝ^ڻw'O~L;,&99ڊb"\D"a0 rAA+++S*Dʊ( |F)))TjYYBȸ#ZAd|OrԔިG͛7o׮]9r$ٵBeggO6ɓ+W;Aa2˖->|xhh\;vHNN>s مhh4Iv̀21eу #这ʈVğh\n8 8[MBl6!C#pCO&'##cԨQC)8ܢ 7ZRm۶}mϞ=B7i&M Dv5%KTjo~ϟ#F5~ZR,X>}v?~ƍϟ?sqq ;w. ݻuքt@Ю](___+#RZmllDA, Xt)Cqukn7o޼}˗/BwXXX׮]7n477-O.<~޽ɮhV:tp~;v3o[rppx^ &''ʙX,FU"HR^&Adq~~>)**DR4 UV!!!˖- i9jH5k,Z:t "|>>> @Ód111[&?T*|3,FbٿSN+,qaXƍ8p_|AT*^7B s)))ϟWiځ^prƍ7nŝ9sF͛7n ݶm:u*11B|||GSSSSSSk׮S+e˖6 H>>>iii/^y> 6#_~CU׿ӧ/xСCO<166s40wAAAnw8pر{#DSdddTD((((//755}}G|~JJ Cr333D.,,400Rܜfw2T*rGdJoKnAdHW^ő]-}||bbb,X)d@ jժsA,˗/h4111d3F?<|ԩS?klFsaÆ)dݻ_zuرFBFmذ!%%&666==}Ǐ} .p8G=|0<<ٳ;v@ҥK322pĉ۶m7o~X,>tP۶m>|rJ={gdd&%%͝;WOzV^>Gvڵrw}AY?F466ɓ%rZzѣGǏ޽'9..8VVV޽-ix\.رcϞ=ۻw/ٵP޽Ç}~񲵵5F̬r@ jyyywDw[ 2B& 2BFh4#FQՍ? ͍_xC3Zjٲem۶MLL$"ТsӦM۷/h`)))6lXl*444>>ʕ+ms-#rbooOv!593fL>!^VV6gbB?ի Ell&N͛Gƽ֭3f 2|p :u WZvȌ<,))I+ٳ#""B׳gCIqӧT3gyj޹ɓ'߾}w^wwg#..n=WuR )e˖3dHWydu\.B`s|W{5j… ?m')))Jt@QH D"2 bAd`hhX\\\TTbVI L&uD 25kӧȮfaaaً/ ի۴i;w\kiY͛ggg7e vܹeX EuDׯGIv <Q}9BhԨQccc> SЄ &L[}y_xpႿFbVV^GH}zBUpQS+bҥKdZ,+aW\\\Vhpx +--i-!BŪj\NB`l6?pJ&zrUzV@XtӧO߿ښf{G![[GU9Bȕ&&&*cT*U*舌UvDh45 knذa111=4h ś^~rssbq̷UVݺuaR\L&!霜gϞ]r%##Pj```ggdoo\Ϟ=hѢHh} '6rÇ?3I %HQA@{C].QQZE`]ĶvVEYQk.*"wBB yqn^|+\g޼=z]Nw̙'ODFF]hnݺf͚/_[f]?Wkѣ?c:>vsNz@дiB87ϟoooߡCMM̈́w9rŋ.\ؾ}eBOʿu)ŋ;v=zŋwYiI||MEnݺ͛7A,'%%#bcc뾇gcԨQUCիWgdd1qiiiMTޚ=dDgW4%7&m+Z9-- G*v5kLJ;vcǎ˗h bjj566zeA__?;;]vyyy6@y<^Վ!|aF#:"A Dn)ݻw;v<|̙3ɮ|W^z gcbbpb 8kbbbbbbdddddD2lqx"Q_ihh\]ǎ.<޿6i$XLvE|ƀR'OT*ٵd}=vٵHT.\1c7iR[uaWWם;w.^Z'J;v옐@qww߷oBT**pĈW\KXn͛RT˗/;;;\f͖-[]}򪇮5R"o>%%zA&!ݻwΫ=K.|y߿Xinnk6[!ʕ+=`42U*8$cyqdԹZZZL&1 6f ǫqN{wE7trrœlVcРA[5jԈ#RCMtttv^~;l0C=|waaannnއ033y󦭭㽼N>=emmmLFц rڵo<9 ]fcc3i$//ɓ'C&Ç/_/_ mlllllqu"p\@ lmm+ykM~ё#GTj֭Hfff._ĉ;wn ~~~]t9~8נvڕMv!*///^xJϯE}Zٳg/X **_[[*p8O}q]>ܿyf&yҥ4={v۷o'&&m۶O>?~|k.&yeT:`]vWo/1yxxG9M=BgP~U(qqqxy.((x@Lg0,`p\mmmAߓh8[jժU~/˥𲉉IYYYvvvSIgee BPnnzY#2^_#2R\RRBRJ%n;tJEtD M-uPP+ٵʒݻGEEEt+++{{+VM լ BPأGbRW^߿?55!өS>}ӧW^A  ܰa8rӡ,hF\\\VX1j(.Kv9-SffM<==ɮ43f xСCe0c0M,Ϛ5Ν;Ç'N:j^77777- vvv'NvJuvvV^еR+DgΜ!7oBܜXp||||||t;w&5t:}ǎ;v쨺Kշh^h"Tz&T@ @ _}JrR@X_\\,ryqqqzzzk@ `0\.WSSǍ^`X|>l&YwP;//WΜ9͛uJҥKxRmxIOOٳgDԔ؄\.p8xxAd p \.Gijj DV(_y:-Y6mOiӦ3f0 )ʷo޻ww^rr2Jm߾}߾}ϟooookk۬/*jeeeee5a&''<{K]v… cǎ,,,JKKBՎAob0L&㥤 8NLL #T*U"W`0NG) JAd ӆ N8q ]wӧOaaa|xŊ/^$fʕQ/_ kpd] 0y󦗗,X0i$ 4 o޾}e2Ӛ5kT*u}N:gϞ>֭[ (p{毸aEET*H$D^9??XH$D9..Ұ{/@----wܹz꺌@ŵk!dhhXKG䂂 @P#2BK$.+HZYKKKCCC&ikkT*#2BF)j;"#*** $[~c<==ɮ vڍ7Rak" Biiiׯ_~-[/_nnn>bĈёRZ111^^^O4wt:oРAaaaC%z͛7i4ٵl…\~ )osp_x|%K bX,D*TG"ؼ|ە@P, xdYjU``w]ƛAZ:""utt*mx}:"#lT*e B#2NlGdRewA]XbŁ-[Fv--GyyK;Nƌs!C0 K!D6mڔvٳgݻhر?sNbccW^}6mڜ=//… ԩS)J={n{{(Pk.WWWMMMnzѢEׯ4i<^}#DbŊyuЁZ@P(֮]m6WWW???1|ÑH$<A踸ߺuƍ^^^\.w0`5-RJmڴ//ѣgϞ=zѣrss?~G=~X"رcРAdNch8???;;[=HgHg``c"HOOOKK!hfX,===?;yll,^DR|>?''GGGÇuD"AdTEEE, !D 555BJ+OAà 2{֮]ryAKٳYYYGs玃EGPϜ9s>},Zh0h8͛]]]Ν۱cGibbb|}}a9.\榣#;;F8"bΜ9sQ*޽SNo ;wlkkfɮdo>'?~ի{mmmMR.Z2@O EvvvvvvffffffvvvVVVFFFvvvLLLzzzvvvII 1DH$n;w{yy>\*Vdž΄# *mAd.7bX2 V투T*+**455r9 2tD}޽ׯ?qٵ4iii[n B͜9sVVVdꍖ֌3MvڵݻwO8rɒ% ld̙/uٵ4W+V7oمf#88xʔ){s HBDGT*}۷3gB(;;Ç8|ܹb*jaaagg? wJRǿzo޼ye||Rڵ1czݫW/]]]+?h4H$D:tiL&&233n߾K p8FFFFFF"qXY__F5=庻ٳgҥ8\ P\\P(DȹZzGB<<D&:"# 2r;" J$hjjnܸqʔ)K,ڵ+4u999>>>{7n5kVAERwܹp۷{yyM2- Jӧυ ƌCv9?sҥphZHHȜ9stk׆ ֘ rCsvvvvvF)ׯ_ÇB\.C۷j׮d}@CP( ?~Ctt7o޼y3@;v8q"Nɷm~ cmڴiӦMMryVVVZZZfffFFFzzzVVVjjjdd$^ITR8L4Q622255566611100hӒ%K㏠ 2L,hnݺ"ӭ ::: BD-lvzz:"_9D8g\VV8q]-[vmki6o|!>7k,h \n]\\֭[zٳg;/%իׄ V\`0.9Xd#ٵ5JB(,,_J% " T*BoEnC%%%2ﺺVVV'OƝ;`bX\DB322233_xJt166nժX,611511 uo ӧOwww+abb"BHWWWCC###ڑBPTy<^ii)F酅l6U@455T*~B.kjjV AdhZ(֭[:birmV^^bŊ%Kov9s֭[vAooA]gdҜ:t(666$$BL%%%P&H’"DRRRR\\8.\^^.׺Hs8Diii^DBMMM<1k e"]z9../H$ YcBW8La2L&S 0LmmmbL&eL&p8mmm)_ƍnnnpqq!L* WߟXS)9QgjjjjjjnnnFOO{D5 _'kwvv\;/x<^vZRRŋdW&I4N655511 fff_ԣ 8p <<|Ȑ! kժUrr2BB맧W;L(~?ϯDFIR.+Ht:$2 *B0LFtDf0:"+ 4MD|wO?ZjСUݝ;w͛hѢ+WuZ9[[ .DFFZjSLٹs'0Zknذaƌfffd<]ݽɌBIII."pL&"qa7%0mjoT*.((@iii8Ux<&|@ X˵߃y]reΜ9$&⌌`C(իW^Bx3}ٳgX,644SD"###ihq7̴xBD"0b񲴴4334&ieeeeeUuL&KJJJNNNIIINN O>MLL3 ԯ".:m߾}[{Y,'%%eCCÚ:" œS߄k8vZ#J*--WhvD 24-۶mСCPP/Bv-MB~~gPP/]diiIvEڵkxx3g.]jmmm۶ٳg4_$ȢE9lٲsΑ]Ki&=;T*sssssssrrrq^NTmݺ5/hkk3L8+t績:LT*-))d8rbƈ~`.B^MRܽ{  """~rIHHMFkݺu֭+/--MJJR}T[!DP MLLW]]]HH*7999999ٙkjjjVVVJJJfffVV1i566oժU.]#\pQ&fTTQQӧgϞMKKS(!>k`6ɓ'Ǜ4F,GDDZȺ58ė@y<~fEEEt:NWTT 2qn+**h4iI6{Ԯ]Yf[nĉ0ܵk\]])ʅ FЇ֚&l"Kc &1b;MLL4MMݻw<Æ #.::zϞ=vGLOO=+%dzX,P' MMMBaq!]|d3`J%RKl<{-eWoIDuuu E"H$244400 >M߻w-Zt-777ooo.KvEhʴ,--mS\\AMOOOMMQ0AWWW_____/p/}X~~D"),,?geeeeecg EOOO__wر#~']D5:nnn^)hYYYgΟ?p6m۶";Ѣ35xY$U;L(JR :^mqfZZZ2X(2L'` Ji΢ovI??kג] ir}&Lo]O9WfO޾}G=& 5j#||U;OOOWWW  RRRtYYY83Č g,,,*ZZZdQT .,,VoM4~͛7SSSǴz:bDzyy?xgϞdW%$$t҅*@=j*J~%q؏?ۊJ7$B |>_KKd|b8r C  Dqq\.ϗŅr\*J$R^=s*N7~趷zzzD"|?455B.'%%~ӧO/^qlv*ɸNwqqٻw jz[C,gggjii>xa<>_)r)D"r9DB2!D.++#TRs N.\w޼yu|3IJJ;vlttS&MDv9yիׯ/^))))) YJJJ"&g2DY{{{NCCC}}}4.r~hwCD۷YYYZŭZjժ)5n4[nսpO?DvEGRǛ]hTT*_]S">j6++HQݚvT* }|>Ba2ZZZ >4~xkfMH$RIl-((PT.T_XPv}bX ap~۶mS|>_= 3_`m۶m۶+Jerr'5/_---Ejemֶ]v_g޸qchh1cЪU+JҦMHQ0PyyytD"bUTTgIYsPYY(//WTT*q)7Lw߯UV=zk׮]d"##GY$VD[[nnn111'N5AժU˗{yyM4ȈrrOOѣG0ZT#++ӧO111qqq鸭#N766fffvvv8ijjjbb' lӬ,;w$%%egg1L& a[WhhҥKccc,XaÆPURRRu^lB,b~ { R7ÛJ%n]VVVSXxh4MMMpgffFPxTxR<G{zDRMMMMMM*j:?(++P(fff6dvbj "bPrrr6m qwJpG\JB<wDNKKCjAd6dY \.TT8L3FF\Ax3gΜ>}zn.񄄄L2On7=<EĜ IDAT} 1@-Jӧ''yժUą쑑~rr~֬YӫW::z4c ++QF988\v ZrcV^Dv-M^Jv!Pjj*11+sKfffmڴر**D"Q+m*))qF9!!!::ի`z./@+<~xժUo1bą ɮ !SSS - ~Āhd _i9p@iii޽zEpp0n+p(‚X vM65''+JNNFD"JYuJk "wDNIIAqQQ֘L&?&pFAGdhNzŋ߿.ܹsSLquuݵkWC!Jwx=1#RFw޽{.]tʕD<==ocbbbbbN:wڵ[lϞ=;vlhhÿ/޳gOx  }CBB=zRvW^}v~Pd]NEѣǓ'O?ܺuĤ@a2ƍsqq۷/4-999^^^-_Y޾}o߾yÇGE6m CZ1b2ڵk׮]D/(J~۷oߡC;UZ/_\|yxx?ɓŋ<:"HJJLLûwΝ;vvvvvv:uNSq͟?̙3UR(D3fdddT "h4>[mK$===<"㗙mmm"#|>^p8 *_6MT 2Q(??Ǐ| 4 nnnݺuɹz˗r !ٳN:}? (СC===8p޽̈+V뇆N2M6|>_~GyyYYٵ7 |b#2BIOOvP(lGd",ɐZOL&)zGdNG) MZN\\\-[O?֮_?/[ۻqxEҥK-ZZxqYYʕ+'N@͝;wx=Bĉs̩}/^ :t1cƌ>fŊK,A]6""ݻw_zŋ^w}!v9k,fҤIzzz im۶ر3%%{-^왬˩h n4h[L-㣢BDOePXX؈#>]sa:ɺ# CCC{9v7nh^=<<֬Y3vX]]]i/^ܹsBP(QQQ8v7oTTTt_~ڵkE"eB駟B+|QLPrn݌-AEFFo]t9{?\B%%%QQQ+V MMΝ;wP(>~sϟ?)uϞ={ݳgOkkfq-b- b``pԩ 6T*? 322݉P(5116r\.p88LtDfXT*舌'ޡvDofͲ"W\\|=99ȨCK,zjOnJŋ׭[3gμ|r„ +mڴ!ss &ر?޸qP( 6lN\]]=ܵɓ'ڄX,H$!CCj;"d2$& 2hr8\^VVFRLL&A䒒i4ZՎT*wDnfh{޸qC=2>חBy+,--/^\XXz7oޤ?~n۶-5VϞ=WZ?899ݻwYGG~سgOZZeUnn ̞={9vfAƞ={֩S?o$MF.\xO>eee92>>~666FFFӦM?SSS.3zinn~ZjM k, srrB@}SՎ迎, wDVTD?P(eee:"7 2)lO>}ٵJս{wmm;w4ѥRiǎ5ÅÇ t#F\rV;`ҥ84ŋݺu -RZZZv<==Ȯ>WBBB֭ǍԳgF[VW&uG-l۶mǏOJJ*))i"v2رcΝ{BPTMrss#"".^x墢>}L6mĉMv耀GN6ӳ6Y#Gܺu+م2)7oܼyƍ+**4hРA MŋoܸUiD"׮]6l؎;p(.[޽{vvvіĦ9s$''EDDXݻ'$$X,OOOwww??5k Eߺuk߿߷o߱c>|d;6UfffV5۬EFFرs8'OL4rٲe(ʵk  BC```]RgϞذX,{{_۷ocyr͛76l6.ڸq/LұcG6- ;wwprr ?~N߾}wܹyfL~㩨G[^`/0 fSN...˖-s|֬Y# W\Iv!ԕ=zw&Lx٧OnڷofB߈B4u뢣qkfѣϏP(dRBqN8uyHahh8uǏ]pϝ;dɒCrbbLv!XT*ڵk$22r̙߿0amݺܹsϞ=+Jɮ5ݻwѕx<."(33STVP(,,,١J{(--e0Bfikk+b,--7,++÷™NvЄ/]t֭3g422"z;dȐ]UީSTmQ;ܹsΝkZS/:zM9O#N8Q&*\)r]~&k?sϞ=Ǐ={6ٵ5~k۶m6m"ѣcǎ9sd] 4]KOO9rիWJȮ si޽{Nd{V'=rȔ)SgM֯5jԨQF}.FҥK.]V\~ƍ;99999D"r۷^HHȊ+*m8lhhX^^Wi.k^^&b`T "X"T*DBijj"p@ ի\\\.$''ߺuÃBhܸqǎ#ocǎO>]KR*ƍGv-T*UF5utq++u֍?>&&-,ZPМ9sK !t}Ќ3p Y`7o444 6dȐ74T::::th/_駟Z#۷{补Ev!|w1c[ZZ:|ccݻﱱdF~Nj/V$B8-Qu "hjjV툌H$G*V "d2R&RKrqGd:NP NoAd ` 0ܹs=u|'}Bæ?~̘1醆dX`'ɮ=zׯ^"h C1cS]iyyys̹p”)Sbq#=<_{bQ(pfqGd_4ǖ2*Q#rMJ.HѿYf-\Q ]׋4hJ4dvΝI&] _JݻGĉ.1YƆZ˗/_r`3f鎎5}RܹӷoF.Iݻ'$$ 222޿?"TÇkݻw޽K.]rŋٳG}ͼyo?󐐐G0uFlMLLLLLz۷Wʫknٲ/?{lرÇGv 7@Ȱjٹ?12&&&&&ԩSu 3Cz-wחF>\C2dȋ/|||-[ɓ+9sܹszڹsĉ|~IyaFFƨQ.hZ Eaa!J%HJ%N KҊ i`.---))H-oT*%Co1"[,KY_H$:ɩ (>ħxJ:‰d555Y,>[6rn`0Fhd'U*տ{۷X{&M>}:63̿M}X,}6BH hiiUY(R(@PSJRd2DπT*!455;"WTThܴP۷_zu͚5'ٳ ]k>ALg ]Ξ={Æ kFs~~MTdJRTӧO>}ZKKkԨQ'N>|z"9??7n!o`ddbŊcǞ;wG}@```XXqqq9sJ :tPhh̙S9sf3g466F]p! oڴo߾7oܸq#N^[!ԯ_e˖uܙ'$$?~|֭֭sN]*.GĎ93a„ 6DDDp_RRqW:(1qjMofֲ'O"Ν;o޼6m?3 !4EAY(Z]\7V{mWVquTRE@`E>ɔ3J2͹ >|~o۪Fꭚy?*'\E{vhihh̟?=(((,,lueJ?~ĉUu WleX&&&8Ԃ¸=0B7;iiiWgH&zBW1ALe2Yqq1߿ǿxEEE24Fc0 < IDAT &`0X,RC,`0~ K.]tٸq׏=hѢs?ǧG?3g*Sy]MMMUPPP9d2) , B52BHOOO <6 *RpXGGG#BlI*"lܸ144444ˋrjC,֖B]viiidWw:s̊+k_/^غu-[8= BH$#2'''W GlӦMClr7o|~)66V}r|̙< Uo̙3o߾=x𠽽}@@@ppppp7YW?Zlé  w1cxxx|Ϧ ۷cccccc333۶m;f̘UMCv),,D81\\\,D"P( xE"^ջVpp,X__Žo̴Y,3Lmmm&IRY,X,* wF~lsrIIIiiiiiP(ĿoXZZ1]\\bo IecuqDٳgϞ=l~??~iҤIƍa&L L&iiiY^^odd!ԢE/vD%:"8LtD&_숌nl(9% LIIyMENN۷H2eӧO744]׮]۷o_TTٵԣ˗FEE^GP(ZZZZ2zر7n?~W8NQQћ7oΠo޼i׮܉аٳg۷'z@ .a^R`bbK|g>F镖[TV ^웕!ނ\.ǭp%wҶmۯybϞ=vٵ@dU4 ёH$G 4hD"iꏵǏ/\0}t<ٳ &$$>|xڵ̃"j_ggW߿_\͛7֭[9{Q5cujɓ'.\6ms… ٥؅ͬYf͚K.}3\?:`k5{7K޼y߾}p7oܯ_?.6~ رc&L J !ʏ8LbpܜS!puuu:X MMM.[eee8rss|~JJʝ;w|@ "l6BLq\333###W7gC__aaaΝ[dsppŋG~;vPTF344bcc/G*,,"3L?V"t v**5("R(8,g |"@UO<vͦbihhTgD@ 4 ;v?~Yf.[`ݺud\.p8Fر#>tP///r+~ xqttcbb6lؠ@hhhBB®]BÇG:tgCCCkɓ'_z~+,,trrrttNOO}y$::ZKKkgΜY~}e߿{Ç(eaay!۷obdur«wΝ[TT rIIIBB۷utt:L~NNN_\@. >-rrr˗/_mmm333sss+++sss333bk6(JPPP``ӧ-[k֬!u Xe-[N8q޼yd@(,,l۶!C6oLv-u)==n .$hh555U*Nhhȑ#===+$ E~޽{…E.))i߾}zz:1g۷oG/XT^zUXw.\N][hѪUxYLR?>00P}W^MlꛕWu{Z3>|@:~={t"e˖Zl'sƌG͟Qz5=U svwR\hњ5k3vXb>OJJJLLRtppUiҤ'SRxYfcǎ/ǂխ^ ,2eJiiKC 垞7oȘ0aBFFF\\܉' &lP(JFFF%%%vvv>ܹsttĉO81zhT}@ :"7N2eʔ)dS...UQNNNff3مP78ի'M4z={l9 ~4 J92((`|q1 ӧO.X`ܹQMS`0߿~Mf(ʥKvuȑׯ_#lmmG1~2nŊ Zvmrrwttt|||Fӧٳg>|u{{L6 YyMX,ofف{QPj*:]Ǫ=zh߾}nȰҥKnBBBbjkw޿?~{5牉IIIIIIiii:tڵv #C+//!!O,+JDRTP&yyա[y>Cl B! m_AQ(8^|@;DLzյrژ0L @: *+qn!333++Çϟ?C~~>^FWW'[fcct?8TjHHH޽W\9}Ǐ>|{IPzrT| +h"??v}!;&̩wDV*bF ²2CRJ%nYP@Gdh\\\ иf'O6lXNNٵ4\jff۶ms2̀RݣRdSn޼ٽ{ӧO0Z27444$$xT;wΘ18**j]$hvpekGqqU6mdhh뛙ɓR===www//////OOO}}}+mb֭[GZ aBDRVV& %IIIH$H$EEExj~7$e󋋋DRY,Nhl6[GGGOO`0L===6C,b1Lb ?"#*Vd2 [h'\.Əg>to߾MMMAQ(sssHn֣GFcǎ!C|Ϧ>|8/ *d8)?Sc HV0!T*hT[/jVDLJT"2ň+Ĭ߽{'ˉNVO'X,3522266&?FN.œ*<-//'VrĤe˖C@c@mmmmmm+̗dYYY)))r!pq?:vѣ9s 6֭ug\ߧO<<;;[To^^^ZhRp_B 2*..f0rL#2BH$7}FR*r9vGWTKǎ͛pž={oߞr`1bݺuǏoAxx[8NPPʕ+_U*ǏC׽y5<<]LbbbBB7.:2qٳgVWVsԘ b߾}ӧO_~o6o޼ѣG;::]ZCWվY^mzիWxyy$7ZcQd/^_ .ӧOT*3 ZӜ3ém~]taa̬,"ޘ522rƦ<?B\^ \#''OGRkiiMLLڷoϘhoTtiiiᄱҴRSS{':88988TNo޼SNcǎMOO?|pK9;;Ad333L&\6ϯ|paaK8CAdqT=LӕJRH$H-Rr9~d|# siPO-ZtҥQF?MR`DD޽{O:5tPkc%%%:uJOOG~&L>}ӦMcǎSJJeX,V-}ϟ_JT*ܹsԩSN ]8p`Ȑ!Ͳ㏁ZYY]{ ";;;###333//Ǐyyyٹٸ/㭆<g%ܤQwJ*(T*Dz/j@'lz.̌ZZZZYY⎃kJJJrrrp>;;Oi㜜PH,LӍyNDX1)ccc'''p 6]K]:s BhƌCddT*3gי3g޾}{A{{o޼y;vLKKxƍ322-Z~iJJJNGv-ԗQFرcڴiMUFRRҁ<f@]R;wqFllŋ7mڤӥK={ɩ)~ԚH$rʕ+W^~ѩS'&IPJJJ>|xڵKfL&G\Cselҥݺu$P]J2##g))))))?~T*!6668~ qmmmsssss/Jt~=OOJJ×ST336mڴM6VVVMkT;FO|bihhx[sqGd<ȥAd:%6DGdMMMR Adhn"""Ο?믿>{I!jdd~1cdSg޽{۷/1'((H=dɒk׮ %((hb IDAT޽xL jhh,\BߺZy2eJqqƍ.zDRocǒ]N(~{d͐&#222_~ڵϝ;:99ݡ4KYYY{GRC@@u뼽BA,Ͼ4--Mi^^ihN GWڵD _%D8%%- ڴiӮ];VZ %ihhXXXXXXxyyUx)''7~}JJǏ?c@:::8ܦM6mt܏?~񱠠XFp\33;l1r1Anݺu놟T?GۗT*\\\\\\:vbllLnE9xt]!SSS"\WfhhXPP```P#2 rIIH$4 ed*" OTÀ 2 J׾}9sl۶r_5jTRR54:$&&^v->>>66iɒ%.\4Ev:tPll,~@31}޽{7+vJNN~ \fee5f̘1cƨTϟ_~͛6lvvv&ѣGݻwTj۶m=<<ƌ):N9NMas999ӂ}v.O\n#l2??Ν;]?8sٳgϞ) mmmkkv+""]vm۶LLLLLL*4A O>[LaccӾ}:oʪn)//fԘv 79655[P(sJJJ>}G?~|ҥ |ҩS'CnM֢Eƍ7sL;;s⩩iNNB~hѢ|1\^^!+) LU*L&S"8,jzD }2<***,,lݻw'jٳgO޽޽k``@v9u.\>}:sʋ̚5k֬YOtٵT_p)Sɮ~  C:tW222߿8q کS'{{{[[ԗf&???99իWO>^|P(,,,\]]LfC̵/WsaawV7o޸qݻww?"Ym۶mAvl8zd\gϞm۶ w7d8޹sVZ}sDcWq ѵk׮]"ٳg8|rJզMwww|3sS5 >|ժU+W|0))Yluqqqww$9o޼I&_#`4֭[D7B"l`` 2ɤP(L&S(wD+-- E,#oהJU*B B9|/rʕ&1xɥK.^oi"""ۗ|8v k5 O|mx4 3ܖ.]Z4i{޽Ν;ɮܯ_ϟ]7$%%8pСC04+BHRx"99O oڶmkkkZMMIRRR^zb2 ppph߾<8r#o7L"''>b8!!ի=H$&&&zzzuSP-֭҇[qܶmB@Cb2ݻw'b޽{OBB¦M m׮]ufiiInаaf͚hE?zh lll,Baap >|`aa>_SSSWW`Q(",pKF$YTrܰ'NxyyEEE-Xr֭[~~~gϞm_W0߼ybYF=ѣ}ݺu+##ƦK.ݺufÇw}vFFuhhرc-2Mׅ ?=z֭[FW@Po>>>d(JVZjHW^%''x}D"A֭[[XXUH$o߾%2x:##CP ?0?nٲ%%7y Ӏ 55!YySр qqqqvv:uj@@JM)ʧO^vի wԩVVVuU3 O__?}6~ŋ\///;;;//:. _1:t(BH$ݻwΝ;w9tPYY%%k׎b6|{Ν;}||ߏMMMr9EAd' B, wD.))R2DPT#2N')V舌8#jz*F3pss9sql#צM~?ɉjرcs;|m*z!c;vܵkW,Dmذa֬Ycƌٶmr+V:t5|ծ]>}Jv!jF[[I Rziiiwg  333333322233233sssq).O?xyy~ F .]aÆT0 ϩ]>}t˗/x1cE׍7i=_ӡh*u֭[7<6l={68p`޽?y͞_&=z(!!/,,411mݺ5ŒiҤI7ouX|||-[VZ*ʼ<"\ 2~"3 P""HWWWR!DBqGdBAdh"##ܹӦrݻ!!!^^^;v1b&lҤI6m@Yf?~|ɗ/_&/ .:uٵ^T*g+|Ƿo߾{.######+++11133/p"gy<311%P999DC_" /ǎ `iicǕXk֬Yv-4@f<]PPőUfLFdɒkhhTD";qĹsb T-#F/U^z>>::b]h?~Ǐ|>?;;;777///;;)HZZZfff-[tvvc'(@}ST&Mrpp2elF b8r]u8@;wܼyxҤI'O ۶m;}P(ܹ5kBBB~ 988888L6-77ԩS'N bXSLqvv&@?. WWWWW9sH$7n\~رc*- SNM%~嗨;w?N:ݾ}{D͍bU^\WW:P LǏF!=="|s8&;"#d2c'NϟOv9ե5~+WݻgϞd5LbŊ5kָ_pʊh~׈={6aAv-H`lllllVaT*%r9999998y?NMMMCCj*144400hMT*-P# K X,cccSSSI<@R8\%jB!񴸸OA(b8C f2SŞr!W^jժO8OT=zt˖-<СâEBBB,,,j}~hx<ԩSNu...:u :t(J%@?4˗ܸq#..СC˖-p88lnnNvkРAgϾu떿V֭۩SB4 ;;!dll!y/X,Phnn. ZGd}}? 2uN!cDBqX.kkk76BD:5k,'׀<=='N??~͚5O86m\=@ݺulmm.]]Q(aȮ@㢭mnn^׍>}"ZH({iyy9Kp8ψil6A44LVYaa!1AL+XTϸ;1N'@c&~czzz]K pMג%%%U瘳8 T*uJEEE]vOJ"Py:FU TT*]vÇ333bq㩓`aa1cƌ3foݺuر+W;wapr  B]|9""B$988wnnnr@VZu̙35 "{yyZJ  \@ 0Zӧ A䌌 &DYOOO$wDhDY#2O@;w>yD/JΞ={љ3g>}z͚5cƌ) ̛7o߾}O|1˗m۶ /uh׮]O>%e4;;*DDZ"\PPæ޽#xQBQ麺l6ե,KOONt: FT2PXZZ*VBZTT$3SuRfff_ 9-PkcƌmT ꏦ&p85Z+11˫( Nv~q޽}BActܹE]Ex{{{{{gffXbQQQѽ{&.L2E"$$$\tԩSQQQAAAmf {-[( APMMM?~ryyyAdB*,,Td BQ9\ZZ F#Bq@.c4BDAPv}.\( ϟ?]AuEЄ ~vZjus΍7޽{/_677'(-ZqaÆikkozhdX{NNN-[tRΝ;߽{!djjcϯ$PRRBѾ9##})L!DJDGd|4tDBN92h ++ &]Nmtŋ/^XbwΝW\Kv]oڴ)::Z$M:ux 7ۏ9r|>J*J S6p%III?z(h]uECCaW] O `SF=8 hE\ DcIbn`!B׮R9l0?ZJ#ȞAAA{{mnܹ.Axx[8NPPʕ++,Rvuׯ_#lmmG1nܸZ^L./^~%a˛7o~u~~~6mΝF=|pǎ YYY\.qկKUT7n>+Q(SL2dHh̨T*|3K.qrPcիGb888ƍt)==!kFRzG̹sΝ;wbcc/\P"##nݪ>',,lΝǏ?~ٳÍ܈W322222[nFYye#`իWG 8ŋzB B77LP^^޿3.WƧOi&bcǎ}e۷oǎwq/h>>>wm%8>~xԩǏ`#FҥKӊݻFW:w,={fjjZPP H d2Y@6),,dٕ;"%%%2BHOOO,8R $:"+FD]4CK,8pA޾}Kv-% gϞ=zpppرcGM/[/55uڴig} @=}cŊR7O"}!ٽ{/*\7ٸqƪU6olooOv-Mȑ#;p@":p@JJJLLLM!#6mڔnjjqƬ 6_ ڵ+..`L6ӧO<׿x={'OZÇ8z;w[>{;vɓ';^[nΝCiiirrܹsU*բEYy#oÙM7nܘItƍgΜQ_TsGEM8ӧW.]ccc޿qo+"d2cbb^zuAkj,22޽{o߾9sfbbb׮]۴irJ|TuVֲ122{Jr**??¸#2&b RbPHtDƃr;FjTT*%M#e+eeennnvvvxf۷siѢF1bijgȮ|/LvI|i}d@s}MMͯIPN<`|3k֬# )77rРAdҴyxx3*!M!RBh̙kEFF"uV]୭^Z}&Ω߿_}kB;vOݻҺukլ\pmu_~}x<~\aD;|BӦMۻwoVVV| x峁;!רQ<==ɮ^^3g7nXPP@vQVӵ7x Pbbzi% ҫW oB 'OڵT۷o#tJz ~ۣy"^~bٸE/ N :TP]NzmTTԽ{{q &!%%eҥC a2/^|ȑ#a4(jѣ4+!--+f޼y/n=`RATݻɮiKMMݻw}s#F5jBիWQ՟|!4~xMMM *ѓΝ;:u*-- }W=!NWs/Ys׮]۰aøq,-- PPPPW>,\I1dرcJJ U6***==ɓ3fhժդI.*=zzjM{ J666FKaIT*RGdH#˕J%NǫP(DR#R@EÀ 2SSӘ7n,XZ Ŋ/_ljj__jǏׯ_ڶmC :͛7/^ի o gϞj N*޸qaHJJ:p@tttcNZ7n܋/.]pȮi300dWQ/6x]lmm՟lBP(JRT>7Dŋ+/_ BR=5sCCCb_&jqxSRR֮]۳gOmmح[z_ߣQS#|>_$MNHH5+W8::4ΤoZZZvvvܹsVVV^^avv6ѩEYT"JJJe2D"!4MRցT*BH Ad\Adqyzznڴiڵ'N DR{q{!CxѣG_p~Ϟ=ݻw\jUNҖ/_޺ukyQ~*B1eʔ={<@۲eˑ#G<خ];ki>,.* .sΞ=>|X}Bvvvޯ3Bh_r!PttP({Q5cujժNէ1bMjf֬YqqqxK.Ug:)2ȑ#5М̙3ݻw>6lc:IكP2TpUhu[ZwjjQr]WժպbU#m~ysNθN"|uϛ7/==ҥ;Z[[_tZ[kڵknnn3GGGӨ$T*DKJJhRLAdY 70";z=ĉL2f̘k׮Yr vĉK&''!***>> YV=9s5k6m4{{fggoڴ)""u | mڴ~ҥ5 999~E"!{ҤIGQT. 9q@ !+V#I$?V`߷߿^t-  ڵk-] ) ///l BgϞ#111*xQdb…/b4=Zf w#T2MƏOp8Nj;YfUI4iJ՞)RV^d2KPGweĉU|{VfΜRݭ&LйsqEEE^z3j`07n/6mW_ݻw\ZZJ9x`v!<())!l[[ۙ3g;Ν;WXXHa046tDutHgϞY9qC;wnϟٳCϞ=?k4K٠deeӦM uss22r˖-ǎ{a!?쳄{/((۷if̙#G 4ήSN˗/V=bUD7o4Μ9}x&ML,_|ܹ~~~__'O x!!!SLٷoի 6̜9w߽q/.cΛ7o#Gt9uvذao߾u떛[\\jsN/ծ]۷oҎιW3rN BB! JZyFcccbX,#hmGdw@zydd$ǻpBu?޿222=z˗/Y[[i&""SNNNN. c4߿RSS F```dddLLLTT˵t@d2{wi^/H}ի?ӠAjhu떛{5K?~|bxі.jҤI6oܳgSNY  oǏGJ4f݋߽{D"1cƔ)S,M///ĉzV mڴO7l JΝ{…7nYΝ;[n0aBS-Z>|':tpB{{޲[bůz?3""3̒\.ѣ=zp8f5jw}W#OHͲtɓ'#""zyFաct(^rҥKǎ'ZzGק$%%%&&&&&޺u綶ڵ4hPdddDDh,!EEEńFCIQTZT*u:ݨQX,ֱcǤR^ NurmmmMaŊRtΝl6|GoFIPHDV2dHJJʥKB%gώO:vh۟|IHHHrr޽{wI=zj._|SN 6l \˖-wر|k.[l͚5h899 "jՊrLVʯfڑdAd Y wD&xNNN.**bX>>>s  C^W( BVj\T*jFIbZ]ZZJcr`0d2njU*UII+ P(|Z/GS8ZLg`0T*L6o޼2UX,f2bb Bkkk>occl6áfZ-1bH$x\.W$U/jxϷj4dnnn{?~'|޽{yuޝ6}sU^l{̙3gΘ/Ց\9x+V?>""… ;wtQʕ+.\iӦK._?9sP(/_\M !/ "K$B^'d&M RAd.hVhl6[ƒbJKKY,0=D= 2@9}t׮]믿wl߿:mk֬VVV>>>>>>>>> &,ףG=z?w-|>um۶;vlHHHVgx\qqT*-,,,挥R)\FCQyO% iggG&$ 2@y:nСΝ;sLHHiuvʕ}ɒ%=z .\Xz۷U*U@@@ll l(,,ܻwƍoꈬh~hO4S\\L;"BllltB?:"iݺCz5~~MF@(**w^JJ M޺u맟~z9!ʪiӦnnnnnn...sl쌌GBLfӦM}|| جY3|=T*+(((((ͭ0p\'11e[y<r<O( BDe2u|3CͧNZggJ6RմZ~J2enɎu<(X`;v'?ޡCKӸt޽{ [l3gΌ3z1dȐ>}evҥK.uyĪ< Gٷoߙ3gGw^>Rׯ3g_|GmذaժU{o^R߭[Vڵ۶mUVV/!$??LbggGG0T*utt|1 "ӎd fX/ "t:up87oFB]vڵ3_(Ji.NMMrJFFR+ظxxxK$DbX B)JL&Mr<777###'''77n`0]]]݃ @<{{{7аggggee_izk 󝜜h2UV)UoP>Yg''nP( M2Y0:Le:ÿi^wn4Fqԩ;z[oer͛7Z&,ǎ;a„!CĔ\46 ĉ;u`xw&,cӦMۮ]5kDFF[jpn޼Y rXXXqqcNNNDD!$??YfeVS*;" BA7V f3 H UII :"@O>P(vie?U"H,WTϞ=1u|y8]!XL3|>̣F.LCVseB-[twwwwwwuumҤKaz>7773361J֔H$4dЬY;MP?^^^/]SP:Mݻwѕ4iBH~1|$b̙[n~mK #F16lh߾}=zt׮];{3gnܸa4w_~78!@cФI-[1bΜ9]ty?3ww8uHH͛7njSB!vpp ˰Jb|YPcjBh4\.BHtDiGdΝ;8q"&&fĈ"&>P:4,J Rc"Bd2_[[[&"hge 2@cj>}F'RSSL#bwwwڧM64BCC?thQ섄l'4R'Ujժoݺ_~?;;qƍ7N*;w̙3;w\dP(֭[=u  ϟW*>>>=z5k[o%-] @yʕ̟?ӧOtAXXؕ+W }Inn.xL i/d yGd.k4i.6Mq Ȧ`F}%u߿qmۆ|^]W-]!//㬬,x{{{{{ͱwT IDATԀXvh,x<^-ZhQrzzzjjÇ ?ct3˗/_hц ƍgZbd <`Ÿ @)**JÇ>|Bҳ 66-]5KЛOZjUfV}왩wZZZRRÇF#hҤ'j`o߾gϞCZf͚M0a„ rʕ+WݻtRGsmڴiٲ%Ϸt/Ro߾M)));^:<<< E3f ڸqSM?,,Ν;aaaUߪM6لGGuDwDd@PZZZZZjY 2aFLAdVK}-^W^/^իWΝOݫWhdfdĄt:6ݲeˀΝ;hlmm<0ݻ|r(CCCCBBBBBZlIC+**CCCjmd27o~޽ϟ;88?בH$2L(,L 1L4phLGdK# U>}m'ʥ_|ڵkW\IJJt:t:ujppppp0 <<<<< 'O$&&޹s篿㏥RH$رc۷oOor?e˖.X,ҥy2ѣG"ӌꅼt3iiiiii khz葜})Fޞ2fZRRRRSSiƍKvv6]zyyyzz6i#6 Z6///333777+++;;;;;ٳg驩EEEB1xzzzyyyyyX,K0X{9s$$$o 2]LGdNd2DbٻwZ"GJJJ.]tԩ'OB0**jܹ;v {P F@@@@@1c!RիW^={֭[Y,VN-],$$$ʼnD˗/{{{[wX,V͛7o^fyqqӧO>|ŋJ`0<<[BܹӡCshjjñV*<OR BFc0A)..kVDt LDfϞݡCooO믻v:q^vm^^y{{w}w5 o>yѣGl:dȐ#GZFb4.]lٲO?tr;@r>>>>>>>j0hMIIr _3'b1p8b֖ [[[Prmmm% ]ϻVh4TZRRh EIIRTrX&ќ)sL'ʊCBB贓)^4rVVV_~e֭'Lk.X\=$&&Vkp=zH/DDVVV4(,oJtss3j-s4fRƆvJ6L&:"tR>}wգGKWu$''gƍ;vڵ7|ӿHd`چyz *\~CϮ1vڵknѢE)))w޵k׺uڷo?iҤaÆatHh`F}m۶;@d2iWS}|rym^^)kh"d҈X,f0f3 eE.>Z|Q1r`0dFh^W(/?dz5i8<-ͣh|gذa͛74hPDDĉ'<==mppcǪ{D;;gϞ988B _R|BJLAdht666^d2^k&NثWy-]$-===>>~\.wԨQ&LtQC>[tŋϜ9e˖q-[lΜ9FbٖѣGΝt9x<յ .))d4KEEETJ <8??E `bsBbФ0lH$3D4*]su:СCׯGGG:uuU0$$$???++ͭswwOJJcXȄ;;;{BB i֬͛~8k׮! >|xĈ}ݽ{wU6H$:PXaGdDT*mllwDAdRY#Zf4pl>dV5uJz}} 2ҥKK.?~PPЙ3g,]Ԍ={DFFz{{߿ĉ7`0Zvڵ !Jr̙͛7/7uرc\W޽^Z~:?puu}*|7_w}500peގ ={L2M6<޻vZU FuϽuÅBav$$$~:2䕭ykwԩooo@жmۥKV}zeǎgϞttKWP=EEE#GwDF ֽ{7o.[lϞ=AAA;vj. gk֬a0u-\p͚5;w.!̒ϛV^jΝ;vЪU/\ff裏<<c BÇ_Z $22~᧟~b2?rWN3>5kV"sΝ;wWNh,~ө\^ M>}eWVTr6m"O2%((qС6l 9r puu]hѶmT*k^7o^lll>}_gY|# t޽JV dYYYU߳3!ɓ'/ "X,dik .h\.!&)Vk "[YY1 NbDZfϟØI&~7%%% ޽{ wޡ!!!.ILL[xx/?PXX}Bh'ONmM/^~ٵk׎7iӦ{KSSSwË14ݥKBHnnkKU5}}VgKJN~?~bXL&3**jz=ҵkWNWqRZZZTTT||W_}}v+uoiժj  T"P(d٥F1?? D*" BBJ% Zxe !:vm߾=##cԨQXdI~rrrήoE, Ui~!..޽{ `0dgge/_NIIYjomccs7t*V^IW>wӴ!h4T_ߪO%'!z^ `z*Yedd~̋/Μ9 o.3E8:::::F!"&V+,H4 Q^xt}R#r 29le899-Ydĉ_}W_}iӦ3fL4z(&&f޼y;w|-]Ky!!d͚5>>>t+\رc3gΤG!4ko֬۷o[9s̙ЦM'O.^5+NHH8v،3LP=VUTL=WO'z̙ovutubʕ9e0egH$bj_YHdQB)eN4^,ϧPYYYcƌ9s̜9s,Yb Rlҷo߾}Pai]\\ryaaaIIR˸|^7M̓/Lt: 2WW5k̛7oݺuVχY4@``ԩS?C:X:ҽ{^zل~if++߿ڵ}t}vmM&M٤IOܹׯ<...!!a͚5;tP||{U~:[n>|k}SN<̙3SLJ!!![IKKx~`|m .pBpp'OX惫T*dV&:C,gccY.kkkkp8柜lefmmm\iƆHTᬵyY% ML&S$fˇG?~oer8SNW |ștP2Ad@Z./ bt:SvD;)Dje:"t:&YKW~_Vȑ#[n={ߘ1cƍgooot>}\xq˖-yߪʗqqq{X`_|a ]ҤI|i5oo;wR JKK3-M<~oGU{G)..nHj~ qe嗼r FRM8}4|d IDAT?ٳMΚ5k׮]999W^ ߳g{'JǍgmmwޘgggWW+V;v,66F'N(ʡCe˖-_|Ŋ}ݳgʤ tDhlll 4hРW]x+>~`KWСCg6l؁֭[פIKUbcc9k׮e˖1c a믿*nݺmذ)dB@ qƴiΟ?/VXA_cǎ .FDDtyРA_H$yi.^(tm6kZsOgYYYEKO>8pU}ށ8y,ޤې^57JVWVPJՓxtfE"ɬzy'Z-[kɝ;w.h```^^^AACwji8??̣b+(3-RT|>r5 P^TTDp8jZחFVkeeEAGd/vرe˖!C :u9rȌ3f͚5uԪm^O3iҤ͛7ԩSo^lّ#Gy7kA}WOzQjՓtu r\[[ۗ7@h4K.Z"8ph$33ŋ:u⮾ d26y 666ӧO_zF1]yݻ oܸΝ{ {1}~aĈRT</66VTnڴi…/޿GO1s Ј899͟?_~ŋ }zf͚O4 w q~O>$$$$99y޽;w$=uAf0N>nݺ~-44ɓztQfhmID`[kJj_vޯ‚ͧkoV f,SN---ݳgOUJV/O(JNg>Qޫ5`2"EŴQitšeժU;v(3H$→Dvvvl6_A$ѡMe2bl0ȿi>{;"l XZZJJKKF^gX.4F-[lٲEΟ?O?mذaٲe;v4h;c/6`iӦm޼9>>~oָqS&*Ԙw/:kdgΜ9szum@xϞ=۵k׶mRSSA(I [rZ' y233?cǎ1bΖ) FVe2Jh4*J&FTj4e2^W(:NTjZJURRh4MII)\-|>ں‡8ͮ \.ѣODB,K(Z[[|G1 .$ rU͚5:uEZ&ph46o޼ D"d2WWWP 0 \Rx<DxFDp8L&`hZBHii)@ȵt! xXM6o?>cƌ-Z;=`|>}ѣGm6|p.ۿبj`.\z۷oT #4r_tI$ 2>t]` >iMMغBՓt x<)qFm>cHtKWP RT*Pr\Rj9VFPT+PbhO21\.Dc\.xb&$D"1MiWGO 4 |矯^|y```LOOϼ+bz}X,3?7ViGdfhAiiyh4t:tDzƦw޽{W\9~cǾ+Dҳg޽{e4: 0`@vv}?X@KX*y]{kd]tҥ' ^iiŋ?~Gq~3l]ɂͧko^<'O>yqVZ%-])0W&gLjV6660+˕H$Φּ4*{ @`337{mqq1M*ͧ y#|Z]X&,HeP Λ7o̙FńӧOW}?...GGGFȴqNBPبT* 2LwAl:y6D,SN:u/SSS;vر1ctЮ]vҥsBҕ4.ӧO>}zNNɓ'OJZtWw*I/}q?ͥhVZzM?u B1iQӬyo]@dooO3#[l6 ]hg2)sBo?H`oo_&LCLT*Iz=:uԩSU*/\p9q6mLp@2zѣGtk׮PƍFc#"""###""~SNEEEu-,, 7zԱ7o矗/_rL&r]v]bE^7on a|}~E$!]0 ۷o_d^oN Zٳ\:hjL&_NNNtN6x666445 #? 77^LHHӦЬi(I&iGGZ>3S3gΜ? L/.۷~iD8++DeBU*V-))rji4Y՚wD6 o6>߻w޽{Bd2٥KΟ?5k06mtر}:ttUddddd˟?~իW^|j/ :DGGw֭m۶tqץK\r-V駟vرM6"[ >l]{iu ¼R 'ݛH$B *w>ݻ3f̘7oixnWgϞƟؽ{w:njLS)J|Φj^帻jܸq_l2ӓU"hZkkϟT,d2|P(T(ȄJ4 OV r:&_k~%b),J/]tׯo۶M899oߞ„Bhywy钒lٲm6@)--xi0Lo߾}:uꄛ^YIIɽ{ܹtΝB++;N:5""iӦ.kIkRoX`Һ|Փu r\|xeO>={woMVSRR6RSSiocկ_&M7B>*Jh(ٳg4FOB<<}J揊bT*HrrDji/d^bH rt :"@$H!:Ν;׮]~Ν;-Z`0ڵkA+ꀕU۶mGm64l0>|rʕ+WZYYv޽wݺuiii>LNNt:2dȐ֭[lK x!l&U*V}y 6Y@KPJJJ6mڴl2@w TBզ<|0%%ѣG))),p٧OSLiӦq D"lٲrј._^ZZJD~~~~~~~~~~~~4r LJ~z-[̝;.iѢŃ{5kӧϟ?wvv6T"d2//Aϟ2 !4[Lܰe0FLGdBV beeզM6mL<"J_~ƍ۷oiiiHnݺuPPP@@ CKt߿ƍ 00ߟ~"FÇ볔<~f999~'!!!~~~MR[דhu%WO5H@^s%KΚ5kΜ9\.E@j߿oҒJo(utt=zˋC`0#""̗ OP>xʕOzB;hݺuppppp0F,D2v 6L>Ɩ|}}駪ܹsݺupBAAA X,BP&/iiiĔ?VT# "BL92KKKDҫW^zYLxuE~~~ @*---((˫p F9h C ={f4LfӦM'":LiggWPg Cff&_ Blmm}||עE  Ԫ&&l]IkFC $?]<>;v/M7?ػ󸨪gf}Y6YEYDAAEM-5L%M\*JMͥR\r+M4SPQ4AAe@aPA|s9}.ryFF>Ogĉ,#ɸaÈΆ233:P(H${PPPV?޶m۱cbbbBFBeu{{ꀀPEEEh42AdBP( 2> N#8l0 ٳgKR9|pbFC|s΍7;Fr8DF  ō7]\\T*** FII CѺu?O4 _Əv]>lӝ3lT* CX|Mr\@ݙG\277wY|kRTojjk222 у:>ի/t??&›7o޺u͛/F@  ߿3m6"XRRޖbBYYYQQQs|>Bֶ޲(JS^^j!d2"N㨱eWnll=fN 5|]c0pJ\5''ĉ2 ϵwot3߿>nѣVZP(lyE"QYY1I&B-zwpnLf`````eNoUWW8X,t~[p===q^[nݺuzgXA'P(JKK+**+**JKK+++#STggg8p wttį֨Ա~uTTԢIATz\zuɒ%gϞ}8;@8rrr\r˗/gffFooﰰ>wbc qcIFq֭m۶566DrXXXpp0\̚5kС7oݻ'B(//AdDb6% BMoBTBp8*fT*[[[2VqeFFCRq9V%T*Gz`6L`">*+8Ǐ/,,7#P(NNNݺusqqqvvvvvvuu {{{ xQ444z\$al9YSS*7agg' <<>>)))--W_]zuXXXN۷o߾}dCCv 6#FDDDt+ѣǏ??D">7lذ/P*666%\B4ɊlRI$TDidt:d2!GZF F"meEUVVYJJJҚNljnݺbT*HbD"J"WBSMqI2T* BTG~;;gupp Hf9$$dѢEcƌy_l?c6ۧ`;v@1LHdoo/E"H$H$"H l6wXl#TUUm{r8gȐ!R.Gکˇ^Z'lP(FcBfju1K$Kݱ[xp,/\_ZZjoo?|իWϯxt\ yR\<>>~ݺu,kȐ!/#<<<=D5k׮]r=<<۸.,X,Viii|>D"H&AdhLJBX,ˊ8pAdyҥKΝP(pƴF&d2ΖdW*h&dΎbl>d2Y,r3jZ/@J$jl9i:L=NLID"\ IkF9|ƍ333>gΜ^zsDz3;pdwh"B ;w#H$Ghgg &L0!TVV矿^^^Ç?~K/=~?={gۃƦN&5kccbfR4Lf:S8LѨT*"2N9! < G=zx\RYYYcQ]]-^zZFc y8>"t3+v444h4UFihhPTz񨯯W8ݨRt#9&:q<f3L'JϮI7/k6mzſ Czz b  IDATw[xK"vH$-lS7/kOZ9bX|>f6v)8|>L&| rT*i!f\.7 B׫T*˗$uR4 8_WW_q*J.RlX,ejťwͣw+1X~}ff믿o߾7~zakxk$X|Mr8@d8tիWnj3NXS Xf̘1c јiҤISN*>?a„۷ 'ڸ"D 2޾l67ƃFɤR8r@&-+"#LZQoEdAd8jaNcMMMmmBeeeJRTJq\[[[6fmmm\.Ţ|>`⼲ F2ЃcX4 i43H NjMd"%Ë$/jdQ_xX3jpbǂ-"^ĩ b{t/&5{hP$ |C]@'؈ =zΝo-ؕt֝$Zݼ=aG[iN7w :RSSE"ѓuǏhQQQp%7hVsz)JxxxxxW_}u7o֬YSN[?3f 4(==#??߲q$Iuuun._\ZZb9W g)`tDbi4a0f-^񷬈 Ad`kkqPBT*pzkT*:NTڢ8#ӽ81i}h5ntexg1]]]d@ HDY"xZŁN(i٘L7n$''$''WTTH$z+""{/M|f#&i~Fh<߀'B͋#>:&M͵}~n<moo$⚇&埛$wٹsmjjj&Ngt54aLZPH:I~'-#G[G~ɠ&fsBB7|e˖ɓ'w|v4 %__UV}嗉;w={_|';/l5p@//}kZٹ-+jPNNN 2 ZqVTM*" 8``DnR b${'}Jvn'f\.Greb"T"͸7AD~牟iZ;NIIzB`0^DDD~)""WBٳ祗^eyr:78qB$͛7wm,>9OVb-X|MAh^7 gΜ믿lɓO޿V~|pʕ#F;w.22dF&>|7.^xk׮}뭷 mk׮?! D"dAAA$Ν;QQQs~Dc!dW*AdV?hZB"뉊ȸ TXd2م p8##`0H$^z/hRuhh(&l6ϟ?ʕ,Yh4;vl)))?ӤIX:t54u+i5:V'E݁ĭA[n⠿Rw!Hbbby着˅F_|z~YguK,Yl̙3goooquv'Oꫯn߾bܖbgggg6sssȸ& ~aY7p@VKcZ2L7n䊊 2hѢpGG6n*44L&޽"_o~ÇGGGCM@غs[#k$ dٸ$UF2l[l1co-Hjq.]m۶ٳg?O?ggg>},ZhС!ټcǎlԩS}]b7qM:M6>|833{ɒ%і X6o򍍍 ,8qP(6mR\bʼn'Fٳ?쳖f0/_~ԩ,5 ҥKO\eHg]vmݓH$zZd Q3d2:t(%%999NNN!!!DH=>>~˖-555cƌxuˣ%.SzHkQ}yxxq-X,ɄB!BM |*P(NF_{\LFAdNGȍDED&AxVű㔔W* &9`P|M8|믿N&;vӷog0&N8{o<`P*4)dYͱɤllR^d}}d"& eɤRՂ1Je|zj:aFhI6MLRTsm >jСCŏD"H W_-)))..>sL>}ڱg44fyȑ Mf5꯿Զ,YfN>=rHc:rYv؁{(WZuQbSN5}۶m|^?w~؎͛7/U1HA!>͛7?j̙?6Fў^?jm'> miQr333SSS=<<=N[bŐ!Ch4Ç۲ʎ;/^|@˹+W0 nnn\n]xxx=%t4qoRv޽cǎ젠 6O\ARMjWbk )iKh4 du:V%&4ͣ&j51U*1$PPP@L6|[!]t1Ќ3[X3۽{d2O>a„tޱcGBBywOn6]v>}~ Ş={曉'޻woŊϟ_~="())~ YxÇBeϹsHC>|xӧOwrr?~MƏopӦM9BhРA~ipp0/**ڿڵk-[vP\\Bh֬YsvƍBǎ|Upٯƍ|Ͳe˞^<ڶo;H۟먣ԩ?^5kVbbө ,0yyym\E"r*++duuu<ihh H*b "BHD!d4r@e4]iӦ &888 dr>}bcc>\VVfݻ .D4mҤIϟ7Ljb866?`es6O-;?#РACnM:ƍ”JZZ+vC{[ۚ5k 6=| BO>x2--m„ aG;;;<^CEEE͟?׮]#3tPО={,b--訣i%%%!nܸatvÆ ݻ7n.]B_,˹{e0K.ի˙L޽{W^a6~_~xbNN&1ͣG6m 3 33#AEdFզW^U(|>?""b޼y၁tG_RRR\]]?ӷ~["X{\xU0bo1a„t[<0w}QNj9}M6ݾ}qr;/Bq7P n>'==-7-#Ǝh"\E;!!aԨQ&Ǎ+VTUU%&&,D;v]BaVVBh̙3g4A!tΝ6cumu[]먣 ܺu={^ouyMT2 NKZ4vpJ%Ͷ7ZN^ORI$QٲA@dG7o^HH6lؑ#G֭[YSSsEEDD@ Φȑ#Æ  xb~~E tyF7pM2eee7o ᔕYuD$s555]Q$m*2?(N|>7(C{dyg\|d3fLVVB0L ~ÇhǏ߶mzpFh4Lx-lum:JPUU^JHgyyymY^,#d2P(xsB`[lRDdj4.jDoFrȖAd@d2_y':::ۿׯ_:vXyyyVV־}{=???!SINN5kT*4iBhϞ=eee;v#k옘ӧOWWWٳgȑ8sH1clݺz#m/ݽ{f߿߲_~AFxb>* }z/} PB999 6r8k׮{yy-X !!3g B{1?؋6=z[]{GY~P(ܹsjkk=΋4B1mllB!Ͽ{n![[&Ad"ZfX!d2j5.'gD~\BtTkhu֥KSRRʘLfHHo=`Y{ݻw~{7}-aĉQQQ뀀#G*ϝӧ_ti޽d2y񁁁k֬IHH2eJrr;Bo&B_)SGj*r?0 wovOlذJ?ѣ}BhܸqC;vlݺu~)BСC7o\o߾s̉pqq)))_Bs?~]]]```^h4ZQQŋsƍߋVG)gݻwo޼yɒ%gϞ>}:B(,,lѣGݻGDGG'%%{H$X,dvvvzIWDhMG&FNL&BH*6661L64 u 2ytm:% B"DDDDDD 0 88#@{FB M4MP(Ƭ멮>rȡCRRR\ntto 4@[6o߾qqqc3cǎ=yd~%%%999''d2w gcccZ* ? Rq?L&⦨rsBjj:!lB*Ɨ3 |VbBT*3~$<?H$~7[&zСCG|ɓ׬Y3`82` &Wg~.g"H'Nصk׾}I$Rpp… 3gر!SΜ9x￯Y&77gҤI1N>իW3ǏT'eqIRe=z'Y??^{m{}ݻw'%%{yy4h„ xW_}t:uӦM{{h[?*%%?ޱc̙3qP(1cǎ;~zGG^{m/ I*feeqyDR]]- ޽[QQP(ˉl6J%ķQT"2BHV3LZ`0BJD"/(z=ѦP(F5; `}f8yRPP`ccӯ_sK v2uuur?jZV[WWhZm}}Zj BRiZRP(ZZ7L-lQI\"},Js8|b;666uʼnt: Ya bqYĦma? w@`0L&cX lp8dX<`|>/u:֞[FM2TG?Pd2ٵsSeмIpBCCCPPPLLѣ/<{d… .LOO?p׮]pF5rH'''kS{1Zk[}2FsӧO9sf+'N1bD_o@PF=zQQQ={;wӹ\n>]acc#=<11111D"@Od*,,sNqqq1>#Ǐtsssqqqqqy(sxxxY 9RXXp@ͭիF1888**jgX9'd2G5j(PYYYZZZjj+W~WN'H@φ`ɹrJJJJjjjNNBgϞaaaSN #&Bx<\ ٳ6l5jLBӭ=FPΝ;N:L&x.W($IP?$l6k4 hDAd "Dv 2@뚇_y啈JRk]DMM͛7oݺyͬ,\DG(dhh(QU$Y{<\{Y8yd~~NCdD2taÆEFFZiiǏG566޸q#-----m֭ , Hzݻw@@@;I.C&ݼy3##֭[7o޼}vCC 8qbXXXXX@ 0[2tСCfee}ws]tillr=ruuj:99yС-,tzmm-ǣRd29??2,Y,eEd+=J%"3 NP((QYh4;Ado0:|WZZzܹApF62 7n̼u֭[*++BW^aaa3f!xQ(77777(l69sFT޾};33sBxzzݻW^!!!...V<@;hy!d2YFFƍ7222~5k лwoooogggE]]]nn;w233322n޼YUUrvv9rŋ=<<п߮]VZe˖k׮[?7o\]]U*UϞ=Z"#D"L& NNN,@P__/Jf3BHR 2NP(ZJ 2Nf-+"= ܹsΝqh޽A/_>x`WWWkϽW74@ ;vo^1B"̙CL̬}ժURo߾۷o߾}dذaÆ Ó:.333======##~S(!&ݣG///x#BݻwͭF3 `AAABRt͚5 ,غu͛9s,Z9۷o||~Z(zxxX|hp8Ad:n4E!`04MCClDq*"tF iii8||^߭[!C<rŋ}L&388_~|A߾}=<<=F#dct>x+L&'%)[[ېHX=z'D"%999;"UUWWݽ{ݻ& !ݣGooowwr˗/?7nܸ{ŋquu%H{Z!/ p8g2d25 FkllDDhd2D\t:]rrrbbbbbbzzhtww6lXllA=@<._|vDׯ믿ޯ_?__߮}Fׯ_~xRP\~W:߸q{x<ctGGGGG=(77W~OII)..Y:*snnn8LP<8icK8?J"HT gXup8 6mꫯ6mڴrӧ?G_tTbl=cƌH$2ήΝ#fx<p88F$ji4Rc2 (sgکŋVuvv5jTlllDDDݭ=@ z\nddddd$OLL<{ʕ+?C''Ru ЁTQNTQQQTTTXXH7KJJp@BH$R=NlחUVVUTT?:=$رcx+Nt:,kҥg^v~~UV?j+WWײg϶Dyyyݻw/,,h4zDMl6[ӑH$ZbB C:QFZBPcceϚdq>weZmgg7tiӦEEEAg6ߣG=zaС?CTTGx<<<<<},ZH&$$9rdɒ%2d[o`0=Lg(?tJP"ZYYy"EjcٖdH$HB;;;@p΁gMݿhXkkko'@ L^R)[q*gg]v-X`!!!ʖ-[ݭ=ߋ/^hQvvH$AdP~~>"3LFPjkk-Wp8JG-`FDZ"A2ѨP( Bw~Ar_Ғ@ :d2qfs-<=-IIIqNNB 6mZdddxx8h .|7 |>ӧO ttl2kϱS`0ƍ7n8Nwɽ{N>}ܹ3gΜ?>TO fٳgϞZD. 7d2YVVL&S*PT"lm&)X,b<bAeARVj\.ǍQ2Ƙ@ R遁DH^$ BHˬGN[hQ޽/^駟h4k\]]|~RRR AdXR8N]] \.K&-WAd6mY`!NGZm 27OeJ|}dyyyyyyiii}}\.W<А%6ݤl6V ,sG*:88:\\\oS}}d29((hܸqQQQPMٳK.rJhh^{5C+l:Lo={L<:[N0a„ ?۷n:u/bgggggףG1 Y풒S6LxdX,@d>O"^Bp\3h, 6c0]>ӌATzJРht:Vh4 jQRzh4r\Ri4J%*ͷs!rggG%ˡ*YG"L2nܸk׮^:..nΝ ͭNR'%%͝;QKJ$B1 Ad>jDd>ߤ"2B'M&"766Oa 'zݻyyyyyyeee,Dyc  w_<s8.Kǟ2JBs}}kךft466^x322 aâ"##E"r֬Ygdddjjj="{J}nN&''#MFP=gqŊ}پ}V\yСK.\9JJ$ lGR,j4|_X歮7LuuuxEm)L&Ďbl6 7{|djD>hYMDrCGaXL&{8l6.Ppob꫹s~oƍ;[ !T\\t:x+ڸVUUL՚+**]VPPЀp8xyyyzzt7[e6?>|Z C ywn;uo?>vXkUWW&%% cǮZfyǎqqq!Sm/f0/_~ԩ,TO||-[kjjnjg߶]vmݓH$zZdeڻՑ73⡚M6>|833{ɒ%d2cƌYzPcbyp}M&ӡCRRR._~3 _hp?VXyr?u6t:}̙'O^~_~p( m?Pm"6C8 T*q9V-ś5F4q88M$?&-ܸѣOΟ?ߏ3ڃ$822rYYY]R,UkkkO:ExA .V*"HR!QSS+(t:::'º h4nHOOȸuRD)SpHS(Z{>00Id*)){ݻǏ'NNNd2÷{9s&11ŋUUU˖- zboִiӶnb='T*WTT_fȑ#K.]tW>h۶m=s;qDZZӷo_bnqqqqqS Ԗ73bK.]f n_~}O9r$BHP۷!TUUuJ#?͛7KZ lKhc -;<{X,֊+cbb"""Ο?pΌJln0ӦM9ryƎ;u-[$1LHTTT4j(> 2ǣh2ݻs|>P(p'Q*nnnjbz^d25 U rCC766Ϲ89`5_uLL [pavvvhhQ[[[[[{ʕ\rʔ)W d2-**jׯ?q۷5ͽ{.^pBX|ػ︦')+,P.pUUW뮭 VڪXTG](*8Ђl !0}+9E!s…3gvM__ݛs$Ik׮\vϞ=KKK\b 777H!}ɓ'7lPPPcgϞݳgݹsǏKKK;ֳgpB0##T*μMFbbb_ O IDAT~}ƍaÆ!lق7EEEFEE;wNDhXBÝ=z!`G 3gxzz~63R;jh~սs΍O%ud7n5;bZLLL=zWv-..N3 eРA)))422 t:N MW!DDf2"2BH$1 DŝkkkqX&rD"uOJ;wׯqt̘1!!!N;۷/ =q6nxZmϱdffرcܸqݕŜ9s>\TTuر=z }Ԧ\]]BD OB˖-S}Upp0Bh 8|pPLLjcRRB ?sΔ)StB\rCj8s&Y5l ?ӧOA;U?!dɒ45";l7N}-~^|I<@{(..!H@QN8"A2dB?}uAǏ1׬Y3p[Λ7ϯlQ3vȷo<\;spp2dȵk״=h:u:|LLLllۣG;\!4x ˍ***x<JgBG$!jjjl6["0 Պzzzbf#$jEd\Q*׎ڄ\.}O?jee?;̙3ř111s֭g鞞+V///ضmcƎkdd4nܸݻwwEP]]]RRҏ?heehѢK>x𠼼ɓχH:Bd/_LLLO,ޖ_RZ5#ݣGէ"\. ƛeիW+ O(7o޴d`llLB"h4L&!\N"ry|-Ad ?~<8pٳgG_ܻw/22rĉ:u4?$q)..޳g^HH N:hÇGahh8bĈǏ{yy矕W\YbB4y#pQ.t}ڵ/J0`S?Rpȑ#BٳHcbb޹|'B(33!ѳgO6M"ߤ7uDM5T15o߾"`mDZer5õׯ p">e?9r͢EAhhG>}믿z{{3 mЩSٳg;vDB7o~իW֭[դ7uDMAwYXXs}B+--pGLL˗/%IffÇBYYY|aZer5õ'>W_xoFsF"/^C-BC>pƃDRFq8Xpԃd2Y"2~*U+" T*D&}Ж+666..N.=zG"++ٳG}葍͌3f̘ђ6dׯ_OJJJJJzRӧϸqƏ߫W|SP}}@ |>^S"~p\.[kjje~Z /|T*G>FŅ* ܵkB_0R*cƌILLl±c&$$H_~ ݰa;/K)x%Շn|MU;99[q"cǎfsҥ[lQooSh67N}-;\ǔW_\zWSiiԩSݻwiӦ@__… cǎz} DEEm޼y̙׮]w޺ubcc_xJNN_r%gccz-[|3gP( &M0aƍëB=xL&?~lѢEO>}z߾}o޼ٞD<8rS7dȐm۶M4@йs˗/_ٳgǎ;vXXX̙3Mfff*&&&ǧB7f̘+V 6ظU@{H$!BAn`p]]b*jU(dnJ"7VUU"H*"d2 ƁBX\WW׼oDWWE&9nd/M& cllo>hРMFl{]p|||6n܈Dt޽{ccc?ѣǬY5I!7bݺu'O޴iSFFFvv6N:tٳB_~9dDpMQAAA7nr>>>C xD 60C <|)))2e_?axpmM&޽;44s7oҥgh|+H$7A1*o"\?Jj:ޮOPh4rI$_ӣrt:dj u&''Zj)));w644B׏J޾}؍ ȹ L&s\TH$d2FDN:7 BՊ!**h4>uuu'NعsܦOW_YZZj{^i޽{'NTUUMAspxb֭Ǐ}ݑ#GvIfׯ_~nzm۶ 8k׮77BT%%%߽{W.N2ӳvZз7jA_ m5Ϟ=QuϜ98jԨ#F5h?#G\jU޽322;vaܹs=/)ھ}ׯ;mD&o|8*6 kkoo 87l``@Du?w03%Ƭpg&=>{Hi/ @8INcccSSSJ#5m47yA]t}Y[[u;©T*!k |N}ARuuumlBdD"u-q7^XLGGG&>"2|͛7wq933+V̛7n0mL&<عs+ԫ\^p!))륥_~ŋ HVRTT ]o ӽ^c"/T*UP|skO4iĉG3gΜ9s:w^N|p^rʕ+-^^^\A?0CP\zgϞ%}]PP 6UQQQ\\\TT⺿B1~?700-DP . WUUZCwiigψطjzǔ-,,Wsssg+==}ܸqLHHpwwo222޽[(XpD")jsss:kOOODQ "#X,B@D"%TAb"oAd|J.n "x{OW\:v؄ T rww?x͛f߼y?~N4hPHH3J",[Fy4 2֍s+|gΜieeɫH$ѣG?{]n)Sx֠l޼9--M(/ riHR|o޼qss۴i =5>eee*JJJ KJJp:njjjeeCƝ:u"j;4L0ǬT*DrYYYII ZXXx޽:5N733455Qabb:ss[n͞=?:ujgeeu%ӳÆ k MVUUU666_Fh4APlv}}=RDd2Jn4M*⼖T*>& IJJ/h{F͙,_widdTTTdkk;r_~e\.W-H$H{V)񋚞%x[Юy 0_~{sdddxxիWO>iӦ+V>/EGs\2dȐ!CsV? "===)))99955@찰ݻw5PѠ`p\HODee˗/_~;Ÿ Q ##H&&&LjhrII Q޽{Nz gkkkkkKDmmmv 7x*]] ̘1X<PPdnݺDf4 '+++;wlgg6ԃ+HDu;^:NU+"KR2 |V^}qGGǸ88Yљ?iӶnݺyf'((.ځR$Mnt_T$SÄz__ _Z"88;5jԨQvލJJJ:v\.ѣ%:/ eeeիW+**nll'AMMMuu୪*P(T߭>&r\ է\.u谤Rivv/^|Vyy9BBXXXٹaSkkkbjIe'''M t֭[n]vڵk;w;8p͟?|ʕm4L&+--533CΝ;illO#WVV"233&.T*Ճx5"2>y 2FDd2;"[lٸqÇkXafW^hѢM6/111۷o5jh5rmaBMEPQS¸` yRÆ 6l؆ ]_ܹJ{[np? Dv;wܹs'??FÇ۷/f܌VgPRVV~MEiP_ 0dϟ?Juť{ HH"00rӦMmɅ! rxx;{D"*ZUUILLě8BP" *"1^HP'h4Պ$ *".^xkgo244_-ZdɒѣGO4)22Z##pM;m{߹tttX,jYճaq\??????P~~kݻwHR.}ׯ#ET|ݻݻsΓ'Od2Q߾}Νۿ3`0 E^ ."B̸h9fccco|JJJJ}T?76662q܂:<.))LfNNBHOOO,ST 2e2LYKNNojkkO81uvIT*@#=z?9s34*ce VT$&t. y_Lh4T }K>}f͚޳gO\A۷d2i~\]]]\\ F>y$--Ν;[l)++h d tX_P1@ IDAT5ɜ2eJ}}ݻ[F+++"tA7cc/^&<!8NVVBgXYYY, WDDxtP]] 2Fd;fl I EXXڵk}}}w 5'r~'&cǎI&}Ǐ>} Ya"?nj䷵ҽaÆ 6 ?ٸD_FFѣG Megg <Z!_:;;;;;CuuPp'>+**^zʕ+WӧAAA Є1cӬ;wܽ{755uΝ2nĈ#F/ ;[40~?LJBogGBSfll\^^neeȖT*5//o_RiD&"D`bD%R);"2B,Ϟ=;>>>222((HC>|o6lXtt7|z@m/Je٨) ]o ӽB888888L4 gϞxիWYYY?vXEElmmݥKΝ;h-ǃ  ś7orrrrrrT3!2lmmݹsgGGG__.]899۷ھ@`nO˖- 6mZ```߾}[n]r%)))--B 06l.ǮsΝ;w1cBH"P(\]]G=p@buhȑ#=W_h[n222 122*//wqqDQ(ss|? \.W$VDhR#d2Ddd2;"g͛7_NNN ֬Y>|ƍp:M%J񹕖$t[}k3h4&42֍s+>%zzzUUUoVVNR>yܹs%%%x+Ų~Z[[<&h?"(777777???//////777//P&!T*/6uT\_d(<@.9rСCNNNAAAӦMcX\"\xĉ.\={1bڵk N1 OOOOOPPxڵs΅?3f 8qΝkddO?>._L<۷/WElhh[x<D !\cX"R(5"㧺$ i4L&spYsJG󒟟_ܹssm=\YYYPPPJJtP*{=zB=z5kּy49%P(?~֭۷ogffZZZ5TEEE<11_<666VVVffffff:u455555nN Օy&///?? GX,kkk=z9'-,,p))Y 0K$v3Ӆ .\w{T*믿N8qyH4xF> ,kǏGǟ8qb,wԩ#GHٳg-_sӦMky<^aaBy'''&y= L&ӫ-8rq-dՊ,K(8.`0b1FFjRj*"вׯ_>_&&&m=@ ۷oNNB窷 J1c7o޼y3...!!თ=m۶O_zիcǎتUN4ŋcƌi;wWgiZZZZZܹOI^4—.\2x`PӧN8Q&@A9RM~%ME&9~aBWįp+ &ٳgϞ={6h9998WPPp=$7RTSSSssssssSSSN0111z v +*****JJJdKJJ1BXXX#F x111SN_f͵k׶lقȚ~ 6̙3!T*)ioٳ{rׯ4hyrruCCCB^l+9rȯzuM&=:>>חJĴÈBH.OmmB룦$t! BXZZZZZzzzozMIIIQQN|޽{\31111666RallV600;TUUUh+`:::8offfoo٩S' N:F:f`@ |>/xxSs8By8dɒ3g.\fݺuIIIK,5kuBiMG`PAjJ6m:rH^^D"8DYXX,^xŹ޳gOxxȑ#CCCO7Ç-C 2B#%%A7< B!*"x<\aX *"r]]ݪ*&T*% H$DYWW#J4M( r}}=TDM"h񺺺BF;w!tŋ#R+ l޼! ׹s粲:8jԨ'N8Q?dU]ٳrJͿwڅ;w.nMLLϟ?ț6m $SFF~#ɡh;Ç?uꔏv|Jd2vflm\ڧ%VuzeL*l(O333q#W}.r U7ENGZAFR*gPϸR|:uԤWՙjjjhz׍A.#b}ۇrss;~Ttώ槟~ZbŹs9z׻i{jL&:tmܹqqq+OVVV$xxxرC$1L"<1[[[WTTb.b2 !/CD"== ]]]"RT"2h͢E^~}\.;;!4n8W5>sЬYT_5gΜ(".܈5k֔$%%%%%!H$UM|Ȑ!>a„NO>E;J?xB(11qرoU>x$59mj̘1۶m۷oQFϠ驫H{֊tYÄ.}V/-AV__O}g&hT\.`zzz 0L{2 `0L&Fc2CC \_BhSUUhZ]]-bqMMP(H$8(HBaMM ފ[ bnoo rLLLX@ H$|?I#}RRR\T*UTNR~pP?f̘ vX:::SN:u[BBB;{u>}kǎޏQ~~>.˟J&9g(.!lF*np3%nl,/A.mbuW>BLT>T U( WßT fl===&p jȘf78ڔ!&Je˖iӦi~vsE?*ݞ)))?C޽ccc=)3ҷoߐUV5{?<x```DFIRPee5B(//@Ճ , D"kkkuuuqAd\hGYYYpp___v>---!!-ϟWУG#Glڴh}/^|Б^lς 7UF~[+ _L&6{6>/Hb1JRHOKrRIvH$W [ᱭh5BF1 |yׇFot:D5&2ٸ@#`0&@@"J%B!H&L;vӧO/^8suT*N<ѵkא'{(--%ٰacD4};W(Ǐu۷333---݃AdV? }vP#Ju{AfO=H3g3:u'|FBBBN:pB|SSXYYI$kZZZnL^W86lDh7AdH?b!jkkU+"h4Պd2*"Ђ+WnذKKKR&M:}֭[U;̘1#55u޽3g">33f=<<x<^^^ÇBYYYN%[YY~z߾}3g455ms``+W.\XUUջwogggsƍÇ={v˖-C?~|۶m{lh-QQQ>>>Ço +jU")jJBW=t/D~p8,;8-N(E Dafu7vt cQSST*tСC'L0fЉ'-ZzYs|7@зoߜPqqU+!J1c7o޼y3...!!A;wTm سg4-----z4BÃؚ{…k2su[jUXX~I&]xq̘1}t366gF$*//BX,*D&DEdH`0b1%K$]]]nNGGG* r}}} ":{ۦ >w "lر!zgKnٲGNBAAA;vPmQ\hh 7WT6s=%<v6j(>ΝvC\lIBշ6Q- h-o߮2d3B(77omPCY~}hh?Gf͛7gRyС ½{Ο?C+/3g%Bٳ&Mrׯ4hyrru={/" l2WWW.sȑ_uׯ_d(i2"offdɒSYڵk_|/k֬!=sLxx7o7qhd禦eee ,ҥKEEŃ"##SSS5xxUw~4chQ]]]ΝuZx CvQ ;!lƶbBdd!zzzDގD"q\bS+o4~vN5;>~@xСve͋-j>}xK"ۂٙf+ r~ c…f>},$$d޼y7o~IjjjPPP}}7O:?P(?{ MP ,Xljj믿6)ikk퉀&P*q{{?R=qqsҤI֭Dc``P]]ٵkWܒٽ{w6jll\QQcU=ӧqYYgϞуh477/..%~56&) ǎ$ ~pDYY1G"L}en7qhd? ￉722  ʦNFBBBΝ;#OeL^ϲA@ NBL&ÏryMM I$IRXP|bX,ÏU/"sUUUclPl*ǿ:D'4qH]%uttX,RYǣqⅪiVX^ŝ:u- l۶o%Yٳ eܸqׯG]ĉϟ?|>ٳgDYfmݺEGG+燄Yݻϗ.]JKK_zE&(ʓ'Obq( IDATL+j{JBBB]]ݴiӴ=ٙ5kҥKE" i~;l*⓿ ]vXXXh{m32BgrzO\yn /_X,VIIys DP!}\yy^nݺիׯ{͚5:dܼD۳mJ&dZqa4g"Mkjjo6|uuu5łSDZ.ggg#DHfKR3.Ҍ!27gƁi\wơg" xPя?m۶~x|"L&Uwޝ!8NYYYQL&(D"q]]t S$_$I.SԎYzɺ|gǿ}?W1' ֭[#GLmmf mIճ>F?,>>---!!-ϟWУG#GlڴhW\;w;;dff""""t[ߤ7uDM٥'$$,Y$$$4S,B_qh*˗/_<==ҥKgՏ=jjh?Y" _%|gغ}:N3F~@h| 盉3F4NTkX`꾯$3Ng2,br\|?k@ 88x˖-MzD*((pww']]]J⊊ պ! L&S, 6"d\NP:fb ɺq= 9sq q_,Qu% Y\ȈlU*R $RYDqd2I ;BB!Yb )}#ӧZ x"""TIN>uV3fHMMݻw/Bh̙ÇL3=n``+W.\XUUջwogggsƍÇ={v˖-C?~|۶mMySGd>>>:::~~~Νl9s# ;|0B(++K= &VVnܸQGtzJek\_m$@ ())"H 6(2`MD{2L:N\ŋm۶}k6酺FFF}9tB ɸX&1LbkkkP~~>˕Jџ"b'L7PH4 ^H$R}}} ":P}}=?MϑB(>>}TMیh BWWWSS䶎s5@ pqq!Zwڅ.T*njcǎMHHh|M QLnذQ R/BBBˆ]}pC7>󝜜 Aر;\t-[ ؽ{7hggՌcՒhD"/uTVV'Jy{{[[[III˖-3fosFBmݺd._ZWW"55%TDOSuuu}}}N=233z~f) vbΝ;7lذ1cƜ={cg vٸq#b$ҥK{}9BGf$܈uM|x\OOO2E=l߾` aÆm߾bfOUUUegg_~R|ΘL&422jNpf2.Ҭn.**"Z$Dwg,d/p\.ŏ,\pӦMӤZYY5DӉ 1BJVVV&ihhp"X:RobU+"K$.W'rVD 2|jjjB0nnnڞrttyQzɱcT[ԫ?ij}{SիWwn">>> "7g>t##o N;7innʾ ((;Ψ8(>3+8(*eMR(]tKy҂-\?99>}prswK:.ٓwUW=䓥UUUk֬ !wygul=G}4sS ݾv %G?zhH$RTJRuFt@|.xWZN:{} +KJJ|y\Vtz^iTVKЋM<9++kկJ7ww 1 AdBȁ&L@h4Oioo&ddd455d2BH$"Zx<&iΘx@ 9r8s}. z1cƼ⋡PN jtM[ne|W_}UY_GkBѣG'{"]Bp޼yۺu+ϟ?jΜ9ӧO) \.W_}'lܸbM2e֭cǎMz?#n7PniiiiilVtG)JE4L V5 U:u]w/B7b `Vt:NDV^NdffF"G.ٝ6"gddnTJwD4i+ Ҥ2C=tZpfΜ裏n޼J\.agP;ܺu^G% w/Ǐ߿ٳgϞw/ oFuNu>;3N2e'Ni SSSSyy7lD&MϘ1CP${jH*JҬ3X,4,XJp8Jh4zh0rZZZjjjZZD";Xt7|3sW"b$/**:pT;vvffhP(oDAdTxZ!DP(Dtd EݡP8rϼd=z[o yN"tԩSN?ɘ]Ҵ| il(oookk/{I&M8qܸqR4n-[7mT]]-ǎo^&ٳs"ry~~&hٚVVfFtT*MOOt驩cFFFzz:BҥO0>fb0fsʢ;v0OjO?T*F䴴4bBa|#D"|bvK$ڈ,ŊoD9 h4p!Xb[pa Л-^x̙(**J\ο nٲER͘1矏Fرc׮]555iii^p !9^$j*B2BVX!dOFs=s=~ǎ6m*//7\#Ə?|Çz?޽]"ȠAf̘rʑ#Gvf B"fX,frl56-BX,^8%++333z}OKzwymf6ye#{Fl6!DVT*&r CSST*x Ȅ@`b1D||Ř 2BF\.DBѳf'{.:t'!VСCB~+V0Okkkkkk׬Y[ǥG1L'N|O9r2yիW7LSLy'/;n}p 뮓dc=֝FFd{ر\BZnkk+,,̘\r;|f" "Bx<^0 K; 2 2\Tl69r䧟~z7&{:ӊ+N8a4,Y2k֬?s󟄐{w999mmm{Y|98rEE!\n]EEE>v!dӦM˖-cWTT|{`00zk;v?,,,gΜ9s &4߷o߬,܆8qɓǏ#G8qtKKK̙ӿdOldHkkkkjj⋺:zoNWPP\E?bbf͚'t'b4`kkV__TTfL[y}:ۣL}}?0]W_u裏>裿ˠA۳>ofMii֭[?s駟B}ForM&׮]K RSS'ME}ȑSnn`0d_}ʢ|n6>YV:@*nA n~@?~<2ꫯ])x<^vv6Kׯ_qqqaayٳ_{_2%HX, A|>HD9N|h4b1E"\.Dx<"n|CN5"aD\.7! Iv5׼;p8O=Ts}\.w֬Yk?`Ȑ!=ztFFF}}L9vX7_Ĉ ,:t`سgOyyoAx޽{?O~斕Ȑ!z>YӾpX, 7' gP($lNX-uj:x@ !zfG`0 sqD&Ad'h1}(%hD|y>K 2짟~Zp͛ ӗ-[D޻wիlrQF;v|={{?ɓ'jd~w]w?O#FXreUU˛5k?|t<8m46ydOz?BQ\\PGGE"fcٚbB{"HGG]@vttx~:9T*6]4hMvj&|I߸d Vr޽ߞx 7RH9!Cddd[;Ady}ZNtGt "ӏR29C*a,Fdr*L= +bΝ_A>k&3KF(zꩧ^~;w޹`X:ɅeMMMLT78:;?f9\.?npIt:vFҝc:N 09섺ht5\8z^3n{{ݻwᄏh"B`=zQ.\2Ś>}u.]zs#2!]h4nBFfR4 B~@ oDb` "Bx<DWs}{.S?=3yk]o߾;ѣW;= BTK&~?͝vB(GQڸx " ?^N 9ϗH$T13 BT*9xfٽ6pHѣG=>=vݻ駟~ţF?~L=ٴiz뭓'Ofeeahܾ}{?vX jjSKK`x$ D~?];`0Fd衤R?iӦ=V4iR'=T0|_|!C߿?///3d0&N4y/eoLEƋ_Ri.o2.@}-BBPEEΝ;lꫯ>2lر4\ZZf=.\q2lÆ ?ð5"?ꎎ.Јi&rRzbjڈL:j?7|WW]u՜9s^}մdO z 444= BFMiD"tS,_p0x& M?Bf\.W&BR)C E|#`hkk#p8FdZxbIKK҄GOF]$@ ڵk7lذhѢ~= .dّ#G|?|̙7nLra}>u:nn `v; ;\x1 40&US9ׄ`+MJ$N4J2/m) khz;~MuB;ᥘ7w34,N].d(K؋Lӿ <4ݣt}Lxtif\L&d;wn4ڲe˖-[V\stkkꪫzuӦM?NW<[[[SSSxCBju{{^OhDcDDx<<}>H$b4B!ŢYD2eʄ -[ /X'{/Zc?s}doL%&p:N {^{1kL0MT(UT\.d2(måP'Q1eu+0չ`4ۓ1s}frҴܚ)if&3MC4LbFJ|9X,RIr>/idH$Rr@ dtJo Pƍ7lp-X1cL:uƌɚԩSC3fnh$ 2!ՄFx鎄HDvH$K 2k3Q|>? 2X0{WA|ͷz^h2eʻhpgRT.Λ jkkF!DFT.KOhD]G,34:4LDKLffU,Y}K,{^Лܹ7/ZO?4ml6[kk+}2aH$*J}Jfffii)]VT4mLӊ5YGk( BN;Wv&fz].|nnnrhTYPмr3.Kҳrr'N8qķzk˖-| /_s5wy^{ g}tc CABG 2ϏoDf٩7"KRۭj`^@ `XhLhDBL>7|++r…^{.K^z_کSnذ+ZNJD"------4jlZJ0dZƧqcKP( gACN.\.Aifѧpttt$Tp8HNH*w,38x7`o?nI.yw_vveeej AdhX:cه>}:x ÌF9N|#D"qݴY" 2Mwtt$4"AP(-Zto tp j:ٳq?xno^f͠A=)m6[SS}!c&jH.jZ^tt`jB0=\.g#]t2OcCCngWJ4L2! gϞ={얖>୷Z|W_}O2Bq8 &?#c4lD }9tЬYR)ŊoD;8qЈDD"qdDm\.@@.P(DH̙3gΜ_~~,Xp5|ӧOX/ k֬YfMEEE~/^|w${^h4J;iԸ766677A:R(zѨnjjZ6'D"H Cw L(n3 vn>|YHW,w32@j,Y[7ޘ6mڀ}ٙ3g8I.\h޷3x.7B4M, ;S AdBxh޼ywqǤIfϞ=k,D BǏᇟ}Yuud֬YSPPyfɓ Sfsss3sX,ѴaîzC܉z @t:]7'ĔN3~nJJBg<@Oron/7\PP3̚5vϋڵ+r`cBZD"N31s3 P(v"x<# r4%\.x`4"@p&N8qO?x`SL2eJFFFxYF{o6lذ~@pu׽꫓&Mrs 3 LH$iii:.===555--M&%z&BP(#h|^‰'eH$:CX9%%Ehڔ ?K.[즛n:tk6lذYYY "H$9rBju(D".~8|.L|>Éb# 2 "B!GNш Zᄎ/o]`}WRR2yS1׋plܸqÆ ~mssF~iӦ1pVǙqCC[VzFƌɓ'gdddfffdd${h4;銖v{[[ѣG`Q ПBtjZV~qNvvU|zhĈr+'MT^^lٲ. H$l06;Nժh !v=> fv.zb1jZ(F"BtLf4"@f2}G}t,|K/ꫮj„ Gׯ_ y۶m7nܱcG8.--N:l0/8qرcueKaXz>#####cĈ7tSzz:zzz2@ jiiimmmmmmkkc[ZZ""Pd2}JezU8  (//_~  ;p &MzwZZZZm4lX "B9V?Nq8YYYth$~Bl6FdBH$Ad=x<|>.+ bٳgώF{ݰa> jڑ#G3fԨQeeez8 ;vؾ};4͘1c׿N<9===ZZZqSSS4%T*d2Zзotz.2L&L3s8---LR.477UTT0e4⛕SRR| M6v؅ Ξ={ܹP(h4Ьė;_pt:VMMMMMM_tz^՞K#@wN:uС}aaٽرcO555tA".HbT*Ukk+ "B$I,c^W$B!Dx`Ħw[yAdHSRRRRRBGee%Mڵ\.7//9m0ӋHIIg].^/ "{<HlO"B r$9DS*cǎ;v,}>\QQQYYyȑѣ~*brFR9QDl6[ccb9~8=zɓSFY`AqqqII}6}Nhq}}==񚞞[XXxuq@ori|bٚ[ZZ;c]f*|~|r++d)))W\qwWv6f}뮻3?. !2,x!)D(%}>I"P(DׄaFdr 0`~;&544=oaɄH322 Czz`h4*J~H$Ǟ IDAT3w:8N9Z f1%8ѣt|>_uuuUU8P]]]__OD/((3f 9˓H$ɞ/@"2333330Ɣi2}jXhv9I0 ʩ :`0H XP(QFx=Mm۶.|.w"ۿ'iDB</ cA#2cXW^|-KCCjmll16-6!rBT*e2\.g~m+JEt>]Ř@e'c!D( #Gfdd0Q鴴TB&s|h4 ͛WXXXTTd2lv n@0dbʴDV,ڵfƷEhZjBjjhzh4һ\8}ȑ#}e˖ǎu.7w)//bBD"QB#2:J"zc "s@ 4"|> hv!C 0Fh yS8bnwtt\@ @8q"B >)ST,F1??i\o_ 9lK_cc/RQQA;kjjB6LEEEs)***,,ǍӞ3q\4LKivyqWL3Aק\c^߾&MxƎ;8Ι^oZK"Fbx<^Ba߾} !B0!LSŴfDB`ȴ AdjZFG_~}߿QXX8yGydc.-2L&nijjlMMMVbX,rFTTe@3FQ$[nesέM١cF";wN<9a^z]'//رcr$4"ӇRBi#rss3ˍ"~V ?D˅_R[[ BaYYYYY٭JێRig D";CRnfb6v;]ؾ}lnhh` *h4 ΏbtofqqC=v/^m۶.Ȅj2:waZb鍆\nB#2m|B}>ϧc~? B\.BKD^yLbBRiIIĉ{챲|zRT*Uaaa[}>&?m߾_ˌJOUVM:n8qbw5j;?s9''믿X,h4BX,VBB<X,x~Ν;fx^ӱX,^999f9???'4"j6MI" 2ӈ,h1 t`Ad h1 .K  8pĉO>ArssU@t.vttЌr#GmA|322FczzzZZh|zW>eĈ~C_/T*frX,}> h4!zBaBYP0Ad&أ ]z;wܾ}޽{kXlGu׏${]r˭`bfRWWg66n|=, F``M&]̤@d2,X3|;]v% !ztAl6~?!p~G$ qdDV(>OcZ 2\zv?޽{߾}~_*|az}phh,++Km6[CClnll4MMM^D5(w*.>x饗^~gXÆ ۵kעE6z^_P1!,Rd2ڈB!}"A> h!H0$= G[nݶmY,V~ 6wÇ0priiiiii]nL@b466VWWfbz&l4ij9==]._ģ'|'\xq7[6FjժD&x<6 fKhDvz^H$|"(B|>9ՈLaXP(bDb?b6\gϞ=vQF)d'RT*`0H듛hLl6WVV~wMMM@JLrB^gz+_/_ޝ#FxgP;v8^999۶m 8M**$pB9 X,Dh9 2M'aFdHH$RQQA۶mkmm C뮻Ǝ;bD9\|~>}֖J.۷b1\.7555333======++.dffzڀP(|衇}?:W6㏳f͊_zFkz<.Јh:ɩ"d4"* X,GQDBО={h1Hґ#G.ZhܸqCɞ#eAjڒΛ|>SlXm۶fKn>O333322h:.\C̟?/ˊ+^x_, k׮ ^ZXDsrrbL&KhDtX%S4=H$r:A@ @O݇B!ڈࢩݸqƍ7orT*ըQ~1cƔq8+*r(rbrJ pADΛ@cccccc}}}CCCcccCCCeeecc#Ӱ%,QȐPz"D`W^yGQT:~Ĉ;wLXGKr<`0B<bJTY|FdPH 4F !pb! CǏG~'MT\\f=AG xDA=kOqݡP(ׅ2˕d&B&W; PHH愧@,W2.y <dffzބ 2ӈL D ضmۦM֯__]]fGqN8>r\.:t:NiGGM ;`0r^o 8JR)uzjxnff&ͦ\6җLt3cfJMr6!F !;>hRY&x@Bh0Fd2Lt9''@V뮕+W.^y~eeeYYVk~:" lD tr\L#2=4"FP( 2{ݴiӦMvz322&Otǧ${vdH˧Lik'ɔJ\.b2hU.|\N[{iU*\Jb[9`0vi|tBv\`tҞiZ}q0M# \3JBP(tY*?drpwf3N 6meN@3L4)gee\~߯X믿1cƙG+ʟ9>j\j=^999V%4"jrRSSI\D"}>_B%Ђ?4PѾZlKdKر%;v0. $i -)\miiK -t%4۲,%ldc 4@,$N,vٲ6K-y:Zf^%lI8~3#LUkgHR;vذaÖ-[:::Y~_r%lw,Lat@ ا~&rU*J*(( ERrRERzN?RIh4_h%P"Ȏ23  Ȉ B###tո7bS&0yq3ʩTp\.t:;::{{{9o@L&d*;h4қLfӦM[`?Ad3s={?e\Z>AbΝ|>T#2{c7A"s8p8݈Lcã4|L&ш _L8~뭷6lo^r%?=v S0>r@6pL3@Jh4EEEEEE&Ig'D"HT*՗~6L˳3٫\.רUNJr 2]/㕔]H$trOOp8wb.hrvFJ$nŋwvvVUUx˦[,..|^rG5"wc<O"pqT*E/"/çΑ#G^}M6ڵ_tE<7MZL&~zn>\^ t:]QQF1cFqZ.Лvw`j /$eP]`.8x}-72h"fsEE矿tҊr$s=kL&NWјrKK˸yPVjjZjFSXXxw` Jexio㡧E"l6Ӕlh483,]??}>V=fMMMW)..Tjr8B !P(; n :BЫ+#X,#pXP r"d4"hd2}{W__yNg{{Ç> ! .8eSW+{<^X DNSj:{AѨjz >H$50dZly˕L& !yyy4N.//c@wlݺu/Oٹ׿5 +..NR}}}%juOO!dpp`0 rуϔL&_}`h#2D""b\.mDFrرUV_5kVku6J&]]]mmml渽}xx喕Ԝ{?,KUUUQQQ +GQz w Pݸ&N셼S[g0@`6fU`Оuƍb^_i555RT*+WZu 9眓dZZZӑbBHooDAdNwQBH B2vD&O#D"a"s8L&p|>0o7 '_ooڵkWZo>^tu!|wD"! +++kjj.[nZ"zpfH$&d2MA<^:@(T:JYS `"*1{aɦ=nonn00,]t…mSRRR\\' "OtC@ƒvD&y<Bm8AdڔL DF\.T*ug}vb_ׅ һ)0L{{[ZZ8pApgΜ}jZ,r>EtDm0L 4*ٳe߻SPzV[RR2Q+ӲOg @@ţ}>n:nٳrѻ!dh4I)`z~˖-;f g* X&ڞ~U*dxB˴9HBѨH$b 2A`j|g_xދ/_bq5 ۷𸵵644,Z^.zM z^0Dnxz{{cGG46GH$8XGV[ZZh4 >(VjgϞ='۷\V|0*\^^^TT/]s5D9sMاGќ \UUEJ<oxxˋbzH(ˍFBBKhDjV\ /w[d ;t ôٳg+) IDAT{9|p:V* կ~U__oX? SP(v~ESz^"Fu:ۯh) gـ2xӼr&4BTd/3-^?tP]]D444{hT"V}ߪQ, !D(fr.;22Ždh4pxAx<6"iPH$Ј 0|'=غu_\tE\.7R2LGGǞ={hx\*Μ9.4mڴ\O 8Nqqqqqq}}AK{i^󵷷{n;FKKKKJJt:hd"Ng>mP>zor$cd ۬Y +r ̙3dkkkSSh4'"gT9??FL% }}}}</FdBH"@#2L&_{~>\lҥKju5u ~~ݻ;88VkSS 7T[[&RT*m+x<^x\.ln=I&JKKKu:`%%%b|8IjYfedP>vѣG_~cǎ ?T*trII N8W\/ym3m4\ h<dBP8 "' vD&qdڈx<^<7ND8\G}gkVXؘIM@`ǎT*e2f͚sΑJ#T&JM6Q|u:Cy^jڒ^o0ذ2}JO= db1cvݻ׬Yp8R!DY,r44t:ir8.pD"\.nP.+++))#) 0@ @q޽k׮ .k4++++++*++M&4YE(.X7ޘ(,O~!TB|>ݾB dAdIhY 4\XXL&KiG2mDN4w 8 <>@ ~_6Jvڵqƍ7_x˖-⋫r=;RrjZcW \v{zz9}v6h4F`2eZe0ŨjZ={G=zȑ#|ڵkiU@ 0l.2 'G{p|[ߺ[bX,w:6V\ݴZ-M$xrx "H$B[iY(&FdPDd2'cO& |zH.}_={4N,7n|7@UUW\OΞ=[ zvp)(((((1cU>r9Ξt:?{^zN,( \~9s9ك4L}ÄDRiӧO pSƥ^z7n߾X֧~.|Ju JŃyyy'DxHDADJB!}FD"FdɥW\ykϣ}Æ 6mڵkϿ袋/_o|h4zjjZq`0h=v}۶m^z\ӱeee<t QPP0k֬YfeBcǎُ۱c?>88H jFٌd8C7xc rmmU&AdzT! Ad.+p #Rp8H$B% !D  "FL&Fd|w}ʕ+ y:t_߸q}d2%\rK/(ST*Ucc،r(r:t:ζ[zcIUUUUUU&K/nkkX,cךfLJȟوLBP\.L&#@ 9f.Jb@ H$<IAdyꩧ/_H$~tMR4דnu֬Y{nZ~ǜ9s\nK< .*Ӏrww޶mz.t2P.++=pFUUUUUUكhHkѠ3Ll.>F= }k*j֭\nMMfO5MKKDo{{{|;D>dRc& ah2E#2iN׬Y?|ͷ~R&)dzjժ^xM.Y??L2b{2 ڳ޽_D"X˳t:^o69i@"̘1cƌك4Lwڵj*Z'*++O>}jP(r4}8 [zֶeF&z+6l۳r9aGt$JٍȄx< !hT(2 C4v@[?tu]lٲ\h2yW׮]m6>eַFBR566666fq;>n޼&;mS~|% s=s@gggGGGGGGgg+b!tꪪӧTUUL&\$ ΂ nx<.ƮZo6]h4b<ohha6dt:F r,Ax<.ݻow}.{Vkg4ܹsʕ֭b_|37^G~{{{GGGGGG{{oc:P]]]SScXjjjV+t8644;V뫯J5͉<ah4ވ#???Hp86,b!G"X6" r,Ad>OhD8 23- 裏>K.}'s=)|> ] Y>;tuuEQBH$*)))4N7*0yh4F3oÕ+WaBZZVoSۅ^uqW|>VhDoR9 =m4:Od2fi#2}!DB"B\._ծ]~>|o:ܮ]x 6h4[o-STGv{ssᠭbX׏ (WVV\BjZfkkk6d2BpڴiVb\MN?~vckkVj>o7Q*۱dr<ɤRFdDNӄH$"Hh#r8fl#2!=> D3F-k֭&[>۶myGZTI7n@9wuuuwwwuuх={[. Bx<^iid2^-`QTs̙3g; ;F6mtZ  J555Rp;w.QZZl ,h4^w7Q* Bzzz|BH&a2,p8ZtȈH$"YAd4"|%LfժU!k֬ꪫr=d27o뮻o|c֭]tB<C2vx(dq޽]]]p"f3Q jDJ 0ѣGi.mӦMw t:6lXr7w Ō3v16LlF2ћ(H$B9v;#Doe6H$Ba"`'iGO&`/lrus=$ɗ^z{GG{nM&R0j<n{{`0H%:Nחg2&W8ۈD"jZke>lm6ƍyL&#***,KMMjH$?k޼yV'|BQlx,RF !vȄ>J$I<hT"$IBH84L&|>A#2L&|;l6{sr,L>3{zz[,\O KD4[UCCC.K$P(45a9N&I`P&BD"Q$JhT,FH$"! oAdc> N$;|Yf߿&3ʥL&K/-_ꫯ^vjN7g4Fyt/-))Qez[[L$͘1#d2y1n{'x"BFb䚚VSGVWWWرclᴶt:B7:=u IDAT6"|>vcBH (,,"(l#Z&2}LR"LW_/~0K/tUWz:9z{k^{\`rcdu֮@ @KKKiiO>}`0hG=###JeXVbillg '˼yv1v\PzV__Oh珌aZ2\F3L__! F~$ !aqtp@&P(_^{챒\(Z[[o. d/D, Bq:v tRʏtz-p:T9s̙3>ef+WLR|>jIJ8]p?|,ţVY,6Fr~/W(\.W,\.\.`^"KBAX,r:J BBhS_ 0)l߾G?Q<_~ŋs=\wq3-K]]lF;2gΜX,o߾ .`Ԫݻw|J}9D###N " GzWDJ4Lx6"4L! )`o|{r<3ʙxñAUVB4DȄRNd|>#y<ۈ\TTFy<^<k rz?~ 6omٲK.tri˖-o;v7P*H4n@vuuuwwwwwӅW^y'HBbi ٬hrpVjѢEtaNKnkk۴iӽK1l.bTWW5 y睷sαӧO}}}KR JRr'i BDb8 2mDNB|:FdBFzŋ?cj:3~7K,y7L&SgAt??{0NnMÇoٲrR&trYYYYYp*jZ*:С6; BT*ݲjt96k֬oxUU!AdR9<<,F5"s8HľP&qH$RXX|~<Ads믿^ZdI3X{V^^}빞|y\.`0 sf3 t:8kyL&CH$fh4e1L:.d3WTXX8#^flիW Bz}]]]}}3jjjh,1{l=:`0H$Z6˕JAd\N"J9mDh' D p8}DZ"|}-Z'jQμ[/z{{_W8 0U Qximmm[lxL&dգ,Hr+0u.>b6:x֭[iZj>A"ܹk /͛=d^ovrOO0F h0L&8t#ّClݻw!tFjXy<^&U͞=>;>}7^uUtFP$D"188844$ !d2Ng7"iA,ci9aS_pʹ뮻nǎs7ߜ}|ݺu^{+ s=#8p8^gϞ=jU__-Nq:w^~}oo/H$beeetQ _B3gΜ9sdzo>M޴iӽޛNBihy睧hr8m/jُ?8+cgp8O=ԕW^OzFpeeeeeeݥT4u8Nrm۶9%Z`(...--t%%%%%%:>S~~~cccccCBH2EKiL{׮]>/',//DtbVVh4g˝6mڴi/^LGđ#G۷o߾{>pZ98L}MMMwygaaaxUUUggZ rAA r&),,di9 "` 2a7@;w^s5 477ϟ??ə}]}ՑH䭷ZpapwlBcpp0NBR!ddd.P{$ㄐh4wd2P(ٗPTjhh({)aFFF7`Aqz?ɃӰV&4\B=T*!D$;aƆ_K`qrMW٩navПˑRPp\BRp8A6is@ gnH$XLDٗeORBt ^w~V& !Hh4"SW>9pơ9,Z> 6撛{T*%*++䆆6D驫(Lr8T:T* e2-eg2 x<Ϥn_;?QN&vۃ>>D8;p8<22 itVA3a0MtC4IS4IMMі͏ 0̗6[1ev[YXl6fl~:ѓ4l,-?dLYg#*n?46 M4&iBP('`dd$@ rgg']WOQ"fi2].k4ӲspT9s̙3> B8p@KKˁ֮]K`^SSP__Ѐ_y3jptZ(NDp8 "Hbz8WTJ$JFdzDkToG&7Ⱦd^/^8ӁIp0Lf/B!]GQ(2L,d$PGi>X*.IH*fRp2ls3>eܧe6L7f۾i6݆vE LcGf_@iOM^^^^^l>6h4;n.QfjMT*ϟ`fP6mDs3l(l6>SSSC=4jpڴi\.72 @ӫ @hav;8t"矙r.]u yyy4P(Ba~~>2+ @ S\.pAL`0 oFxáAh? "&Dˁ}G^z 6򗿜9rdr|Ϟ=_x,Xq1e76d<D#++E" nӐq\.wgZx^/ :nf|m6}}Yw911bZL% ݥgTJ i1T*-δ;))shǼF1  fy BrL&;P9%%%%%EP]4Kfffffu]Gwm6[4\[[?9^o8d ljj'''DxE !x<Dx< #&{:yk&:;;oFͶgϞO禦իWt;wHr&aINFId2YrrL&d .FcǸ/\:<6q^l4Lʹp]@;Limf;aH$fZ,hP(|>_ H$JtC">_l6[V/p&-KOOO]]ݍ$%%T***Wn# 0 $˗/_NwCPwwwKKKgggGG֭['&&lv~~!Ϗ .bqnnnkkA}|sNύGjjhV\.jB!@@ "O'''Lf(5kڋFd~{| "ˉԔ{vL&8::DTbb"-9㼼h8eL"BRRRS9hr:grrN'7\.>99i6m6xs>P(IIIf@yQfb8))\:L&SP|{+Rb@ss3ݎH$RI "M[iX-******Ɩ3qҖ)[n%X,s%I$q8&DWbXpxbb~?x<|>?DfXPvl07>>SO=5ǛC( u T*J_hyrrtҼnw111t:齧Nrbbq4LiL9.Z,JbX, ʒFG$GGGfرcѻL&M$+ J,~eA$/pz^ !`[[[_~e˅\2M ŞdXPP@7b_Ł@pꎎ:=ipll,##rn7ϧAFdBp84| 2V7xH$jmm~6g^z寿:YP___4j<88p8@^UUVSSSi`n0׉2GCv0ԲfƗvhrT&iXY"H$$ p8\ BP( ,gfN6Lͣ&tFZM#**Grf"8 IDATϟ?#F匷zkttfGsɥ0KzBѣGWXxHdll윏J~nFd  bt "GrAd6M#!$\5 |z.//뭷—kΟ?r^ӧOa֐VKKK222RSSpn@p8ݠQᮮ6~_ccʴ\96<%,H bT*J:366622+Ç`lA.Ƕ)mS*4F!ǏӲ䖖۷;>i(LPr|Ad&344y:F3:: l6vK$BV "xOjY*`NILL,,,,,,<罡Phtt4N6fd29r?|IOOh4t#--Ɣ/Lp.\p~X,NG˒ϟOO|G}>d:CWV bh4B~ʲZP`0nX,Ad:mD|l66"ӓD+'??9Z(x}}wuWr1y|Gz,+++fqqNjw.?4(|m6M$GɱǏLNN>f3,"Q pcXjZVsb1th4677 v:ҀrZZF7oZNKKKKKSxH$ZlٲelkkkiiiiiٴiSgg'7Q^^ }JyAh#nh4Y$E"!șdۭR<D"C|>Ј 3]vmݺnrfH$rwKg}6kG[[>|+ 痕{・:{0I$ר ?/L7Vx >垝N;\~iuRRRRRRzw844Dc6ʹ`(JS8c޼y ."477\O9r `FFg >pjDNNNJqBP($n>xT*%x<ph#2 "ۙAd tן8qb׮]+Vrf^xO?=pH$ZH$pFggg8 .\rBef(Lv8cccccc;~xCCݥ_R,kJ@Y.GGbB$ r" Ͼ+Lwyd2E"B̜7o^FLZD0ꚚBH(hjjjllh`ZZ}vC$f]]]LJ$DBj6" 2Ɍ^/  ^AdYo~w,]t۶mb8˙A"}WRR~x 8ٳgϞ=ǎ Byyy7n\xqII ˍH$222?Dʣn涶62:::11firJJJ\.W(ɗҒJ`0B#G vV333iqrNNNnnnnnnjjj|~eeeee%ݵZ[:NLdɒ%KTTT,d2T`vv6}y T*5v]VFdzH$BN@ X,Add2}>'|>6Mi""i6m~p>ZSSSkk+=?Fxg!X,VIIIIIɃ>Hloo~`ZBaII`+RRRdkookZb6Xd2F B*s8Bs!l66"  i㦛n:tΝ;c?2gyg?B!۷oǎ;wHMM7~wŊJ2ޫ qTZyuN.d2Ѥh5###tvG''''GʱU42'J `4L1Z[[z-FaSꓳѡ񒘘X]]]]]~zBȩSjkkvsE"Z]]]]UUe0/^LRcG !'O<|DBq8j222VWbZY, "{<.Fڈ "3  ׮]kZohRd2566ҤN**--*tRIձccc'O[ZZ|ͱ1B*..j:N 8-4Zݽn:BhllmiiٸqfDzezΝ;cGDőHnkRHt: !,k||\(~Bz|> !@fh#r0 0twwYbݻ7///˙6lؐC?x֭۷oD"W^yO>yצe=¼v:рh4LFd2i v'ӘrjjZh4j:55UTX:& r\./^8vn8q{O>$mԝQTTTXX(xV\rJBmiiihh뭷6mb+++,Ydɒx >zSO=ziDRX,9p8p8BaRR!$11*Ji##0LڈLAdߏFd) 7n߾].{93ԉ'^[Ns\$|MͶjժ-[\wuBp:F$^c544 v:d*JKh4&555--m޼yC``0DGɓ';;;;::>{0 V[TTMJJaNxUUUUUUt饗\.Eo z> vvv!f9K$@ @߾jH r%''[VXzəF䄄B`0ڈ  ^^+=YOgee|l믟>}z>߮Vm0xsvM&Аl2L===ptT*MKKHOOOKKKOOHKKKKKrx4p0vpbbԩSӧ#T*Φ6++`k0w?Fwq!r555544444Obϟ-KշVPP ۣAdB-6>|X!DFdHfN' "k4CG"6MaX@BFdG9Dx{G?o[&\/~鹼u]]3<{dkݺu , @ бc>@ @a0**==IyT*e#TZ]]]]]}b!h4 y' sP(+ @˒_|`0dɒEIҸ"cX:=vPT9rbq(lZMȉ,rd2*h# "ADr4 .}e"\K/_n??pzzA_˥)@```ӧO>uӧ׿bB8μy䜜\.9555tb/B0dѾd^j9N| n{zzxW}>3KYA՚L&՚LG !G26"aөVfs(p:V'$$x1mGc*ڈd200M9o|#99a޼y^o._Ry'?Z?Rp8999999S'''OA|l6;++`nJIIYjժU-[v.[\\?c…4(p1LVj׭[G Gimm=|pKK/x| CYY`t%̤j !qQQ!ȑ#D&vF hq<Fi/2 "'$$oDFImmmMMwA_tڵ}}{u]k׮q` B-[l6BL&h49++ !3h,ݍD"O}>l__͍Ӗ" É2 vww>|^s8|>z~_`T* PBzzzΞDk-(1"( \.LfZ|>!$!!! Bh89s8`0 "́u;wr-+W|7Xp!v oƋH/ ŧ~zW^gX,Ok&>&p8H=L} 7БM6 " ӆfu]H${Çڵh4Bi(q6 (((ꊎ˿Ky7lVuݺu}#`ttc IDATprU-Rtʕ+Wf撟|I%/^wpb0yyyyyyr X,mmm{ΝO>d0LHH(*****t6++dw0Gh.g,'''H$DF1111 \.}#cfi8mDF.gy?'|`{9K/]gkmm馛+**.s\6h8:b6=zȑ^xaÆ L&pѢEUUUUUUZqR\vڵk %755G/Y`ZjժUt;vȑ#{E|>_\V-**bXq];\Z-[bGBl>dT:</99|~| 0p th4x,`0{sFh4555g:twٴi_d upqܢ?nw}ꩧ +,,j6;;eip8SZZJG dRS&b6Mh4&&&B\.j v|>#WPSeȴ,y:~|o}{9վ}VXu! /_zÆ kasŢS___[[{^z)WVVVWWWVV"0̈́B˗/_Nwi(72Ji&nM|7?\nfffnL. pAdFC9uAdDbȵ 2H$Bdz<DNHH`2軛H$BЈ ?+rfgxqDoooggidFFFNN%G</3ꊎ3SNUUUM, n'ꑑHDq\LF Fl# 2E pq ]s5A{9X]]]$YtW{bYvm__߁-ZtqG&Ԑ3ǖS"K.ʊz:p8Y[[[WW_d0:ZUUœfSg466vuunBT*>KVV VDl6{ppb8FdJe6iE"d*  F?.~>ɔFd"k~={⽜٭9''GP|ǚLzlllݱ:0uBz{{iYr]]ݖ-[paaaee%-K.((z&Y\\\\\|Bi(v˖-PHTUU\^gfh4&v!0B9o޼L!t0Zݕ儐gp8LJ #nD^7ڈK4LX,4"qرիWKҽ{NjG~ bYj뭫C̑KC ۶ms)))PrYYˍzo}[ַ!`16i.yŊ4G :nȼ^ɓ'{{{8000`6D2%L "j~d`ȁ@6"+JBx<.K& zb6"B!BOBi|d{gf+pΝR4˹tvv|_QFqŊ)4Jk֬Yf =uOVPruu… Lf 0첲GyrIݻ{9BN+WX|xx<^AAAAAH$222QﯯZtP(LMMUT4MMMMJJUXXd2hY&BϞ)f#re6E"LNNZPvӦdr!n> Mӷ' }gWzwx<^sϿXk&GH!^Jp8jkkӟ:NRYYYY]]]YYi08N 0,駟>sL&dŊ+VXlYbbb  Fh4SzFh4 L>`hh(AјrVR)ʔ50w .+ !njN9]J,Bh#2Œdf^BV+Bf B:::.pkVkmmmZZ%]j~VB<={NRRRkBT:}* NNNmK/:X,EL&~ 3MRRիW^M uuu<C>_VVVYYYYYYQQ-)))7tM7D1LۻwSOEPree%ǣmnc g43SP* nTD"cZ 2!d```Ju8tWPӨAd@@|ȈL& t "]A#tAdY`޽555^{k`` 55;~7ӳK6,ؘdcccgl6199'ƐH$III"~YH5*J. KqtrY,lX(p8vnLX,NJJ󓒒}\.k?``0<ÄO>䩧 40=T*խz뭷B>7)ꫯ?pbXӝ^*6LmmmtfE's)dLݐd2 a@:n޽t[ |;00PRR; z`f+JLNS&A`BBxr1@6"Ǟ2 0}_M7/hh48nmm'|R\\|IWN8144400?xFlD"Q*r<%%E*Λ7/11Q*F"x|>diwE$&)o4ccc)7T1o޼t]XX(.?ːZ9&6ryRRBJIbzMUBHBB9m60 9 }}}`0Hbj5{QEA!.GZb/^L`z򚚚Bkkk{9W\rjd+D)垞jXv{CB! %rYhRnK$sVCLfLp8i911d\Tz/lǓ@nf.عsM7twpR W^yޫԫ{{{;:::;;n7!gffgee-[,##C~b\rsNstt~YFرh4O EQQQaaaQQV-..V(ӹHorrرc> 0LJE#V7o^FFF\EOexx?Airu:]QQNt8c `z+VXDm۶qF&Y\\\UUEs^/P(nob68{w/--^re˦@qh휂j?ncǎE~bD"9T*2p`ZiZ=00IPhppp4PbBnASNt:r !HAdGOQv\!<"vmwyz;\tFqɒ%_8m֭6lxij`mmmNZꡇ*,,˓^O$DsfYǻ{zzzzz!BHZZ… KKKKKK.\> 3zFKKL&+,,,,,ꪫFFF$f;V{]ĉ===Ǐw?B^/--5 :Ӏ`hZV{=B\.Çjkk|ŋWUU K/4P*PHmmݻzwBbʕUUU/p8^,@lBq!g2ONNtĄnlĹȔ#R+MLm{ggg4lα H$P(fsyy9t:wPBX,mDdڈLd=\9 m۶롇z駑BtFFFj9rLϪs\ܿccx%%%eee{oYYY~~ l쬬k&:899򗿌D"Jrҥ˖-[|yqq1zre28wvvF"BhѢ[ouѢEeee0>))`0 H(mjjjllljjڲeK,Y|˗,YLPX]]]]]~`0V__/ CEEKVT^/\Vi(9tvvݻw޽6lؠT*ꪫzժUx$,11111.Ĕۓ'Olh^)bM)W̱R4vP \ k͚5LLXlJl6s-\.WRR4d2cC_.+rwG?o\E[ʞi[\H}}Qsss0h4˖-{ꩧ***/ĥK.]l溺lذvK믿~͚5J2޽`]q=ؒ%K222Kby睄@ pOڸqcBBŋkjjx``eeeeee?0!dxxiӦ@ ]YYIs4^}yPTTCGٳ_׃>vi"yٲe2\RT*^|yeV6LǏa>AEcq8HEgg%D" `nj]]]t[.G" !Rt``R*n0 `0R "x< "~6M{%Ј _쥗^{챍7{-p8rsH$rw8޺ܹfrrO>?ѨV{wٲe^]H$+W\8o߾|󕖖Yf͚5-Bfڵ_fjVկ~l2\)-----}衇!7͏㼼k׮]vٲe\.7ދCRSSo[n {zzjkkz}{gUUU˗/z~(jkk۽{ݻP(TRRB@/&tD"(--G"fG4l?&GGG{{{c$l6i2M''''d|B ;::\.&Oy JL& "WP~=A`Fd`0^/ p>7o'xGZ.'d|( >^{^p=SSSSVVFXQrÆ .O>ٹs/gdd[;(((2ڵW_}#Heegt:;vرcdz>rw[$ksl6d!===OeXt:]UUUEEEee%~Le0 ].WCCݻwBbʕ555:.+CkDT2!+]e>66v :11111bxv@Y*d2\.eg\WJӽ{t[&\p8<44;M,s8z.6 ";NBT*DŽ\c2 cn4"WAǏwׯwuuu'77ԩSgΜ/;qDMMMANNNRRҩS6olmm=k֬3f30pdddĜ={'N͏*{ݻw̜9S]] B---8|鶶#GX,K ^8))) 9""I޶m۪Utuuwfp"YMÇ ש9rdh2N*9RFIIL&|^~&##ֆDP($ @人H0dՀ@|N!GEE-^Z |OZZݧ-,,֮]ܹsQQQ999+WSUU%L&lذ!;;;&&OtR+++  wgΜٽ{w^^͛g̘@t]w}ĉ={]wDW&''陙 h, 7KnPPPPPáBP^ IDATٳg|sssHvrr m222222FHX__744444544+S(%%%UUU%%%555eeeѢ zzzϟ[XX(**x< > D?t:`tuu BF^^!DPxT}qG䎎QGd@ \/@`@ػwʕ+###!9 Om޼4PsS/666D5X[[[[[ܹ~۵kW\Mti۷w޿CCӏ;faaAtQCɎ;nsN;;0///K bnnnnnu}ιs.]TGG袆,11H___f. 0\=ztӦM[lE AT*u̙3gLNNޱc޾};:uԩS"sss222vQUU%!!d2qdLt YJJJ&BI^|yss剮0@h4UULSWWW[[[SSSWWWUUUWWWSSWSSSYYrHQ(YP5jm*_hQF=!z0!D ѕBmmm!yyy(&&ֻ1DcPgg'Jc"9]˰Ō͛7188 N_~|+W6H &L0!??͚5[l111!4w…kVTT,]4,,LNN芆> cXDKLLbX,|/kkkkNNNZZZzz;::TTTL&Ͷ d&]]ݠ7nܼyȑ#t:~ĉ'N477'L CRɺP(}W^effln za0===ȸ;2QNN?EqJvww?;::DBD?Opp?fkd2zݾ}ϟ?Cs޾};88xݺuwL&sÆ VR$3kll tҜ9s544hx177OJJJNNͷo 01 |#B(===--ܹs6mRfffL&f?n۶.99ƍ{ SWWwwwpss!RG"pCekk>OqW^UTTTTT{^|B#F3f %ƌzi1rrr===---xQYYwMDh===)d2;;;i4tD}̟??#ѵ S8R]}}MBCC g.ZHAAݻ,r;]v_Ǐ3f E9s 'NHt9×[vvO?bŊׯ;v=G*jdddddzyffffffJJ!9 )))͜9s̙丸8???gaaioo7>9:>zѣGYSVVVRRR\\\RRRRRVQQ%3@LLW4z/ t:! EAd%%FPGGBH^^͛7$ID,(Jww7NGuvvn AdBgΜ3gNt-쇻 `7oݴiqE,YrЈIII+!D&W\9y@ X k֬///Ot9Fۼyg@@YBB$(ߣjkk knnf0,b]/CnPPPPPPGGǭ[O:m۶#G:;;{zzzyy6T*]喖%%%III!f```llljjjbbbllMX xzzz555mmm222 AQQQ4` zwDnhh@uuu!BEAd'..Gh49tD!tyKFDD]˰FPP raaN8!%%Eh]!|}}ӯ^It9/CCP//}]`pG9v ǏϜ9ܹsp +^MOOy֭[\*Ͷg2666ߓp8gee]~끁D FtaEEE.//G1 cccccc333ccc<_\ii"5^^^;d޼y[ZZT*xNlqqq*r,(JOO(, Ad._?$$̞={>;uTkNLL~2eÇ)ɹ|~aaaFFFffߵkRTSSS;;;[[[;;;`PPP6mڴi8:f8θq.01 ;;;;;;њ|KNII9p@[[9ɴd2KN,))A)((<}TYYDx8X?So߶8!!!*ȸ }@K$CIHH6mZ@@!<^8qF &]$:|…+WtwwKHHL4B|ѣGϟ?"""ÇӦMͅ9֤$#!!O>]z!|KRRϮAl7==/^ FyQF "h4QF!޾}L"T*L|>-ꔌ 0lݼysԩ~~~ѐB t:J}mNNNRRRBB YǧGAww7B3%%ݻVRPPpss8q݊+|}}}ӧ!CCCmEGއFlJLLܻwӧOVZE??(ʙ3gƎy~ `ikk1cԩSg͚F5~A__ڵZ>Zlذڵk<}ܑ2>_[njsѣGWUU%''Mx<ڵk^w^EEE&///Ykkk55QFT*UWWvuuuvv^Z[[jnnFuvvvuu –~+IIIMe1;Ǥl6 C&''رD"0LK7n|_RRR ʊ;|eG& B</???''';;ƍQQQBPSSΎbX, `(~G8nܸ>AdLSSSkk(LRqqqAd.KZX4`+J'N}I60\jO.\믿_!J233~-k;::f͚-[vu~Ѿw+C(gddܹ_,Y2~lѶ-:phǏqYf=yÇ ߾}͛q\7!ɢh:~?WuuuVVVzzzVVVnn.˅4999..͛\.WWWӓ8::/}333333322%$$lmmmll.J>ݻwWVV̟?ũ=FGGL&!+SSSZ[[7m4z/__~ݺu:::&&&B0..4??_y 2@sqqqppp\h455"""Ξ=W_]PϟUTTz"//͛7SA}tSSS|iʕrrreee111)))x,_|^ܹ_ܺu#:th…222  Ǐ?zh{{{tttPPՌUgË/Ʌ;88޺ukO<駟֯_o(jnnVUU=r䈿0("4GFF믿7;v»SRRZti``z?1sNLLtwwWRR*--BPQuuŋ}||Dϟ߶m[mm͛׭[GݻϞ=300追o߾E_^~]]]]WWWWW'GNVQQ5j֨Qx>YnnnzzzZZӧO)>d2)))ڵkIII #G4iɓ'M8>~Ԛ bmm wVʕ+>>>mmm;v숌="//...![[򄄄I&uuut>OP٣YRRcǎe˖ihhtuu]v NQQʼn.eff&..^___RR>KOOOqq'O={>{ Ͻ.))󇚚+WiGd**!!bŊ`eeeyy[.\+w^ddÇ+++Ecڵk[nN"pCZZʕ+w!#JU#+쳡ۭ[;6w\J\ɼx>0::_ "uww+++Ϙ1cΜ9aaaǏ1s˖-W~w< ̮=Ώ?aaavȆx522rŊx۷999_~ZQ:B&UUUq(YSSSP5jhNNN^^^G[[Êq"9%%\RRfO0a„ 0m\ ?~akk{=]]]o߾}%oooД)S=zT^^~y___ggj11EEE''ǏzT< EEzφ8U T{{ǿKܹsE`rh4'..|QQ~O<144|w{s@];ʕ+ eeeT*u{oXTT;V^^߆ u/^zg{xUSS#--=z1+))[9PGG322233ꤥlmmY,ֈ#ڵkIIIo޼p8 7o&''())PrT꒒:w1cN< ]]]__+W;v, !tڵׯ_믁4 1113gTPPpwwOJJAd* Y_0a~:NDt!WSS^^zE"ttt8Κ5k͵Ow(JYۻeQ|gՆ ֭[555_~ޙZ*,I1cz._a===!>So߾R6>suO Y=G|@ ///|0**JFFzԨQ3g߯dڻOWTTTTT<ʕ+/^r!;@d0IJJ::::::ϟgfffffmݺU X,;;;ehh>0(((tuu]xq޽nnn^^^rrrD iԨQ7o޼y344۷&Mrwwwtt\E\\\]]...!111PUUU ,JmmmŋJJJ\.L&|>L&wvvwuudP())k BAd~_$RQQQTTYZZҲYl; n~d2¦Ln€`HKK}񗊊BzzzxѣGcGiii111۷o<~8Bhܸqx-?͛ǎ;wn+++70TVVUGd11nmm3fjkk} ?޿kDZn###B+W|ma||+c |~eeg!--mdd?-ϯ())))))...))9}tYYHh333###|!IOOOOO'zzzx IDAT=ze`2l6 𷈋EEE͙3L&r8ooo166666^|ywwwFFFRRRBBݻ\]]ݵ.ƌsAPB!J=`7oEee宮.rrr<D"utt 2N'HBPJJ !CR-i>oJC۷o'O\__}ŋm gϞݹs'555%%F\\믿PVV&aaavvv5D"&''wjWW׋/رcʕ3gDEEǎf͚={6Bɓ86k,|/kkkw~nۣ7BOP]|2p>}!4r翻ydd$J=ݻBވ~vvv[[mbСP(::::::'Nx/_,))yY~~>R(ѣG Je2L&/VWW榧:t[UUf3Lw|LXXnv5kXYYyyyq8SSSksvvvvv޺ukUUUBBBBBBXX… <</ꈌ?? 2Sr8:i˖-[nœ] 1nݺX^^.//oooflrJqq9rdeee***p}yyy^f͚-[?I?9gBww>xxxK,_z?+ ??!~O w#F追JMMMF}|||fΜ_ɐ盚&%%o?=b+?~ubbbp[k׾[… <(Zc\ܹs oCUOOOQQQ~~Ǐ?~_QQKƳO@欬{)**[YYAgqƄW&%%yF[[xyy9::~ڏx<^ZZ>L:i$2}}UTTdzX,|;1sϙ3]\\h4ڒ%Kv~zIIׯ_555uww߿Ν;xErrr222LLL;::$$${nbbBt9,--n߾rթꢢ˗/'&& I&M4dD۷wE ܺug7dcBattSpKCCCoV-<P8o޼g^~ٙr;v˗/rss/]tҥ'O())M9r$;mڴi^PP`ccdɒ[~ٳg:ujʕ;vߊ;666 #55uX9UWW榧=|CZŽd2Lk`mmm=?& cxv{{'SRRx<޸qMp,--Ewŋ/^p!;;[FF7. #xƧUV)((0 N4 66믿!땔H$ٳO8qU|wGGܹseeerrr_A"’>|--- W?n0,_ԩSBB555Dӏݻl2mmm++'TWW;vl-5k֬YfϞ[NLL<< >&266>xmۮ\߅ 4.\ H ,  oll}S?>fKKK(Gusssqqv$>۶m۰ammm0Xddd={]N-[OOS|~pp nܸ1 $$$bsss~F'''GGG333h!111bVXzeFFFfffBBŲgX2bĈiӦM6gff_tRRR...gʔ)D lٲe˖ƞ9sf߾}~~~~~~&&&D2III555D.))Ш ?q▖HPdddZ[[߼yC NYtSLx$i#2'p~ѣ~~~D>J{{{jjI$$$RG䦦(Cyxxdeeo޼y޽+!Bӓ\u`;w;6(zy.^СCD(** 1eʔؤ$'Nv˗/744xb׮]JJJQQQL&]jUrrrgg'@ Yf۷Ç]]]999===7o622?&K.=w\CC0@Q(6%K-X@[[СCD?n2lkkkkke˖Ҹ%K,Z‚p8KKK-ׯ_~gϞ9s&**jʕ,+ OFF[ZZjbbԄ';"+((5#{UGd9^ǁ=88xŋ-"7Ⓚ#r[[[ttΎ:qĤIx !$)55˖-suuUUU>>>;v찰((( Z 믿466fdd@  [… Ν"N ڵJZZ:;;RD266^x_~!''o>6:wf+HjjjӦMJKKzW^mee9mڴݻwgee]]ݥK&''ן9sfܸq?;׮]fnر7n,** .++1bDss*BJ,[]NJx BHZZJmdrgg'N=\.^O" 0=~kDl>!84663fɒ%w),,\ti Jj{zzDOsss?~ҥBssW^T,@,GGKfgg]`;v÷~[[[KtETJJڵk7o|mmm+CCCϝ;W[[ѣ˗Κ5kȑl6{˖-<  "ٺukJJJGGNj/n:bĈGIJJ-XĉD `0O~Ɇŋp8YYY6m۶bkD}}}^znݺtGGSNc#F<ONNuiii$p!tttETt===x 6n 2?TTTbbbb` E 抦USSt?2jԨ]v͟?̙3΃qF'Yy<^OOB 511ٶm4[JKKg̘agg'233'FtQIYYʕ+GMHH3fLxx8?S:;;3+WߺD255 KIIyMbb"Ɍf2ӧO?| OKWW7 ::911qڴijjjg۶miiiQ7##_UMM600022ZjUZZʕ+ ߿ohh` 77/_޻w/44˗ڎ[n-))!Fqƛ7oy󦠠`ƍrppZts"JJJ_y󦛛۩SΝ;Ftzeee?cJJٳgTY0X੄B(--zGI"D#t:BH PT! 0D}ӳ&u0ܿNEUUޓ LEEE3gδ(--=v؋//_] eeeqqq[n믍-,,BBBZZZpk>h4F[vmyyy@@(--~/^̜9sZZZ ,xg5Â@ z?~|СGq8 #W~ŬY"##BCC_xAt]CP(LHHpuu?rȃ&MDt]_dkkM6>|XYYy۶mL&3<<ɓ'D@ARN8˪ .Ξ=[IIIMMmQQQ뾗oӧOИ>}ɓ'Š+>}z֭#Ghiimڴ`NњBMMMV8rH@ =qJ?,) wtt`x_}Uiiibb"77XԅWCCC  8ihhXUU8[q Ҹ7N>HLLLGGڵkK,}vSSSkkӧT*D>}/6nnZQQ122rW4?ǏXBMMGFFƍsqpa˖- zzzl6ѣB=555'OL ?L(++ϝ;ܹs suu=v옑رc׬Ys}|yq8w!ílUpBGDD/EEE##UVo@C"\\\Μ9SVV߿_KK+ ''@P455kkkt:\YY{YQQ6|-^Dh4|Bfɝ#P( 2POy-PNNh4' ϟ?~~Ν;l6D{{{ZZZTTԂ l#Fs9qq񀀀 .TUUlHEdd2bbb455߳SiiЧO׮]{…񲇜⨨(KKK33 O0AoDYtiqqk455,Y[WYYyQc&=rH##ÇB shlllUUչsƏ1}}ŋ_v `PUU駟bbb^xammmaaqaQcZNYYو#pE|>\'Ad9992,L&P 2Dd2yD0Z*>>ҥKD™3gd@ "sݻwϲ R<.[PPPXXɓܚ*dAAAL&s̘1!))+1111gΜoH&]]]]]]8p՘???Ppիzzz^Z\\}ĉ[l1339s` @UUUeddddddff9sٱX,{{{ccc BtCFF櫯ꫯ{_t_N4ؘ2hӦM6mZrr={.\iӦe˖]qi񭭭 JuD9r$~ގ' wD&\.D""DyQOOOk\KKKssѣEkh4rY҈uĉ5kִ[nɒ%# _|8y\RR񤤤͙L&7nXXX?%!!1cƌ3fTWW'$$$$$lܸqŊ_~xijj~ƍ&]]]={8;; >y͛k׮]v-$$$((v„ NNNp@CԤ$$$+))M8`],-----oߞzԩիW쀀ӧCCK!>ٳȐfjjjoof0xɓ'5^b3ɤR1`萖Az۷nѱd2ɫh!@ <<77>lii!^^^_|񅃃eANO2eʔ)gϞ=q_|Ct jjjjjj^GG>nfff8loo?|n@DWW7(((((3==m&%%b<==LEEe֭W޽{BIHGG[\\QQQN ++Bt:!$'''DAdL&s޷:"C`0yɓmll}`PÓ^cJB=xo)))ٵkP(|%qII Χ3L3n8###yy~ KyyyN7000447nگOӧϞ=+,,|iEEP(133srrw\Sg?+”p j(D]:8+նRG"[YޮjնwQF.QFYZ 89 Ct_~4$333RT*cbbz}vvvZZ<  rpp`{^6ejjJk !w_{=ȑ#ٞ:)KK޼y{YzΝ;'Ld9nnn.#J[hee2ljjVi#`hnnn0p86h/ѣG~wq9w=ؒh4K~JڵkT,c8''yzz2 %qrrz߷=8pÆׯggg߼y377wΝwޥI$77:999::ۛUUUݻ ߿_UUED^^^^^^󎷷w޽{֭[nhS2!ڵk7nܸ|={!ݺusuuuϬ*+++**޽{ok2,44t̙rO>@ٳg||||||FFƆ &L Ǎ7w\xx}wلsΥnڴIzzzCH8AgfI>{'N8_ZZZ2dĈÆ {<@ [o._\.# RT(tB "tAdss*D'z}SSy;xO6"777Bt:]TKDDD֞?ʊq(((xrhbƥ7nܵkVXp¿_ hiK"=.))!HRaJett40/Vok333 4y;wٯӧO߿Xhgggooo񈍍qDO .г2G3\|TJr#G!i///wwwZyȑ=ݻyݻwǏ/**2^311H$vvvO7,,,,--B[]]M/"ڪ*zһM1b𽼼 ֭[=zx{{}<0 {˗_ۇ>w#F`Ig Hƌ3fBNzjjjjZZZlllEEyJePPStH#F1b!ݻ'OȝJmjjrvvNMM5>En{,-- CFdNVE"=Adc4&Ow#G( g究~kBo6AYfݻ>X|@ R㜜j0LTTL&cɩULMM{ѣGʆ:zبV|1OL~+6<<v'WjzɗYYYѥT\!@cbb2a„ &ܽ{w֭|0q{yy=) BB.]zjFAe2*ɡ3JRTqJ^z`P(4 xɓ'O0o]|[otRT !UUUΏ5"Bjjj{h=L/[tFHD\.W>Adn˖-˖-۷o]:'k6ȝ_vm?/h49994vQRRBJ (hazi퍙: H$rQ۪Ubcc͛77n|ЧOG #"""""! iii~ÇeByZA<66&ٓ`gg7x`Jvwwg{RhyҤI}{ڴi񎎎lm&>O>0ajZD"3f̘1c!.]JII9wܼy$IhhhHHHhhb;G=zhBHiiiRRRRRҾ}-[&dx^֮]pÇ[6BGhfffjjCPhkk[\\l|V,ԨT.]BR)!>kffV^^jMMMj t%Zri"Adv9**.]d{h-4 L2{}yy3eJS]]w^YYr׬Y3qD4Bk׮}? ̘1y/! 4h B^zjJJJrrr|||EER J"L8qĉϧ9rdٲe\.WP+ʡCZ[[=,𜝝m6}N4^JFfHD 2ڋѣG4/`{h 4Z&;;;''꥗^̤RaR0LϞ=i;|p*ooK>tދ/^h:&333RT*cccu:իWiSr||ÇT*}}}-L&ds!tS>fckxpp\.g{Xh²l'۷oӦMÇg{(x"J+**z=eeeAdBjK<6"744 JW|>`xwݻwy ǁVRh833x0 0111z͛4yŋߟf1CBBPSddddd$!ŋ4?oC:88=z)dF1ƍ7mڴ͛7oܸ100x<\.z[n]l5#8a>,,, !555iiiiii)))/VΡ!!!!!!rczyn^vmTTC_ juSS KKKBH P(lhhPTt'UDV ^o]tFe˶mF`01MiZXP(dr.jVWWyf{{RȄXLI_z#GXYY͟ RmۦO4y5k888=P( ży!7oLIIIIIټysllR C(:0++QF5hSSSSSS?j[[`a@{1vÇ/^xڴi߶m[n coo_\\\UUEǥ)+++?| IDAT2ljjZUUE/Lj?&s8fFdv/>}:۳_R㜜j0LTTL&c驟nnnjjj|Ԕ S &;w1={zyy-]tΜ9/(詘ԃ.^ۗ*Jc JRTBsrr֯_h"SSSࠠ@daayӧ E~9;; 9]bii[ɣ鑟p8Z- ik>h*Ƒ :W_}5<<|ŊlFɡ㌌ zN*2 T*ٳ Op,--Dx"قӧO?sLrrrݟp8QQQիcccnݺaÆaÆ=5E(911>D~~~Jd\n޽{=gBHYY٥K222h.YVOOGP\paڵK.ݿ; nݺ?|"JAdڈLS =nllD4srIFdpЈ NoD;w7Ю 8㼼rg}f0<<::o߾ ,XlT*5oݺu̙3gڵ+!!&444,,,,,O>HbA6ydBHmmsh(?VTB/00wuue{^hrҥK_}Ղ :k.܌ raa!!D"J BÇCf|>M{V+z`rDr{ 2LӵJ288 É2dȴi-Zd<A䢢"BT*CB,,,81!D,s8*q8Dx4L/t:>OCQX@P]]MuUUU4faܨV B={R__ԤjU*y2nhhh4=ESS^tRϞ= !bD BE"qJ$bㆥH$BFɡ㌌ z*M*2 T*ٳg{'H._7Ȓ%Kl*++++++***++KKKFeee#յuuup|hN"ى Q^^N555555t?feeeiiI/Hn :$>/Ϛ5kȐ!ot\ZZ6%߸q6%oݺuٲe2dJ"prr2j׮]KMM8tPBB!D*P20X=޽ӧ/_'$%%m߾ŅBlll|~YY!D*jkkk>obb4d\Q  ?xu:}`0ZGjjjZ~l/B;[fvMMMiNÃ<-p̄B!ϧ+O=[jnnթ 5!DRiZc_iAA1|++++++kkk++'?ZYY?ar CAA q^^V aL&0ec8Ο駟V^gB񼧃vݻyyy[aa{?ҲƦ[n6660Hdjj7щ&)zȭ-++lyzƦk׮ݺuڵ+򲵵m6Pmm7ݻWXXXXXxE%ӻA,--lllZ&---+Xi܇^|z𯩩{K-.Uysww?qġC̙# ZY!gΜIJJ;wӐGςNB 0 0 }X\\G544>}PRpE$_y7|w[n?~إ 2}^^ϧٚ˗/_|y߾}1$$$44t>>>\.űZOiiirrٳgϞ=k0&L[ZZ\eܹ~ҥ۷jSS8p[^y啰>`Ĉ۶mߡ Ν۷s8BAJ7Sqrr3f̘1cԌ .lڴҲ_~ ڲ;0Aeee[aaa%Kx[_Np8"K]"ih4B%777`s84"PUUEKnݺO<@Q\\\h#/P{m"6[ܻw  !<ٙٳ'bJeTWWx>>{4iM`"onnnllF7x#<<|Μ9?#-uuuN:~cNJRaæOڣGcBVMOOONN>sٳ5ߨQFxTTT?~_bŊP;;;c@ {w !zʕ~Ϟ=G=zPF 2d׭[gffP>;;HBHNNӧ׭[l2D2x!C 2{bRT*p֭/^xt]t ׯdDGG+I&ڵkذalЫ}z޽}CCo?AAAAAA/?uѣG˗FEEM<ӓ1HSS?gϞ#G ?Od2l֬Y1Ç^~oP(؞8::Ow3gNJJݻ \.͛GOLLLLL?~mmchhhxxJNe|MBH}}}FFŋ/\}vZܳgOc(_~W\5kֈ#>x\rjKDsOoxL, ZL3 21,TAdVWWWwҥGn޼BIr#ۓ I,;N\]]K?~|ɒ%}U( ϯwޝSYǜ={vÆ [lvSFF7|ߖO?T*___c. b޽#F5k/rAwww,!o6lT*MII8cAdBÇ,,, C}}P(c.KȄAd4"t|%%%\zUwEP 6>P(|>a<2fի4m۶&KKˠ~:urwwYYYIyl^ >}zNSSӡC6lؐhѢ'JRzQq.]ڻw履~7.&&eovݺuYYY_|Ypw޽~?_?~~؞-%&&XO?=wΝ;i]|>0 VTT=}SKaÆ >a.pvv3f =sssi(̙3[l36w5ju v_Z"HJKK~ .k "i|𡋋!Q$1!DV :$>sݻwpp p92$$$$$>jYYYΝ;wܶmۖ,Y"JR dw^PPbBc7lpΝ\\Pl?T*lR^^>f̘M6=TBYfk׮ ^hѫhׯ[n͕ƍ۱c/Cu{^zʕ+ҤIN277xEh W\?ɓ###kjj؞^;ww^~~~bbbw>cƌCֲ=&|\.޽{wNNNuuuJJJLL !d˖-/\.ڰaCjjVe{dڹsi1G;K$B-E0 2!D$B>|H&4jp8!ѫ6Ɣ ŭ[>aÆFDD?~ܸq)))<8~?p|@׵k׉'nܸѣ͛JQQQ`"?YYHض~`ػwŋO^XX~z8СCO8eccquY`ԩSݻn:WWWO3D_XBT|g[lll"##oo]vmѢEӦMH$?Fa{E |ԩ#GsuЖz}ȑJaVX :둑AAAo^r%-P(NܷotdX"p8c#2yLG}4B!=JȴҮqqSطoӧ^{+Vx_.hBHQQG>sJ%  E`````LѥKG~gSL9{M5X[[߾}ĤK.FdbRQQA !-ZlوL7 4"?u&&&{---ݵkÑB&::ٳwލpBHHH@@Ν;j5=#ooǂwqss3>W\֓A z~Μ9'Nۯ]ؘoiigϞAw=**jÆ ƔOFxÇ/\8D"!ܿXVVF5"_F< -@ Itp A#2@R>C_Waaeٞ گ1R78p`̘1s3FWF#}dz7`g4vcǎKlӺmٲeѢEgޱc@n߾}ΝSNe{' ?z1cƜ={qڂ?駟k-Mx^<<<9SNEFF7aС 4l- \{ÇO2jٲeJ2&&СCa@^.^W_=NB% "رJ2FH 2WBt:ckaL$"۶m[z7N* ?ЮѼ\\\֬Y믿;^<==޽K޼yxyyBWZo҇ݹsgK.=}tCCß9s\plp8VZp9s233^Tׯ/ɓ'= <'NH/tx\.?ussń?XCIJJZ|yxx%0/k?3{M{@)*",ۊu]{_˪vbEPi6QAR"K }ٳ*6{.@mmmXXX/errr6lذiӦ~߃D" NuFF1c9w8xڵՑNNNSNqqq9~xAA1/]q77@99977={r<?2|?~ *J B !D$:AX"r]]ꐈL"H$TA"26mڴvڽ{޺uKVVp:tnoo_jeHHB>((hذa{KJJʂ EDD455]]]bh4%%%__ߪ^TYY㣤dhha޵ ù|EEE|}}_xћ#99s"F u/Çǎ+!!aiiy֭twB| 1_;ioz?Ҍ&Ns附jvv6433!pv뫮kCVf:tEJJJMMUnmm רz?Pt&M.{ [[[999999[[SNw3taOTTŶmZ[[e2tZp{{{EEEu_Kizzz7nאqfoܸԔBt܎{y;#)WUUU@ <|T*ڵkOnv\dI_Kz~= `P'HARRrҥ QVVB(""C矿8fڀܛvǎ:p@GE9|\k;~Ξ=vاOy&//MMM!Ҧ?~;966688!$,,looAqR^^~u !TUU>˶6O%%%T*Çx2EGG#Ǝr###B7zݻ<㦦&j2B}̘1zzz"""rbbb!*g{ 0բ֭;`]e͚5]lmm޽[\\А/ lmm;SYYEEE񇢢"Bhǎ=ƃռ//CNN!x􆆆7oZ[[Gm۶>|0/SEE}})>>~„ ';K=7=C 4@zhee7Y*..N0І ss/fbu%((hll!rMڛ6ǏG^:===--mժU'Ov<7Gi7o"믌 qϟ? YBrb @ HHH<{e5556!aaڵGޜ`444`[ZZZBn>8#Դ;v랐JҢh'Ntvv\`˃wyӧO߸q#...%%%//]30#G;.//!mmXHjmmMJJ:tЌ3ƍ {nuu5f2 +q^^GUUUa͎=%"c_=<<̆.$$$!! J&KKK+))صN00>,))ٛ28®;j6|q9o/^xzz`0ҝyAޖ}!,,,z)//!doozӧOGnff˾wzz!ޖ8,%7brIz~Ko577/\@ < p႗BHIIiݺuO>eXxҥ+Vd`lllٲ˗/wplB sBHPP \ȑ#B>}š={KD`X^6rHuuu+( BHFFFAAǮuB~eM(1w\q}WSS]k`X^-D"f#9NC`|ݻwT*CRLf}yXly6mӧedd?rw=unn.6}kwzZYY)++maXX>8wzǓSUWW'))y=gggcǏ'NXZZ}7o$&&Ο?[7owޕ Datttttt=ziw >y7JII=x`vnޤi#++[]]abb;Ezzlee% CFIID"~] F644U+((V[[[xxxm?BBBǏ?}4,ilu{7#yoޜkӧaÆ?~Μ9wu||y~= bvPSSd0 *[odɒI&bw,+11999SLquuurr ob222kjjFicccmmmeee0]zu޼ySLx񢈈 2?"}YXXXXXΝ;ӦM7o F=~x byxxHJJVWWXرcGFFD"UTT?=,^͛9۞={*bJJJ#߿ΝO޹st˖-Ȼ)}9yE;sRL7=[D"c&.ɉwssvw\.+++%%%##͛7!}}}}}}===}}}AAA^=x p֮]kjjm7i/tԛ7eX lRJ'={'ўf0NDtBf$nnn&&&o޼9r䈸xiio/w|2qccceeeEEEUUUeeeUUUEENTTT^7qqqUUUEEE짒 k]kkk{yy7֭[xGL&۷`DEEEFF.\|0^k/d2[ N'&&&$$;v_~=zMT2fΜ)''p999#LD")--522`0!*ǚŠ X,,((}}?}竂5 ={466jX޽C<{ !D&UUUd]]]]]]== EEǏMK̟?_FFf'O233=ၭ'&&foou֨}GPP;ؽo(6ɩp6_Cz~k|}}_%((X]]ݯg EEEذCPdODDD[[[o޼AEGGwH_{{3 wϊ 7Ν;F7ΝkhhدCXhl;w.ZAKK<-Z߹s';;9y$Bijj;:4qDl°_UXXw\ %%%2eb2c AA0c4,,,vrݽ\]D3g6t:!$..ra֬Y8'N.p8?3B}7] f===ǍwС`Oۃtuu}||*++{9sڵk;˖-344555]|kxYwMeee ֭D׾wbv}w֯_߱ Zo^OcOM}};v/[NCCf@c0/_;w vrzU{{{7-,XЇg/nMsq'Ov8&nt___111ss󠠠xzd2.] "X"2L2e˗i[2|ԩ;J###:l2GGGmmm,;D"ikkO<900ɓ ? X*:f̘rcުz@ܹs۷o !",,,00[CDD~˖-0i R,kҥ$رcx28888(**._.Yd„ \.ѣD"XbĢE\1B!4g аaPʲ83ڼyΝ;iiiwX!pq`ҥ'Nprr;=}ܹs+++o޼] ֮]{<;;;]vͽ}vXXXzz:@2eɓ͉D"ѡK.EDDL:X@p\ww46WSS%833*00pϞ=={K֮]NÏ VVVt7>}Ç߿>777++ !fdd4|pCCCcccCCC}Ç w533;lvRRRdd۷sss坜<==ٳgqqq>}3f?\9z+V>|k wRR[lrƍ .̟?۶mBdRT 7n3ŋcƌIJJZ`Alllaaw/^|Il[[[mm-?p۷[YYh{uqFSS+W`Yϛ7ޠiEEŎ;?>a„UUU#233Bxӿ8˗/tKpp=M5kVTTٳ?Nl׮]QQQO<,dJ.^?>wѣGO6_7YYY7o$pr.\X]]_' qcuuufffVV۷o_xq隚"%``sOOO[[ۋ/hc`P#H666666{ʊ6m%$$ALQQӓfgg?zǿ/4i$'''#g+WR .9s B;"ERB!yyyp8uuuXl%.[]]E!d26?p]A_nƍ'NܴiӬYmnJu?{'ɑӧS7^@>iee}?.!!ٳgd2D1ct: pҥ ӧ;V{{߻w[}mذ",Yr5WWWcSoӧ߾}k ^^dcbb&Lw8CDiiiff&/;9''ATTB__?/bXk֬?7o޼uVoWPPe*xyy)))CIOOQLL̳gZ[[MMM9s>>>׮];>믿7nxyy>}agΜQ(AAaÆeddL6-""(33sҥᥥFFFYYY .<}4BHNN_ܲğ3v>|7/'OL:UIII\\|ԨQ۶m{1-)N_dFhh]2B̙3BBBƯ^駟effnذAKK.))iʕYYY߿߿qrȉ'v޽` FH\~?t|"hnnvژdoo{{{QQQ}~Of'ɴi={T[[w8|Jlyyy.[UU%..b2fl6!$,,L D"ة"rǸ@w =zi.koo{ׯ_t)!!A__ժU~~~sqq)**u˗/GwP}2$$$444##CUUg&&&xUMMM:::X?={vÆ W ϟŋ˗/`عs-[,Y~!_TTaeew8?ׯ_~:--^LLFUU0Aw"""f͚eeeuu)))>P[[{޽7oFEE7]EER?~wbyyy;;;{{{eeeCbbbJWO^jUcccYYɤh'N|!LFF^PP~޼y/^TUU |rUUizz9s.\klllllĵs@o޼SyyyooYf9 ---.]~-r> dX;!.ѣSNݾ}[@@֖Jhw]\\L&`Ν 166;^BgΜ ־p?;wP .=p~h7n5jԅ 455prss䔔55qƍ3f0i}YRRx}f'%%]~ڵk^^^xfcbb_|I Fhiiѱ-&&FWWp˭[f̘r_~%--C,,,RRRfeeemʕO+--]r LYZZ˗Brrr͸v  /^JuttL2yC60$GGGGEE466:::Ξ=}@䧟~yEjj*;od2O8q̙>HHH`jj{`KΑdMM͏?lڴiĈg/^_>))iÆ 6m2PQQpsرCQQ~DO>]vmff; Ł߰l:l bbbbmmmcc3~v)//wqq;cׯikkxzzC |atttLLLAAԩS]]]EEE ɓKKKLMM<~xĉwﺸ 9sMRRROOݻwX3==#*++_IJJVVV^)))3f̸qDddkNN˗/?*##coosΤ?bs Fqqq֭>|ʕ+[ZZv]TTt}__ߡ\\\lٲ,sի5443i$lŋ,dИ1c:泲l@b X,֝;w~񣷷رc9NRR! OO>]]ݝ;w°?޿O??^RR299yڵ̇H$Q@@@HHHVVVnnS⼽^`0G1c8::FDD1dccsȑgϞyzz޻woܸqZZZ+WLHH )))'???;;{͚5>|uvv>~xQQ1Ǐ&Lw8|J"KKK # fG555]vlK[[B[ё!WaЃ >|᪪ʓ&M:rHVViiiyMƎ~b;R[[e)SUWWGDD,_\II 0OVIIɜo=<~8ccc;gΜSNeeeUUUݹs)==}ܹfff֭njj;;wݏ;w8/D޽{߽{ѣS޸qcܸq4m͙xB֯__^^~IQQ`uuuss-[U"AHJJƎ?1** p4BHVV!`0D@/hL&S"2!ڊMJ櫷9$"=044LOO,))))))++khhhhhhii5#"""/VUUݾ}ĉ7mt@W>| Yd)S455;V\\O4IPPHǏ;e ?lذÇ111#G Ú5k] dꘘ7otSKB߿?//o֬Y6m?444/^L2ù{ȑ#߼ysĉ WWW@DDD6l؀kr<4i۷oO::ydHZZeϞ= 3gLOOwss_`HǏL&{y􈈈ϟ?---}}}6bKKK zwЛx IDATt,VLL<633366|w__7odddHt33L1qr,11q͚5/_tqqٶm9 <=.++ylll5bΝîR( C8_ssݻw/^rmmm]\\\]]uttC JLLy{{Ϛ5kxݩqFhhh||}zժU CYYѣG&L;),,\~}XX˓;f# <ぁ$}ѢE;wxJKKГɓϟ?kkk;;;;;;CUQÇ{ݻw/>>}vvvG !TVVӧO>x𠠠@^^~ʔ)SNutt;:_jjj""""""L66ky zǏ---QQQPbX> sNMMͨQfΜ a'---XFݻwFᡦwh`hڲeˎ;:j*c͘1c"##]2iҤGt}}}ЦMv9l0OOϱcǺ! i4ZNN$"FQQсΝ;p,X믿r8d2YPP͛NNN| w.( jhhhiiijjb?UUUUTT9WjkkKJJ x9ǟ>}*++.戋c4 Jhw;@ <_bErr/BRRR{]d a"Pmm͛?n``p!{{{#ꬢիWߙyɫWƌs133>=!!!**ǩH o߾}y||g ĉ0544FGG?zF7n4.O{BXYYM4&줤$l'L>]BB膔jggO>EEE O]vΝ;Ǐ駟T*ޡ04\~ݻuuuXF3 #}/;wܸq#ޱcԩ޽{t:}L&s޼yoNNND߿666!fkk{Io"ΧOdMM{СC!!!˗/WRRjllܶm[ss3BL&+((B999 \L&={JΜ9ɓ ,;" BBBXVޟ yzzN<&зN:uԶWnܸqݺuNNN~~~nnn=ޭk֬a"""7o;ERL\ii<@`0RRRWYDDDb,B tzw7mt=33;wV8dXzetqܴ *}ڱӹslah,MII79M.lhhƊt666744y!XFhIII%%I6 VeeePPЬYlmm;nT__|aڹsΝ;Ǎ͛n 􉖖ׯ_t6-..nnncaaaii C(i{sssII A"=liiihh1fum&))>0n߾})))W^ ٽ{ȑ#͛ջ˗KII͛7?dϞ=Ϟ= پ}O>]0䩪8qbcǎEΝp.\H"~ Dd.-ꘈڊ:TDfDBF GfϜ9u]v-]T@@t7o޻wqر[n>}zk%%%CCì,mmk׮a0ODDK  GsᨨĮ {wXg,c_R NY:ԔQIT#-NATuUN6IVJʮo31{SVzcskє~ہEd m6 Ojhh a{zzЁǨb6-**jllLMMMMM֮]L^_Ca„~tܹs\r+VX,HG ,,,⪫lmm={u: <OѢ***;ӳjժqy{{a7n܁fս7YYY۷CիSLr[[ݻwCBB핕W\yE __ߤڌ0'''---h!eԩEEE3g ^t5cƌ{=zʪ80ˣi4Zppq/^|ƍ3<DYY9 2++K[[]\\>55 GxxxDGGceEd^^^0p"rkk+ZGCijjpl6H$PD01 {{{+++ b'''<fX t:]TTήѣL&֭[⃘=rrrΝ;PSSS__Ǐ@G ~?? ڦ+((|z6쬭MPfΜں|+WTWWWUU]reǎVVVndBBBnnnϟ?𡁁GQQF)Sܿں 80p8U[[{+((bIOO/**tΝYYYӦM:}||<<>>1o{X !&hѢuXYY}q;qD^TTe˖4`ڵk^^^K,nǏG͛7c޼yW\v횿?:U__E[Z IDATDDDVX#G~K.UQQT`NXXxׯ_={vtttww7F55Yf566bF1 ꚕ+:~Ayyy:0kd񖖖wYYg||˗J%\^z nݺu!!**.""NDFݻw|||Ğ -"wwwDt:2@KOONJJ:qB#tvv:u|񡡡O>-((WUUAjoof͚m۶}zO,""2pHYY`婩>}z^^Ç̙ٙ@eeeSSSǏwެ0''' ؆ᬭo߾gbb5nܸ777cmd8qbFFFSS:zZZZAAAeee111jjjwB.99ݻwdz *++8p; wȑE-^8==8OTTIZZ[Df2$_ww7BA'"n>>Ed< ZD9~(";z{{^|iiiUOgى֭KJJb0 ̛7ȑ#ݹ \MM͠)--h===...ZZZd22""AWWk׮yyyK0hhh;v~۶mNNNx@JJJiiimmm 808::޹sNNNӧ6.---//600P^^~ӦM_:xɓ'g͚Eӳ3%$$a0!H,[D&芋Dd)PD۷oi4cccO>-,,ϭڻwƼy븸8 b7g##ӧOJ"D{䪫'I[[۝;w'O\YYYRR{,Dt.cWaaa```ZZڤIKJJ5R(**-QVVxMrrϱy={$&&޹s\{/^>}u~~>q|||hb677 㾾>^^^ׇ6p"roo/V=p1X:cƌt''/|V__͛7nܸ?cO>}Ֆ-[(իYfiii]tnG~8AYYٯpN|X@@`)))ׯՅ|͛_~ӑoݺf熆O:USSo߾ ]]]}}cǎb'Oy?8"x%555++obgp'"!BRL&:S ݇ለ455xt1{Eda"20h=jmmmnn%O򒓓%H/_o޼)((jii!nh4ׯ,Xpݻw[XX|F$WZUXXx弼55-~ 7o^jjjnnb@@:%%%---kkk38ЉRRRHHH0LtZ ^Acߏ :yOꊎ־|M.\@ӿp*`DAGe\~[ }&"X0{{{YYY)))T--#Gz5%%eѢEAAAzzz||| YYYW^500@u.ɥ#bbb2f0ihhUWW>}JQQϯh5&M?+**6lqƱ4|2|ڵ9s挍hyDdAPkDd<& [Db0fͺx7\]]?sMMͮ]TUUN:U\\+//?}zպ:KK,))y0k,t!gggRPP|Ǐ?2!!a䌢`lصkWyyn޼tϟc `@^^>55a |r'"ST&)** ("VH$:mL NDp&"ߩ`ƌ VVVɓ'˗/WVVϿ+H$Ұ s$&& ocOOp8-B/Y$33SOO{А`aaA&>짟~RQQIJJ2C#?~ܹs555K,)**:f{{{޽{uԂ޾}joo\L]]]++Tsᦢ{{{>>nNDFKxajj*..~]UUՏ=uTCCòӧO:tHCCcݱcǖ.]f͚go8\__?p챛 ^^^YYYaaaܱ=Z[[NtRx<~ɒ%/_<{ϵjjj΅ yy6 FAHHh͚5?ɱ QRRrpp{.S’%KSRR>}k׸uL#ҺyfJJʆ ]区:*aYDDA>>V^#:*|2?à ᖌa"2<<< HZZԩS"""*++XRYYibbKJJN<6i$119s\xQ^^^SPPسg :(ʗCBB.\o }DkiiΝ;=Ӄua5qĴ2kkvH]]=((<<...wFO8eԩ/^HLLh :sС3g=|pĉ䤥ERϟkq///& 0x<EdOxʕ+nU,_555o>dr:/of絵CKK yN8;a„ٳg/\X>ꉉ+V.`KOO/**t֭oߞ2eʼyn߾__KIIѣ3gtqq4iRBB ƤC͝;Nx,_MLL \WWY, 70c<<<ȿeiy|F||ݻ}}} p֭:#`H466:::FFF"ݝz~~~ccc;;;f``.^YҗLDYdBxx׾)01L&`0Lf]]dVB QT)))*JR%$$%$$`3 ACC҂>ڃd3_DDؗF?$%%ǼC455^ooouuugg{JJJbk8ܺurݺuE`BFFfΝ۶mqƱc̙zj0())8::o۶mŊ+`'O̔:W@/=HKKd"^FAO ecEd_hyLD"2---mʕ>>>kcc>L <<<*999'OdX^^^/^`يfff۶mhSLA} _cm۶gϞAt/+++))(// ;BR¢2'DZZZГ`F6&___d2DEECƠւ򊊊ʊ P"AT*z7&1GR f֦&oii)++{ z + AFF0a„ @hMy)__ .y{{ak͘1#99ʊH$FFFbB ͛7o޼]vNNN6lPWW:c|XX={V^}~m̙X?11xܹ:їBcTQQCD\\=]7 =J&Ed;x`={98C`zܹs.]}v0^^m۶mذaY|>??P111A.]utt}}}Bioog6x?`իWCCC'N1_ZZWPP_PPPXXى ??qMMMdeeD"q8s7444440 n7:''ڵk555hIII---uuu--- mmd|W^xiiikii滏:2F[7oެDo"ZZZjjjI@8iѢE!!!Ǐ :א322:wܢE(J`` q'_vСCzzzK.u0~jjjgΜٱcΝ;gϞ4esA&**:}t:4Zn5DǣHKKs8*с H[[z桥L&sW=˝p`"2EtȞ={֮]bѮ1333333--ٳgl6D"h4CCBp8V<///w~f={ٳϟ䴷DuuuKKKt"ĉ%$$ ?/¢Ī*Autttuuuuuutt=8`ٿJJJ|'PT*Z[[KJJ󋋋➞AA)Sjjjd֬Yc{{Yf:thWRtzll/q|||vvvvvvaaa...nnnT*tz.]z葯ҥKݫu.0dee^jjjz3gΠG8 R[[+--b%$$q8\{{;zL&sCk'"N@gϞM ޽{7nضmw]DGG… SSS۵kq>BHKK#R__OR߽{G$ѹȿ"rWWw"22Gzb`0MVUU% i&QQQCA^^^zz222q8Ѻun޼vڐ!MBPAW6'&";wŋiiiC ŋW޾};++fʚBZZwÆ Oh3gܸq#NA̙3ז-[6o oGqssSRRZjq>L&777E人:*K"c;]NNn EdSz{{-[tww;;;c ]>}333[ZZ -[fddddd$&&vU777~~d aH[ZZ>//G'"nڴl֖r֭7oXYY]t„ X B@>}wuwwյ100˜х`ܺuƍh] IDAT355:xyyuuuuuu7l؀ H~~>:z[n8q9s̙cjjJ$ 6D -[qmmms ;v\RHHNc ݹs'""bJJJ^^^BBBX`NNN틎 \b,66]UNN8BPEDb]]hllDw@'"wvv䚚t#w2"r__p}}}ǏtRLL ޼y榥%,,lffvYqqz޽{ܹs;::.\hkkkkk;<-dAd2Dd""}z{{>F}}}NNNҋ-^f͓'Occc{xyyn߾d2/]4u3fa>b]|y…ε;v(.. [pBϟ?___Ν9s\vR^^'''`L&fxyyajZjIIIXgfͺtRII N߹s_uu5ֹ|||JJJ,Xbhhu(08BBBtŋsss)hIJJL BDNvY EdKHH>> 666uuuϞ=۳g7 G4AAA[[ۘG9;;ǫcwww%Kp8'Oݿ˖-'N:3s?/_MKKљ4iRHHT[kԩǏ?{z\\։;z .\uZSS|UEEE:u.F7qqׯ_khhYZZz P{ӧOkiiTUUa?Q(iiz FAAAى "ɭhps'"sPDA222ӳ,,,v܉u.uvvXZZd}}ݻwwuuyyy766fddt>mڴ`__ߧOh/''ѓ6oެva:s̴i޽e˖5k(((`n񆆆8Ǐ***:::feeacll{\|yٲe0kikko޼繹?shhҥK;mڴEUTT`ruu͍111177qw&(((%''744/---X߅/:Q(Aщ}}}dc^^n2B"㶍Ed1 GWUUrJ333w޽ .s8XB!!!K__L&[ZZ%%5khjj>{ÇXGq山EEE׮]: }vUUccc^NjD"]vMVVnll:/˻|쬬,999[[[f0YXX<{,<<<..NUU{^~,-"KIIA/Fp|||"2???ǝP]ímΝ=tRFFI||O>}ZCCoڵ:Xp8۷srrDEEMLLt˗/.^jժ?CII PcERRRvv6D1c]QQ֡n|||{-((`ٚ, PIBB֭[նcbܸqAAA>>>ϟWUUEX`"ɡϟ?'HӦMsssc2Xsޤ-"SԆqqqˋ ("X,2 @㶍Edn p&MYZZߏnnn!!!؆Gu޽}HII$7mڔ^[[Kф/RUU/cK&";;; o:ٳgZںt޽X0&Mx}&o>4\޽[hQqqu`xL:5)))99tԩHEE ? >}uuzjJݵkWyyÇ߿`b J[[;--ʕ+ƍ{w8BBBƏu fIIIC&Kmmm>$f B$щ=f܉#, ٳg۫ r9p#`Źiii Ϛ5ʕ+'N?LfbbF#H_{SNikk;88~Uy˿{[__aOG||ɓ#""NC311MMMKJJNkRSSL۷o8qB^^D?( 'OlٲƦD 7ZXXbddсuA3iҤ˗/_xqXgH$Kaa{lN\\ȩ0܍7߿ODDDRR҅ ?p8LFȽMMM藸8z)poH !&~z]]k׮7UkժUVVVXRQQq̙kkk+++Y&;;2>>&+++,,NVV_\reɫV266.((999SSSvDdxbeee,A>K._<''gƌX' x<~˖-Ϟ=c:::7o:` 255}%9^^޽{fddyfʔ)X'222.]:{s&O?`hO : E={6i$gggUUհNs0 ߿˗4mX_F-[lBinnR444bnˋ @ T2E `HÇϞ=p]vßRRRJJJ޽[vӧOBCC-Z$%%ח`hhpBUUܸth7Ed>>>An߾K??? A:66ڵkX'CCCÇ;vq^zĉgϊbCC\SSS^x1uT+++oo1ZdÇ7oޜu=cƌ-[b GUU5%%%..gϞ::!!!X ERRN;niiAw@E!&"FDd}|||}}n݊/_FDD sB0躻;;;ijjBb7v777s8Ξ61}>l(x2 0P( D^^^r B"d2zg4jii~fffFFFNNNGGǸqLLLkll1oĉ .>sέXb…IIIcf>JFFŋ 7oތ522: pww+(%q )S9sf߾}  tssK0-_f۶mG0a֡Yn]``իG΄ Dd<KKKyzŊb׳Edt42zba䁥dlA`NMMutt TPP:::֭xK477777 uo1Z5/48N@@D" QK2|8fp8t^_%d2D" H$ B&d(_:d2222233_xקkllell`hk׮=x Cvx{M:8rDDĮ]>bŊ-[hhh` DTTرcׯwss\~}```֭QQQXg?h@ 544Wل$8FC$Y,ہ㾾> @ ߕn޼immo޼6zΎ:l6`0q]]wcSSZ[P( EYYS]~~= ,Ut疖:{o///LRT*URRRJJ },-->бǙo޼!v244D:/^8tEDD6oBH6at@222jjjg$rJLL "((vvvy%))Nv炂m۶B©S-[?ʁB)))'N駟]p8ܱcϟC c Jݳg/sСX[[۝;w` dʔ)顡wNII9~3>CZZ%$$] 8  AIII!..."-"|vttd'ۋnXDƔ+Wx{{߿ݝs8_u驭WmmmUU6f2=yxxXIII**!!V%Ha=gHs{޹ ^^^,///###/999))syQ[[K&MLL\]]uttb\?V9K} &)..~̙[~LnY6m*((0555666007LOOҥK-Z4=QYY)))9iҤ۷~p8 .zJUUu{r_É:{lAA ...sfsʟN'"r&:o>?{!z( p˗[XX5_~EUUuǎC;7ƍyyyb_Iٿ2>~\?o޼QQQNMM577Gۻcǎk׮577Ϝ93<<\BBa744<eˆULMMf͚vC{q?D"\bdd4wÐ?c=<<^gccsӧc Qw֭vvvk׮hcӦMgΜYz5YA( zJ2 MMMtaOWADDD'"X,Ap8 @[/f_իW6l6_z6 S[[_eeeuuuRRRhUNN9F `x7Bo|GWRRRAAAJJJIIIQQ=~NXXȈFL6K ☘'O2Lsss777[[[0<>;}"uuCmݺ\NNxcɎ;O l6WUU577711166o縸8WWג!Maݽ{t۷o6pgp8IIIv׹=m鄟x;ޗttt=z4?<_[[1ȪЏq/""lٲ˗񕟟8w! """-?l +㳯cll}400x Gv_|y~~χ%E/|CCCMM7n|]FFFpp׍}}}Ν w8NLL֭[%$$bbbFc޲e˞?7VٳgYhъ+l{ ݴiӥK;V___UU߯P__OR䪫^ĉX%.MZ[[===yyyOO8qe3Z[[>}nݺٳgqǛ?~֬YNNN۶m t҃***X,Wkkk+((HKK;uԁ T*L… 7oq"tA!  rPPPMM͐IIIAdr!2gΜ3gbl9z{=n033۵kWJJJgg'ñy\?96 MSb!423Y4YBJvD!!-E+U+-MP'߯y} ,TFFFwn(**ڲe BȈUBB" ۷=211A}'N <==zԩSw lu ѣG \g}ŋ#: `LpA@ Yt:[sΝ'Nh# !&&{˿|<,ttt`KzzzB7o쿡TpppuuuPPBazBdDG` y̙3JݻҲ8kw!??ޞ@ 3_%,,l222xg `0|}}sssB슊 W\A:uUAAEQVV^f>@ 8::z@C`; EEEGllÁ$%%js*lhh* Okkk~~~~~~aa/JJJB|||JJJJJJؿSL+իW/_,)))))yś7oT*BhҤI***XPTVVFFF^|@ZZEFFf$t:'44[z|H..ٳgb}}Fv|ⅲ˿p& y(C۷mmm+**H$ԩS߽{w…+V߰TAA[RZZ$$$|O'Nž0 GGǷo;v)c, ?`4<}?66VSS֖H$ =::СCx'cX!/ܹ믿;w'''GVVvܸqUUU8PWWwͦ&ӣ^VV& PWW'$$҂"K.|2! BJ>|xΝ&MJII166ܼy۷ UUUbvrr'LwR0f Θ1cƌ%eeeX]˗/;1m4ihh|:uʕ !! a-?$"(##kׯ_H$cccB>|xV"hQYYh40''HܱcNٻwĉyyy>{wMZ>WI6u ˆ+4ouvvw_ _U6`WZ[[G3 go>!F]]|||޽4ik=3삂OW`gg*?+\F:9#JEE%33w Ǐ;Է(//wuu|.Ӱ3?V ZZZ>~HRdĜw? (D0$k֬ؽ{;vmk1ݻw/^xʔ)Þ}ٳgiiiiii>|@M8QSS I~`ppp0_~@ _r%))'ibsˢsiihh(,,dx񢱱!ͭꪤ3`[DR555gnnb0#!(''- \bb=A;::ש %g7$?wܪUF∾BNNn~1:"sppʮXbɒ%('5B@ x{{/^800 !yObi&k짊}O޽{wС~ !`0Z[[;::::::;;;;;;::|҂-P3M"& KTTTLLLDDDXXgffrJ55p}}}C}*L MSS3**ŋ{qvv޵kҥKAY[[fff{;899.]}t9  FOO'''D#¸qB ETT{dBdi _9sfΜ9o٤s cȸzjOO@_tիg͚mwQӦMd,,,볰1_.((/((C *))Λ7OEEEYYYVVC<޼y|d0==YfD͛ؼy3BիN:rrrB]t `c>{„ o߾=}~v߿ݽESSSMMѣGΝP(,0`vvv**,,̬<ӻ|2Z|Ϟ1-RVV~%BHTT 00D"-ZAAA![[[ ӧ EOO{I oUooo'Z[[_{_21RRRbbb\0̙:{۷."xٳg[[[c;Qc??+Vرcɒ%y?~˗,X~3rqq9rHzzz׿!8~x:NPmmm4BǬG]~ÃJ;v;fiiח<,XJ̌OHH<OYYY=̬111;wڵk Q\\\JJ\ɿؔ<)) [leem۶N9sfÆ #r<!bbblll/ٶm۞={B)S$n ꀅ`07o^ll,Ǒ#G` %g7D޽s#:!|ر?ݻwǏQcBMMĉB D"-Xr@ϟ'$$@=c rB:]cÝB۷o4ƺuN8\8yB2^'UV=}x755Knhh`(766555a+sppIIIKJJM׈`9sS]]=""b,>NOOOVV6118F[ee={Ν;7a„[T- 6WߺuϧϽԜ1cӧq. ?}taa{zz~𡮮Fr99 :::1 RD"["ϿsG>zw]z}M^^Vll%!㓒Νkaaa`` w:ś7oRRRBCD"͙3_ssmͅC9!N,===>>*7Ҁ}}}^^^111 $$$?'N|3w{l5NNNiiiiii &b@j_nncsss笭rrrN}sܸquuuRRR76N"TInn.hoo:} Dܹs޽Gٹsӧek֬yы/ܪ\QXXgfffiiiii9yd0lh4ZVV;;;D"͝;I{yׯ__h޹ԔRRRƦidd4s̙3gN0{v뛑chh8111?̽+?<`켼po}UxX>Ňu&M$++++++##}1ydIII|Ã1pႧ}\W[n-Z(((,p,G8q-[AUVV:::Y8?ZH|&--_=~8++ALL k!D$tYvv6F`cch!666ssx /^XfӧOm5\RRRZvpܸq#<<-rpp044sZͱ)))<<< ,ptt455ўIMMf666111x=}}} $iFFF|||5POOwvvȀ~!cjjYcŋW\믿eT|իWrrrj cٲe999xf]]]555RT.I^^zӭ[\\\dee###u:_ݺuk,6u0޾}o߾sM4$`$08,, oė7p0u5kxyyy555ՕO2JkjjFFF***23gӧO{{{dH433KLLhnL}qΝ"Ϟ='^|D"999 >GM~~ѣGBϏ?>'''޹%"""+W\re]]իW#"",,,&LaWWBUU!9h(jkkccc⒓)]yxxFbO?;bhhݻwG".;v8qT!322-[9JܰZb涶V!v7n$&&xUq)++zJ_˸Z lmmde˴O8wiӦRGGnjiӦn&O|ԩڵkڵ'Oc~ZaƍΝG?9lkkC566 cR(DbccH$a !D :"@d77~H$544tuuO>=iީ***K,;Kcm_ QY!÷))) 5km&..wo^\\ͭw`0bbbbbbrssǍgaa1w\ YY !!A $$$TTTTUU%$$B?~\bELLLdd/2 Tի#""N>rJ6e˖G|g!Ȓ%K|||>nu.vuu-^Ѧxa }}}eeeUVVVQQ]B_4n8S@Rt k V__۷o>}***w۵kם;wwmaaw"XBټy3g6lذ~hvziiH;;;2)**?~ɉBٳ5--M__!-&&VZZVVV]]]̅lll)))8'#2:::=jhhx]a"++ٳþ瑓}yYZZBGS#2:>SNzxx<}4""bʔ)xHHHEGGX,((ڊ+//Autt`+ppppqqqpp/;fN:%99YMMŋNJNN*dӧtttFbîc͚5YYYwޝ;w.T!x v]|||%%%!!! aaax #""!4g ???UUU55(ן={*d&Kј;vHHHR"""ٳg޿Ua}ƍ(2tCtttxyy>} Uȃbgg7oƍ=_^^ȕ+W%&&Ϝ9SPPPZZzΜ9'OLMMmnn豱?~3󣄅ܹSXXn:`qqqbbb?;,%77L&GEEg`BdQQO 999?~gnE_lK >-M "O|ڴiŮ#Tkuڵӧ߾};"""%%eƌx'ՑH$WWR''UV\ P_gĉ W--- @ ee3gXYY䔔۷Ȉ >jT*w۶md2jt##knܸӧx'hlmm?dhhzz>|}'%%EVVDcǴiwqՂW^ݺukÆ ynzڵRֹ Fĉ\\\w;ceW^ ;~8Yuuk׮qeeex)((deeyzz.]t坝x'Q("c˱!...AGhbbb"##cbb$%%Gn?.[l."""K.;@@ 'Oѹx"BBlڴiԩIvvqdee333?'JRVVtppz_SSӲe$%%UTTn:`N_|]KK IDATGAAamMظtR eee///*/g͚ϯs͡s?~3נC9RkiiގwQYY9n8iii|;ggg7qD??3fVVV PƖoggݻwl@x֯_ɓ'S(A󴷷haƍ<mmm&M]!|xm۶[ݻwo C9RVSZZ:qDU,'''*AQeeτ Dŋ;;;5P{{ÇUUUq}7vZ`WBKHH2ٳKb ܹwOsϞ=vjjj;.;;{ɒ%qqqxuwwggg;w?055źdDŋݻxjjjxgիW?ww`gg_fMmm-މ`E\\\xg2Ç%%% &;;￱444B4ĄD"a544ұd|]GGܾ ?_v-**JTTtGlnnNLLttt遾F[tFDD7wLMM,//_x1F3779s B(99ѝ;wjjj:::l`0|||֭[AAAo߾ 4Ç+**޸q /_FwttTVV޸qC__(UUU̡?y}>s޾}#='e͌NArFAA!...77wxg X?o|q L2%,,lo޼IJJrvvTWWkmm-%%oll|ȑ;;vNNb6III7oޔ]^^^r 77: vx酅'O,((;'2nܸ[;::x08333ϟ9s&77wܹxqqqikkZ*((())&66ŅsRTTtrr NKK;5vrrrO>]h… lBÇ:::kkgϞ9r޽{ʻvP(xL0!%%kӦM˗/Uc…鸌.((숌jڰBAGd,}͚5ήa=q/// 6 ::H$xK̛,bn{{{999...T300$ !=h--n˜mX`B344zd2yݯ=/:\PۦYt(GʚXwi!''' UkkÇBFFFׯ_ǫN/** BDggǏ|:D" h JP9U9ry'UPP)..N$ϟHٵO2!̙6Cy[n&p+z{{UTTӒΝ;߿сwF-N:aee{ ݻw .S/>t萨H```OOމ`97oRWW/--;OAAAaƍ }mPOOVwˋZd 9BqŊ+\ӧO@vDƦ555q9O_ӡXy1{{x„ 97yɅAAAF4` Hz&$$̛77C3g}2*)) }yZ[[?ݖgdd0_CCCg ?իWXE&Џ}˦&l Jegg'~|86D:葲I&=z,knnK.Z_;vѣ aÆ $a0/^Wqqqkk+:??rE"H=K @SRRo޼/888aFU^z'//4uTshK.ݾ}@ ,Zhٲeg:! KOO0a˱7`jhh~occ#''̙3*==}$=,//O__~~~xg:::/,,l2fOB.\y[nЏ=2669z]*++ݻZ`[޾}{С3g draa!F&EDDٕ G(>'tuttrr]]]ZZŋGso$))w!a14` vǎt:ƦNՍfT W^߿‚֭[Gt!&Ǫxgb#:of7 ‘, Ѐw!}6Ʋ***6n(##nݺgώrr[[kkkiiiӦ믿wޭի;wB2հ͛7ʕ+G_DDDlmmϞ=؈w@?L4iӦMrrrIII{*N\\| SLqvv>ydQQ =|~~~Qhh(qA&O:w,ۻrɒ%*?YY'O[n…nnnx'ږ~z.a#D [!ՅͮsvAԆ@!2RSSo޼%&&6nݺ3wQxٯUZZ TQQ#u2665L(__ O>yCz({fu8!AZh#eY/PGb3f@޼y$//={TVVڵKTTtR'O\j---/_,,,흑^\\i&ohBlbbrݤ$Lͽdɒ'O ˫xzzFGG777?/_:ujٲegϾy󦅅Ž{*++9w@??KJJJii={%%%}}}SRR^n:YYY2|iӦEFFVWW?&MhѢuvvmٲ@ lٲ%&&fЇ\޽{e՛6mRx'gguֹڷo_aaiӖ,Ybee"<`$i߾}޽;KOOO@@ 11q_,**=3@ 0 㮮.>>>= Jb15yFPggVqq(7Bf}'''.{{RG? 333Ё^~'88k>|8((!dkk;G}-ϝ;իK.!˿? 6ѣGkkk=zСo;L0!tm7葲C)((roz5]௫ߕ /\PVV3r#2ӧO/_\UUL&h477R(/_^pa3g7nw>o޼a $%%zdkkt{{{11iӦ_ʕ+o޼AYǏCBBUTToo#GXYYqssƍ_veSSS!!!###ğԒ`EDD)))III-_hʕYYYuuu.\;)%lllk֬x7o^x"//mcc#!!ƶDZcǢΜ9cee{}Ippւ X93X|TTTFFFGGL^jUuu5ޡ` x1H$ ĚDbWWVGذBdX!2 vD&`ݞ\СC...xhmm;{3^Ç%%%yB_^2m۶mϞ=7_n݉'Ms 7776S3c.xxx9r22蠃)kzí[W`MgϞݰaCDD… BEEExB:__ .8p|jjjzIzzzZZ۹ FnQѣB:.((H& FGGGAAAnnn^^^nn/xxx LLLfϞM" #:999)))99!4yd---mmm-----yeee999999yyyD"L&L`jhhHKKKMM'ڳg611100S!__o޼=˅5544訨ܻw{_ `D=x.**Zj8މ` Vs玿q~@'Nz=Q`0#"",YrʦS8qB\\e˖{577\FIJJbH$Jyh/P ?;wb-Zrʕ+V466b6}}D|"$UU xzz~+&&B %RSSGJJ޽{lgϞMMM722lEoCI^__#AAAcc_~EAA}_> ֭wutt|tA]zuVss988={ H Yjհ~ٳg999Xq]]@PVVƊԶ\HFqrr****++`sppvZ[[_|Y\\\RRR\\˪*ǧUݑd?.| +tBbbbӦMSVVVUU+4۷/n",&X%+##<ˣSL177777733?~<j )!!ɓ+W;zٳ7owc Nq㆗WSS7BFرc޽k׮ 9U^^.''C##QZXXxϞ=nnn7o~ݻBNZvI$-///Fʔ&O['rςBdAz˗ǎ[|9qҥKz(mӧO]ի/; 1#00bbbw^d !t֭˗FFFKNNnҥԩS׬Yw?ӓ|cXX؄  v[5Goo\YYDŽ G9gA<y #$$d֭d2px'B4-!!aΝx:"""?vww^l١C$$$>b횚EEEW\tBhժUx~cHSSS׮][[[{E; ٳg͛"((H& iӦrOO-[ KSYYY^|I$'M秫5qa%%%%eeeگj)GLLL\\\DDDTT_BBB̯a\\\-\nkk!NW[[[[[BilllhhѰ3DĪ嬬"i%%ɓ'ÃˤK*++KJJ***;jkkT*???vǾg7nܧ㶶bwh섏>|m`'|%%% I&)++πHRR!͛䔔ӧOIJJZYYYYYm߾}QQQ}͚5999***ӦM;wΝk׮ݵks```PP޹mnnwEbb" !DDDtzoo/]lJ[B$2+0չܿۛE̔fhhXZZwoԴnݺ5 /tDrO=Nfee&$$X[[9rdҤIx'*6eʔŋ8p;66uǎ>|n+߿߰aիWCBBd7請;===---===++B hhhȟ Ǿhnnfc4ۆj6V 0h4Z}}=v7vohhhll;F ///;c'y/&&cCť#f͚%khh'okkK"===]]]O گ/ 윐piggg nݺhѢQhffx X[[F ߿oaaoggLJ"899?~TRR!!!QYY9 \x1,&&~KKK;$$$L2eV!#?'NmذAFF\RSS8ѡ<m۶Nm0b#MJJ Ս;׉Y~=B]SSj4߯\qFlٲoȅt IDATxQZZӧO;;;onhhm77ܠkvuuQ(NewwwOOϧ3뉱VP,, <===̗ݟ& #̗!^^^oz(51cɓ':::Μ9RSSckkb KKKvvvsЂss?sܹxVVV4-11!yǏ߿_HHƍt7oޱcg_gϞJII44 #??kI$ lllN#^b͛76l؀wx.h4Zhh;+>>/NMM9s+**>loo/!!1ŋ/\PTThѢk׮ijjN8-&&>>7ܸqԴ 8;;;;88TTT6n8///_Ξ={ҥfffp?{@ feegl377)--ABĞ+DAQ(`0D cl}tMBd3 {I&߿#((w7y۷x"77w…7n022Yhљ3gO>5kSMM޽{kkkݻˋwR]]}?9[GӱBd2|D!ɉ 3 9}VKKKzzƍ0YÇ---N1dKKKK6m$##]__wFN/**:}euuuϟ?~ߧnذALL #((h˖-c.hD"!D&߽{7---7nQSS]:0g~~~==7nL>=&&K.`"9;;;??66;2>}ߗ, -CTVRZhRZ$jM YBDq~9?2q_Tw}222)))0#yTL2%;;[HH0-- 8uڵ/_n۶ ,]]?_}ZXXX?ޡlΝ;W\<ԟ L622z#BMMMX+"srr,Jzj@ `Dd߫mʕnnn-z䉮.މFR[[իٳgA&M~7n|)--*;;NBBB^^9<< |EGGǃԩSKJJ6mڔ7o޼ &xyy'!!naa1o޼3go&Ou阘#DkEE)Sݻw(p@ ###NS266f|!$""'^^^P[[BH$b"2+++̄x0e˖}xgxa4VVٳgc ܌;vttthjjjjjN< uuu>ykKKK>|XMMm $Gww7BHHHHZZp <<|٩bnn~I###vvQY}WZZpŽ{B? nnn^p [xq^^6 222 iii^^^jjj۷ov10;;RWW֭[x'qppUDRc2LԐ '" ~? ڴḭGK, ;syBD"JIIIJJN0ʔ)?lljkk^|˲z$ uVEEŰ#GΟ?_]]:p@llleeU[[kmm]QQ]UU( `iiiee5k,|ƘK.577Ϟ=yxX[[%%%1OA[[ʕ+xg0 =~-މ`>,Z͛FFFx̞=[VVŋYVVV6''G__Ν)))&Mzmێ;"?~_Ě888:::D"GKK cb &"ӹw //oVV6qߗ۷/))[P(C\v77о}>}TSS늊 Z󸣣!$ :|555MM &)<<"""66 ZZZhmmϣ[[[Ϟ=KR,X`bb<`6::::::Gyf\\ܚ5kVZwAvvܹw"x """`!^^^+VؿѣG ㈊>xiܹqqq666x'i0l9DdPSSӴiBBAMD:J2b("߶m[XXٳg/"$ixa:#"H$JvwwԼzv!,,LBBBNNNNNnĉ_YXX૽X !D  W^gٲeWǍӊT*ٳg&&&[[[8`kk+''7 899\okjjY ;g|VV֢E _>sL!ܹs6n8uTh~~o޼pǏ; ܹsɒ%;߿VFFH$,,,ԄA6ۘ!ˋJ$ vҐJbd("%%%/^tqq;UU|Z ]VV߿hxxx!))),"744\r%111''CKKJ\ܹs$i4aaaOOOOO~{ܹ...666d2wuu7oޯw"ڽ{wFFӧO:QVV{nttz@@իD"޹`(!!SN4}tVVV쿩 X@ ъ]]]!l B+cE~ 1*))w(;;!++籃J۷oAVVVGGg6 X58%%ݻwXG!&!!1a111QQQj/ޏ?~𡱱Ǐ?~lhh766 a_ӦM:~!?>///9s|uAAUXX8{]vիG555/qHII)00pݗ.]SRRZz+`8\YY #Dbll,6:118,b OOP8///iii''o@qrr褧3=~~~Z!("ST0X)@ N: b m߾=,,lƍ؏?+Vuttpqq} L&/ۏW~߿dž~<33ҎMDZ#YXX_1k(JKKKkkϟ_Jӎ!؆6԰α_h =z۷)))7gZݻ_rpp0555::ڵkfffΝ>pwM/qgݺu֭+((---;;{ ZY^^;<<Ç_a._q={xzzk|RHHݻϟ?/l޼~FOO/;;{ӧOONNVPP1 HZv+WpL`:v옋?\}R?鉊ZxΝ+F777kkkgg'OL<D EKK ERoݺ5c ϟY۷5557o477'NNN999&MNQQ1_ cOXXXAAAdd$6q[(ݻwmmmkjjҌ^xTYY9 N>=?DQQ1$$f׮]׮]{ ޹77[,,, ~---ggn|oBBB[[[8"xacc|݅ t s??"2 m1???B"C'"cHa7oږ:99gA?^RRt҂mmmiӦEEE-Ze i.egg >}{n4\TTٹpB###MMM _JX'11n {SY$ {, pF@{{;]:>>>""||| x%""q萐ӧ lllqqq6mrttljj+ ˥K455`244,(( ݿ\\?333C@/$)22R@@`͚5?޺u+މPnnְ"rEE++kwwwww7vРVD&_T*LD"2]JJh~~2qٳg[a===qqq===O.))W'5 jkk3gSlllLLLAAf``M:PWWG.%%E/0?bUuASSS_ZZZ{{{ -"""&&&"""""",,,...,,EP?~lll#Ç>{xb|^^^l/..DEE?G5661cx81ti~~ 6ڵ $qqq&&&x0ndooo;;͛7ϝ;ɓx.ѣGٷoۋ @6mZnn.ecclmm&"wttH$@RD"BEMD"2 Zdi׭=EEExÇ~ƍ~~~gϞmkk[vիWN xyš rΝ$nnn''bbb EEEEEEKJJcǎ@}}}z-/555o߾}M_6-988\6``Ʀ-7iiiYYYiii챲70y󦶶 ''v5//&1'lll_҂ P(mmm?vοɓ'%.ثH$çQPPPPP 4@|ى'/^,--nݺ?m 7onjj:q^lsssO>}ĉd0III]zΝ;7nTUU tss-1_HHh˖-AAAxa/^dBXոELLLHH ;!""UF&cdl"2vLP#@/...?s̺uCG xxWWW;;;'''y޽aaak֬򒕕;)|GZ7()) !$**N^RR_kkkBD"Q\\bcEEE<#ݻwX+͛7wܩD****))H$F'5uڵ+ cӦMxGƍedd-[V__ 'Ndff._?k-|֬Y۸qc\\\dd ޡ ///111ϟ?={H$~~~ _S(ZDnjjRSSC#Rrkk+BL&S(]LR }͵#zzzxǡΩS.nݺwb[[ۛ7o*))߿?$$H$n۶mƍ|||x ݻwo~QSSӮ'nذIJJ[ljjƞQWWپ}>~ ejO>}iaaaQQQGG++ن 'OL 'M***^|Y^^^^^~֭o"p/BBBʿ @VVW@DDDDD䫍ҊWVTTrqqM:U[[[GGG[[[UUj|@NN.$$d'O춺!lֆ"}}}\dZS!0U-Ɔx*0nggގi*)((زeKFFNpȑ_w͚5pQ]]hѢUV\rΆ~򥊊ӧOgΞ=M<\GG'22rʕtJ}tvvfeeedd?~8===''K@@ȸ~zjjjYY@PSS9s,8+++=====M__j…'O; L7**>|߻wy𤤤HJJ!33ٳgvwwOKK[~ÇyyyNKOO411z8uժU555> @RYXXkjjlmmkkkeee]]]711O>wuu 𴷷#a2==;w$%%2BLMM ' @QQ1---&&&77WMMWAAʕ+UUU^^^B+))jiixb֭X)$$T[[K{FQQ!TQQ}x…gϞ>}ztzhoovڵk""""""*++߽{w%--qBF󛚚߿---?ޱcG__}rrr( IcttȜ9sRSS~>>}~B,,,6lHHHhhhxũS$$$9e˖Ծ>\֭[W]]}ܹeee;;2s1rvv6H422%Cxx8L^v-.˧\pjjjn;666x~^^>>vs]666PCCBmGGyDPT*6wICAFǝ;wH$RVVqTUU޽{AAA{y򥍍MPPbxx͛KKK Sy̙3߾}PFF$777VDڻwnP()))...K,)((Xz'O޽{wUV)((Y&''755]vMSS322r+//;#ׯ/^XRRrժU]M***JKKCBB/^ׯ|rcccZZERRwQQ`....gΜϟ2eիkjjPqqq1$%%?_"+V277_pq zzzǦO/^BX;BUD&X )"c2("R277QRR;CQ(rׯ_[YY-X`Μ9555۷oOHHPRR_fͫW<ItwwGGG)((\|Y]]D"]~}߾}!lX@ (((TVV"BBB:;;}x{{KKK744>}_OO@ qqq-Z߿YjՕ+WCCCeeeaܗ-[FRӷm6yd1/2<{SNUWW<|PKKKCCرcNͭ<22ÇCX޽`ffs022ڱcwUUW?""ѣG^RQQ J1FKK޽{eee .Z㐺:77wNNomm b7&"cE䮮.cl"2i ?u…Nr_z8qĔ)S˓#""lmm/##3lbEEE[[ۉ'6m{f7m4]]?s۶muuu)))W;χH$csssdddN/::ZKKkƌ|a񿥮uϟϟ??88XNNɓ'xG\*..Ç...xb>>dMMٳg1>TTT/_>00o166[vݻMMM)ZZZ>|Ⅵ"H$==\z/DBjjjBDݍRD"JǴF2"2wϞ=ٴiqQZZJ ~҉ӧO߽{mۊ&LAsssHHVVV_mmmBBvdOO;vBÆaEP m۶QYCM8qŊ'NyƈiӦEFF>\OOƍxS]zӧO=<<C~ŋg̘w.H-++VQQqqqy5޹fͺ>W'ɗ/_.++[`6YYYSN=~8FcZFFFeeY[[qpǀ+nϟgdЉDߊCLRJ"2GQQQ x˗/QН^^^ڂNRVV~[}ZdaEEő#Gy޽*MMͰ0*w('';vqA"pԩM6-[ŋ ^̙3pu(%++cƸ"O8QPP L !66>77瓗[[[˗)TVViii߸q#>>^MMÇ&L; ׻w:dggrW^ ɓ'VVVѣBFIHH -"}!dhh8 ʕ+SL8}8މ5ccǏ>|8 ĤD&55uԩ.\;8ejjɓm۶y{{S]]j*uu1'ǎ XjUXX#r #[[/^uvvQ0>AKKE?}D$iSذ<<<英CLLL򴴴'OwnddÇϜ9tSIIIֲ'N077/,,swwgϞYXX]teH$ |Ǒ~(SQQNBH$n۶ӧZZZw;` ;wIqqܹs3ޱ> //DZyΜ9?;<}K񵶶bI$6!ߏ cI۪UN,^zRPP;HZZZ,Yq;vܽ{788x޼y222^^^d2Gnnn"""/fgg~zcccXX^5k,UU՛7orpp),m"K$%%V#/XŋIIIx'lmm& `Lػw NύMLLJVV6"""77{֬Yfffϟ?;߿?00#$$;vLJJjcx41@ xxxM>ǧPɓ'߻wƦ 8]ZZJ׿]]]XXPPe``,6ʸ"2_~%666...00zCUUUI&222444rrrΟ?w:ƝH]])Sܻw׷&!!ʊus133>ЊYYY PDE__ҥKܹciiw"/-$̙3۶mpIc Ο?EWWW2w?ҥK/^onnrqqy=ޡb~~~ [=::ѣGaaa [o&Nzҥ_UUU555D(55svvvtttzy!v[ȶ6 LiEdl3("-77WWW۷?vtt;өbgg;WPSNݽ{ѣs)//_rSXX6aOOI&ݿիW>>>RRRDGG%KōWqCȉꪪp_\xzz((\lժU'Nرcݳg -$''GFFFEEijjfggZPP`cc3;@Ң؅Ed!!!+++J>8&"ܹssZZމԛ7odeeN1\IIvVVփ&O\RR2n 3KMMuqqܰa6T*u޽nnnv:swϟ_|sss))O>uuu1`1,##CYYY@@}}}ڸaɒ%tMzj%%%NNΉ'ZYY ;D  ׮]PԳgϚ;wvL60$dsss999D|rkk 'noo/,,윜33 Э[ ˳aÆlgo!t"ck۱ ;::jjju Ƙ7o,_@ ̛78,11˝<䜜,'OLPB/,,,tz욄ׯc+**"T*6\6//!1Ѓ/^S&"ߔN68--8̮NZZ_XXUOOOVV ޹TjjjQ~~~IIؿ}3fdgg-]EFF!T[[{YfH$ЧOccAR }}}'O^tiHHHAAKIRR۷o;::JJJ|}}T޽{mv…ׯ_Ϟ=!t&""޽{<<<[l)***,,ܼy377;wΝ;}O-zkΞ=gϞ}ҥKAAA]UVQ(7ou;%zyccc~~~77LN,!!AZZܜ1y޾}K{ExwU]c/?zhgg'Aooo`` BhΝCƍuuuNupp0u^TTT^^NUKdddbbb ]\\5j.]zڵ~mpp++09[[B CCC___ ~^3f̸~zbb5k?@/..`3!nnnPww7;;;BB zH L'))GWW;- Fl?w߳"q***8qaqsqq [𝒒丹ӃwQ²qFE~͛0 +++w  6.Ξ=K%$$$BBBSNxnTXXXYY"HC&"/#07AAAw^l燞]߂u a"rkkmffSΞ=Ç?\eT*IIId2)..NGG6::]OOڵkBBBߒ~O B<<>>^BdusLMMCCClmm5 lmmI$=D:u uqq&? cnn⢩yq777z .\믿۷8@ L:~K ގ:ˋFa%:.OL2[+`vttܵkW@@@LL lٲg"h!@?AAAJJJfffա߿rWWʕ+{wU1cƾdI1PQ"{%e(ԭh_%IRh!ZdmD*#8Sʌ3~19cy_˗/}6^)dӧO)ʰaBABB?b߸q#:::((^EE[jˋBێd2B,^ݻwDg_dN8dbk} ;ՋzNt?G-x_B1 d2t؏Z[[d u5FaG{熆F;G_> '|qϞ=UUU***g@% bŊRkk3gqovܹ}}0ݜ9sN:wY5u/^xV`p77ñ*{{3gn۶7**J@@ǒ.^!**q޷@ƔM2͛7p(VQQz;8+!!Q__oUɓ'?.,,|/ؓ @SSS__ېDR .`,KXX͛7.HMMMlÇ1F9qĎ;B:::طd2EXX*qG##7n͝;޼yɥ?L@?RYY;::Լf̘pB555]]>WFk@Xv###+K.%_~Ր1p=3J%%>FAA[xϖ-[W\]]/},Yrnpy„ ^^^&&&xOjll\xpQLLLGG???7#2QMM #Pkkk=;;;QςÇ^]]{ߩ/F%$$dhhwCA {ʕ+ԦO:}tUU՞={8BsΨQtzNN/?}dhh")) AdL}}}ZZښ5kddd뭭###322jjjݻwرUV󫩩}; D222۷_xd2 ͛7ZlmmB/_|͛w͍̙:x -[%ذ!ߧXBAAA;w}v]]]cc'Okddč|ѣGsu@?E& Ԓ%K^~Fn߾Fט)Shjiiy}{dddTTԻwك\]]477qu~#F9u׵ك;N>tRn%%%wnp^NNo@@3>~wE?cŊgϞŻN222joo/))_uDnkkC!$..ミ/ -+X:~8Jû~{ܸqxcYYY33U UGGٳgDAVX3Nٹ~z>>ɓ'766rv_vQk.'''oooKQ[[[vv]fϞ$ڳfھ}{ZZZMMo(d2BH@@ƆD"1 ˗/Uի:eԟ}7nܷɉdbYhѷmwdXk׮=ƷrVtt4B#Wg <~֬YW\ꙅ4no<p9~8q˖-=}_uuf x'O,--ٳ>puF~6lR)))x[D"qÆ }0" ݹsG]]]NNڵkxOc2OMMŻP(\޼ypv5##!bnkƄKǾ"xA lL&s;v񉉉z:^EE:.S?^QQ˗/_VUUť؄[[ĉ'R(+ww pvbXݏ*>}±>q;wܽ{fffs133333Ú1,{񸶶|Bqpp#&L|ĉ[nN>>ܛпH$;;;I& }}}[[۵kڲ5fȐ!!i'**J@@ʕ+QQQ؏p9]|YFFfÆ ݻdiii)))999iiiYYYiiiiii999iig@ ͜93$$d„ 3g WVVƻ_`0͛G"֮]չ/^Չ;v'O"""3GBLLLcc)SƌwE@"h4v57555a%%% !?/d"X,2Hd0!+,^ ixӏiii͙3g}<'OlllTTTJKKǏqex@ottt$''޾}[JJjܹ>>>CD,+**jʕ'OTSSqQ +++lnܸk]b oݺGӕ,------,,h4ڿu[_~}…7~b#L&sر/^rʄ ~y^ЗX,ɓiii௾ٳӦMLMMm۶AmɓK.G_bjj*&&E":;;?|PWWWSSS[[[WWW[[:lc]]Ng.$$$++++++%%%++ ''(++}%8:xGJJJHHHee /^k9ro֭VDNNNϟwssD@HOOP( xO4iR^^ޝ;w w9;.]شiSbbgB/^/_<<<\PP !DP:;; P(!y ?N0ٳgϟwqq***޽ ݻܹs X I&q[Ǐϟ…ESTn?Xƍiii7nܨUTTMO>=!$++cH$X#F_X#F\~=//Fr%lݺw܁2#!!1|&MxMqq qb͛7Ǐ$ !D&忳Akk人KO())ɊrrrXd:%NNN[nǏwQחN|||˗/D^^^AAAVVV@^^^vvv7n\lodrRR'L IDATªd``¥İ!XB;N& 2vi:K"~#ϟ?wvvf0wŻǏt:Ϟ=?~HIIɞ={.\ؗ0'N>zHEEeŊsQRRތ.\ $ӧO-$GqӧO/]OR---WXࠧk~EEE!QQQ===CCC=== 68p#@ ZZZQQQvvv<|kLLLzz_~fffx<==f͚u̙u ~3c,  Ƃ Ο?; s{{߽{W[[[UU⪪Z-a`deee.G^H$RppiV\7ޢE FHHPPP&ڳgnhhѣG7 7nܸvԄUUU ^p1''GJJ zаZ^^D2ǂK(K X,LO\ 2wqʕٳg1")) :^EEBHEEf|#B s鳩x}촴4kkkNPTTT鲶(TWW޽{޽D999%%%eeeUUU%%%%%%999XH_ ށ4mݺuK.w=/^`0-ZD$4'N8cƌ~@H$.[yΜ94m۶mxJKK=z[" D]]]$iРA!"g%HPB|||L&/k֬8x 4I,Ω}H$ͼ 0---NZ ZCAAƮXBJJ*33‚Ryypmm-{ Dbݻxj6c WW#F|Wtz^^^NNΣG=zT]]M$nja---l$d5k.\QB9q… VZvZ25n+//w^RRĉ.pOFDD|w~^ۛ9ӳ"==Դ t۷o+*****޾}[XXxڵvLVTTTRRbUUU{k.)((߰aCbbbLL̨Q.焆siӧϟ?HDDK}Fݿ֭!!!w9trFFKjj*폤sNY&bggW^^.!!0:t(!!ӧOXj2޽ >ݻŹ=cUUU~D"ȹR\\|ӧO :̙?>BkkknnnVVVffÇeddF1#F1BZZJ2dBJ.]G=H ><$$ʕ+ B}ȑ#!!!yyyx8֮]x333+1ݻwYfȑF&†kbboeewQ7MMͫWx{{# ,"ӧ={vʔ)x3D]]]EEE򙙙}0)F{{ccc|}}UUU`ꮮ0YYׯs[|QFFN:uȑ|EE3f̚5k?{sssvvvVVVVVÇ … MMM{?RSS[lYhhO8o<;;;_E.{-_ݻVZnpQ-,,7o,''wQ̥Km޼9$$?^31bWev7}k7E$444444~= vʕ@--m۶R'ڼy3&gRRR;wpss. }||lmm.]yfw#F9sfҤI"CCk׮qcdvvРAuuu>!>>AdS9?C:,Ad gg挌#G]@SWW0FF͛7! +((8p@bbb[[kzzMGӃ޿yS#F{ Dsyb2339ry0eʔ++KǺ޼yBXXX888lڴiԨQBBB*oǎڭ[8qƄUVtϔZܹsG{g&''ǯY&11qŊp3/_\bŋmmm>|8#P(Çeee_.++`M҄&cur3...ׯ {1cDFF]͛7˖-»fhhURRbddّ;"?III29n#]T O>_;uuujjj\ܹs+W.,,\ ȝSNݾ}m_.uV11ӧG\NN{#N{=qB2dwCZZZJJJzz׭[giie!,,nݺm۶ڵ+<|7 vں:???999.݊=ZZZT*Ad BRAd x}}$??{{;D$tD.b2WޱcǢEvLJwEV]]݈#7~AAYoH![ FDDL6;3#Glڴ}ǎs%}Y} !$++>jmm|lhTT۷uuuw~{YYYW\zjYYq"##rܸqc;|p//Yf 4cǎ:u1==S 4(222,,,...**j߾}ӧO/7/^;v,!!pAbbbFFF_]f2hW233>܌RX"M:t(|A&.]:cƌE988xyyEFFJJJ] 񝝝ǏOOOFVܹs+(("(JXX̙3]nvUUU5mڴ;w]O H:::O>>77WII"2v۫_x+Wz ,&&6d---5tP +KRRR7Ǐ:;;߹sАc}nݺyfÇ߿yÆ WMnE8q{'rcAd \P(mmm_:"Cյիp7X>p)d2t~{ d===\Z cׯ_E;;|>}'--d2? rCCݻ痒3[ZZ]naarJGGGַ%"hkkkkks'N̚5bYZZ:;;hjj]#Oͽz˗_x!--=cƌ333K999999555%%%?~ٙJۻ@sx+W󅅅LiӦcBXwO'3/_xŋ999,OMM{4Fqf`0`899=}tݺu.\8x𠪪*E}LNJJ7nSNN:gWUUݺukHH+.7}`РA.]^lYFF. ^v!77ի 2D"(JGGBJcXRu "3 n ^|zÇ]o޼ٱc 7'O]ڵkVVVyyy) ,,Ŏt:/tztttdddssҥK{ >11ܹsݣP(&Lؿĉ3f̘1cFUUUjjjjjjXXXHHF7ne򊒒;wICCiU_zڵk׮] 733;vBՙo߾qtt\bֈD={#NKKKKKKoݺ5חů|DEE3}t???/]+Wlll9;~PPйs|}}>|H&9;8#¢EgΜI<8sLg)))FTWWsXSSXBB!{NaX_Fwޝ8qܭ[TTT.PWWF9444++eٲe~{ d}}ӧ.ܼyѣG|9777_gacAAAN 4:zƍ.\mǚ3g`c*b [[[%|}}}}};;;srr_w^&%MMM544O?}4///+++;;VTTrڵNNNZZZxHSS388888ƍiiiNڸq#B9rرc-,,ZXYYYϟ?'ɦNNNÇǼLPPȈk,|%l^)))======]]]aaaj<t萮.E+11ׯ[ZZ7Ν;=\Ç6lǎk֬YhٳBA>|@ `ݎ) Af7E@Gd'O888$&&خwå r\\Ν;/\ّ-qlzۏ=*##~///iY邂XD:m ȑ#[l ^lWI趶K.-***--}ٱc޼ybD X1[ IDAT`ս}!DR͛G555#TTTTTTNwǏonnF|*---++ͮ>ս{;ڿ};WVVvvv"utt,--,Xk``ю?d=~ɓ'O<믿BX.yذa_5D vqq=zy"##yp1dȐ4kkYf;wa>>x##={pjXg)++߼ysժUK,)((7dɒ7o***b2=={q|X11FWAd-DF;"ca?My̙S]郞훛Fn@6001c%ݻwoǎZZZqqqfMZbAd1;\SS#//y۷m!++>27n8pʕ+bbb\-7$((hjjڽ9\kkkiiWٯ[nUTT|!!!lI>)))iii/$$$؏xTjFjhhhjjjX K!H$sKKKSS۶o]PDD111L&뻻c[}qٚ4tPnz\/jjjׯ_OHHXxqjjj\\mooԩӧKHH,_SR(Ç[ZZ:ujٜq 7o\~vvvLL f! dff6sׯd+Ꙟ@(..x` 577t%uDFt>f#2lWW_|K0>| ##q->>gΜ9v옧'ˎ555 ,puu3g UWWWWWZqMM {c}}+R(vV\\\\\\]]]@@@@@.̮.tU[wƶƚ,}նL&b)**vuuݼySZZZZZZVV ggϞ:u*!!ݻwƍ;sĉjkt֢[DD!TVV_~=k֬OFEE}XDDd͚5D#99I&M4 !`0srrrss۷rJ~~~ѣG3fԨQx z6m4SSSkkk??]v}.&Lǩjo``{e˖qd@qss3446mI\\ٳ˗WVVƻkMMMNoAF ySS;}G"ihhpuu}qzzb{D^hϟ%%%8BݻwU_TWWWVVbi>I"ءXKKKKIIaQ}UI755}C:33\II̙3>}bB&eddPTTBUUU'N(((PPP 3wBtuurtOOOLJjhh;vlʔ)D"#e Bɉnoo7113fȑ#.7n:t(444''رc#F("Hϟwppb2C YlYXXԩS9n-^xԩ dnn>iҤlwC$9DR {{"766u"#CUEEcSSSnn.d2gJJY,ֹszfX_y󦺺{XVVVNNNIIi1?% 0_cvwwi_u~evvveeeKK L2,##,++}zO>~GdddfΜy!Nց "777c_zޮߛ1###WX1dȐGEGG{{{CGj0ȸ rsssss°---mlltuu9r7&%ooo3zM6B&I@@ҥK^ZfٳgRRRz?Q8::Ν;733ٳWDEE/_ljjᑔ;}L\\|„ &L@ߙVVVVVVVp۷:dɒk׮]4hPzzѣ]]]SSS{B8p… =٤IrssMfffa_.\[fͶm.svL11***@GdI&^|Y 7mGaa={pϞ=+--}yEEBH$*((`SSSSvUII kkkkkk#:n}}7oBZZZ/PVVWﵷwuu b544(/+KK˚!!-[@B177777_z5xqNNNnnիDDDLMM쌌xokljj9f̘-[,YJJJ)))s=qD뱲5k… mmmy/`h tqqYzƍݘ1cvh``0k,tuu9(v!$!!%qKK ???@Z e'AdIJJrwwwqq9qį@ 'Ovd2 ąǏ?~Kc"""XF[[Fihh@~7'((/^\tŋmmm϶m۶mۦ;lذaÆ@'dEE~/ uN77hyyy gbbbbbv݌u֭\RYY^FFb;>ܴiӊ+:$--oIzzz/^?~wI6lذ{ޏ_BBBfffK,yS:݂ ={;t_;Qqv"8#2@hjjD"drGGG#2 .󋎎;yvWaX-練Xyyn^~6l;?VRR»Ro9rȑ^^^ؖW^ܾ}{߾}zzzþ044aZBBؘbݿ?5Hcc%$$-,,S, B5''Ν;7oϿe˖ &xxx=ztdeeuر9sr4YYm۶yxxHQ@@QLb``pYsss+ݻw?>gzzz^qq_mu눌)WAd6 gʕ۷o_b޵D DDD~j/>|Kܽ{k,СCǏz*W7nܸqmzݛ7o^|y$ؘ@ ]7jԨ+V899 X̙3+++CBBNϟ} 6j˖-pH$8qtҤIuF|yillMΞ=[QQ1HEў?kahhw@0a„ &`]:::^|T???OOOYf͟?޼y'N;lll씑KDA 7o<i]\\\\\Z[[33ٳg033 .++;h䔙ItttA&111ss }7nܹsUQ@VVɓ'ä*onmmb C糰{!2B{HcCCBL&cwW~FfGd(D]o߾:u*xɓ~Mccc;"AM5gΜ)//3mڴ~]n`%"hhh/^TWWKJJ\smm۷o544988 ~ruu̙3͛qڵ}(z`ccݻwofffaaappΝ;eeeսSSS `TTT<==ݗ-[W\b߽{iҥX~[`͛G-<{lDDDHHiuu5މE Ο?hiiٽrw LT*YOÃ""#(D gϞ͘1CAARRRx+޽{D?<<{lvvv0DEEҽ{̝;W^^+̓TFF&;;[SS311aZZZ,,,ݻlٲ!N 腜KKK]nll~8fϞ{_ &RRR>tqqqvvްaF;>{D\#2V,""jiia0ؿ2NNס/5FcǎXbÆ Ν#Hxh4Ĉk⢧ŋwΟ?L՘ c6lؐree/ZZZjkk;::rss{SZZ:sLEEE]v888SQQ{jkk@h4񼲲_b+0/.p$++WYYڰaQpppyy9GիW~!sʕo>l!H'O{;wX 5ccc/^hbbRYYw"0mٲ[TUU#B +D#2NgUkm6jkkKGC?~|"PNNu]]ݕ+W/_wFfkk={ZѣGNm  FYYN||}͹njmmƦښڪ*Emm-Tj?I׍ ???//(`kjjkjj_| B`[[[up~~~Ϗ%%%8}0qrrrrrjiiy7v驭maaagg7i$3$'N|cbbÅ2Ν;?~bŊɓ'o###kkM6n|` LMM]dԩSo޼w"0>}۷VVVϟ?ⓥ***7odh[!(Bt:L z4فBd@֮]y+W@_:"766 ZjhRijg.\4n8ѱe˖۷olذɉJٳ]]]֭fnyRqqqMM۷sǎ f a]MM͆ ?~,$$xv*NzǍ7u 6O9E:g0Ͽqߙ3]~=''GYYyVVV?=}*V_hҾ0hffӧwbF|+mmWXW sUUUDD' N/**CYYYIIIqqq1W?~<[P(L1`V0cWWWvr?~,ZEE{r_ؘ[\\\RRRRRRTTTZZZRRRWW\[LL _\\\HH{%1ޔЀ X__?B)**JOOnqa>H$JIIa|'N85kqss[ZZZZZR;vlzzzֲxgH$޽{g̘}244 BxxxYY… ddd7N@@J@@;X5455322/_>s'Nx'c ''͛7N\vm(VUU>jZ︹I$Rcc#V\\{AP mmmX!2BB䮮g㣢/^wݳgρlrȑ! 6@o߾544\`+Wg1+XH|jw577IA?~lbb}wwSN1QPPfJ***b.?O>>((mo222X|M ѣG+VPQQ*DDDOf??؂v B̼rʎ;555988B<<<.\xMgg'I=h4ZJJʆ $$$Bjjj{޹F:D&MMM+**rO>YYYuuuoveeekkkFk׮qssUUU9&H?t://oXXTPP8rݻyu!JJJ8@$D";;{8lllʬ1`Rϟ?; `1^Vزe ל9s,lllx vN.anfmm=q[{\5inˬ]t)Btᥥ}ޓkkk߿Gyncǎ1$%%aU}ٝ"2i_tx:x𠐐Pss3A~*@#s/_dee---;HozAAAPWWDv Tsss:Pؘ̳ؒ|!! 544|-s7o޸cgL/^.""?M&Mb}񶦦FTT[BѰ;˱qzߝO'[؃ɒ'T*UPPPBBW^^>11=### ɓX0R07oܾ};>>>33FIKKhhhE644dff>}4999--EHHhҥX{?Ѱ7)))77@ Ϙ1X___NNC۷Ϟ=KNNNNNӳ\x ?~h hmm---waڵkׯãN<ֿ,,,>͛1Z[[gϞ>}zʕxcHSS>Ǐ 6GGǯ_޻w%͜9SCCĉ\555"x骪ݻw~:55uӦM L&ct6$?~dI`;`)--544lnnȀ*чNwo1ۃ???BhCkjjjN'X1BH$~w vt:}ѢE޽kll镕CuO>-((;w.Luvw}LS[̂`V<Ȥ^tؒ$x髬,ӧOW\www_bT!T*5&&fڵ222ڡ&M CyykݵZ%={>|ӭ[vttKIIM:uiii,0\tVLLlIIIfff7o۷oO8akk;vB:::ׯ_z]``͛:::N F$BCCˣo999ss˗/755prJDDĹs猌><4ڵkÆ <?>~8khBBƍWZىw"0Vzzz͌***yyyM@@B0_#( 6&My"XȈÇ#6wZXX&++;k֬vVsrry_ PSS#]Ν;׷oF)**t|loAIIi˖- ϟ?GݿzLu_FV?a ՗a˗ܢ(6nܸz3gᯫ+11IRRrٲe/_\fMzzzyyyDDի'Nwႝ;>>6&&fʔ)aaa&Lؽ{7v ᬣ^zuee; ޿leevLMM-**[pallgVVxxxlmmo޼Y]]ƶf ;;8)33]GG'::zh&ݻw~l>a„͛7ݻчD"^|fff_|;+N>5өdCX!rSSGB0dee>zH\\8`P`?|ȑq/]ths u~~>4L̙3!t>|AAA]3 $$<88800!dɒ_v[iӦ?5??O> @  ts .]/..jkkNڿS'O<0߽{,`000hiiԉI$RTTKF۱cɓoD"DrvvZ1IC AFF%:"X >>~ܹs̉;D!..o --RWWdvi!!3g㝅5.\xmkkkaaacccǎ:tkxzz*))IJJ=I IDAT٥cEKOO_|Wppp^|3eʔ?3::ѣO. amm-!!1iҤ-[,^!$++wΡCnݪܗo]tO('''OOOKf666!i&##ׯkii1 tAyyygggyyW^yzzJIImBBBJKK²Mfddt-sƨɭYFMMիWϟ?www;ڈC...ӛ>}ADDu'OoܸxßcNNNԼ~`O'""'gg~}ZRRӦMEEEx'Y,,,Y2Thh-777۬Y˲vz1vEGGd)))d p;;;wwwqQGǏ YXX䘙DEESRR,-----N~hܹO|2BhժUxb? t钟߷7gΝɓB[n ;  FddڵkKJJ533,!!!cccKKK$8 7ojhhxzz\800PNNPiBB˗/999 (A N:UYY+##i&iii;wh4⃃#88822իӧO/,,Ν{}uvQ[[;QlƍwIHH022*--;V^xb*:xL4UD& BDmã#2"СC# [[[B LCCɓ'7msdllS:p½{ҔnZSSw(|z<9sɓ'X {ŋ ̞={ҤI޽spp;ѯ),,3BؒGRV ;v?[r >~xC9qqqɵ3e˖Y[[ j 2eJBBBbbǏLrar]с"::ҥKK.urrJJJVX>mڴ{ \֭ۼysZZگnw^o߾nOMMmhh;Μ9=xS())xf-''gB䦦&o& O[nٶmYn!rhh(gϴyxxpJ2 ,ٳgODDWqq1ޡ .#6Qxc8Ç߿#Lus/..󳱱}_/mP***JII9qℤ$މ4ccO>|СC&&&>|;`IJJ>><+؈Gt҂uD^4L:CGd/h7n܈;ALL̲e:;;ȺJJJQQQϞ=>}ϟ eeeT*J677utt|FǏ777^ (S__[n/Y}DSVVCRRҜ9s !L6mZdd$FHGGG"Hӕ,X0gΜ3f[GGezz[Rb#_! 1/<<|8&t.\XbYXl8!666!!aj`(//SWW_f(޹Juqq޺uCD`̒`߿۷o߼yO-!!ao?~HII͙3!nffVXXw^ӍBbbb.ԩŋɹ޸q=|J9_矯^;` }UȣLv횩]ߟoqm۶-777''GBBƍx\|||W^=rH@@U}}`bjjs_ȑ#gϞT1o޼V}}tQk׮]{YH$R(쭰0JHVNNNlɰ >ioo_lYllkaawֽ MppB(;;[KK hB$=<<_H$.\ӧO>}SWW߼y[UU5ؙ`-:fgg'!!w--ׯ?^~81Y k-puPKK˖-['NwQG6f .؈geeϟ)/5ӧOnhh; `XP(ׯ_ommʹ];"xe...{{ݱ  꾾QQQǏ߸q۷o6–-[=zK]]ׯ_,...+Voƍw5#ҳgTUUMLL._w0:?>33僳@ 1 bI$R[[ #X[[Ǐϟw'l\QQqmwwwmNN&~j͚5}\sΝqqq<<<!EEE77[n}|Ԅ O>5触8p\XXXSSs۶mT*uΝyyygΜYd IY/+++//oܸqFFFYfW GHc=zemmmkkkcce˖ j5kL4[^^;w_@ PPP!??ԩScbb~uq111111g2Oc3bӄl777WPPݷo_{{;+mG$(ʁoBRmmmEEEi4Z/+oݺUYYyǎ ;Nvuuرcʔ)ݗ^?'AJJGv{{ @x!sm6UUU))){{ _HH(::QQQ7 888⊊6mt]--S555nyFYY 88x0 6mڢEjjjՎ;dÇ#0v ߻wvʕ&%`Y|GVVVs'=H@@Y,))Rd2D"a\+*:sL~~T;w{}aQQѶ6AN>kb#|@ 🎃wݻԔ!off5Μ9ꪭu?y$V4w!"--JOOg0066;hښ{ĉ[M>]FFllllll .$%%%'OXa=Vw^סXtwc=a/[/ikkߟB ,;D 斚{B(..n#ay<<<{G!}吐дiӺo˼@R:O2ePFs玕L$jt___666f׉'̙3d29??y1]]u};y欬ׯ_oڴ M%44 Nc7o"O8͛/_\vMMM !~V}ҥK: `D`"3~~~fE={deen(,Cz,hr_X&lI[[vTLLL JKKH`zpY}˗cǎihh Ù+2qqqSL{7hK,ay`lJHHӫ; -[ ;w |(ccci5~x=+A^3 ?0}t11ׯ_ 'Oe0'?~Ė?~!T^^kP(>>>✜?I">J}ɓ'׮];uTNNNl|eeŋo۶… iii  Aʼnnnnfb>KLLl֭W^}=F;)Z[[rج,=YGGt#/_֬YD;;;>> 0̇455߿߿O$ÇB<`y`lΖSPPxYhSPP@""##Y;dS faa<=RPPbgg?{???f^=3cׯ_Ν[UUC"$$ǧrٲeuuu=–_pã=\kkcxxx~jnnp鮮.j$IUU5>>kNZ4-///;;yyyyyy>|@IIIN0AAAA^^{~ZTTTXXXPP455!&M6i$uu)S7ҥKoݺ5׫IKK]NKK˫W222_xQXXRWWJCCݻwk]v„ u(ϟ?H{IHH077~yVSSlOѰ ׯ_߼yŜ"++K[[[TT9HS}OcC))*:%\0B;P UVqagg9sfxx`ł3߿^}9 eoW}%KDDDH$҄ ***"""o-WQQjgg{AmmmqqqaaaIIIʫW^j8޹Xŋ[n=|0y,,qȑ]v%$$`¢˗TYYx⼼ */^x=>1cXppphjjuBQ@@… ?~lii @`0\\\---}X &555sG;@h4TQQ{EByyRL.^gϞ:2eaa奮Pf!2H>}[# DVXX%|իأBRRR XQ2󿲲 T*9f(**jllDYYYCC5k`xRSSoݺE"#Ν;;;;=zUvvv;;;O>MMMΦhRRRNNN7CBBlll6lpuuV>bڽ{7N_h,//ouuԷkvP[I֗uo̾'F!T{6;wuu544 ͭS=z9S-㫨?~}7>899"""={s:99YYYbY.. .xxx\|YHHUoݺ˗vvvǏ&ZZZk֬aU `,z葃âE?n:cjjj+V`՘X(//^"Bcc#q;: IDAT{ԷI'b(DTQQ1gΜΔ999+Dr ҥK?ܽuܹCUUUh4GGǝ;wb+^rD"))))))-^{jaaׯ [[[BD"QBBbܸqRRR222mn:;;ʪ,//Vϟ,C>}сڶmxllӧ####"".]߿ZXXH"tuu̙g]]]IJJjjjzyy* [TT4z 8q"$<<|0&RUUMMMc.J԰XOЦ?}I >> ={^~͛7544v9< X^UU&WVVVUU}611QFqppHII7NLLLBBB\\\LLLLL Q }kjj|R]]]SSSSSSUUf'IIIorrrXe ;;:;w~޾ 33ѣxAWWWFFFBBB|||FF!@OJJrqqa=̙3'&&ѣ^^^hŊ!˗ 6f °0S?x?2e&L.**JNN>ׯc71T*ŋn@4MDDd͚5c}}W2aGe|pYljnn.BHLL?vlٲ'00!d?==JVa6H8{ɓ',Y6c QVVN:Ū2޽?]vȑC$޽{eee***.]ԗݻwFEEryyET*N3 DaR >}4g~~HHH GpBQQӧO UTTwލc^TWWoٲ%22RPPaٲecf!Kr;~fسQ Y+R(JEHrUJmʒ+RhQR*KY"d'`q{%e8s{ܧq\ϟ?Mv_X,3,"""$$$$$$((+?AњDbSSWf渥y WD8@{|r 6cƌ 455y󆏏ҥKN_>x߾}5444443f C.]yrqq%66̬ݻw C3=} ҥK{`ddz̙2Tٻw#G{wH/={v۶mcǎYBuu5g08аG\UUx!-gmᄆ?'t C^^^Ν;H rrr:[͛7C7`H7o?ݻwSNuqq cW\ٱcǑ#G˗/.\x):}WAA̠ϭbbbz< k?|0( TTT2W[-[u]2`7ot?FqrrvvvdAAd*..^hx||0#njӧ-a;P(>>>'NpmmmZZZиN3tr}}=Eڮb04#$$|!((' ÃS '''G3BBBL&3fAZ[[(J[[ MMMd2H$n?^LLمie$..N&]\\Ξ=iӦ7oޠܧNxbv9jkkcbb1̂ LLL,Y"//6KNŅSLܺu믆p7Іvvv6l`I$Çkkk3 O=}'N+--⒖^`ڠDEppME 2׷377΋/niiyЕr?'qF׳g3fLeeP2<<>++su1#Ŝ666.[ŋAxGDDDFF}wҥ#7bcc] aii-**rtm++H˛5kѣGƍ۷o?yd~)<hSgdthfΝ'Obiiiׯx"ɓ'O8QWW7-٢=&&&<<<>>{ٲeVVV/fwibggKhs2*>|III5aaa777gw-*??_KKh=iAdamm)!!r)555000::~֭222.566Z*55ܹs֭zO/2cO :;;ׯ_݋/]-''GMMӧPBB"$$d $''Չ!" @&UUU ;VRRcǎw?QD'5UUU555555>8N@@@HH5SDDDNNFwcƋ{dfdF1ۑĆAgRRR'N4iҤI&N(++K I޼yd--_/?Ӈxxxzd¾| Hakk[^^ րAɉp] s%$$"\WW}Ǐf)K.ݻwuѢEW^_tiӧOWSS:ujO)/_^x… >|:O?~|߾}Ν2e/_uZƤ3Bih4{>} )d~?ڟ? ;{lPP(zjwܸqO<ٳgӃx@wֆ nܸ o֬YQQQ˖-ȼ荋+<<\^^!++Ų(0Z:ujPrrryyyM [xdWUQ___SSSSSSUUU[[[]]]ZZRUUE&p222򊊊 ]]]{ӻu[F+))z}}= ǏgGQBٷo/Ǹq.]dlll`jj`0VZ%,,rJ'N0w l,r䈈ବ,ee={L8u㓓t:5kӟ?vZ[n^T*d888v9AAAKKˠ gggv3LDDDVZw^vd2899YEEիIIIAFF@ L47{ ?ل <<}tffKMM%ɽ<}򅇇wcaV^^zꌌ r@`.---2L fϞzj77ӧOw敒BR/^:EIIɹs^J"}}},X0)))!Boi4ZqqqIIIDDQLLLCCCII׷*++Vsw533cw9Q?~|̘17n=yXg ~zikooo_QQ?h7oTTT CxIIIf.b„ l*NNN+++++?^zںu:eeeKKK--(--wر;v-\;dR?#%%jժ;w?.]x%''綶,K#ÃѻD"P0B.|2t:<SVVTVV&h7nXXXXXXXYYݻ̚ #''544GB[0*))u~ĉr6Յ [[NWBLnrr1cv?9sG63666??]E\aaoܸ!""t://oggg`X^Ӈ@З+Wʆo%::IXX844t.FfΜ*##ϟ?²HKKCeFqٜMMM''';;ޏ аr .p4oܴiSTTTqq@([^^1??G]o^UU5?M;uTP(\\\ r+Vܾ};=== AGRXlAdgh/^HMMVD޴iS~~~bbWURRbaaQXX 7vZ,PWWdɒ޻Q(!!xرOΘ1¢ 6jooCiii~~>H$b0^'T*B vruum~'vsܹs5ThhHضmMqq-Z?N1UVVRTA$$$zOڵkΝsuu511\xq .fy --K}}}X?ںrʔHcccvF2NN΁#!!m۶ "&&o߾7"bbbCA=zdddtit!4yL"0<=z*""` N2e__| "##cjjjff ~{>>>VDerpphll޾}O"壢TTT`0ƍ'((=mڴۙ3g()) s,{gbb… ===]H?d2VPPPRRRRRJOOP(ݏEZZZvkkk_r?s?TKHHHPP3LMM @v획@љ:ujpp@_~=ڞ.<<AXSSpssٳw߲裂 66x<~ѢEsΝ:u*L~;AAACCC33K;Ձ!1w\>>A+Vݻ666]]]ϟ#ӻw455t:Ǎ:˗Q ݻw;vx2+?muԄ RQQ2e ZJJ 1a^D/Ι3wܹ}LףG^rBmݺuG:wԩd۷]se*nݺ[ɱ x䉯oBBΝ;{ꪪ*--33\^^aMFʰ/'''888<zhJJʌ3nnn>|`0]]]G^0rZ9lk IDATӧOdݺuDSSA[ݿr;vR胻x< 蒒Ǜ񱻴_eee\\\LL̋/h4ڼy󬬬!;o߾ŋJKK_xqx8ի3/`SSS/_{kkjjj<x16`0zdyyyǎw`0 FII) `ڵkgΜ)**Zvus999׮]yfCCҥK/^ wqE"BBBKKK͛rJv5]v-,,`֭#YSSӽw2T.++Coz(988];֛7oQOOdL:::n޼ya0+VxevvvhӦMSWWq\vҥK.))):::o޼1>|ի>}H%AAA;v m|+W:;;mڴFAxLiiikkk33YfA6 HߏhQΝ;666 D7oެYN>= [XX<{L@@sI#ߧOi4ӧO%$$~z@믿3 屆J9sf_n0hdrAAAO1 @9VQQ8q" S몋:th,|l鉉QQQ&&&666-i VTTlyy5kB 7B_`/_nmm`v6TWW?y$444--mҤIzy d6Nfedd N'$$<|PUUn Ɖ'v޽nݺ`RJZZZRRRϞ=ϴ+V}VSSs%?|r--(AAAvF ˗/f̘um޼?L6-**8rrr̙~С>v"\###!!JII0:ڵoΜ9x עH$Rtt4 111x<ܹs˖-[bŚ5kphQT>88… #` ~zAA3lbee5'A8888pAd99{ΝCZL~ܹst:vڲ2AN:uttGpA|iӦ;wX(*lkk+,,dfKKKKJJD :N..Ǐ?~YKKEDDwѫ 999))ӧ߾}522222QÇ>LNN={ٳᄆ ϟ_xŋǏWTT?~ҥr&f.T.(( psswo`w&_|zjPPЗ/_ͷm6k,vrrrͱXXpeaa}RSSϟS}} _PSScll˗0 |MBBٳׯgmc>yА26nXPP HZZ6ѣGnjhhaÆ˗cXGP"??aaa 6(_o޼ }ئM(R\\y"?...CW÷o444***VZuƍ>V!***6m  .\@Q߿bO:ecc3ꘙ9(++6̙3ϟ)kvZQQ 555&M۸qc@@ӏ9xAd N`0,;( Fݻwx˖-zzz 0b++#GL>>%%߻weee!BťvFsrrjnn.((G c̘1jjjhN]]]EE?Mee%Es?~ʔ)SNUVV2ehh23***2.7GPOb?F_1 </))٣},@`bب… /^R[nURRbwQŋ7nܸv_BCC?˗x<~t֭[7or`0-[v F&111,tẺ:AD}hh|PPq8Db%_"*--mΝ/_aC0bwzyyUTT899ݻWLLE4iƍwڅ ȦM򒒒d>>XAB(((TTT:u} 寿{nUUKHH?> JIIm޼yǎ^ꯢRW\Q֏?B!B&sss 8Wyy9b2228q /_444TTTTUUUUUUVVVVVVUU555!ͭM6ydXNC((( H3FJJJZZZ?ҢbbbT0T*ׯ 555پ=WWWwuu!"!!~@sD2?NLL4L $%%~zM?鹺7};;yݺuWOqqqfff!!!ݡBQQ100ij rss۴i\%}8x`pppUUkٳ'::://œd2 T*,[,..N\\<<<|ƌx<vvv"݂ȝloAd0deeyxx<ѣSNewE`{ܹ{mmmA6nܘ=(""E T*UQQ400UEѮ\rȑ/_2SNt..T---lii566I]]ݪUO8{:F_UTT&OޟVXXXRR~߯_2?~ 3_3a .D: hnnnii!H$D˗z4p&L@r̐@RQQQXXX^^>Q^^^UUUSSCR899EEEEEEEDD>b̘1\\\ginnFh&􄏞DoЧMs>@>>h))ɓ'+++ßϣNSSSi/ol/RRRP1:CUUU;;;>>h3f`y{{geeXu߾}III CZZիW&L`moΜ9Wo-OYFRR˫M@Mա/~ yb xxxLoA{0I8huuu 虿˗/9z3zGOh?q Fϟ?w(1 </)))++;a„'2c\o޼wرcRɷoߖ/_iddi4ҥK222{WYYY///haa1cƌ3А,S]]-!! -+z͛7&&&*((|7 FM6D'''Uփ߃ٳgmllNz 6(~zݷxL&#2f_/^2 .WWW_>**JRRȑ#`04JKKak>}?|P]]MP0 ///LѱVǿ%^^^w0>>^__kaaaj]]]UUUɩw)--EKJJN0Ge0 vvvo@@pF&Mdoo_TTt-4Szzz۾}9szw }||6n8~!?A RRRfϞc555vWFe˖ݸq 2!>}byv13.H"DDD̛T*I fd`?~|Ϟ=K, k= bzxЃÓlNHHXv- wޝ5k֐~$==}Μ9%%%rrr薭[y&--Obbޗ/_eJHH `khhpBpppcc]dee[ZZvq%AAf[nʌ|W\a𖖖ޭXʔ)SlGVǕK,ikk{䉲,HGGGmmmʅmmmpqqIHHh,//%#++?""BRRёofPllta0ƍn[[ܚ5kN:5_Z[[WX̟?們ӧ?~TPPc---"##Y7..Aq}OEE%''Q 4 ouuk أ޽{Gݾ};_{ZWWʕ+k֬awE̱cv_ ]G|KKKnn H!'xꕟ_ttttƍI$#fz >T^^>wI q߭UTT&Oh޽322'L0X? ?͍Ƌ{lojjN~yEEl/RRRٌ& 0ooo//]ã3Cgfffs̉9sO`0W^Xvmlll|||vܲe?xA__ƍ˗/gwE`Yhح[%%%<5gvDFo7dnnn:Ņ&{wDby.6hjj~1,J0nܸǏo߾ήӓq233ջ2ikkbFSWW7o<*S11 Ɠ'O={6eʔ`[[[tY;v\re„ $ )|F̍oo߾{e˖Wtuuijjzxx,Z?0otqܹԪO6msaꚔ$,,lnn~UttҮ.//477/\000 NQP󓑑qwwӧh"]YYY=D" cWWHܿ 2`$kll pBqqҥK/^@h4˗KEDDꄅPWW!%%FRZԋ/.\x֭\D}rmmϟ傂2 Dt;~tss"##?>m4###77ŋwݼy}]]]lqog={vy8dU'0ɓ''M\SSsvWFիWjANNqܸqM- @$̻ x!''چTdd'z.v횵5sףG[;V[[;֬Ys-}}ǏdvwbիWum۶3f$''33g޻woۭEciii8J2[O2jJLL466^|yhh(đ`#4Cyy9ڠKXXX)))d`0?fcc3Կ`۶m888~ZEvvvvvv$ F2eVxxP ~ stttvvKAA lllz~TNN3gdmӧ:tAsz Av111|nnԩSYq9`,ZH$&$$ C Gf!,r񲲲 "۟\r}^^'!!9))2KetnyAuuukkk^^'`. P^^>qD*??{]ېZzuhhӧYXBnj3N<6q#H 6mvvv'jjj\L!뛙rqqb#GBRw9~رcoߞÃb_G&?}{1'''mmM!'''_v .a߉Dž ={'gώ;6}Ν;xرcV\yϟ~wqa`LMM={9eʔKAA! m&ݲeǏ_df>*z*Yvm˧틈x=ٳW 3{{K.mذJ~ ͛z\JJ Ǘ<@KK ZPP}>nݒE 9ÄFY[[եKKK[lٵkkܹs֭xyy%&&~?"v#FDWWWWW2;qĩSݫk/۶msssCd˖- ã#0$::zϞ=Xf͚v'''yyy{{{6Vp=ֈqYbbb4|PP(F:xڵ(bŊsikkwߧرc~~~RRRӦM{% Duuuyyy>|(--b:^xqVǧN*((Xz=ytaaa?o m~c{ɱeeeQ='Ohhhy{{{yy߿ڵN)))ڏ=رcэ K]]}ڴi}˗>Ȩwo߾/JKK111aenn=z^?~trr>}:&">|z5mTPP 2 rrrD@Ad#w 2S~wΜ9?r-X,NSTtA~cEDDѝu=۷N=zTRR~„ z#ѣG޼yr~gSL鱑GD"D&fff|||mmm D۷CCC֬Y0mڴ޻=z488Ù|2&&N\xQJJϟq[SuuPLMMbccyxxt.F)nnnۿ~#ŋ*満2{:YZZy &L{˗/>}:88CAAap'HJJZl۷.]|vΜ9sExzz^zǎ;wDcbb={F"_g+//:KTTTRSSutt>}*!!;^ŋN'O{'` 2`0Ww[l!iggxbWWWvfΜn )SRR\Zx<Ǐ:uj˗/w?JVV6+++.._LLL~:SA k~aս}||?GAAehoN_fMKKKSSS``` Y?D"!mmm[h899‚BGGGǣGnܸ#abbdɒ!ӧOQT{{wޅrrr;ѣӧOuHH9nc4sݻw>>>,`ҤIeee/^?~~X___WWW___///+޳-BbL&ۣrrrrrrcƌWTTTUUUTT^YDӟ={C$mmmO>kgbt堠ww3g***t___6V}T[[D VըQn/j$%%MMMMMM7vuu}=XTTс痗NՅuK~x<%==}޽VVV'O޺uY8uի{1cƇBo޼ B-ZJe cccMNN5k'O]=====֦DTTDƮO#د_눌]8+ ״ٳ;޷oߚoپ};[5~mžLۃzncׯ?}t>xO$={53^5kΝ;%{?΢Rzzzfff7oE*((YYYo믿N>]UUbaaammݯ4BBҥK/_3[[[ ꜜ>744`IIIʎ;VFFFx477wSnii͟>}ϟS<<>2ǃout:]@@ի`;sܹ#uk̂=j}QUUUSS6#[zz#G>|h``qF///^ZQQ@p8LIIɩSvލXp8۷=<<8U###Δ_Ν;oܸQQQ+ ߽{ghh8=\]]-&&pLL?|`nDƆ]tڵkEEE8d˖-},H$8pݻuuu$ {`޸q#))IOOoΜ96l''':t5/3`wT֭[HgKJJ ZZZp8ĉ5luuu=}Ν;khh֞7o6k ɬ(///(((,,|mAAmmm8NO6+N^^>%%Ɔ噛ܹsǎ0Hd2>}s)//ƒt2;%%%;v,<<\AA_*R---}y<`OXZZ<>>?[\GP/^è3fpYYY}_L& GEE͙3g{ ]~=HD%$$8::M6-::111 ?@__zkjjsss򲳳xI]#wǏ߹sѣGD"lΜ9s(**b?y_VV҂onn6mZccc||DRSS۷o'aii)/b!H>  F8WAA[:YSSSTTۅx񢘘 z]%~w'NظqcϣV:}4^6pe^^ޕ+W9sf@/ G׬Yzҥ># TRRZl޽{qo޼y{7oJ%"""vvv믿޽Y111=sVe˖q02oD =tNuvvq#G$'''&&Ν;wՐBy577?|ɓ''O233vgŋϟRTgg3gZZZ ccccccSRR  O>!… ʞYEETTTN>>۶mea0{-//NKKFƍB4F,XÇ0|||.J---KgKHH,]9  ù޽{_AduuQLLdD QF! LFDAA^`=|PYYАۅp޷O* ^#Ç ]HSYYs;gЀ3fL_صkסC(? ~[n#,,,~ӧxnWnnnnnn!w#GHJJxzz:88rRN/^dׯBbbbXXWWWOOoܸq!&&… O>UPPUWWf2v?0RSS###i=zt7n;3BRRԔ}#JN.))ikkCwK' qEoٻwo```hhÇ/_|M6aWFDDTVV"'N׽ϟ\kGT{1NJ0L ^^^" ŋpG=:00χvErss;sLyyyߛ+ &jkkaa0!$,, FW^r 0 edd@jjj455{n!ԗD"qѢENNNVRUUuK0<;wDDD$&& ̚5+<UUU̱$+++ lll.^H$/\PXX8j(&k.wwaApr{{gg +}o\YY]>6P,WXqҥsyzznݺuϞ=,}nmm.''銊ǹsyxx(J]l޼Ν;vuu]pa]]+v xb^^ǏL&=T[n_v-D:u԰: Ԕ)S$%%ݻaÆ>>EMM-99y`cBXNwvv"ۿv#Ad0򜜜]Ő=WG tuusss]ӧ^/$R 8/AM6? %00!ܹ7n0 [n999}mMCC# ǏQQQvvv>>>BP{{ rrr 7|EEEsss###V nmmvZZZѣވ IDATmv=???Ϗ&L4 OEy׮]!!!}2BN>s DB?̢D "~iV =vXNNNYY)EEVPPسgϮ]>}z{Cg4ݻIIIyyy7ntKJJJNNvppɡh =:___ %KU:;;+^]]MP6#{;Bӱo$iر8|"F544p0©q'%$$EѣG.\s玄DPPѣ9\1(ŋc@yyׯ_ܹs߹s 6n8L3ʂBlw~~ϟBX2d=CVVsvttDEE]~=99YPPk׮]?~ =uꔰ0NZjիuuĉ98۷oO:uܹQFqpZ02P(vvgg'k111Vd_kh4HndYQ7X,<+K7oܾ}d*((tK'kjjCPPoʕ"#xǏ/^x閖,5"x<^LLŋV" %..NHH(!!aڴi7zl]]]2!~D+"" 0ҳgvww5.\(--ݗjjjt:JCCb"#p8X4g}1[n 2*mmmL&]#..^UU*Fk[GCnhhYtٳDƍ9\1qyxxZ[[saJCCgxx Νk.}}YD*..fŎ ʺBMMM1cO>]II/SԔI&:uJAA!&&fΜ98NJJjڵ+V1ʚsrj6zjss˗sjN0555555566655}DOKKK[[H'( 111ii1cHKKKKKKIIJIIa R퍍؍=kİ|111m_VV{3f vl0>L&ձo߾]XX>YEEeB3܎eҾֱX^^~޽[lyӧsss n߾`LڵkWFFDZpИ1cDEE[[[̙`0_LL L>_o ۄ~2@߻wonnnwvE`͘1766vҥ}j)//@YDDdz2mmmx<D, F2R<8Z}AšL#2h<<YX \} }`ʴ/_,ɂ}agg mF<, Q$$$***/'** "hv0#2q8\kk+ #\KKoΈz ;APs޵ߎݾ};222!!ALL,&&vCS5͛7_v"ϟ?wܨ͛7kkkykѴsNss󬬬={ 01clݺ뻗" +uuu cܸqJٳܜ#.b0EEEEEE߿ATVVVPPPQQO;k'&%%3 0H$RCCCCCCcc#+s:p~"cVWW#x<+5m4,58rccǏTtUUգGjjjZZZB|||zzzZZZ?ŵa鶝;,>> [ GHHg:YQQ}T*jڴi)))Fbmg0eeeyyyاLqq1بQ[ZZnݪ)))ٳg͛7ydrll,Wa!?nhhLJJ.500PWW>#@&N I&%&&`#̙37oL&ree%&& "dCGd0HIIp0UWWC_'%%!,qGA͙3kD{{{Й3gf̘ѯX$1"##׬Y#$$r~bXy̙rJDD׷}19Ƃ&222?p666۷oAO&fffM2ĉ)))6m7BBB-622Ojjjۿ?Gf?F+,,~mNNDSSS֞6mںu봵544z4n}04Ç߿/.....~Amm-Bhܸq&&&&&&l ?yÇ c %%|7HKKKKKL"JJJ ?|Ç.aaǛBdFcǎ;ʊ}#B-gٳB}jo x99VW9֡0֚l4hʔ)%%%o޼yuvvv[[&L Ș4iR=jhh\rdb_H!#N$?|}><}ƍcƌHF;{kQ._kGd;;iii&)""򵥈߿/))o``ɓӧqh4֭[O8jժ ;78"??Ǐׯ_wvvf19P(VǪXXUU {.333+++33Xt|„ J~ӧ?~ϟg̘,!!+,,,$$8jԨٳgϛ7nue"8ND"QMMmѢE'NHmhooEZZZJJJff&־l„ fff#,E$srr^~묬R`pԔB&)##3?fff#alvvv'Oϟgdd `G&M B!DEEǎcooo#}͛[KKqȪgeebG.|r...۶mSRR*..:VVVbd7NKKkX>MMMEEE<`0JJJ233߿%$$dbb2}]vM0a4SSSSSS={'O`nn^]]|& ,m, :o<;;:RVVʑeCBBd?RL&ݻw{ׯi4رcmll&M?2m5vϟ?OMM?:::$%%̙}t111n pbZZu||>+CeƌwcYII),,l`;b̺@ tuuax| 2tD#ܫW&M;CCCn2#g{{{۫ԼxСCeeeNNN?HĶm"##ʸ]Hm۶7o|(33¢BYY˗=ST333XP[[۸q8~@766޻wȈd!!!AWWWSSSKKKGGjAښݻ|ãk==Uczz:M:u<<<ϟ?{ioookk;v̙3Νkgg72:9,XBܹsgtvv***;v## Xﺺ:999GGGvQրQԬ,ʄ.ggggg &@<ϥ>oiiљ6mMW _|И9s̙3mllXkBL&N.//b X(8Beeew;v  FYYٓ'OrSQQ000066600022!*:jooog~~7o$''tuu x4cƌ|lʔ)˝۶mTtzRRҍ7;::/_bffƑN# //ޞ{…+))-YdѢEZZZ.B<|<`2;v쀎=Y}KKK/^xɒ%p]"# ǾBֲGccc=ى씕x<^HT!!!GGNj/Κ5k~5|||غ+wnjjz{m`kktRu/BRR2..n̙SLyĉ]ɓ'>ydݺu>~n 5Hp8k@<:"C !<m۶IHHphll\~g$%%8(x˗/֯_}b>][nBݡE%T*B@GG繥{nڴpxGtr9K3L󫪪z]AAA{{;//ի 6EEEvvvvvvfffFFH8qe q8ܻw/\@&%$$'M>}ƌ# [Uv0tǽGőGy&,,,""ԩS^*z={ٳ FVVV\\\XX-Z`1cpF^xqƍ[nH$ggW:::B:uԩSO8wƍ'N/YK^^5>>>,^m{sssAA}rbbbII 4??|tk]i VVVvssŇL&399ի8nܹ7oc}=III-]tҥIII֭[n)SPqqٳg;88ܻwޞ㳳{q߃ "aS<<>>_G|n^~}հ0V0eee[[ۡ,UKKƍ8xG|үkBLfUUUAAA~~~~~~AAAQQL644411?hGGG~~~NNNXXDb0! kkk[[)SKB~Q777H!sD >{lCCӧ-,,]Ԉbnnnnn_?~|„ 'O޴iٳ]W~ĉ3g455͟?166vQ#c:_YXXXZZnݺՕۥ~>drYYw2> [?ZRRRw-))AϜ9ȑ#vvv}|n{{CBB~hbНwaa IDAT׮]cǎuBoi~~e˖s"Iξ}ITUU `/J#!aaa&uD_눌j]֮];v؍7rsBhƍƍ ذa+++PYYիW#""x{zzzwaooܜo>IIɪ]vwL7KG|q kۿDR(ֺngL -t:ƍ .gp---bbbjhhٺu+Fp!7Xط;v ~zZZZBBB.../_VϵkצN*++;zhGGǤ^~@8u~,G}6vW͙aaa{233WQQ={իW܎?~ŝ;w]kڵᅅd2!$..nffÆׯXM!#ʰ\$###++ߟSUP(!!!ǎ?~IIIdd$wNNN||U~Ah7YZZ)dNHH3gmFFzzzۺu:::>]垘0m4555!!!555?… lhh@aW} t˗555}}}.\)dսtRUUʕ+O85#Ohhŋϟң祠ɓ VVV^XAd111@4k M Ǭ= ~qϞ=?+]ˠHJJkjjb[ݎ߽{ QvҥܹSGGpcL_zptty-4"{|IZZ:))d:88ɱٳ&Omy}aa!&444! o߾=z4c P[[Auu5!??gϢH{ͬ?nݺۻ3쮻;v%n'$$̞=Å  CII{֭UUUUUUUUUUUUMMM!AAAMMM---uuu <O"߿Nl@gg'}ÇuرCRR_ٗ/_8p)Sp"`1SNݦMbcc߿ߗ)))YYYŭ[-ZDRq8\``'$$$kjjJJJ) {7000((;0&?͛^xB;Y\\>ÇGuttĮJܻwXYY[ue0cB&Lp%ǦM322]H|ȑ^jiiA=y h4---OOOK.566ZA!RSSn[#G***RSSs~vvv؀}!Ǝ\SSʲwk矵666߯%HB{ҥK!!!5k<}ٳgu~~~997o ghh:2#!!1~Yf-_˖-K,>>gΜڲ㣣C~055 ]bEWWW{{{]]]eeeAAFC2L2XǧuJf(>]z`fu8U[n=:--mҤI.WgmmǏڵѣGW^vQ%11!GGGnrppѣ}=1ϟ?߷nݢR=Ofaa1w eѢE7oA/GMM-**͛~~~7n܀%VxR6m.pɓԾ=RYYH$kbbb6lI@`G-՗ ~nii䔜&̙u &LXfBuuBX Ҿ4innn.''넄SN!f̘ѯ1'O055-..~́Bp}}}nݺdn2huT*5##Igg}/_]yYKK`GAC=E~뱻Yf+<{lqqqXfݗ/_EEEo}d--tΐ~:RX!$ //"''v0---Ճ>>v-#N֯@ O>;weΝcbbLLLأɀ( :RRRX[`:::ww`0\]]: ""R__Vmׯ__v N`ee%!!!))HAaIqAAA2gZg2ݻwYYYG`U${{>^F$=<99Y\\˽߼ysgϞUUUihhL|@xN&)""bjjjffffffjj~u[>_x>| Vt,44H$ر}XVV֧OfϞ=044NHHb۷AAAs̹sǻ7n\EEŅ /^ {{cǎmڴ !qIiiiW\FFF999cqww_p h46IHHqFvvvA`DHV߱BC}vVZ!d2#""ch FXX7CqqqEEũS ӱck׾z*99\>|N]|…Hwwwnzp·oq}:4NPPhĉXXCC=QTTvCqT;vD533vEZr%@Xre{{3gPKGGG^^>))Ad8 "c 2L*joo;ɝD"y{{?|0$$dŊ. S$'66`ϟ_dIJIIp8??}!(y󂂂؇ر#<>k4-##C.""~[|ٳg+k 6|:EDDBBB|||eee-$)==ѱ dɒWw/'KwDEE9r7999$$DRREիW))).B }eNݽyfCCC```awƍMMMΝ˕ų֯_*!!jkk{E֘?SPPݻuuu$Μ9޽{7nHJJӛ3gΆ  2ݻwҥK׮]+**p&&&[l9LEmFRwũWͲm6MM͙3gr|fC\hx⹭MIIӧO=ǰvtttuu}*77^ o,G)79tuu%'''%%!P_~}ܹ۷O45u?888222??_SSs:2d?͛EEE!~mŊsYO`ioW'"~hoo=Jر---SN=u %%%oݺeaa1D{ IJJ0aySHHHIIIaaХÅ #""/_,..733[~Pmܹ 7aѢEt:?]Ѝ_gg緯VQQnXWbbbXGdl voa#2Ǐ{{{_zΎaF߿n.?ҨQ:z^jmm% CWW… c~HɠsL{/<<}Ӈ>|W\r劯cǎ;sΝ;wرcժUVZ|y͛7k,++++++]]]]]] @ $%%: xaaa<1Ύ%%%{A>>[Ϛ5k~&&&/^(//'HvBĄ1JBBǧ(!!laa1.]B3===--m+WtH+:ٟ322ݻ ȿ;Xnprr2u/AUVyF#8 ##32>\^^iyG?{N zH$UJJ AOv\QJJϯ蓺􀟐 Hol:Y`0vvv,kffAZSO.:}^:==D"y;v?w|>]ϛ7?aPA 4-00PLLLBB޽{`L@bgM3'NtRKK uvvFjbb3f̜9%y K]/JKK嵵Y]XnݺS{ˏ'$ Ceei IDATa贴44 cǎ[XX(**2:"jkk̘1cѢEK.]jyzqEmݺuCN͛] 2n8tɩSKlkkdv=L=1k,A<==_^VV֯Mh411cADD77Hjժ?w+ }ݸ5kПq8 Bs{T:Ӧjjjmv-}}}p FK.y{{S('N,[QTT\|ݻK''͛7>}:##cҤIPTaaaoo 6 Uɠ"##Ov!+>>㩩$IUUqϞ=2qqq""".g,555lݱ955Ǐ>|x}QQ z{]+))uEFFFhf p;;;f%%Sj XcDDD~Ezz:Nj{_a%%%+++c;z-A^^^R?,]wǰd...33ׯwg̯_v]Ñ73g p &[n-^㊹'ND檨 ԰P`/_0QTAAAYYY*bWSO.:}ddd[.113f\~]XXxL6*!!Ѓ-Z/<1---O>9sf6>{lnn;w E&ύ^^^=4~֬YO>LL @_7˗/222VVV))). 0Bz*@ؽ{ʖ/_)dhoo`^^^vvvAΟ?jeeiLJJJ}}=^ [666= AcpC444H$644~8ᢢ ---_||ᡫ+ `ll|qŋSN-]TWW2 ]O+X,߿h Yi4ӳh4߻^x5 Л1N+B BRT*FC/555f 1 =CT*nhv)(Ñ?>}_Μ9sU"(++Y~nGaNcضV*J&`C|raCKKݻwyyy666ϟ??|/?X,N+ ٳgT*5!!1bbb}ݸ@CC3c\-ojjRO`0?ɓ'[۶mC(44t˖-oC>J-//?uԊ+x1wϽ}988f͚tRss'N?~v}dNJJJ۷o߾}{ZZNxx~x֬Y>0lؠMmY]11+66ɩa %%%}8/((@F??Hn? 2tDÔSFFƵkע/^ ׷;w <#wDX{{۷ؖ,YuXLL9ӫAOO/%%EMMxΝC8mζػw/ׯǏbŊK. xEEEIInjj:/RN:ULLlҥEEE...ɾӧO` AR);;͵k~nݺN)dA0AttGh߾}ۡC#WWWA\cǎϟ?yh} A#rժ>U֭CdN8w63#CccMԽF(t>zOO̢GUU 66v87A)5jΜ9߿6{ɳgVTT9sÇFŲp3f<{Ν;VVVlllyyy{bcyyy---oFa߿i'r@oYZZ|`uE{]XbҤI+WTWW_fM1\\\/ *zAAAYY`V3577޽CWW=UTTxNNNIII''xϟz4366^|y%ɂgϞܒڠ7Avϟ7440iݫWrrr~IŠ^xJ={:]2 j_h4[[ۮfItƍ^MݮHuʂ! SZpaHHH{{{?F$""%0#yo=kv\WAA%KԘ 薅z`0zzzgΜBڹs'SaɅUnoݺu`lݺUFFB0o!!!;;VzX,[RRR֭[;iҤ?xUVVsQ&mARSSǍgkk Y>}1/_}Kџ988ذX-:E U?Ї\Wt[nQYf-Zښu>xIIIJJJ6mZd ///`gg7~xtxǏeee֭躮׋V0ӽBCC8`eeFk׮666zxxlݺUHHĔ|nURRR?SNafׯ ѣGϟ?_QQ1 ?~ܻw?~ᱷ9sK;wNϚ5ӧOiii,cYmmÇ|gffoڴѣCPphѢ;wl۶^Bo({y$I___@@ !!ñ J+3DՕ}Gw(EuuuJJ X/''\QQ1<<0,--O:vvv555m ݻw,Yގ _`T޽`tELL@0(**={㑑oߞ1c 7bMMMO<}v\\ŋ^@oqsstZdii)))VZZIIIӦMc~Y&MبY}8Y(;;ҥK eջvkNJJJJJk\K <@K!#oll<% ࠠ..ٳg۷ʊեW-Zr>|իW_2%$$z^иtҒ%K f̘†'O`0XNXׯ_BĢE?h4III!!!}})S鉉1=zȑ#YF{IIIy=J\\=d?'Dd2NcX,.:FAL$޽{Wjjj899AU߿`oo'''NNNVfܸqD"߿_v Aj2p®+hw Llvbll =?s7Jƺ3gTTT̲JYɓ'Ҭ.vvv /_zB "&&f``0e===]]]MMsέ\RPPpǎ.ypG9r[nihh`,bll|ҥ<999,[\\͢[khh`1 imm-"0PRRR۶m۶m[FF͛7O>k.'''33o|eXXXdd$Doƍ\BBBYYY\xQHHFTHHh]Wͭ:u LUUٳG{n@@ٳ͛76uuuϞ=w^ll,׌3bbb̘׏dEߵoTdff6E1Wss{.^o>)))VOы/611AmbuE eʕw_d l.]5k׬YrȂ X]477ϟ?͛7.ĉt:BF*$$$,, ]"!!1u9sٳ'??X,%rqƫW`؁,HabbBRSBBOet:ۆ#T*2"QBKKԩS>}zehhŋyxxmmmeeeY]BRSRR^|miiy GGn`daʕ+W H붒ã5T˻rʕ+W>x9X[[HHHF&޽`0wqvviC{%_r\rNj nULҥ7o666Θ1ĉLNNΠ 6̞={cggguQ+--]lYrrǏOr#_~/_?>"fiQVV{YYYdd>OYY͛7d2Žϟ7nݾ}{wO)ߩ^rWܹ3|V S,CXXd' %%7o07|tڸ0L{AbL2eʔD4&$//obbbjjjbb2gc!2DŽo6669::fffܬ. q2㺺k"K&((IIIzzzdeeo߾}o߾|2""b͚5ZZZ.swEFFVVVJIIڮ^w{ahh۷o7''Ν;"""6lXz(*]|YOOo˖-!!!0i0qƍ-[DMMMV=ۧjժ耀VW4h4??{N2ÇOkkkaaa{{{888ۭdIT˖-}5AyD066~MDhsrrvx F-))+VXh|ݻ EJJDEE_$ӧO_hnn222:pɓY] ` 4L{9.\1c O>?~$&&BCı@IIi͛7oniiIHH 9y$ ӦM300guUVVG&988&Oe[[!|߿#KciLr~o!99y݃XҠӱcǞ={|5QȊ+-[zf#Zrr;v5Xe̙SN]j%K:4mۖyС-[`XVW SQQs_GPƗM6988,XJ%%%;vxҥKF%# <߹s }f 2 T*n;" 1 F?^^^GGGGGGA===xyyY&---555------//F 466͏555'$$DRҒikk 344D;vؘsС! 77- III{mll`0A[[{ҤIa„ AǏ߾}szzzzz_?~[xx8D5k֦MFhncccf͚տՋL2U cǎEFFN<ѣG3g fyy˗/}t̙I <̙}Ĥrrr}ŅdrTj i|򥽽]CC)++O8QVVv{Z[[}W4\RR 0VVV ܰa|````ɓ,X`"##<==²;t-VT VXXQ^^$..,2~"XYYYYYI$drUUUSS 8NEEEKKk=Vdŋ_SV IDAT6c䒿~EFW.yթ?~>tuu]vםwemmXD">}ĉ<<}tɧON8ȑ#ٚϜ9ŵnݺuAS|JII9~ӧOԎ;fggD-_eeed2AAA &O5ny@#Ȇǎ322ꡀs>|c6l7n3ׯ_pBݻwÆ FgG999JJJEdeeWZo߾n_ qq_ٳ`0gϮX펬>72D䬬zA899&N, RRRoJKK/;CuBqqҥKm۶ C@QQ1%%CSSѣGsAdƍ .../_.**ֹs8/ ÊTPPbUUUUUc ` j]8yb:^WWW[[[____~1&?~زe%@z-H$ڵNNNgΜ!KNjkk-撧L"..2{JJKKoܸq޽ۣX~宣۷GGGϛ7~y7nyzzQF|u:rJOOO~OJKKO:uu66+VݻAyrqqWDDD+))}O)))-[lݷovwwGN߸qcѢE_|"[UUUh`MSrssHIIȌ?^JJJVVVJJJZZZBB)̶2"X^^^^^ +))Ѐu/;>gܹSGG' @IIoFmmq㢣>0a%$$|||DbVXQPPNJHE&Ѧ?~&G7);`L*$$!gϞ)**2RGGGȸ7~m͚5 Fz.999yԩۂիW;6sDuֹs/^pBaaaV5|֭[wޭ𰲲gBCC%K̟?dz-//֭[D"~hŅ]_繹:011Q^^{…xʕ+Ν#˗/5k4gϞ}IZZzӦM+W88ӥKvQ[[mvnn̝;յ=rHhh(:='''J~Ad55 2}@JJJ}V^^^VVVQQA$KJJDbMM :]PP3px<H^^^Aغ:R $ J2"z ihh@IHHǣii4tNVV{NNΑ#G6mw(G H^^۷o|gƿy===ccӧOd:QVVvuu븼.;;͛7ʽLx񢗗Çѧ#MӇs.y˖-aaa999[F 8qbժU[X_Q(ټ.] tF=}RPPե|D"dee lmmyQ`@y믿zy,##iӦ۷~fffϟOJJ6m T*ŋ]dggAAdGKK 1#\[[1(ɈYF2͌EDD>LJNP---/ƍ*--Eo\pa޽? Z~=O-L&_re,*.^ᑒҩ-nCCY}}۷o%%%{A"k׮۷o[XX;wNMMmKK>9s渻(` Hd @BHHUȏ?޽{֭ϟ?8;;Ϝ9x骥%22硡?ZdXuuuxxxhhhDDDCCӴi d&,,|С 6t;DSSӅ}6qDAبTjTT5cyĉ|AdXcm߻WUU9sf.]h:g7̽{-ZDR;勦fzz+4M__tzؘ=>>~ܸq}l\\ƍ֮]?x%H-aooO 666rqq nmTUUaÆG͝;"Рjkkkbbbll,""ꆋ섄ׯ_GFF* q_|˄2l```mmmjjj``4A^RR"&&foohccØ ulr\XXX[[ 3VSSSWW'RRR4tzjjjHHHHHHZZX NgeeDEE!cַoFGG?<77WDDiƌp-55J__眜. wUww_~|tuumnn~ϟ?EEEOYt:=&&Ҳ`EEo߾AAd>>;;k833TGG'44=D]n߾mffv9 A*yTc.YOOORRrv$..~ȑM6{#3f`UK....\Fׯ_F jkkN8}f Hiii_~%ɼFFFfff8eSVTT #** FOIIIMMMIIIKKb}}0:P(Ҭ}+**TWWG3 a1K[[[OOz<:>`S(O>|)##EXXB]]} mkjjbbbA MLL G^zzɓ/2kEGG[[[nw\zu֭;߿ jmmE툌OWNNxa` ;vzۅ Y]':n``vI@^xqϞ=555X,666ϟŒXT5eeM6]_sppXp7w -$$dD"q۶mwpɣswss+--F-CHHʕ+nܸagg7SSSѐnNNJTVVVUUUSSCTRRAw겳rrrKKKt:??VSSR4t H&SԢ"4Vniibʌ6guC"rss;59.++#X,V^^d:tн{***%X,JR(0Wz…"4EP899ѯh[AAч>33gT*Hwh4ZQQQffׯ_|ל6vvvuuuFZSSsпQtMrJJ // > bVVV>SX\ccУGf͚W^5:tݻqss755!meebi4clII` 2`0=zhڵ7o433cu9?=z`۷߸qϟB77ʨ(A޽{gddT^^.--Xk„ K.ݷokG'NBWCBB̙~ӧOo--->>>7 o, ?\'OR`pps7' eΝ~~~6667n`577%333''st,+!!!///999)))qqqQQQnn/ mVVVNť/EEEF ?DFGNNNTTT\\|g*P(?@%%%UZZZMMM]]SSS5QL&c0ii ]@渗"""h(lBBBǯ---_|ASYYYh?~vvjMMŋJHH RQQgUUՏ?sJqq1z~)++C<?MQijj_\\\CP8222$$$**L&kkkmutt2 Ѓ$[[[kk@Դׯ_щg{5??'N#E" spp`ccRǏ/++7D ?~\244tʕO#K{{۬Y ...hCٔ==s粲z~Bhkk+**>{sݶmѣG߿?7n366ƬN䜜ф VVSS{򥃃CkHNN:ujaaaAWYY`4__իWýᣩ)'''??BSGtgF r}}=ڻN5444 q44y DrC0yⲲ FKQQQQQQ <x<0''g_лrT*=iXc}<""NDPYYYPP```Dַoh4*kOp8c\\y3f̸yŋvտHJJ^re&&&NNNΝPc 722222B1rq&$$&Qs7oޔcegg Ndd|ff///o򖖖 .ϟ?+++|رcS ٳg.\HHHp W[[r'O`X NDEE)JrrrhhhppY]&J666Ϟ=9s&//3g8qHRRRAdYY>uDGr IDATx#: L?8::.^XSSwڵwxziTTTRRDJHHA䌌 ---ȮsrrTTTdFRRk޽ ,PVVv͛j*<?w܁쎇YbŞ={,Yr…3g dNdSSS2`K˛9s&ʮ9sfZZZxx5v4wMq8,3RSSSWWyQSN]|~ժU6mrrrCȌXkkko߾WTTF;988MMM[hQuu5_:6}!zC;v0$$$44tʕ˖-300pvvvrrc`ggw >}- fʔ)IIIh˧Ndee{5< Hcc# MMMDu'Oɓ'===n޼ `[N2PJJH$"1{lsss---^#((hƍsСVWWW>>>;;q񁁁˗/߼yYIfffBBBXX=D"3%wKVSSl JWRRbRvvvMMMIIIL Irrr:+**Чx8f49agggujjjΞ={2ykעٽaggWPP6 ۷o===T1J:B}?~ܼysTTD:qĘ1c88_̙3_|do+ի8UBBb̘1{|211]ooFMMϟ;>|A#B'xIKKۻwottڶm͛MrF@xQRRR7{}u2%MMM)))T*5""˗/Æ 6mL>}:k]]]:Zcǎ>|pJJJϟ?gNaaEYXX$%%1 1膆555z:"x`„ ;v숊|ȑ#y]_juuugϞj}}}ffk A!!gmp8$%%:r%K0QQјkkkccgϞihhpQFQԸ7.[%gL3g͛2 bbbh877ϟ^c?7OΜ9ի }Nvfx"HҢP(ZZZD"QVVVXXׅ.RNII100̙3MHH͛7***D>|8JݻwYVVR}CDDD"H$>DEEQT4>eYf)))LVXаi&11k򺜿WneeeOE h4  BS I@@Sa~hF Ado Κ5kҥzzzGuqqEXbbٳg/]$##]qvvѣ;%,,.Q HQQ/_斚m,--D"k H~~~:xŋ!>}‘يоmmm .KNJJr quuu?~8.++kkkC@ 5H$H& pd2cشiS@@={<==I$RZZZ[[[:"pV8 ?^OO/00pժU݌$O<033KLLTVV`۷o_dۗ/_1sssb;s挑6Gf+..644Q@@gׯ_dtڵnb0*++9c!Cx]8cǎ~Μ9ݬ3NNN tqqA'N,..3f̼yaSNMMM?~<#AdBP(t:˗QQQ<@Cp00x{{#|raaaGGG^3iiiuzKQQ:" BGԸ "c0DDDDvٲecƌ9z++l޼2&&Q˻.Mة#2g7ƎvZ///;;;YYnFJHH<~211= ''wm۶޽رc:::=ʀTVVWrj’sv?\2@9rdnn={xZ[[KJJ >\XXXSS ':::D"QUUCkk[9gggwE>###**jڴiFii鸸I&8qcU:rڕYWW۷6lL&ΝB$3g>89}t^3`|v?\WW ;6tAd ?mvԩ3f򺢁۷٬Ou֝>}ׯutt}6bĈxsssn ͞=o߾z2'$$puSN_0իW_vѣGzƍuuu~.Ruuu1RZZsE!`+,`0БMMMBQQQggg--ÇCkd|Ç={2 bbbvA3ghii߿=uٳgGGGw)8޽{) 9s֖|UXX)3gܸq޽{]Shiiiii999!ޞ@CKJJ   FEE H$n7o ȘGJJJMMM)))T*5$$ӧO'ӦMU9 sF&y]d``ݻN^tڲ2MMMv!4 AIIIAX,lkk4C,--߿e˖+Wƞ={{+*=7o޼n:6D穀. 0D+Ad۷oӦM E[[544bcc(ܱc;NÇ'$$$''wD"?|`A&NH&y]&ϰXիW,X0cƌO3 4jjj~?KKK "!O񭭭\0nĬXٳ.kmm}ن+V_^UU[WW7wܤk׮yq޼y3|ӧT*JD2ljj* Z[[#H /_@ ?~ŅI?E*&&0x`ccc*b :RPPm CM[Ι3B={VJJEK7ncff&)dAꄄ:mnllDׯ X,zzzΝ[v-;=ӧfff3f,EFFFxx-[455׭[}s?~=zӖ#_9BF_2:ׯ_`Xeee4dǀ8&..f͚5ki%%%d2&%%ϟocci&nBP(t:˗QQQT*5 `Ȑ!hd[[#FL ޹sg̙III***h@100R]+((98FCDRRAN[[[ׅ:1ą ֮]leesݹsH$ϟ? B ϟ?"d̘1[lپ}Y٥X,B̜9ԩSxΝ;׮]fʕ+h<;-ƾ蠠7nr璃! Xk˗/HHH9SXNNNHHׅ_/^8tPttZ``ܹsׯg͚%++=$((x%N= 7eʔ)SQu֭^Z[[m =𜈈HTT,2.n 9B:9RTT,--es !"MV\l}}E={vo޼aݾz꼼ӧ>իhii=z{+??LUU5::}||.^va2g-6}ӧOsvf777//?ݱAKK($$%S.9++ r\ִqyyyEEXDDDSSSFFFVV~F#""SRRfΜǟhBBBMvƍA={?>aMrEEҴiH$Ҍ3pCmmFKJJ>|8 MLLX}1!!I>|hcc(""`/_~,`0Бx<_Ɵ 2vsskmm=z(;b<---'Of0/_Ӟ1SN)))-W7mի/^;L&|"""ϟ_dɟyׯ:)|8:thUU[nɓ'1F!߿u֥K?vǏ=XϟŪۅ[ZZ,˗Y̌. o IDAT ֏=N6T*޽{^^^w6lXΜ9cffrCCCssp9>ᴴu:/^) ֆuSSSsss1;!!Rinnnjj:FBB} JHHi}S/_Z[[9r$H$Hc99|+((ӕ ӶK. \b;Ϛ5ӓ{Ţý㨨]vmܸQSSL&TBBulll[ttt222:mTTT dQ7 FC CDDH~hF XLL[mmÇ]\\oOwChhcv_f͎;T̙3;{{{߹s۷ s%cg>}Ȩ .߿}@ѣG߽{ޞ3 |А !ONNG;咳!G+++մhC;D  A.------%%5b)))111n|uX544TUU} }w_WWW[[[[[q$$$z'iĈ~Æ C_ p|)''kcQQQ "(###++ M@5!!!kgϞs] wloo'H߿<)%%J޿TJJ^gLLLbbbc޽;44ug***F>tP.o`-)))------...+++--gQQQiiii0lذ!C&NE v=JMM N ='ׯ_{p8aȑ#G6_d2 @X,VYY S(NOII8q˗yUǏmmmǍGܹ3===55RȠDDDH$D/,,RQQQ˗/744$ɶ_ DAA!66tT*[%u\.@DAAARvhkjt%<Qf2<08;;޽{˖-+%---=dРANqㆊJpppv??'Nhii^KKCU/ 333o"Ϝ9s ]v9;;iccΝ;9>yBBEUUihh^xVyyyG.ŋ?FCZZZcǎk=`0>}ÇܜܼFADDDCIIIVVO,%%߇߿#fѦÆ RWW3f̰aH4iA,;bȯږ~ذa} #WUU}=ۗ'A455455555G=0~JKK qQQ}MHHHYYYEEEEEH$d_ׯ_g^~)Sx[R||ѣ#""Ʌ3g͛7ϟϥ_Ǐ T*JD2lbb2>+ӦMq|b$cc㎍---oߞ3goghnnR6668ǏL&رc##2pɓ|||v$Hs̹wO4sǏ_~ɓ'}||(JWYMMMnnnܘϟ ϟ///߷o7-YYYYYY25|ƍ? 9_ M̬zAAAUUUuuuKK˵kת5lz4hРADb׷ T/_An_.?222  СC-,,JJJ??>c~~~NNNAAݻw ZZZƏ ->??ml\^^^QQXTTTCCmlldd$++ M@$??֭[x<cޱcիggggWWWH!:t(BP(t:˗QQQqqqbbbfffd2L&Lvtt4Drss `0RVVb " >ЎY#2E_pỂ7RSS>9{/.[7ׯWWWTWWwlvwwxܹs[[[38w޴?]AW^YYYM0!223%%% 0aÇMLLxXL/hjj޼yzzzvh4UUՅ 8q9KvZqq-[ohhx_~,,,o``6[4-;;;======--ӧOL&s&&&cƌu$UeeeRRRbbbbbbNN6lt:ӧOiii_NKKF& U HEEEQQQqq1+ A)))1XYYH$*))ZVSS۴iŦyݻOvuu'M$(( A(,,Rmmm:::hd]]]/^XYY-Y̙3PTT|2FCCC###???vf:tC\]]kjjh48~;0 `08Y 2`ikk۽{ŋ555y]Q_۷{3LmXXYEEE”ׯ_wqq9qĮ]X $ ĄF Umii# ٗm۶y߿_EEׯ'Ni3 3:tСϟ?CWx\˗/! 0jԨ]\\N:eggL7o"##?~.++kbbbllݻ/^$%%%%%}MHHhĉd2nԨQ..հX,0FȠM(SMMMׯ_?wܛ7ofΜasss>y>///,,D"`ggddd(**r<U^^>|p+++2<}t~yDRRn߾=//oŊ;w_w.^ݻw\3!!!--MMMK.=z(~K600OVNNe0$̙3jjj=(F>z(::\FFdȑgϞ%&&Z[[[[[ZѣG>|IMM㽜3tllϟ?G5s̙3g=;a09򊊊²2!C]ASׯ_O:uexիW?EWFFDddd>&\tŅJZ[[s<`06/^p12:ƍƎ5jan444Ƣ}pѣG4NkkÇ_NRL-t1<(,,^h6K&YTTԑ3זA¨Q:eddDDDx];۫W?)--nݺe˖ 6E'''ss7o}lFFƔ)S<<<߿?~8**4MSSL&HSx^W_:իϝ;Z\MMʹ4}}'Odg###CCC???##Lw9D铳/}||v"??[>zuqo׋?|JAAŋ[?1cƜ9stzDDDjoo722jkk{u\.**哞| #<<|۶m+Vرcd׮];;88 ޺u#tQf̘q.3P梡F 4, @P:% ""w޵kvӦ7##͛߿777;w.?`ܼy`„ .;wy]/^ܸq ʊO~/޿sƍ1c899-X@NNu\MMϟ Wyyyss3 bbbhXVV#s+^~jjj7lڷx544ĉن/;w.,:{ڵk!!!UUU6m0aF+((6 x<^QQ_~Ξ={SLuQqҥgϞuvv$L&6;;;33SZZqT*5***11`mp_DD kgϞ=۱%==Ǐ#G ޾}{֬YT*MoٲѣF< AdߢhŊϞ=[b+ϟ?O0*$$Sϳ/]n:vyyɓuuu̎;Ƒw --PZZjaaeee9[^o444>}mٲe+W܊.?~-;+N0AAA!<<{tݻ5hoo_pLJ V__ɳg~}>h[n{ɓ=<|x||ow_|yeeettEBBBЬ:y  𐖖 b^`dd$ s&[ZZ߿ogg6a„O>"dɒ]vqvCCC<ҳe[$Hmmm ϟ?9r)III+W-(]]]MM7np( 6mڼys^zehhhbbzNZZZ999ݏp3fHHHhjjZlن TTT¿Ɍ;~xllI|}}`@jooill\|ƍx]@wѸ)S9rioo/--- #2f r6@7 Ɠ'OΞ=CEEŕ+WHJJfooBBBX,A---,ݻ??ɓB C 166&?3fK.-))9qD"ddd O4Bsdf:^XX訯/))) jiiEksSSϟ|}}]]]I$H2|u9NSSo #//_VVuuu FFF޾};<<|رOz%mm혘'O|I[[ԩSL`0:nnnhx={&==SXXX_(dd7onذ4,,hmmuss۴iӎ;ݻS F:}4+TTT\]]Tϟ?߿w cc#F899ݹsg4)wҲi̘1"5 IDATKHH biiiBu d͚5ӦMP(Ν:t(c=377"""hAcv "BUvER,Y^ii'OHiӦaW? ;wDEEQԹsK{HYf=e1*jnnniisYYY^W544޼yhѢDF_gGd ]re… ӦMu9\:]"""喾}LJJ266C&L8~uz3Ϗ?f̘QYYwqrrr|}}CBB444v=gΜ>}`ׯ_/,, UUUӻo(͛4gggn3LCBB}.4mƌW\yA@@˗//^lkk{y^ իwuҥH!'}VGG!!!4`֭YYY555??vؚ5kfΜ )d@}}ǧ;v߿/]4uTMMʹާcbbnzH!SXXطobcc)JLL#]/^P(6m={6|*` )))RVV^f H|]]] KcW[[ƍ?~}EEE ,prrڰaG*?&Hyyy?޽{_VX!--흑)yyز2[[ێF7cM!AyG\\A: rǭ[V< AdӧOʕ+MMMx]?c2NNN_UPP걄;>ͪ}a "r*AA9sܿ#qرc²'MDRt,&yyIKKsLfhhy{?M:d2Ϟ=kggG ܹSWW:~͚5s }͚5._Hx|Օd>%)))''DC˗/mEЫ٦Oܵq 2t{1}}W^~筬pCGoǏ5p@߯t?b/\nݺ r077ONN~IccEbb"066mG;w9ROOo:YI[ZZ>}e bllՒӝG-**L&_|:%N>cvOOO  TWW'!Cܾ}ٳg; xQ^^~߾}晙 EPPץDxx\rr6G }vhh"G&`pzzzEEE'NJNNG`233R7nXv-k_Ǐg "GAdE[Gd _|źf||֭[...=qℴ4ZLLLDD*eff rjB___ sqNM7^|icc`f p0ǎׯ ۻ C^^ӳ޾}ӧOoݺu'OB%ba̙ܮk  &11Ӏ۷w1mڴDzFtsٶ qʕ]iiiرc\= _X`a0 UVN#?| JvIh=wFYs2;z622Bرc100`wլh4tN ,!\rm333 xo߾^ikk۲e qqqinnԴϞ=544Ʈ_m(--xⰰ0͛7Xy]?~<MMݻwv  FEEAQQNM+++U={#2+UU˗/7.&&S(9sA%%%Y;"7A,O{):::;wܳgya///L7 TjVVwP-})B2FDhP*kY*iQti.)5Օ( Y IQd/cf~g~)ffs^u1< ~Lzw@-)))HKKAz̙֭3A~9ဣ,X ˗ވ3{G<\Ԗ-[rssMLLY رc>'L=x 6v8z+f>L05`d FDYYϯP"!!ኋ8lwwHX 1ի666BBB())mٲ%99ً9`{rѢAׯ_ӷYf…9VXX844Fqrr*))#qee%;kB >>~Æ ]]]~~~֭C:eϟ?455eddfR˗ -JNNn۶m^^^bbb T^^_#G0jehQ2eBoʔ)ގh پ}{mm(SC~㡡ٳ ?SOOOnn˗/_xUYY ԩS555xׅYߣQXX~óqu)++iooIHH033cOJJJ277fn%SF?ך)򴴴ч|A\~455kB#Ϗ.sppؿ?Sg .+V8;;Ϙw^++0!h4}rrrVVɓ5,`QQQcccccc++0EBPPP\\)YF~AA/Z ݲ(@yh&Lؾ}mxxxdee+++xyyѥ黽}VEEY?0o޼qrr477b4ť0..5=Ǐz MM|A؆_Y|1,FH"\vZyx}ёU}[ ^Zh+ A[[[XXɓ]]]α)))իS ā***BFRVVVT*{~ $>_LO8zޠP( Jm5ouwwgu6 =S(ϟ?5BL'#H ihh8q0>BBBttt222N '7JNN|ի JEEIL(RWWƍSTTtqq۴i"gqoaccۻwSЧz}vk.Ɗ B˳x^JKK/^xՎ%K;vl-_599y͛G 500`!())֖|A /ZQ0;;k׬ӧLu %//)((8Bd...ژBpqq xbL&3!OS:5fϞ07n=z400pΜ9Lbhh!Ǐ[455P -44fఖO<Ș;wnKK Gf%MM[nUTTzzzsӧOϟ?uH===눜0wX>L %33f͒\zueecRRR[[[ff'R ?k''U!#Ʀ`AN<۷o>|800\A]vܹ`C ZS y ٸq3ghii1gg,QM\\|۶m8NOO/--m$woQ˗/pmmmnnn?uӧDb@@ K.E0=x񢳳SOOʕ+sp޵c Ν;zzzBBB B~ڵk===׬Yؑ$**jcc\WWꚓcgg'))ibbB$kkkǸܹjbbn )]J7nWפUo޼ܲeKWWc>מ={;OijjBѣG-|qqɓ'c D"%&&2v'UWW3vd{8ǯ[^^~]suu]hd2YDDӬlmmmׯ_?~< JJJXGhnnF;J)Y>߼Pԅ 0knnNR}6o{,77&F...NNN{1{ɇsBNרk׮9_;iҤNtNL0Ϟ=[v ??'Oa1lllnnn?fuuuFFFd2~GUUUpp|\ IDATO) Çx<.y=Ç$''@kkk&'''!! 怋/_d#l9|4ŋ^^^AAA 699mmŐ J|2e#LtҶmDEE0Hckk7oވ2pE577'&&4ttt\pĉ}}}k֬Eݻo߾e}.{{{2|Ν333g͚UTTXIbbb>|6o< 33ɓ'ctww.Zg)T*5>>멩xzyy͝;w޽;v`}%K ^~}Ǐ={7o %ߢE.]x|ƍNNNqqq/: fmm+3acc3]]-[?~FUVݸqɓSάե+,,NNNg~:DPTTtww_r%gccahhȌ)ccc3223yqqqlllzzzXG?޾}khhs]...`LJJjϞ=[lA_N}#V71IVV6**֭[III$闇*))133={ŋ1BFDNNn@f~~>VCBB\2oMJJ_p{;8&BEEEHHHFFӻLk?f}n ߷oߔ)SbbbZZZ"""Gr2 +W\rU`;zhbb۷ f_I .0;HSXXx666Fssscdd$T!J۶m?~B 5k\nn.>_xx8T!0xootk%%Ǐxeee>3@|PFFܻwoΝ633cf!H>|xӧOƏoii؈uFܾ} QXXH)//?7 vvv"GPЍƍ""""ѣ2 "8|?~h"!!>>>ovDnllP(P 7:u@ 0|d//u֝8qc>|xEXX؛7oRSSYBppp ؘ5o<}===.]֞5kVFF޽{kjjVZϏu͝2o޼9sc|[իwQ0]paAAAXgaqq7gɒ%III:c\eddl٢"///)))ӱB^^̙3RSSbXaa3''_QQQyyѣG?yfyyy__ߜ X?~xxxppY GPLvd(D1hΜ9yyy۶m۹s9s~xHWWyOOσXrY H?,7ܼyΝ;!!! / `alohnnrʼ'O -\pD"@R"?~XJJJMMٳ3Ļwv!//~I&zyybpss_~iٲe X',22ۮXzlll7nܸqcOO։6rUh _x###0fht ++nvYfauDYFM[[۲e.]ĤBgVBrrrkkkttvppsdd$Z.]zܹ;44,uIMBBB;"^V-@!20//Gf̘q!ʧ|dɒ䑶ĭ|SS_edd$%%!̞={Ϟ=۶m+..fի.\puuُ?޺u+}ADDDIIݻ}}}[[[B ^8==`ߐ.))Yz͛77mTUUut]]݂ҥKSNϭ;{ΝH--LT# ]]]06UTTp8CCGTWWbuo흐 ))ɤ캻yxx4 sٞEEEvvv&&&D"qww߻w ߿ulhhh/?~V,4 9`7oA!20W|||߯1x xև\ިIP0l3f̰eRCUVݼy3""˗/̘5Μ93mڴ  خB$֯_C epGٳg3c:F^bʺxbuuCF,斟/""e˖>`菖9gΜ/^K%-]͛7jjj֭kjj:Ǻ{9|ÇNXb``0y/\0;;{a?7o$''?xwg4ڵk׳gbbbddd7 ``ggή${UPPPVV&)))hC]|XXXXNNBd6`YhɃ`F\ƍ۷o߻wTUUmmm+>>͛:::Xzk@T* ;;ko&M|򄄄d33O>1ijll ߲ev>rHttmvv6ccP7+++[ZZfΜ؉Ç .1cFMMMdddAAEE^Cq***TjFFơCxHIIĄ&$$L<ȑ#pgo.[l޼y/_|hgϞ_^AAӓʕ+ha6q%]]ggg~Nnܸ')((GDD477'''[XXܽ{DZZ֖D"ҋl0Z?~fff_: x<w`BH M[j ''F$T* :}K/^uo8q"''gEENJR1n„ !!!Lb999uuu:::o߾e,s…ǯ\rBiisuuu-[FŠP(jk^|ɩŨ)˗ ,X`A^xlٲ?0ݻw;v8v옒[ZZGvpppEEE111#*RVV?&M:t4g{{{<_QQu(Ǝ={())fnn^]]uV(((@7n N"{ ;v;wƍ<u0a€BdV4L(Ɗ+֮]˼%UTT>}*""2k֬ϟ3if <<<֭+**sNkkܹsO~/_f_|~sd3119sXaaajj ֡! o߾rGGǀsMT*566vܹ3f z%ֹ>>ݻw\ԩS 6m*//:XC,X͛/暙a 1GQQQCC#22ťLJu#ܻwfϞnRXXhnnlٲSN1u"㽽o޼tq%%%߿RRR歭Xa)<_TT6bgg~! u4mnwBd`)>>>??-[ P?0iҤʯ}}}%`49w C"--c===SSӸ8&p_gg ~(vvK>z۷7n";apG/^̜9dz3f466EDDp8CaO\\ԩSuuu'Nx1cٳg?~u1"??ւ<(((XzӁ0cǎ%$$vtt`mC{NO0a\\\...p566x55WŸ?ʊ577[ZZK. n (qqqǧOڦ[YY?2$$0 &%%dssn驪B_}CXXA=(D XWWŋiӦ%$$`h(JJJ `TqFZZHd,111NNNϟgDB%%%m"p%%%KK˔_B|JzJGGׂ1P@@ ^~maauE@@`ӦM{쑕]|;w>}uQH$N>}ڴi񞞞eee&&&P @(++wބ l"##윐ۋuQ.44PMMEEE .>ÇD"@^^رcO>X|u%K,]4++KMM3~yٲe<<<̞0ppp yf999<㓞μ'HHHsn8/S, )** ȸqP(vŠXܹsw H%%%g677;X4iREE[50L hs|:R\\ %%5o޼(SSWWW9sFOOBBBk֬yaiiczzz6n(##3{l__߇BdTcccxxٳg544nݺU[[6|w?xMZZz޽;zh}}}bb32#yyyK,aͤwp0V[ZZegg{zzIIIbF3fܺu+,,Xga<S utt?A_I__7j1e.^q Voo544f̘Io+//WRRB#͂5 uuuӦMpJMM]|ӣDDD=*..VVVٳ6lzd55+W۷}o߾;w1#ݽ{wNrtt$XB&>|KR̙chhK_C|͛7ϟ?ӧO̙cdddnn>eJNNNJJJKK{-77̙3Νkhhޱh򬬬'O<~KWWw:::Py KJJvڽ{>ldddnn w-&&f޽{eÖ$544t͚5y}RRR|||bbbggܹsѦ~ƍNNNO޴iY'11]?3""CeL2yݺuRRReees}="7npqq9r)++|Ɇ 9"((ʄ"))`tKgg''++k̙`4Z|[lmm=WAA'N~ʆ Ҋؘ7Kww7D:|AAܹs7l`mmy...{A_.Y7<}Xgd*ݻܜW^}􉝝]KK}ȸv}}}˗cmD{#L~Y6Ç.\u'NlRjb IDATٳgqqq555Ǐ_`Z,**u:;e˖aH$GWWgϞ]]]=Mcc#Gpqqeff~ϩSoCBd`UVyyy 'Fvڎ;ƍhccÚC pssC_BBB֭[i4Fk޽{7// /~{=0}qĉ~~~7of͌999!!!ׯ_ⲳۺuy׭[ヾTRRZv-.5֭[q%333VNgjooEtKJJ( 77)Sp8:ɓ'7ӧ’⚚&((8m4NKKK]]H}= x<ihhp8<?ZJ)Jeeeaaa)..egg2e ZKK }|drbbbddd||Oe˖M0h#J=tѣG.\xVv.++5kԇ``Gh H:\[n yY(;;{ƌeee'OQPPxY8Ý;w O>=77}N8sN&g "ƮXüں{/.^ٳ 9ӧ/?|ޤ߼yӧ1(ݭ-%%Ƃޫ>}ΎXh??ZV~-"XRRbddnmmcN4s׮]tttXZZ&Xw``KdeeY3/ZOOϛ7o JJJr*.--8(((JIIIHH>j[[[sssKKKuuummmmmmMMMMMMmmm[[ <<ףgj_WWG&SWW?555YcJȸ}۷p8:FF''O8qb˖-niiӓLKKv@}1---.....ӧOXJۧh&2tuu EGG[YYܼysTK,vrIIFAA?~l+55H$ッ===wܹg...FjYY%),,(??7O8{nfO'""rUV-[ի̞qhd2̙3nnnvwwwssKKK qttrss۸q#;;;JEw+**hIdggիsyxx@.ҷtww{^Z[[>RRRGTT5?uo@hO>utttߣhh 222hܢE"i55I&!~F } !!aܸqBBB BPPPHH~o>}BQ(6􄏞[[[ѧMs>@~~~􄯦fjj:qDhd2ѣGQQQMMMSLquu:u*FD!!gϞr/_,_Bܽ{P(h䐐>>>}}} ˗cFvvׯ/^,XUVVVuuuC"((>AM77` ŒDIII666/_fs}nj?zKR\\IF5mm{iii1{:^^;vۿ{nV޹s6m*;;qCCCHHȅ ?>n8|||C+W***Ջ??%S{{{[ZZ*&Ʋ2 '+hMZ6aN\\\ZZc` }=Ç'F&z3z@RRRh?f11?J&͛tRWudי3g/\ ((i47o={&--ʩ`TTTڵk֭ꖖ7nܸ;w̝;ӧX'b G*//___?ppp]-*0/DfCpovttܰaC@@o{nÆ )))V ?~}ʕ5koo?qDkkk}}Xַ}yvv6HdCsss Gd&Ldޙ˗/w9z]0lP ,5.o߾㳶&ckDEEKHHddd_=uT!F~~~KKKKKK*+M:&lrrٳ좢޺p8.,,F@gg' hN'֬YgϞSN1ƦvFFFTUU)++C_(^^ޢ"`,?~|xxxRR+ s322d˗/Y6/*00PWW-Q888/^"## ɓ}}}kjj;W{{YRRRBB7T!Jjjj&H$Ykk[^^&Uiiiׯߺu+gή흞t%YY٣G+++>c먨ťnܸ,:::x0AAAV!2Z!(D;wڵk=ʌpDDDDǏ[ZZ|}}|Y";"+(( cwڕI233O>gΜeVVVFEE8|hNN\2,,LII$22jjjf͚UQQabb~˗/III7oVRRPUUs·n߾rJ3n=HNNf}LKKK;;gϲ~v` IIIgg爈d h[ZZ455aXa̙W\xS`jjj#"//0t?cAA.Aƍ a0>}z~~~̛ 77NLMMe\to߾ETTTD?'~ ?~ȲIcbb֭[j*___ƂIϟ?/++kccÂ~;;;z˗/***'N)//Y333aUMDDDҲ@ &.]ˋuQ@ a۷VVVg egc1H.//?zϟ7m$''뛓Úz+Vڵ+<<,$)))&&`#C/ "sss2Ʋ[?vb\\\\!??_YYÇLqBd H$ ;O :z}oo/S ݰa'''S'5Ew!2ydvcc㈈5k\~}ʔ)VVVwn&&&'NLNNa@L&eee555???ؘL 8vyFGGD"ݺuƍ˜hnn^h۷ǍI`6%%%~iI&yxxDFFj0lٲeIIIXga$555zGdAYPP-AAy,pY6Iݻ0mڴͥZZZ~M/D3`̓yfbb"HdKJJ222bꪎW\!̛w +**젤tȑhNNNYYYOOϟ| ,MMM<87GEE+**DGG/\޽{ $FLL c F#3gpn { aXFLLƆD"<}>''VRRĄH$b+WyY ~X Hgg'B X :"X2;666Eۛ1c"uuí|pѢE˖-gBdee>|@\\ֶhƍyyyϟ777:`ffsN__߇*((`ήXLpppeggWTTx{{O8j0ڱ̚5kUUUXa .9#&&V__?tuu;"spp||(Du'O;w bbb]]]x<@ ttt0v 5556EwD@?*~ς ?~544^|h, %K\r~!!!==p{ڴiXgbcc믪gϞy{{~` 6<|M4=..566&&&222$ӧOXgum)))0kjjjkkCD^^~BdzGd^^^ Fp:"(FvqС˗/_8_ٳK.ݼyÑH$͍DήnvvvDP\]]i4g{eˬ}}};#Gx}PRR9v온h@@@yyyaa11Ǹngg%K888͘1P Lvtt uqq:\RRbŊիW1JSYYHLʖB& 7o&&&D}ʕݻw{˗;v`hLB/Dex_UUUddu͛| ...FG`P(/^8q℩)1--<..!""]II ("ˋm};w$0bxoo˗/;v +++{xxa~1M'NG]vDR]ZNNNz(A~EWWעE-ZuƏO$w>>7n:ϓ/..FDTTylll @GdL>}d``@&3Rׯ_+--UQQ(++~ P6%B7F??XXCOO͛7oٲG:;;6̼r7o^vmbbb7o,..>~xCCúuܹk_]ooobb ??ݻw昘ϙ3gPg/0 >啔u"AzsssYY{M3`ᱳ~:HLOO߶m[^^ G`qss۹s bbb?}DPDDD;;;DDJeBd>rqq}v",F͛}Y \QQ p8BfeeBdF<==333ʀἼCCCϟ_WWC [lx˗/¿r&H IDATWWcǎ|k޽{ˏ=ZSSzj~~E]xW0Ѵ>{\\\s=}4swwoiiy#G  ATS455{zz=<<&H]xGGǏYYYS'^^^?gff𐔔uqqV0vΝ;gcc|l "Sg!(DIJJ7ol\\q~K~~… ]TT4??A:::::: ᤥQwСsc>11FGG'--m{uww899+Mp===_|Av[[[d˫W޽+%%-%%vVYYl:ݹs',,ݻwjjjW\z=;";ʖab%<&&%K`DCC#--MEEի#%<|X__?899)))a F4C{yݻweee +++z٪*77ͭɉԩS߿:#`Zcaay;;yss3q~ ///OQQ L"xxxz!&e˖޿*-r|GG۷Ϟ={NBd<vDFDFF:"0߿{nl<}e[l~/vY[[[IIqK+kjj~#ӧO/_+=LMMKKK?|cǎl>>>sss???!=آhW^ݶmKXXX<<<[[[}|| |wNMM:pqq 400: Lk !!!""":::222[lĶ`}YSSҥK'DP\\ /_͍W<%{N(Dڮ^޽{SoP3gWVVR~~>Fcbbph!,"0F444.__?&>>>aaafffD"[#cbb)W033JG䎎۷\rգI]]}555AAA'NPQQܴiݻw']w0?yË-VQQqqq)))zA}}OBCCwܩȈu^0Rjjj޽P9ydPPPxx)Y~~kFDD466ۿ}Ɔ:88EZZ:***##z"//" D"y@GdT˶mΜ9s!dee=z{eee//o VRRjkk ##BƖqL 4jժ]v9;;sZbEbbbeeٳsrrsYqӘzzzjkkСC/^` :::޺uH$~NNN<<<...De˖IHH .YѣGRRRΝmnn9~???yܾ}֭[333N4߁\tR==O~~OTUU===h41@wY~|QQ """?ŁvDE<B){~EwwÓ'On߾|rkkk33Ǐ,\a311ѡ? RZZ=α&|}}~c~_CC#11044r/^xV~SWWWwwO|o333/6͛7oի訨nnn33盘(**u 0rkc888;X+d2DB_wwwwuu}=A:::NNqTTTxEEEw^5c cuVl8,,l޽~~~Xg}222......MMM/_zS/^lmmh"c2ss3gtpp:Έ((( D"ד=y+h(DZ[[-[S5k߿ѢEVVV sqq011!X+%%COOmbŊ/^`ޣ]PP˗vvqq9}trrr666&!LLLh11Jݲe &888.]v+..ٷo_KKȼyLLL͛'''7"P[[hhh D"Dj888 AGGH#ztokkkmmmmmE_Yy<,**Z[[KRƆ>1 #2L,mmm jjjXeee77}e(EEEá?211~ L}322ڷo/q9slٲ%%%_|uٳgulbbbbB[h!rXX0z坝)--hjjChkkp8BNFT*?sqq sssKHHsss033|}V_BVqssKJJJHH򊊊ʊ1 +XYY>KKK zB577>:dt/</,,N08 L.YYY>|WTTڽ{7*0*z…#G yDC{捽뽽x@ ^x)##ceeemmmll F+** 8HDDH$ 9A:::~ĠȘ"UFtmaa3`/((/F;::ֶ?~|׮](((ߘF`|͛WXuSRRbggWZZ?dee]tb ŋjii!|||#ܫOYYx?799իW^JII핒211166600:CRKJJ>~DdHIIGRRRDDO89Dbm4Z3WSS^kPUUURRRUUUVVVSSϐ0H\tG@NHHh`ɯ$ږ^@@`[ѣD"ɿAFFFUUUEEEtyVRh1 {ť1s̙3gjjjk)`ӦMIIIG =="44tr= `$޽{y*SSS33%K|~Too… +++"""VVVZZZC ]~}OO##\~~dyyy}}J=(D͛Kjhh<|8Euu}nܸaeeu))'OgffzyyݻA<L}7ouVrrq&&]vLjoogddURRRPPPTTTRRym:dreeeQQQAAAaaaaaaQQQUU bbbZZZZZZX֖QT^^^%%%%%%tWRR'L~N[[[qqqaaa^^:̘1c̙A[[[EEZ&O+999999?~Ǐ;::p8&Zy!--uR0z{{O:?hjjb򌍍uuu>| 2`cTTTdddRRD$ú^_ب'""3p XEEח=**ra?^dIOO''daaz2^ nݺe˖]zuD?nnnKKK;t萛 zaNNILLLҸ[ڵkzzz:qؚG D]f4" ޽{5Hdee200;w5:::߼y̬3k,)VE"JJJh4DLo߾}͛7oh4L $---555---++E__X__N1yyy>|ɩGO]]]EEEMMMMMMCCK6l(,,~UJJj…VVV ,?{K^z,ٴiSii˗/999}}}7o<䰸744HII  @HOOCBd@.]~)V4:]QQaddq!wwwA.]o߾~ŗ/_ӧO'T*UIIIMM͛-:HEDD\RZZӧO#ɓ'O_[(Jnnn|||bbbBBBee%ӬY 􄄄8 h4ZvvG?6622244={D.u---޽{mrrrgg'77e,,,` T@&|Fqqq/R=E 200|։S[[khhɉu QQQ?gaa;wo&&&u:˗oذ,t/VTT(++XkaiiieeeZZZeeeD"VFF8 "5*~ܹcǎ{ɓ'/^hiiQVV^`鰗vijj422bdd:F5hqmm- \\\JJJhcի͛7ן9sfӦM|yOG=ȨoX[[[YY͙3gR\oLc^|ihhuVH$Ғ%Kdee!(++䘙WWW4559sfVVx"V;;۷ocg2'H===-zSffvaa.s][[[ o㏹s熅M6ы/?˗/G2ŋ<<xໃ)y``8dSRRSRR޿Ϗ%kkk^>33?{E~1CCCϟbŊ BÏRiiiѡEEEk֬Yb0$&&޼yvvv .?$777::͛YYYjjjk׮]z(ֹzA%%%TTTc...#d29008ceeUUUШTjff&&9##Y[[ iGLLݻZZZ0AH$]]]nnׯ_Ouzzzf̘ŋ!rqqxbǎMMMlllrrr+0JJJ>~xb>:mݺ͞MMMIII/_^v-F SSS988bŊ¯jll\v/Oww ے6))iΜ9k׮vw߽{wʕEEE222m!ɹ)))鹹d2HFKGx7n8::hyh;wiii988Z TȯEGGP鮷7<{]FF]U=Zl;uݻ'UJə={2ii͛7>>!!!>>>h&-[eeeMN>=w܊ qq! >|8"""%%q yP .׭[w7bgr+((PVV?y$Z ȪU r@@!9>Ο?z%KyI$={>|`]{{{NNNrss;;;fɇjll w=n F =rH]]ݻ]]]Wc!''gQQQVVVX'Lq;p@EE7YYY111۟uV\\<ส ʪQyyy۶m{ݎ;=::^x֭[P ` Y L.//=~][T*477ӧO%%%hܹsr[qqqqq<̷H$'tǏ>}ZYYI@+d%%%q"@䂃WZebb2KCAAAr^^^NH$FC;|JyaA!2`{Hrr9455 %""B$џ~133 :|phhs&H!SÝ;wlmm_|9Z #,,n?vVV~%VV1J8B-?rrrKǏrC6nsuutttҊ:`ٿʕ+3100?~jڵ3g|@:`hyNNN111IIIEEESSSthE{{/--TmmmG?>$$duq0I30o߾ׯ_o޼,77Çyyy FFFh,8} ;;ݥh'i;11L&xYYY 555555 <I~0}XӧNNNXAb``(..˷H$vvvc(D1tԩ^:88n..!![xxxdrkkk?;;;OO%K`i722rٻv sh3g,_\JJGeaa766޴iӬYnݺm.roNKK;}xBCC_|ifffjj*(( ,,,T*P //m6Bhmll.^uV&7*|吐#2 >|zj##;w'$R]]ֆcff@ˋu 4>> vrr: iӧlllLLL,,,\\\ttt------pppHII+)))+++((Ϥj*tK[[Ǐ?|{** 7{ٳg`L3f̸v횁ӧۇuA#rEEŷưԠpA0)6///22 #rE deeoffxرSN\  'Nعs,&&&X\\իWgΜqss{իW1vDFaDW^ ֯@k M>33K.Zh`M6%''MUCPvء ^akku04FFư%KZ*33SPPpD"X[[ Zs˗ BBBP^ooo___IIW^͛7D?w߾}7n: ޽{EEE^#,,,ڶXg6޲e mmmiii)))dbb={ϟud0zyyyyykjjbOHHXxqrrƠN_h<|q&==}ҥ쩩 XǙj/_o]vڵn:''3gΜ?h0ݸgff\2!!a,f”gϞxի555_ss_?A RYYigg7 >>0Z__Ν[n111,^W\{.TN>>>vvv߿6`zjmmIϪ}:t'8< իWĠ7>z"X__uqoooA-,,DEE_@]\\]]]<8yו 8Ν;`}uxxxLLLUU##;ϟȈu@}o ^z+ÇY[[yzzxbժUۖ qrrvvv^z0`Jy󦡡ʻw yLIKK#RWW׿qBdONNϟ6+edd'?^SSsţu@]]݌ XZZ ,U|9990a 8777---;;;CC~mL#oذAQQUJJ:))i3gt Ft钑?????˗ﴍ`$ Aϟ[XXHKKGƷmhx<><33ѣl߾]HHn3@GGVWW~ HddXGB8;;9َdք<~=]ϙ3A3g  ȬY}JKKH$tV??/_ѣw֝;w222jhhz 8*889r k׮-Z… XcD"ݹsA3g;vl()&((...h1}HH %%%C HMMEcХq<tDLmmm6667oX'f̘ HssMMM#?iFF_HHRpp00>~ӧO֭}'Zj8ݻwdnXXXkxfgg߽{wVVVff]؞>}zeAyn;.]xbvvv}}}XXJffSF?֯_OP=z4gL. F^rАk˖- XDD䉈8qDUUՠOG#/cHߚO>!A} <Ç*++ϟ?/$$g??? ӓH$mܸfΜ9rrrlllczR0*~Lt"n޼m۶{yyaeaa 6022?Xcm7n@Ν{y))ŋGDDB1`*ٺuEH$1555C#(IcV 㤲r֬Y|||?:#"&&6pOiΝ:::hQ`~qPDVVV__Q9ڷtuuٳ޾aLE|RRR_o룣Ϟ=a===vvv2#//u[D!!A'lmm1T*3((hd;;;999ffH[G[Ν;׿%66A!!100@uũATadߛghhy> `5AA;wfdd,X`ڵ =ۇ;!_oA-YYYٳgwA+ oƌN 9ã8֭[x<}fud2ƍ***(**` L]]]nZ`//2888~(aaaC> D""1b 8Gü~7oݻ'%%ui-33[~{y}}}%%%G),]HHƍ_:~s***ONjjcmm ~=0N>>t Lf``p;~w _cccvvF) A]yA; ֢ Doׂ1cF{{ȿK?ɩz{{y޼yW\H蜙afL#9׃=ztR^^޲2zzzYYٚW:::ܱPAARXX0A IDATԄ턯jժÇY޽kaaqܹ۷׬YgϞӧOcTwMooϟ?\r֭ R^^ۻm۶{ ` LhW^ݸqcbb.V-Z$**w={|= 66vMMM<<<8liicpf,Tʕ+ yvttP(<<2w̜s8;;AMMÇ:>3K #*2 kh4SBB‰''@1̲2EEElׯs'N >~SO ߱gϞTuu_ӧ<<<8;-ZnccY׹{.B?~<'A_|Ǐd  9ҥRNNNOOoƍsvHaaO} _ıczc"--Ǐ={sOSN!zthrv{"##/^ҋ CJJJMEEe…sqwwWQQržnw!uٳgaWlܸnb7C^U555`(((py^u̙annn_Q3f̘={cǸpa!?GVVҥK_ztROOOJzHDD="""00ܹsOc\뗕u;k@ ӦD"][6{rrr233?~ Uȸ+**b?hM>>>3gfӧO͚5d2͛۷Mرc?>qD;;;n[&n77n8 yHMM166YxǏkkkSSSmll8?}޽_vrrV2B@ =:!!#3~DŽ B{y}}}͟?!陙i& { !rWVVTnWtssCZj߾}IIIUUU4!!!;544<{l̘1: #RRR֭;F nw{{{--riiK~zPPKKK8C?0z^'55u...3g,((7U׮]^tɓ' ttt2̗/_B2 BBBo߾_`Uii)ޡ/߹sKUUՒYYoe"įBWa@r5*jaaQUUw?BH$FEE)((pv###2^]]ٍ񣜜e{{Ϯ{5͛CW\VWW7hѢCtY...~A#jkkϞ=;{ V\yƍzX,Vxx@yyyr.GJliGPv ɜ2ef---L&6f_InWdX[n)1μ|p!^'c>>>2,((8o޼.#333B[{Co1gΜڵ+W켮jCC6E{u իWd##Tpؙ3gH$ڵkٟoWUUenn.$$wގ~Ń#99Y]]]NNxg`1 Ϟv@MM B(11Q@@@NN!Թ߇0w# &yf{{{77x)))X,BBtLP9;ѣSRR"##oܸ)s$$$<}ٙO6MWW-,,Ə?{/_pd iiiuuu'N,ȑ#SLqqq۱cG^^^AAAhh-Sw[nYO0`SS}D!..ȑ#&&&&&&aaanbyzz2DDDv"B7==qذa"""ׯC\\\w@'>}ܹsVVV_ӛ0a֭[ .!2hhh TU]ׯА7oeuu'MMMw{#L6-++z?0 PCC{Wt:4ǏlݺӧO ,;OHHH?~٭[ѱxm۶EFF^^vȑիWϘ1ѣxg))UVdkk{ҥxsss?~lbb2wܱcKIIWDDDxxxl߾ ,#Ghkk~o7n߿888XNNǏzXXXX||+G{nϟwww/,,r効9'!2|`##,zzz555O>;غuիW/_w?ܷoȑ#EDD?nddw"xNyyNAAݻwcbbtuuՋ6o޼nݺ͛7@TZZw dee~lkkrrrBNNN.\!ߏagg37!ľS\||1c._ÿmݺ!g:҂Cr[/Nvv6Bhٲed2D"H$"8qDлwBcƌIHH^tҌ3jjjzZ555PII ^/SRRڴiOQQ&USSh222R XGdV^鬟[\]]͛r}a-***X!rc[!""cǎܑ#GZYYM4 *!ٳguyN\RHHx?L&{xxdffJII3ãsaw0 ;H񩩩xf͚ٳ;w4\bҥↆk֬a_pjffb ϟ6 D͛7˖-c\##799A)4mIIIQQQiiiXMb݃-[wJ;o`--nKIIdԩqL&&;;d~k666Æ 9p@xxxYYcWN1 d2>SÇ?y$///00p7n8tPquuu!,_򒕕ڌXq}:%%յ $- qqq/_\p!vu[dddKK;^~ڝ;w8p)d2"꯿ݬ*99yʔ)F˗.\LJwޥK.v'Ü9sƌd2SRRv"++}ر8 ]vn޿?sNjxbƍD"R#""ttt÷oߞoI-&rcǎ]vmܹxП$''ݺu8::W V(| O:~^ "T*@8vX1ߊD AAAX;7~}[/>B(22*6mOIIAqvwP(˗/ 2D%""" Bd ݈5jTsssZZZ/xJpܙѓ'Oڊc==w޺ukŊ2228fD"-;;[OOo ,~'EA&) ŋd2;tS^x1a„ &tttܽ{3gs ʕ+7lذ{n555;͛Ǐ9Po…yyy[lٻwΝ;)~o{ܹsutt>|g`:uJSSsժUg.((cNNNO~5qsyyy :x`iiBvvv_?>B(,,333333sӦMX/Bgxxxee%O0!gϞ߿w]|'VmmmyyҥK[/BhժUKJJh_ 5*222//977̙3=d280}t aʔ)WN/???vo52R BBB]zn/Anb2d2Ʀ8GrrrBJJJBURR04K@@@WW7>>8o&[n~t@x-~SttC:::3jԨM6>|[qÇs1揥c7zvppS8jbbb e999x' FttBH[[;22` '^~ЪUN0X lIE{{{XXB3މmT*xgG677֡ކԭ\s}jCCCc&9eʔ,--L&6fտP.͛]۲eK Fڵg_[~6oqnذ'/۷L&gff2;C޾}K&ϟ?ϝƍ7~xaan fϞu'!?%ĝKj++g7n;1N3#ኊhiiM:uڴio޼;<ɓ~~~gXv1ԁ IDATcv{&//o7nILLz MͰ%ϟ?믿JKK]]]GU^^sҥ?#JJJ߿5jԨC\~3Tݻw/^tIIIl{8uuucǎzN+**N>Ν;YYY .>lk׮7o^aao̯ikkR޼y8 m$ܹsk֬aqi<~z 99y=\DDa!..ȑ#&&&&&&aaanbyzz2-pӣ `jjo>??.cICC!DRWZyq|}} &"""))i``~ ˗/ܴGjժ/ٳzyyqvзhii-Y`g+:b cbqI~Si;w8;;_x1+RRRƌ6mӧB555RRRX'-ޑ7k,555lٲaÆ{>|ԨQx'>߹sgAAAF1}۷o[YYڵdݻw8Futtx{{y{{/^D"71g^vq„ pu/ˋ;uTzz,X0x`s03gDGG{{y?B/)))sΩSݻw\wQ(9s=zTBBbǎ֭~zuu5yHNNNXXؼydeeǏڵɓ'=...ɹwnjjZjǎcǎdw?\yy V\!]]݋/8qܜHӅOss-Z4mڴ'NifԔ`rȽ{۷!&&{ޞHUUD"u[LREDDz;/(ݾ}{ԨQiiix}R]dW^3dȐӧO~ll߾}Ç722q8LBB"444-- !4re˖v AAA\]~]OOѣGQQQ/_4h.1:yy%K\r:))ɓ'222nnnϟ5--f͒[n}]AAAHHȴi( I稫{xxܽ{ʕ+zzzΝ033۶mݻw|wLaXQQQYYY-zYyyɓ'N oijj PUUݲeܹseeem>TVV;?ڈ#޼yfsssURRRV:z(A8zcƌ4hPVVVl zЮ]=^ xݗT*N^t鈌;J6mlٲCKvzow#BJFF&))?VZZsǏݻ Dkjj9srܸq=8p ޹zEBBڵk߾}kjj;wR( .p9 F8uꔣÇŹߣh>LJJ~ff&700;dwLN_~UzzW޾}.,,lbbbnn>~xCCC 诊JKKBxg&W^|2==H$[XXXXX gϞe˖yzz*(( O<ӋlWWקON>}֭FFFx'}CEE޽{=*""d_|faXT*UJJjΜ9]}䉉IhhHdX_"`0z/A!2KJKKϟٳ+Vp///zEXyyÇMMMSٞ'Nܿމkbbbnn.D?~|[[Ç+H&;x%K?܌l2tSrs?FxV;! 0tP---mmmxwegg| ;r=|pN___[[O>aEXϟB222:::ZZZZZZ:::}4`|1;;rrrDСCx}!!!6maĝ;w'O|ŊݵkWjjIn Ew|i޽ǎ۸q+LnݺeccҫC궶>}SÇ_|9Db2_d2{/g7ܼysɒ%RRRϞ=6lq477S(vP__bB[[Bl!tttn߾~z}}ŋܹ*SNݻ +%%u]SS)S$''0GR]]]cbb޼ycaaQXXͷ//O<\O&&&6nܸqa?655y&++ݻwo߾=ydaa!"rrr***QVVUUUEEEş>}Sqqqmm-BHPPPSSSSSsҥZZZzzzD"Qw()))))͘14333+++'''--̙3 !*eeeiiiYYYߩa;vРAVX3l0.*..Xj{_\t1""[ӧO~޽]v7N[[IFFtWݼy322ݻ򁁁K.;'ƌ}ޛEMM۷_?ERB|||!zW@nnnQQQ6m~*V}e0t:JBdĉ_z}ܹ 6xyyRa<(**^"//=SSS(n!T*9((bEEE9 ***̙~aWWW3n ucݻ|vWbbbqqquu5{4??AAnVh4֩b7Fhϟ?chXBUM6 +TUU.$- 4hдiK޽{WXX]ݑWZZс 痖v****n?, $$uuu9BQ___[[?F v g(,,555'Ohϟ2s18K,qrrxܤI&M;vVVV...'O?ׯO8ssӧOϜ9J&%%YXXjjj^;ڌ}b絳 P E)))oiisΤI8 +Dg0!FR[[[B}ܜL&/_|{ <~mۖ,YgF_K錚Zrrݭ[[`0$%%[ZZ^xbŊ;wҼwޝ?JVVR/~aͥUUUXXEEqFAAۢETUU-SߩYUU맰v;^^d2w7y'ND"aMh4B&,,cǎe˖ܹxÙLի~jȐ!w177;w+WٍzD"khh|ɓ'mۆu_v-g/%wj1 ÑMMM ؏---_ec]$ Ñ---t:c İD"QLL#utt\z588ٳgcǎpuu=}c:+DDD\\\\\\ϟO",,,lmm'O FKNNrpp믿3ѣGCUUՆĄ 1k+++srrz޽{׬Yk_G`ȂXd{?(D 4(,,lѢE6l9sݻ w.zQKK˾}\\\ #Fܾ}{ɋ/>uT:"1o2>>>vrpptW^wJHHKsqA]}UV,&&zJ~ɓ'.]z据ʕ++**@oٸqwƌ3/_kr߅""rssG%$$_~=lذ9sL2ŋ9F:uj|||\\K޼y쬪zA2[]]ݴi߿U899]r3gLOOwvvvss;}tnn.;)[nm۶mܸqSL9{?} U~~~7n荍`˿~VDDN}ftDi4Fkhhv`•D"aQ!aaa,vV 111QQQ>>>.?ֶm۶{Ν;744TLL DaȢEEEBd:R80k֬3g^z+22rѢEv풖;d23gEE4iRxxŋmƝxD"1LmPCC/_~-[lmm}}}uuuaO>M:1%%EKK3aX}zpp… [JJJNJ?~wWXZZZZZ"jjjpcggsN;;;wN!2H䵎P 直(+++---...+++..jkkk㎎ls1[] aEbbbRRR򊊊 4hε,X ??ĉܙ+>ƪd2B;D[[ۓ'Onݺ^^^k֬)cEGG]|'ŋY,{97D"u{8JHHHMMݲeĉ3bĈϒaii)++7h4ɓ'9;f̘hKKK^;7 ޽{7uT ceee ))9s̙3g"ZZZ_xv;l0---=U,0++++++333;;ݻwFrvv9rQ/ M>>>Çƕt y"Syyyyyy߿/)))...---++kmm4HQQQBBBVVV\\\\\s06:NhuuuuuuX3?uuuYYY񥥥Tbbb jjjC +`0|}}w=iҤ۷oqjˀ577bXG䆆5˗/wpp9tжmۖ,YB"6m^;;;3K-[j6. HBdPȝ=:)))!!a͆~~~ϖjmm=x[nIIIq,:h~~~7nhjjG=yFKK͛IPPFz*33 omm2d!C###oNcڈ#f͚5|p}}}%%%c>>>om!2Jmhh H7A!2hll|sss㼼BPVV6lԩS_%%%!!^ӹKeee%%%%%%X1LРAd4YSSS]]დSFFơC-[ky@ijjZwct:JwIHH߿\>?ŋ׬YC&==={)w`D"QPPo'N|ٳg\]]5/^BQQQaaa^ ;wlݛ5k֨Q]/377777~߼ysB JUWW}b|oݺÇ&lAdeexX#jkk+++KKK?}T\\ӧrjuttlmme pƈ#,--z]VLJsg566>}455<.,,D)((Λ7PSSSQQ& C鼐Fgdd|˗N:::|eŊW^ݴi3YLLAPPG]uҥOOOSSS++}ihh {_[}ڵ ݝD"r6D8D`W^b9boosN>|heeekk{I(wEGG'&&ɹ,YdxuL&sݺuڷoq>D"N4{ŋ+WTTT`7&HIIaҲbbbG 7Rkooh4laUUUeeeeeeuuuUUU H EEEeee}}}EEEWI$\m۶ѣGO2URRjnn.++)(DO4>|aZZZGG/CCCCCCYYY3;ơ`|?i&&&ƍ355h...{YXXd;"exG6ccGlذAGGg>>>rrrxGmmmy 6lwssG"X_8`Wn߾]GGǧzĉOcׯ_^xm֬Yw޵+ziwܹxY]X,O0ro߾evYL&ST "(((**'&&& $$$,,/..$2NooomooMMM4MDD]'---_aEչaaa # `ɒ%}^##I&qYYYb~Y|A!2߿ׯ_3 33e˖BnDmmmmmm'''P[[ۋ/>|ѣ;v444 0`ɒ%T*b?Ū?!d2vmMM$amm=u'Nx{{GEEoٲZ}EEE.\ =O{{EH$}{D;;3g޽[OOo.\7X nܸqĉD7.^xРAx3*++mll>|hllw@ dddddd~8I]]]}}=>Fc?j;؈5 ?񉋋S(v3LgKHH`[a˖-V:rѣGG_~kĉO<;v,f0]Bdyb+--uVLLLbbbKKː!Cƍ}p`'%%=yѣGVVV&LP(x\BөTjll?abbbhhx⩱СCw߸qڵkq߯eXnnn׮]>}:Gq͔)SBBB(yRRonj4zӦM< @`0ݻu aggx &@ ݻӧӛ7oVXs77]vQTcccii7orj,BVTTts```XXǏũ#!!c BBBaaaNNNPH$ȑ#===|f͚l[[[*jbbwLh4B2!ֆU1لCCCz;zj%^h[~D$ ƧO'յΜ9SDDӧO7nܨ111111y&(w0̄ggg)))s}ܹs&M*c?}{?~|sijj^|D`ƍn | ?~ɓ't:Aok[]] Ȍڎ3x8|0??+8A"xѕ+WEEEqj\@"؅˗geeHJJZ[[?zɓ'RRR3f>|pCC'ROzCYYy>|t钵 ׯ[ZZQ6윛kee5gkk뢢"CU3f 6UTT7 t:Ba0A!2N8affaooVRRngg',,w?ʕ+o߾]]]}5II+V 8pѢE+2c" 3Z)))]]7o(((3x!tWXٛ`OOυ ={[UX!rii)LnjjM>|ٳN:t({1cnܸ3iҤ[QQqݺu>2$$dц/_={W?~| 5kKttq0`@XX wֆw(H$YŋŜI$REEE"""`XIIItwwWTT-.. 244$x ]z,00jjj>|;ޝSѿcߗ8ʒ-kD)[)IDERwntے[)[TV dG*1G1=~sΙs5BdAهhhh|244ߒ~% ڵk7lؐu(0EDD|Ύ;quu @]]333z|w*&&vܹ#G<~X\\\OOѣG3odUWWwEMMͅ %$$gN7uwwyzz^|ܹsX'bŊt0M,Xpҥ!???ŨBd&Қ7oֆs3͂ lll^|YZZjff!**m۶T)NIIecc#444555,\t3rJJJbbbKK;*J`;fiiN)]___]]݂ :::&. ww caaqppC\\ [nuVQQq55E9;;sppܼy><<\KK MRUUʋ/ťWEE̬P/\5%;^!2zZAfN6(DCEE77ާO-,,DDD9/^ ]VLL,((h`ۙ7,,,Ң|||455ܹ#))imm][[u(0ܾ}?$,VVV7n DGKKW[[1Bd''ְщknn|葰bhhTsD"1--AXXXPPĉ˗/E[[[TTԎ;8deeZ7==]EE8ӧO={&%%>Ӻ0~@@ X"SSSϴ)(D3]nnݻw]\\JKKMUU5...++KIIYLL׷\`"YYYetK?@AAaddTXX#QQQWWW[ӆ@ OÓѣG=<<_N&g{x˗Ϟ=;ދ1ݛgff4 %K=~ RUUSk׮ꕰ0qч---׭[@F ,055 B{O #֣mhhh&?ԂBd0s޽[AA!///,,СCX|򈈈2ss'N0a|||Ǐrvv^x?ֹXA!Ta``pqqӁ.\ ,,Yn޼YBBbfwww>yM:.}}}-B_|_O>ɂ ޼y#''w jjj^rŋ7nLII122JOO<{ڵk19 X[[=z3DAA!==҉X',ĴϜ93]а~ A!2Yzzz?.!!鬬,MMMCraddtqqyb||<֡ /DFB!İ{yy}꯿ :2332ѣG/\L$y@!rNNٳg}||,X0V\R__ξ~-[ܾ}ׯS8 ~Mss3i yL x^WW710ޫ:;;޽kmm-$$$$${ڵUVp8cYMMMn5f>***"999--;v466b w```9zzz**QĶ3mllYXXN??ם\]]uttvys 薝B^^\󳱱񱵵 Bo ()vڕ+WNԿ; yGGDž fMiiio!2@^r={аٳgh  KA4knnnnnnjjjnnG;9ð011xnnn<999yxx899ׯMMM MMMBcc#zhoooooG{zzƻsVVV‚yxxУ=777LMMM OJJ꒑ٱcǦMTTTuڼ~5''G\\8p»wى{yy߿F+`֯_/!!6]ՑCGN)P ff'']]gϞcL7ǏݻWBB/g t}`2!!!Ls!!!{qww߱c}||0)sOzzzrrrBBVLLLwyƍs`ZZ^A.\:::Ƹ۷߾}Ns崴zzzzzzmmmQQQa``sεkΜA KKK++++**ظ999x<;;;???333;;;T,NGGGOODh "H`Fmͅ ë/^, ϿxbtyҥdQTTTQQQYYYYYY^^^UUUYY<^ >t55---÷hmmE<##epp݊= 0=ZZZRRR?\TT̬yimmŋcn޹rʾ}֬Ys+ [~ѣG vڼG\vP`#< ʕ+<<VUU|𡸸8..AE/_|й0uttd۷%%%cҥK.]~= 0s+x<'݈f’qٲe˗/WPPX|$L֖^guuucc˗//[ ~а}۷oܹ8Scٲe/_ttt :djjz''\@( z?ȸR枚۷cgƙ)/D掎GDDy{{`㵵x': -y$))%''geyVVֻwf4;;[[[[BBѣG؆bbbjmm222&»w>s̴d/_ߵx<^[[[OOoƍ;s_~}տŋ^:::999EE+V(**ͱ ܬ̏?Dnnn555uu5kHKKϐcL/^x񢰰H$rqq\j?44̌^zzUVYf͚5V:&322RRR^z#!!nݺu֭YA$33*&&FNN8S9""b.\ lWTT$%%;YZZ^zuz 0 @Z___Oj#5`o- IDAT@[[XǙu%͛7߼y>7!JKKIJv횵?SZZuH$>zãGDDP`v(// :۸qҥK~ĴaÆ7jjj cnļ<}4++kppO]]]MMmXg>mmmYYY/_LMM}uww7;;mttt0xݫWRSSSSShiil"**u@W^z*;;{``@LLLUU?: fff[p!q666Շ>x 4ںu))iz===7n(D-" $''>x83"l۶MRR3VZZݬqttH|>p=ww KKKOOO$ݣGJKKgZeaa&77w||<V1>>==OHHHSSSSSsݺus)3111>>ɓ'ZZZp j`` 333555%%ŋ}}}˗/Yb|]?~ٳ6 6YF]]}n?ߍQaa!:11˗/444Xӡ?##˗iiiYYYjjj***rrrLLLXg믿|||/^`>󓐐t钒։{}ALLl;.,,r׮]ݣ"///lnIII7n|q8:t萜 ]?o TVVfaaQTTkhhppphii?c 0|]gqqqAAA==ijjڵk//c:ࠋ//IssOڵk_|YSSƢÑndNAAKbl022*,, ~%Kf+W8::δ*dA$%%»wﰊ~vefeemoo+WMGɑpvv~yWWWVVuYY{n[[1гgxxx ߾}gϞׯ[YYA2 55ӧO߿/''zjaaaOOb3OYYY:t0((Q666wihhHNN} -Zt\hooONNUSS'uuuqqq...P<3ihh9s&<<ڵkP yݻwX`"yyy]6XEA1wppp2sMtD*99YOOo۶m7nܘFkh]pp0}o߾۲eT!DGG?~zŲW^/++KMM@ɓcʕ+111 666mmm)))ZZZc8 F]]Ç]]]D"AAA|||UUUgΜwވx$𔕕8Y &''755xYcc#2 _|핔0DCCwÇ_|\v*dAׯ_?hiiaaaA9&&i‘ Ο?_TTT]]weee665kxxxtqqQQQA;4Nooo^^^JJJ##ׯ_chn~, ***111X'S]]]<<:#]vQPPʹJMM믿8lذA__›[[[#6\#f``h9//oy'Ge`#ZZZ6/륰PBB#^ X~D488HMMOxKctttF dggokk~Kt ;;/_mꆅYYYal߾"::Af<Ӄu48qܹsNNN/9MOOy@@Y?I$%%<?~~ԑo޼YjՋ/է-&RSSܡ!~~~UUՕ+WXB^^~;ߺuΝ;'֖?44رc===BBBƂH$&%%:u*11q~~~XaaaG?5%%%8q"))IUU5 @II DH,++O^^ϟ _bŊ+/b(,,444lllܴiqf .:th…/^\n։۷oUTTM`ӧO;99eddXG,{ cdfܹs)))YL2 AN%N椫Wvvv:99ad\\\Vttt|odHH̜BFo۶̙3YYY񦦦uuu***,,,aaayyyP||]q"[ee%zq~s`p8܆ >}ή;s{IKK8p¢̙3PsIn o߾[zQqq1֡拎RcUUU666aaaCC[nQRRZYY=zٳg۷o9ӧjjjTTTP -jjj|aaaMMM331~ m1ͥƜkD;dA&6˔Bd@vUUU3ʕ+IMM%qNV?_re7]]]Wo3N:%))̌†Xd ="y@NН/t߱ 0544pqqUUU |v`H@@ ,,pʕfffw܁rf``̌tlaeeuK.MÌB "@vΝ{NCY[[رcmmm>|qㆆFQQ++[JKK|)//G@SSsݺucQSSqF M6}ۛujC0f222qqq ǎW:]KKrII/֡99gϞ}QNNܹsp)ܜࠥxbVVVeee''ii退􎎎>DEEmڴib=L644ta]]ݍ7~tw2%K}z]-5^vֆ 55%$$ f>{{P{{{0ϓnN xZffn77iϟi֭[75 uwwsqq߿,`Uo-eee!!Ys։ioogaaADVVc+++rmʕ6柍,,,bbb\{nrLtOOf<1|stttYY\q1a8ONHRSSӻn߾=###66Fc¯f6"feealgkkv52=ch&'_~yaBBœj|4hcjjjAAARٱ񣱱ϟ\b``u5""b/^\h։--- .x񢥥x'TqqBgϞmذajdkllu0gڵݽLrvvv @ |QXXXRR g!!WWW??}ٳh`j$''gff~ S֭[G155---)zzzAoaК"xB <k`````=Fp  ۷ONNnD7qh(˛hhhn߾e˖]vpsschڊhcA7n܈. B1;wX[[KHH b` ߱cedd#0spppl޼ի(D!#Ą Ոއpb@F+V:qlr4l޼͛7***H>}>EErd[[@WWݻwù9W]]]II Syyy-ZƦҥKSn俓t444y󦪪h '8992uٲex<޽{AAArrr_NN۷oذ^{{{BB==O>|g8JJʈEEED [oooMMMmmmeeemmmMM Zy\YY@ǣuƚ肐 ,^kw `]r7o^n]@@sxxxHH"֡,--7oÇKkC^^kW<4446NY۷s @BBBo߾:qľ}tuu%%%rLXII˗oܸqU] //,::իW;88tvvb Ammm>>7nwCAAA0===%%%%%BdgA6@>LLL+A\~z!!0W^jՙ3gpႋKooo[[x]&֭[...y244:zj۶m-]x322zxxD~~ݻwMtAoooiiiyyyeeeeeeUUUeeeEEEuuB*211%&&~q{\puu}||{ɦM8H$nڴ)!!a\:::=Q=Lqlhccsҥ˿~ۭʩS<==XXX7 `VF㔔d#_RRRqqq7o&k$t^;;`tX#6)@$UTT^zuI'''sۯX"## HcHXw]XXC)F544Vxnnn..Eqss]x1777Ӏ@ 8qKII)""ܯoH$FDD899m߾D ;w***ug:88.[UJJëv1:"r+777A3 ߿*++lI WPPZlYxx8@:ĸ`srr())䒓dݔ_~Edee933pJ暓***ヂۧ%(((++Ovvv}" Aqqk׮555]]iPWWauuuWWWAA+H<|aW^/++KMM@ɓc蘛011=yepX~o_t-888//ے999SSVVVCCC< ,ehhH$Y[[bы/љ<ǏѩG1>e{ktҋ>§w :s OYYY`` -..744|!==ɓ'7o<cǜmۦ&!!㩨xxx֬Ychh%((}cǎƦWUU?yڵkvvv[nURR*d0 괵===?UÙ_~ǎzzzX󝕕UuuuZZڸ@`J&&&  ùс 333ARWWu^PP`pppppp`LyG177?N8!**:KfRSS7:u~;顢FdeeE_Ơ?~,((8Y挦B_k֬A5_CEEE UUU!^% ԩSEW8 # zyaaܼyAkk'Nk y޽{'pkӧ---5;w񚚚<cİ\2--Ԕ|f)󰎎ׯrssXzJCCc\݀&?Q~bVTT F/$ غuNNNh~X Wfdd|ꕸ8ieWWWWWׯ_ЅQ~ڊ.JQTTTðqrrrrrx...NNNNNNjjj}LңG_| &0gII?ݝw$$$nݺ5+G\ "9888Q Tdaa: ,--?~uyD\\<;;[^^Avv7n޽[UUUAA!((`*edd9rɓ'W/^ܳgYH pww_'S뫦>l߾Ν; ***gϞܳPwwwvvvfffFFƛ7o񲲲222RRRttt?ݕe***::}o¤1lll2!!AGGgԾSA]388HMMHt ''gKKK^^,i\yyyNNΦ&NF{ףчH7SKKˊ O0o?:55ڵk.\HH1m&4bc9 eo?x`֭TTTµׯ_777aqqxҥi=wuu133G4bv0uww;::^|y~~~X'N>%&&v%ĉDb5 /^>˿e' ȅ{D0U|ƆuA#r}}=j*A,X瞕+W>~۷owqq 3ѩSxyy̰2Mp8yaa{&jjkkkڜcǎMM٣6--˗iii fff |||-77waa!ł x1h4D1.^ۑkj෕dc3X{CCC>4$9 CCCCmmm.DFB&|ח bff711)))6n܈322×&77wq 55}TTT~3gppp` ;;wtqqIJJםHaff,z+*\p8ܒ%K>|u0}aΝX/***XYYpK,A뀀,ýX[[)6W\9~|?Ȗ-[V^ii 줧TRTT.?{~ڵSx&K|||BBBZZZYY/))ᑑ9x𠱱Ą^BVN:%"" #Diii7n@DRR 5`k, GP^^>11ի~\DDL?L,R]]444vvvRRRӜp&1AcǎSN<˹G8p]F0 CCC3 H urrCN!""p];;;iii/^zuTTԸ G|1bz2VYgrrryyyX ;7v3/2!g_~qٲeX/***嬬,zzz W:"mK, )--wuu :Ξ=ȸw^``ٲex<~ձCww7--r||5k͔^~}ȑիWsqqYZZ$$$~: @__ZT1}}}666 9b)&&fժUX/I_~=88~ ŋ[ZZ=zT@@˫ \WHHH:޽{Q;wvuu}۞zzo;"geeijj%1jkk/^qF...++6//O>]pa˖-XgD111 ,<022Z`ӧ?Np8\||ŋUUU988888TUUCBB=zD*;~Ӹ! 999&&&LLL˗/?p@nn.9GT{{ǭw@JSS3<<֭[7o1FFFf!&5JTTAff}ٳg8 **ók׮ ~09DFF:::o fM6߿СC钒X'cw)iE𨩩EEE}^^kF ö#2{ L UUU<u2B+HX6bTwoiiއ`qpp9rή}ޱcG?!4knn "666...X_zzz~嗓'ObeFHKK366f``X6ٲeKSSSnn.Z|=##2G&۷o߹s'33aӦM[neee:wݼy4..NWW,`LDm۲rrrx|x tDucd455ڵWRRmD&p֭}-_QLL8{L%ޝ唿?ݾ; i!Yh^"n[23 "{J6,a*оѾk;}YCuZ^?z9s;:{׮][nvÇΟ? EIIIyyAAXZZ`HJJzzzfggJJJ \]UUn:_VRR9rd 544 lvMMMXXEB .]0`M444n޼YZZ4{\L9scǎ{]zU@sqq`t]]#Gx{{߼y4׮]h...Tg66x>\rUП͘1cٲe...ϟ?G2@p[nxBSSlBu(˦M|WQQ!"==mG= ;" 9jjj~ҥ]v1֬YW__~ESS=аauuuWZEc\]]ܾBqq} Y LSS p:>a„AS%,,ᑓm۶ UUUQPP@uuϞ=NNN}#%%u޽˗Ϛ5>d2 %ógϬ!p{E̙r颢3gRN8;zW^Q{>>>&&&Tgy֬YTg>涺1Lggk׮]xQAA8 500 ٵkW/ _1iҤdggkZYY}kIJJZ[[w Ad%P }oܸct,yy8pڵk\p ׯ_fgg_v̌\f;87o}||>[g1O<=OtN)))Xyd>PzAAAcǎW<%//\}٥Krss=<<prrڵ/_zn&CDMM _!kkkG-y;G}}ɓ' MMM6nܘs9svxxxBCCGeiiZ[[,XiӦSN^Г9rdŊSL9z(Y{G ѣ@ŋ7n̘1666T'Ͽk׮/_3Ok=:CHHHeeeGuDodR(D.7~x{{{gggW~:Ak׮e0 Xjի077'"33̙3.]SN}iammZQQuV11M6}טu͞=q:uj޽TgGRSS544hll&ߒȎ(D  FJJ֭[oܸf͚|sA,kڴi:Keoo]QQadda,lc־ӧ222d/##cڵ XtA?~n:yyys͝;wԩ[lB*|*''g„ W\zF\|N!涥~~~222_y><3f q0xgϞߢT'.1lذH??{С!!!T'>hʔ)Aܸq#ɋ޽k["((X__Bdw=ZUUJuE~k[boo~-[Fzŋ+**N2J3CCCEEE6o޼vZrwqwwollܻw}ʞp3f̘1cY իWL&s yxx~ǎW\N;;;N4)77PЧ7~RAAA6]NN=P AVVk.t-==gϞ޽{ܸq7nΙ3 s隚yyy_R Tgw< (DZ*;;ի666T#v1qġCRq=<<&MTVV&پ#277wJJq;˗ӦMydz/suŋ}VTT`f IDATpʕ!BwzҥK Ftt4B~xMMM %KQ 1bğm۶'O(++S1Looo--ҘOOOdee?~d2}Ǵi=zTUUՑ/ SSS8"@7ݳgϟyuq%7otYfʞSN]cz̭]b-\0###88X\\8ˇ F͛7988F r*AOG'M[n֚ՓU7mDuށF_QQQIIIkb/D&B@@zd'O?8''ʕ+ :WRVV~TUU{aȑ,+**꯿*2227oWvvv5kRkʔ)A۷СC>>>h?ԩSiii gϞ%"33755pBFFF]]]XXƍO8ư?~O'^t׃ȾН:thSSSBB&??GcFM4)***""BLLlh!cviaaabbBudqqqFe0mWL&'''_[!2777Nק.X[[[[[>x ::zԩ0Ffdd]k[n---:WYfiii%%%ݼyӧFFFTo޼yiii޽{ _)~W{̙3utt޽{Ku(Դo>###&sIu( g||Kuu5աuVGKIIݿ BHHs~rzÇϞ={ԩwޥ:ˏ`0 cV:uj/_\h??&Y">>TM^| HPPѣG;6v| X,Pmm-9fKJJ~^~mcc3|p GP6m̜={\\\RRR7X[nYYY}ѣo޼4iչ333ۧ}lvHHQ||ccc'L@u. <|pС6mھ}gP$55>}뚚T'^>$$#rrrMMMMMM[#2G1A!2t+:~ĉ3g۟8q8MHH(::z̙222֭i?իW˖-6l/_gϞ˗Κ||| G1}L}}iΜ9seP۷C !=sOǠ#2|7o֭[YUUEua׮]{wqvv,,,6lݻwL&Nl(,))Im6...ƅAAAZZZT}vjii_5kO2EHH ,?G O^^^!!!'N@G8p/u޽yPVV֤Iƍz@?GќRSS'M4|;;,CA/foo_SS͑Ao?[LmGdZz/6y;vlذa_QPP0uԌ7oQ266633;x𠼼|}}}eec;w~AouǏ󻹹XBBBP=WRRҐ!C.\@uޭr7nܐ\pݻwobxxx ?NmV__-[lٲe,ɓs?vvvg϶:Zr̙8555GG9s樨P LϞ={Mo͚5kԨQ|||TGMݻw̙gϞ 0`Μ9NNNTFSSΝ;w---"""\]]߽{~?D+뛛~}؁֬YannN+//o+_n߾剿@m۶:t_~)//:PѨB$ ˋ>; )++dee}(00y֬YRRRȠ:`Oc33-[ z[[ۋ/IJJ'O,)):`m۶)**YFUUѣGYYY^^^B~C ٹs듒P aaasν{EFFRzɓ'ߺu݄333ɷ4M@@vo---]#P 5kVdddzzݻw=KssǨQƌ9x`kGa+W0qqq"ϐٸqUTT9Ψճ_psw"WW777~իWA}NX,"̘1FOO/%%`prrRwspp:zի_^VVFu$99رcf͒5j?3nܸwfggP.\ɓT//˗əyzz>y򤡡=BaaK\]]tuu/_{ѣGc*&M4~x%%xOOO~~~C@`0޾}`nnˏsrr޼yaA->ȴoSt2ׯ/]tϞ=T'%$$̝;755u׮]+VhT'N:rʪ*==O<s"ϫ;q޽{ lmm7olllLu\\\Tg;455g͚?$$$3F[[… ߼/Et:}߾}g}LKKӧOݻɓXmiiiaa1bĈSSSS|||dddxxxDDDII1clmm544jkk>|xǏ >H\\݄fgffx"<<<,,,55kĈֶFFF<`XNڰaZd lٲ;w.Y77lҢElaŲΝ#[XXwy/@!2NZv/&@o߾g==3ghkkS .]c>>>SSӈ4330`@7'Ϸwww755: {/5&>o޼'%%UTTdffprr;cTUU13g̞=ây*<<۷oY,S]]}Mlll\\\lllRRRKKѣGehh~W>~ѣG?'bРARRRTg4,+##իW^cƌ3fhٳUVΛ7ۻ/ BUUU7o>|ѣGN[TT9a0L...KK\_ZZZPPVlbbiSYŋSNݽ{ Չݼysڵ۶m[|y F=l0''s~vXjj7o oknn>}gu]pݻwTgSTTT,YR\\#YYj..|IId'ON0{ڟUUU~:66,MIIa2<<<ZZZO555nnnvTeeerrrbbbJJJbbbrrrNN#Q| MNNYK!--[L@544ڊ гdffZ۶Ԥ:5K.}5k<==q1ÇǏ8pW+))%''osrrrssGGGwm/C!2,!!!+Vqqqٴi4Չ;DFF_ٳgՉEDDΞ={رF..ώ,++zѣ9$O>s玲2XxqVZZ:hР͛7_,}͠A\]]kjjnݺ!**:} ɓ'w[[[=<<<8nܸ'OwRRRr,6MeeeGIII^^^FFFJJVTTfggW+**$ ?o&$$o$''!$$%%%)))4??\MĄ "6,..K~ aX׮][~}qqʕ+7l؀XPkk]vQ>3sswiii}K#YM֝ IOO \h:TwظqcDzUu ;"srr]t ,8q{VXsN~~8tQQC\\ݻ]\\h4ZW~@]]]JJJFFF[Y[VV6F@@@ZZZFFFRRRJJJ?bbbm xyy?[\UUb`ٕ5))))..&3 899j"iMMAaZ E>ݑJ斒&O !!!aa<<a„{߿̙37n\`AU޽{k׮ӧO]~_:|^LL˗/$.ȇ-Ojjj>>>Ǒ#G:t3fTGBk׮ErW`2UUUjjjA?~<$$dݺuo>~1c:߾4@Q\\}ztttDD^@@gFǎkjjZf A,,55Bd]BB¯:|;;;VUUՄ ߿*dRYYaEEŋ/dff zC>{ɓϟҺz*Չg0aBHH ɷUCcbb.\(++hѢ|# Ԝ9sZYYȑ#NNN߿?w!ѠEEEΝ(Dj 6mРAT) {uvv:KbȆYYYjjj"!""rر7--MWW7 {䘚{.**Ʀ?@r͛7;;;sqqQ h4Sbb1cf̘1iҤCAO1nܸ/ 0`A޽# QnBd},,,.^X^^~ĉc*((0OR  \2i$))KFEEرCAAt}MLLbcc;ҶS\\@9==lEEEk)wjwwwet:dꜜ",--cbbf̘tɓ'wׯMLLxxx?ՙb2ꞞK,:>y$##CKKrR ߿DBdAAGN>=888==}ٲeݳ022|%z$w7o:;;+))͚5СC!!!ӦM۹sgYYldnٲ#HHHwq.LMM]z 444222}w޽`YYYemA!$$Bd DDDBCCSSSvO=FQQÇrrrzҥKMLL޼yk.$۷o7o޼e##ϟS(Fӭ>|222Adggo>1?EYYyƍO>1bmll,//hѢK%DY޿7aII~-!!aʕ999svv~qFFٳ/++;iҤP#CGy󦠠͛7BBBƍ& =֭[w}5 )SS7l@u>,DA ~e1c$%%m߾}ǎZZZ_fmm=bĈG;q#G?^\\<:::((H]]P߁#!!AZZzȑƞ( IDATNNN؄v-|]YYY #--mƌƍ۷o_LL ,UUUwqwwbh4nnj|`$''M<&%%1O<8qԩSoݺ~ 2dĉ Q >x :PfܸqMMM][ZZJv:"t2]] 6DDD>}ZLLl֭Ç8qΝ;={)#eee7nXzիWmmm|Kwwwee 6S.]PdaaarPU |q~~#Wt:. &`}}o 2\`驩3fX`1cRSSNPPPy WTT{Q[LC*Lf\\\xxxXXӧOGaiiihhh``@u5999..ŋaaaIIIl6[CCJQQw^TTt??[[[ammM:1?*)))n+## L ;[[[ޣaX,]]]}}S=z/8qɓ';7رCSSfԨQW^}<ݻSRRfϞ~z]]]CtgϞ-]433s;Չ[]]͛aÆ_+""RYY-1?Xϟ!--o``b߾}ɩM[ZZvo޼t%Kw!ݸq~cXd0*++kРA/^>|x,.\8x`rr]O'ի ZZZTgƎr1#F1c wonnn<<<=} ޾}W~͛7S k>|xƍrrrcǎ:tO0!77w=z[XX(++~Puuuw% +--m+`zzzjjj***TLiiiIIIɭ|||C ! abcc;v9..y^ZYY _dɒ .444ppptpaaᐐ &ti>ѣG?~ E޽{vXQQr?CDDSYΝۻwojjɓ7nhhhHu(rׯϙ3g߾}RRRT'(..?OΝ;ܹsEEErrr vSOS]]͛Ԣ" 8884p@yy[_KKKQQQnnn~~~fffJKK RVVVWW$jAAѣG?^^^o\Ą$*##S^^>q-9uꔣceRo޼?{E>yW}ɓccc?W ĉ6lؠ1jԨ'OjժW^EDD|ޚƎ[\\qM6ׯ]vÆ l@___ŋauςjjjlٲb :Nu"rƍp§6mڴ};w h+篫ްBdo&;IOO"0@^^^QQQ^^^AAabbb¢"""&+++sss 򊊊X,A4mjjjmmՕ.hnnz]]]g͚%((Hu%44tܸql6;%%ECC㻶?V\EAIIӧ:TVVfoonݺÇwg ;wtA3;;; sؘ=} 7޾}߻yݺut֭ǎSQQ2e Fegg9rرc,eժUTX]]ݶm;r#GP޽{?;Xti@@ŋY,'''F#+㫯>gG?Ԑ= N /Appp ɬ&_ֶ *+++++[qqq8PAAaȑdy  ''G={ٳ9zjww9s渺Wns5UAHHHwE*n#--zK.ݻwĈfff cԩ]l )44ŋQQQ]} hd2999ۮ |>|ܹׯ?6n8m4CCm۶M03?@o__[noذETTP=]fΜtRչ7=.. dddlvnn.At:m--Q #?]֢?϶ⶅmW',sss4o&{0> :xÇ ubX.\`X?ć:=@vrr3gΝ;w<8c ==UV͚5뎻cǎ1cƘt!#---BBBm@!r||uɓ'JSSի)))^^^vvvڛ6m>}z|^ܹs~~~ .\:u*''_|lذa'Npww}ĉ]bȐ!RRRO<YZZ ߓo,F:իWΝ9sې!C7EEE9ڧLw…@7o\xQHHhɒ% DDDXZZ>zh̘1sGGǺ:%%;wTUU={ٹ;WSSsO6l4iRy#RRR=z̙iӦZjT =<<Ξ=kgg?p@A>}z}};w>Z;QF=y "sssoT;v,??Ϟ=O>:tQ@@@cc#1w+GGdN:U\\}GII&88s233Cr7ijj*((h@HH;YvmEEʼn'y400uVdd$a._b~3x---AAAcƌ־zU.\*d sNbb!C|||L&աYYY=}?Y ɷt mIBdMDEE>|8x˗+++oذmI 7nܠhw=HHHwn*CXX911?󘘘{?+.<<<ddAAA jkk;w?~СCxٳgkkk޿qFEEř3grqq]v-++k˖- mlmmWZallCu"LVVVկ_h77wYYYQP }tرcW^}yUUU+Wtu%|֥KZ[[EDD~l }y NIIݶmKbb_~鬜AdGd999  曛-[l̙gC1#FXxAvڅ9ohhh8j@@9s߿?eNNNb|||o߾311qqqiz;]]]IIɰOWVUU5ǂ?6mW@!2b.]"lGujAQ OAA޽{uuuͯ\S}͛6l芜u<<}Çcbb&L뫮`h2yyyyyy҆? N  ;;;'&&>|PLLA]]ܹSGGgԩ]}!2:tK\\'cHIIyxx:t(!!200' TinnOAA)++kCu^qmϞ?|@ lI;v2dMffwVVֱctܘ|4)55n&Mʢ:,++rYYY S@ahhxĉ{Gᅦ dGGGA9rgv"!!ADEEL%tرcǎvm۶yzzN>}Ȑ!_655ʕ+gΜ9׌vDfX...Ç_hQgwqqqvv~… ׬Y3|55: Ҳ"EYYY*++kjjwB툊 JIIHIIIIIIJJJJJХJKKKKK%%%9|;&i_VV}ٳfff cʔ)k.3gvsT ]h4Zuu5ѱ/^|UTTTwwhǏ?~|nnn@@ɓ'?vXggɓ'q7`XYYYyyy999ٹiߣZTTTFFFRRRJJJLLLQQQHHHLLXPP遪ȶl6Dַ%%%r8pך?]srrrrrrsssrrOp/%%E> ",,,---&&־XXXC>JrT&Y]]]QQAɓUUUVVVtt4Kkk+yoݦٳIIIrrrӧOIgXZZ~zѣGGIu(zzzaaa"hb .>kT h4ڨQFUSSsgώ9RUUuG짖.]f7n'Ç f0+Vxܒ%K$%%ۏ|9AU~\+%%%((---[l?û.m۶y7n888;::.X3X,VFFFbbbrrrRRRrrrJJ ǧ>>++++++KHHu֭[bbbtٳ캹M8-[>|ȑ#ZZZT'>|xBBN°QF͞=^v-9#BdJ>}eeecƌqvv2eJ+?8!!NibŊu$6ѣ7ntpCCCs_AAA3g4hPffȑ#GA+(([;wܹ333SJJŊrիW Oml7on޼y޽/_[ZZZXXN䕕/_|YxxͧLbkk+##Cu@qdceeeaaabbDunػx*gB]TTDPӔMFMSZM1f h6I+};?s~)nqz}'Oŕ򚘘͘1CMM߾};<<<**\^^vҤITobŊׯ_oܸ mmm^z5{cVV}zݺu]r@CC)S>|KЏ TWW_q@E7oޜ9sQbbŋ >|ˮ]:oll2dȢE=JE`W$H7oѨQ[[yR99ɓ'RWhmm}E\\܃>|lhhhccccc3r.0 ǔGFF޺uΝ;՚VVVǏ777:222111jjjӦM6m9w> IDATCXXXxxݻw mmm p>̙3w9sɓN_7eqqWWW upp`zŁgϞ-..&$Ϝ9s 644477w󵴴:th؎;|}}߾}soo[nIKK/ZhŊ***T@N:qĻwΛ7-44 }}}Cum۶SKZZZbcco޼IMMMmmmHCq˗/_~,166hTcbb""""""ʔ\\\թ%---n gXcƌ>}::~1:,,,,,ݻwRRR ,pqqק:P,;;;&&&<< c̘1vvvvvv6tÆ !!!O:|ɡCΝ;WTTbxxxDDD***"##mllڏA(**nݺ5///::ZLLlJJJ۶mˣ:Z'O܅U]u4̙3nnnBBB999-t钪UHHZЭxyy &?tr@CCC ~[pa/B&ϯ,,,LMMm׮]ÇWSS[~}TTTcc#˗/7n8x`>]z5/;;dرaaaT.SUUbŊaÆ 2dݺuUUUnnn}[/^`2nnnuuuTuuu>}~ Aeee "'nn3fܼy|/^1bG]6m""44k+..Bd/;{lkk 0FCCӳԩSo޼W4hA-D篯o?/^pss]B]]sΧO&Mt)SO8q߾}>>##cƌΝSQQoll:wx"N755#KHHTVVv1~mʕruuMJJ۽{a֮]Qm^XXCG]WWܼCv-;;gedd9rDRRٳ&L577߳gݻwZ,˚۶m[|yaa'TTTh4++d111333;;Ts@wu7.ZJJJT,--_|7zh{{lCA[N___\\ʕ+C 9qDNNN~~y󤥥N FsqqIKK8qO?dgg?1^j@d4 0MMMOOh11e˖˯Xϵd2k׮h]~p V{\plII4mҤI!!!aaa:::cǎtn888%BXXWGꠠ WWWrvMM͵kedd8qBAARTTtܸq;v숎jkkoߒ)S,Z:77w"""? nxxx\\\EEсڨӧO̙coo?f̘ÇRj׏sNnnSX,աx///;;;YYYEE%Kdee͜93::N\RMMЫ߿?77WSSˋ`P  LMM[ZZ^z"--Bd>288fff^^^eeeT={455Y[[ w&>}#Ϳedddcw99]vݺuKFFfʕ򮮮q +);SENt:WGk׮h4gggbMM͕+W^z4;;ٳ***SNYdɹsZondd蘛M;^vMOO/??ѣGO١=~СC477 Չ1bϣ.]Hu2!!}ƍ666߿:@d27nhee%//offvaf͚{xxxXZZvNj},,,^zy{}@UUURRӧ-RRR,lʺ%'''&&ZZZ=zTAA*$$*zFss#Gh4ڵk׺ Ǐl#䴱믿DSSÇ{xxi߁l@#쒅ȃ jlldxٳNÇ/[, ۷VVV999#G255]nff&1Ɋׯkhh,]諻5NNNɣG']=))T's橩'O:@ͽwލ1˗T'XLfvv+W6n8~xQQQMMͅ (((߿?%%ۻw0aB}GZZԘ1cVXQSSCuF/Df2A(DFFFW^sttYbEׯomm3g`wk…]ODD5)))11ԩSCC6d1Yܾ/^,]T:t?OӓO:ѣKjiiM8q^jnnqrrr[[[ ˗UTT\]]KJJkZkkk??'OwSSggӧP<&zݻw_tʕ+bbbT'111III133377'݊bǛ6mpqqs玲?~LSSSVZw{ݸqC]]ߟDg؟JIIA" Q^ ^p۷ZZZ...K,!_RNQSSM2Lnn {{8>@uÇw񛚚ϟ?]YYY˗6;OLLL۷gϞ )){M0ӓ -[h4y{W^xŋ)))٭\\\Ç1b2Fspp~z<<qrrjhhC__Ǐ?w:ՉwZZZ=nI"tsssϞ=yĉrtt rttrJ"!!q+Wv} rrr.]WVVfdd`t/>صk[ZZxyy3gرb)))͟?ӓ갽]KKKfffjj*YLJ6</--^<<<nnn۶m%7._<88=Ч I7o>}Ǐ¿t'NϞ=,],?`̘1#))իW hjjHMMMKK#R"y#_2zUV}v˖-۷o:R[[+&&vUT]]tYYIBB{$ /_;:::;;1)))qppTWW vDjjj-ڹsgM\re…=3#os\gfC"##mllmva }||\\\֬YS__ٳѣG'&&Q𤋮d?Z[[k$??;֭[wΝYfΙ3'R"3)S|]MN4hWgffn۶m޽=UUU+++pppPWcnn7o޼y&$$EVQKZ[[Ϟ=k.99sM4DձcOZ'>x}38A$%% ׯ_m۶7od[[[snB&BBB[[LΝcUAprrZZZl߾=66?566.A677*#..naavZ/T!VSSm۶aÆ-_|޼y]tRuuu~~~eee;;O@ұ'N3FXX1,\JJJJJ… CՄ_ؑ (!!!##} ''gPPN߿}Hmmܹs%%%ھ0x˖-Çu`ܹS__v+We|yݻGȿCؚi4ݻwٻmݺUSSSNNnw_LLڵk< Y?>yo׮]366;v?ǷhѢݻwt:ٳg.\短P 77[JJKyy9աSSEEE[ZZjjj CGd 9;;;::JKKwwܙ2e`uu5''g5m4)))??n vttLKKԤ*C[[ۭ[.^xm!!!GGe˖R|˪UΜ9C]9s洶899yzzRo?~},"BTTܹs}.{˃رcǡC}u'ǰX,kksDDDtf#O~9v\jO/<{{ucǎٳ7 Ń&d ϟ?i̘1 mmp[[nDλvӧO[/Y;;v'O~M6:uj#G$_$]zٳgcTTTRRRZ𝜜222^z}SN (((ͭ!bРAÇWSSSSSIIISt5kyzz._/{knذNjkkgdddggݻڵk 0@ikk{zzhiiܹS^^߿;fd2NNNA9s SwW0}988PXL׌3 7o|###}}ǏQr """A?^zUKKիW^^^]]?,Y`0n޼٭@`0X,N533]bE||<{ x`qqq7羼wWgrM~!O۷oo7n8q㲲yyy'N (]W\ݭP>===""̙37ovpp5jivdaaXX__rСE3Ueff^zT'/[딕 BpPN矶\\\"""111L& 8y$A ]x/pss=ztrppS`2_hmHHHSSչ(p >>>{{{SKKKWWWC͛vMlB]tӧ?~\WW~9{lDz왽*|8{~co%BVVcƍG{7l@y|w{M4 K.H222?A0;Sd^'%%wtH/_];3I~JjdAx1.d1S෶ toill|GO8egg'jjjgΜvĉ7nzKJJ5j77;慮@~LDhhoFLaBnAIIIhhRRٳ,Y2bĈ|fffn"⧟~ z葹Y?˭5NFFFN:[DGG0}ʔ)(//g3lkkh:FRRӧO_cOl`` ))Y^^>HSOaG99>/ w;,^ ѹ[[[,,,|}}5ffddtIWW,ȝ߃o޼9sL |..aÆ-\Ç'dggkhhUVVRkjj:::ٳ[gnt:|ǒWVV#i4QNNNAAaȐ!틒&yfII3gtI݁~#b˖-Ǐ?ydII' PX Eo nnnnnn!!!~~~'OrqqYpv=SLħOzf.^.,,,55500 ή290 IDAT44VVVNNNK.USS:`kllnhh ?)--% ` #OMd2O~ }M-^g ;6 g۫?}6=hjjBdR*d/߽ RRRJKK 2N>|Q?~NJҒ&&&222_D nֶ֭ΝSTT:W?dddK򱔔F+//6 z#mmmmm={șMJKKŻoL7775kLҡojj^ttɓ5gW~cg/teggg 8sXjչsUTTRRRT-/wc"""_rss yxxȏ||| "?v_Ny+WLHHXzN477 矎/^5jѣ͛Ca10 ¿;$$ɓ'bbbӦMЩ^NN۶mxϟ?5jTO ЫDEE$&&Rkdeeiii,^CWh޼y٥eeeA\tiÆ 555vvv/_fQ__?|pkko7a2/_{Y~o-2 /555/_Y[[{7nՑt&gw$#G{WIIeĈ]t̙3Ϟ=MS@B"sqqYYY9;;Ϙ1[ZZ<}[o݅ȝY 5Aϟ?755*,,w֭[N0ɓ1=৥;wX,V@@Mxxx>Buȑ#Æ e@!2|7n<~XBBƆ]aoooiin}Gjkko߾mmmݓ*G:Hkmm?#""899gϞ`++^gЌ3˓~}kk};i$38ӦM: t Ś5kVbbWԫ L4>|6 , Mܹgՙŵ۷_paڴiOVRR:Q߶tw644&L`o }[||wvޝ1}t99y{lTzӧO~zii#G޼ycmm~gϞQ{ 4 aaaU[[S 8999999-Xl! BCCQ $11+Vt syyyݼy6ׯ_h+V:ױXe˖})$$Ue޽{Bu>0))b|S= [bbbZZښ5kX,ODDD|||_+&&VUUÓ{2e Aڵk?^TT}ѣG+**WGP0  ['x΄ ^|Iu //={x{{R*WWWGGp|}u_3fL`0\]]_~U@`nng//#G@;VWW!&&d2 աZcc#F[lYttqrU$?yà =]|lٲ(3337oNu  ZBgϞhV TTǁkkk[xݻ/]'zy@ovܹu͚5LJ,=GRRr͚5)744̘1#888::]_{֭iiirrrcǎuqqա===d $%%HMyy Xjՙ3gҖ,YiffF6Nf= @~KKTSS#;;;--mϟ>|NL& edd888L&0^^˗/;;;Ϟ={޽( m Nh"@GѼ<<<֬Yf͚u 닾oyyӧeddpnHmܹ3qD@4lذk׮ݹsG[[߿7C k dee :YzuccѣG-YYY>v횙Crss):"Àw}QJڞŏ=;vǍwyطWlG䤤qqrr[ L\\\>>>gϞ=~IZZՉ.^ӧ'OL>8ڵ!!!Ϟ=:@d27cccAA@foo5w%KL0!++D}^jj*A  ܽ{744 .ӧKJJ޽kjjz 555_~|څP ց&L0fPcܸqСC7n(--mggBua0;"4'++kر(D%-["**jdd~  hϞ=377_r[BBՉ9sfjj{}ܹsWVV:ϟ?۶m[ss3ա]]]-]..*^Q+B@hjjrvv&b? 8qOiiiLL̘1c~w===55[>{KnBdcbbCuޅ߿ߟ '''YYYXod2"! PSS3j("w=qDppWcc#աw͝;w̘1L&ӧ 777աy󦯯oddځ7o̞=BDDŋ}M7>}zѳgP}]}}F#yG@o߾>prrtf<3gJJJҖ-[ѣGKJJ }P -,,K 4><<۷;vHKKطo_NNU נA؅<<<(DUh4ڪUrss7mtСC۷\BbbfFF͛7㍍… srrv믿/h^|9o`HKK>|G͛}UVV.^X^^~Μ9~~~o<)&&V]]#IYZZR/_޼ykjjzI #F߿?33g20 AAAvG䖖III 888P;IJJ;vȑ#<9rӧ?}Du~"%%eƍfsNZZŋ98pJ( !!A.TUU7nw\0 =999ކ#FXjUNNNDDF: tBBBnnn999n}waÆQ䴱zN:UTTdkk+))9s̋/~ꀽZrr qFUUջw:tHCCtĈ#uTvd55@ׯ_DDDPJOO 6114ic߿zꢢݻwsrrRbRRR111...߿駟BCC/ǻw455[ZZ,,,߿Om[nEFF:ݻwڴiUJJ͛72P\\\qÇ/\0//8**jƌt:=//OUUuҥ۷o=Nݻw)))L&STTА8|~vIII^JJJhmm7n܄ ,,,+**wݻwݻWRRB }eRR˗/_zU]]a``0qĉ' P{466zyyyzzjjjPb222۷o?RRRϟr; 1ѣG'&&rqqeggP Frrrxx/B//q,--gΜn/͛7۷/^:Ȁb^xO?͞={ԨQoذa˗//++KHHxСC*++՝w}(Aӓ"ݬ,˫E~TSSCTWWgffgeegffX,!!#FUwZZZ(>,%t+ #///===bkaaa tMMMATUUd2t:A--- 644477wr"^^^~~o!Hh||| "BLL ~~~^^|=͛7Ww޲eˎ=:_N0AMM-???&&)00܎Bdf{=p:s̪U6luÇMfmmmllϺN€6bĈYfQe@KLL ~znn---ʪUjkk=z$**ojkkϝ;w޽=B iiiYYYd9W~~>UVV%%%yyy)))΋377UUU?~,///(((******,,,,,,**"+444444:]]]UUo*JJJRRR7233kkk 2dҐ())IIIȈpȶr/(( V ɏzzz=GP]]b NNϖ|S}0t:888DDD '**+ $$+,,, +**J1  J \~3 IDAT1,+ ݝŅDXjUzzxUr;h}'={B=zʕ+ybbb\\\֭[n]CCý{n߾۷OLLlĉVVVVVVC:)՝9s&A:ccccccOO;wϞ=bϙ3GQQ`2"""t:=##biii1zK=D&&&W__ˮ{nQQQEE{?؏a4mZ,X,VuuuMMM?Zڏ?hdA\\\rrrd5Y$K}LAAAAAښ +++??#---22#%%%%%%+++))),,L.!!XՓ]Fv"|r镕&Q@@\544&OW+YYǏ+**j---7 \utt^FFFZZ,ے0P466o\WVVsppH2dYA+&&֟bbb]u϶j*_qqq ))) OyyyYYY111ooo{{+Wjkkoٲe=s}W/QQQOQS }ϦM޿Oıc~y!**.DnOVVޞ`$''Ʈ[nŊC:uPg&֞3gA3ȥw߾}ʓ'O:u*777Ѯ#rmmmvvٛA~udCCywf Ǝ[WW}+0l`''';#jjj؟666{  (---(((,,,+++)))+++--Cii)ľ!Yp,##Cֿ%}Se6dW$E]^^SxQQu C QRR5@ĸq^zu]v]zٳ&M:T `|] ' .?;2^!ŁRg]u_ZGuRuu Z֊B 2 @3O a\?xs>9!HÇ_d qڣ)52M]ݻ׮]_UUU 2tСC2D__bA\.~e/pؿFF_uƍ_UKKw$I,khhL&ObBst:3BhL&WUU SRRRRR!:+(,,7JJJB!BL&YZZzyy ӣhD? YD/333KKKegTA m|1*| &,[/Ÿ禦***<O*|rTJ+T*%d:իW,L&gdd=/ˋ|GIҔG={,!!bGGGgggGGGGGG6 ϟӔ(Ewܹ~zDDH$277Od2WZbŊɓ'oٲ輠033ã1҈N@"H?r/^QRR2/=ɸlvAAjjjB)((3L7Li ׯ__ti}}Ν;.\'ȸpWWפ$P(u= }ɂ RiHHH/o!# FD"\\\\\\uuuч f;88XYYAO*(( ;s -ZhѢROOOPPnnn^^^yy9ȠٳG$IQQHH dddddd:\.wILL&xyy.((rׯBd2L&f<t &x{{K,BCCՍ^|xD>BDd3?xb4jԨ.Ν;O:ӅD)))陙ϟ?Gh4bp5x茌 2+[[[:#H~~~χȠ^xaii,[F`$zSSSRRRRRRrssb1F Q;;;[[ہpm3***ls81 ?Wu/B8###!!!33 OMa L&ĉg&: C 5j?*++;vvqq1B=zD"9qĚ5ktuu>G]pa4MAA/"2BpȐ!"ҥK&M":Q/X+ \..%C5t%KDEEeff8aذaÆ ۻw/BQRRGYYY-^ȑ#>>>7o|Ⅾ߄  !XL&S$ɖP燆 .ԔWVVFPY---cUUUiiibpذa#F6l"1Ik׮ ?~|hhANԕRRR\\\tttd`<8;wK$BF755555[T*f@70166&I^lDb9F^@_$H###޽8zC6̌t¸'Oܻw/--MYYٹIAHHΝ;;AtaffFRdKLLL Edjjj^|)矏?D!))-''ښ,& 322LS__ԴgRPP :2 @qq;}k.3zhu'O ƍgdd?]vo{lLrttP(0a7TiӦ]tI(+99ٹ#9 7oݻWQQadd/c``@t:]ii)GFF~D@}O>]dɶmۈ܅|}}rssDl&IHj$^RtڴieeeRtٲe}Ûy<A:JZZZZZZN0A˗6 h4fY,N'.>![n566?>A@W"b!$B555>/ۿRfk׮}۷Ǐ;$@))).\hsLNHH"2"""Ο?5@0jԨ 6lУ g͚5k,Pzzݻw_>uT:>qĩS;V~z'GGǘUV]xq߾}D xZ D"2^wsDrqq_BD<######%ղ^r~~w-H(d'''===PPPp̙#GPTD&%Icccss3o,?gΜ9s'OܺuݻX,=z4Χz[JR yd2999TtPbbÇ/]s@}}}s@<+W_ϟO544&Ol2WWWH$ٳǏwM:ܹs233#:W'YYYx<@zKR+WȺq}.".[ܜ#{ĉoXXXK}Ν;MLL̙CttEEE![555." L>|÷mPDbii_J"|嗞\.733VVV"H$J Dg b?xiӦ@s|7|Mqq Ν;6dȐӧ+((m:v،3׬Y~zFtfeeUSSEdQYY)ٳg!ܧ'"wFc%DEEE򃓯\R^^RTTbٲ2VRR".>3g΄8L&ˊB/TUUCt\;FUVV޿?&&&66_%...ޑ#G%\>LݻwǎܜRКP( ߵk'Nwޑ#G n}H0U~)<߉oO2ʊ:zhHH=MgtttFEtv5kϛ7oƍ}|xNpA׮]۴i):dԨQF***ںu… wڵwހsmT*uڵ_|Œ%KF_/D(+++$Ja"26OwE1hhh%[Ԕ ʑxe\M-,,Hg8BR555ُ./"cX,oϞ=Zf@ 5j|Ύo9sg}affFt"Z?B9sqׯ_b'|2 m믿-[WFҨ+bI+=NwѣGGJJJ666D' 3fLHHAPX\\,?8955#  vvvяmZp{83gכݼy!h"Dr/bbyJaÆ1kȑ=tѣGD?>cwCL_*9!Om;mj-VJGŗ9~x $D" 6퀀 555ww-[!ٳtss &ŽW#ڷo=<<.]$iӦXk׊D"٣\+$Em\"K.ussSQQa23f̈瓒N$]bΘ1#~,8Jş~H7qĔ%%#Fa0)wuuݼys/|+Ν;vvv W_/R9=ytDd=bllX,2eNt.0yd%%\z<8!͖ NfنD,Xì,A_q8fk׮544(++GFF#\駟O"EGG?|ѣG cȑÇwwwIɽYuu֭[ƍGt@ :t(J}*q󝜜仛K.=|0zӴJƍh7nnٶYl١C堠GX5>>FmIZ ׯ_cudkxD0;0 wwٳgڵ…  //ڵk˗/744LHHذaٳCBB믌|ߡ?wܺu렅܏)))ȸ_[[RQQt"!|W^effڵK]]#Fo^ZQQAlTAX,>}^x׶B/,,422ڷo_qq/˯pر55+W&'''%%XBUU֭[Ǐ.\}tѣG ơCRRR{{ook׮ե[N*nڴ[SN<|p={WTT$}._,6upxŋ?1/+WdAAA}Zɇe0=O]]ŋgϞ%: ލB,_P IDAT8;;;vʔ)D?666ӦMȚ?CwW'R(GGGu+V$$$\[[њ5kbq;yijjjhhܖ FGduumIGGg˖-K.%:H{455kjjL&^mggޔ*tttRRRdJNNvuucLk᳑dsyUTT"""ڼ^˵ܣl HDReWnS.kkk~xJJʒ%Kd Z駟8q#f⍷~5455_~!lRYYItqϞ=Æ upp :Ϯ^ _z 'wI&UUUI$??oD]I]]!H$G}$['gffGGG8pb0VVVl6PVV&`g&:^?(Zt:WT˅BabbblllLLӧCBBTː!C<== d2V{]BD|>bQ"6}&|/B!$lKeo㚚*JE)+++))!h4 BB3?H$~f0-˖ ZZZEQ&BFD"ߡ/H&NsNUUW^v6(#+ #( j*l9666''ʕ+W\qqq}0良\E@_Aw5}-((hFڵkWEX @ b?g~544Gl6͖_X]]aaaRBWlirrrΞ={q"H$S Wy\#R\.7666>>>&&رc"HSS7G8}̙Ct uRiuuuMM (AuuuCC@ <N |'kkk&}277yᮕ FC)))ӐT*>pbEEN C&?ߐ=wܺFri`Ɗ{o2NPQQQVVVWWWUUUVVVSSSSS*** `hjj{Ц'O~zy6lg5N7n Z-X,VLLLxxO?$[xY}zSNH{Z[[%'NxǎHJJqʕ+7nXJv:Cf͚5k֬IJJrss:mjXZZ$ !Bo@tt>ĉ~:Dvvv>>>:L&dC"(;;;!!!66ŋ۶mH$oxyy.$ ZWF0~6 _ NWVVEX555eeeUUUuuueeeVQQuaW{!,Jq):WWWFxMMMCCCCCA]]2Be7o3ֆiii .TRR裏&L0~x}}}|3glܸ1$$ؐ4iRbb޽{)~_~E~3g-B_~%B(,, wgΜ.YݻK.vqqqtth>^|)&xp2J511o';99 ''b?~CĉBlժU JO/_>y#!!h4<)֖w-Tgg紴4@566VUUUVVVUUUTT1 |[tRe=T|[#AQE6IVtuuutt!|?c̙RTAAD"I$'';;;TXX([dɒ#G T:nܸ qFGNx봮LlڴimV)R'N(p;vm[=-Zٽ .<~vbmVڳgO-ZXXv0DٳgڵkO> JJJ6mڴzjB.9xgϞ^O>@.D"ʕ+wMtn#oo/^tDjjjyL&@w/FJ,z˗/^xUiiiYY^!-ڢOebjkkq/*9xI7]]]}}}CCCCCC}}}###`tZeBT*U(jhh?N:uj޼y'TTT߿_CCcĉ;wRH*J;oeee!X,֬Y.\o+"#RRR~|EEE333ٳg;;;K$7nGEEI&\ŦޙۢXXVVC1qQF?dfgg|mۮ\˺:"rbbɓ'?zt0=>88̙3-ϕT*Rp1!!!v?z!CZd2#@BBGT >|xNNH$9r;wk-))͍@$W322JKKB4mРAdgg^r۟8qbܹDg=… ӦM>|' D$H$Nx#11Q PTY/yD'loq\jhh(,,,***...***))c\AH$x5eeeCCCCCC===ccc===###6-k'zJ˲rXWVRR200022_ 211177755522P(>w1cƼ^ "T*D_z~jVAAAG裏n߾M`v͛7/_|>>QMFP455T:Ba04MEEN+**jhhP( EEE:B BQWW'PNNNPP,X{nb@ HHHdgad2Y$uǾEdiR/uP(444LNN&:TwrIII...DgEuuuj2ohjjWlyϗ]`@s>ٳgƍ322:y$BH__sp@ONNNHHxYBBBvvX,VWWwssswwpwwviӦ W0PHҲϟqQQ񍪪*٠A:P^D")//oN.+++---**z˂L&2|V 444TWW744ں:@osذa{6lXjر6lpqqIOO?Μ9S__N:T}۷oϜ9sx@ hllǎ/jnnn'.7+))ijjm~΅xxsf@&JV^MPv5{l|V"H7n7޽;vX%ϟ2eJ蝠 =-$$dRJ>y7t_x1hР#F^BXVMOMM-//G)**ZYYWTTT/ C""">1cDEEM6M$]x!dll|>{dDࠤDtR"9;;7n׮]DC"0///777777'''///??WRH$&&&HBbxa>@#Ԇ\&ihhhhh\5 c\5Y%%%:`0TTTdr\\2((((((H?|ѢERtƍqqq֭Ѿ&){yyEGGwyTY*FFFܹ#Fرcwk՚߷댇7w zݺuǏ8tT*eXMMM!2jժ.ߩD"166.++-yⅲ2BPDu͉'H$X|ܹYf{hjjFDD8wj=8O`EW----,,)Cˋ-*(({.B̬}OIIIIIINNNNNNOOollP(...ήκD'9bXUU544j| DRPPpp7?. ћJnU@x<^QQQaaaaaaz&dmm}&T{ò>6R(555\דڼ{[oԴr** lvppiDŽGEEm޼9&&b-[lڴiZZZ]XD޽{wbbb]]O֯_OPoPDyq!yѣGZs;]g5""".]ZQQaee!oD"ӦMb1㓑p_| jƆf;::Nh 2Q$͟?ѣD'vBF]tiҤIDg}^sssII|5955Oа&XN!EYZZʮe:N$q8dY5!dll{xde"\~~U||'Y}LeeejjjZZZzz:>T__ֶnEGGab8pUW{7BaEEE:ĸof?Cu]Lk$D"hYf-^mLJJ:vXXXH$:u꧟~*W`W^=ݻwTٳ/^ܿ/A}֌?@@WsνpB4ަgϞݹsѣGA d``0i$QhhCcccmmmIIIQQQII p8d2uȑ>>>}Q׎L"2ZOOϒPMRO8g?TWWW333 % B155o'[ZZyedd8%11}ɒ%޶m[XXXvv6BƦ7h*))R#.888ܺuO>f0Dgj"(===)))===----- _OSOOёflItXTZTT@&-&&&Di 1LVWWځĄknnljj4o<}}<;wdqNx<ޭ[Ο?mD2v)S@_ZvV-֯_H$d~rr;(Jccc\r|==1cƌ3"WǏGEE]peB!p8QQQ|ɤIƍ!"2D2y;wB--Եj֎;k)77Wp-l6[6 !XYYYW޽{oߎ`L&OMMMLKK{5BHGGd6w[v:|pqq1AQnnӧO7))ASSwI0C"zəYYY2d,?zsoَt)&hd„ AAA~~~ ׯ/]tH$OOO!CP(. @$###޽ST;uI&b}K'mZse5@?bŊ#GwQTomm7f @]Wkk&Ӊm%;ⷭ@&W\{˫saD2}ŋ{xxtsXTVVvԩǏ?~˖-...]xqʔ)$I*ٳщz݌3{jrfffFF NMM#ݧd֭7n|ɓT@ "2!bq^^yyybL&[XXl;;;={dGGܹ 455EFFFFF>{D"988!CkI%$$IO>-..eXD $d67|<޽{_f0C着zɓ'O[n)((v~ӧOǷBBB٣zꠠ uuʋB[lINNp06$33Ǐ?r$d^VUU%:&oUWW\.!dkk;lذÇ>b)((jʚ=@7J'>>>..ѣGRTQQQ$ɏIVTTsuuKKK`nH+W|wׯ_z{]Ѝ\СCbߵkPQF9:::t Uvv6>uٳ[U]]ȲvrFF>x)_Mfph/˳ޱcw}keePQQAt@6Pp2228ihh@aɶL&ܼ'':::NJŋ/^XPP`hh~~~DF"$%%RrLLLSSW``g}RWWPWWjjj>A}}=W Դ;EEE:;=t:]CCCUUUYYYMMM]]Ntohhht555555:ï***p/9!!AAA DtR0@I҂Դ䂂DB=<Hzz֭[7n܈K nnnt:]YY耠Riaar+++BT*mllL&422$ϯ{EEEgϞ={l^^U``_|Nt`}Q~ /**5i_]]ݹs.^CX,J{o'D^H %XKEEE!uuuGGG3>opzX,.,,r';;;-----#̜qK1h0wIrk ΢| ٰǏGFFR(??':ZKSNp8ǎ>}zGEdMMM BP]]ٳgD"F`` DpAh[;;N/{Ozzznٲ￯սw7JШ&: _r\.7''jjjR;]vmDDDrro kEGGDDD03f̙3ÃPw^x100Pi=x`ppWDillr/3eeӧ7VVV0 555)))iiig|槢5dkhii1UUUsOmnnF&s3 F5w f؉'ZYY-_֖Dmhnn^v~ 6s}("@דJ3f̸|H$R=껥7w[n͙3ɓ'qmjjBikk3LX,KKKօ{%::gƍ[nmnnVTT|ȑ#uttx<AJ%%%^2,,,D!]]]kkk+++|Z[[v1n8yEEEmذɓ'+V4i"ѡ>Hwj,"ÇgΜ%SN͘1奄6/_<~xhhhuuYlbllLt(zZ;Q 333333 39D+//.w>D5^c/C]6mڴ &CO^pŋ:PDm۶͛7 ?^~th<ӳf XnΝ;Bt:ѣÆ c2JJJ耠'477佑_PPԔe߶A= =l׮]=zۇ FtwPD~:.B!B!$@'P(X,v~`Bm_SSӹs6o\[[aÆokϐŅ\.@lZZZWfff&&&FFFFFF}hP(,//ūW^|YZZZZZZ\\w:D200_ 9477^j|>?6Pkw^@@ٳCCCo,.]1cƲej[8tD"H72BHEEmǎ3fijj˖bY59??ի^BhAɷ{&ꀂaʆ־~Z~!h4mI$j?WSSRSSe7B/z*ryihh\rO?%:NUTT?x@SSO?ݶm[Rc~,Ś5kւ :x`H$o޼!ʪd2'N\nlgBCCcbbׯ_/~gߣغo߾ .3O^D" 4100yʽH$xŋ򒒒W^~陚ˏs?чo f|Zz_Z'M4eʔcǎu=`SN544\jVЕ?~{젠Ç#lmmg͚jg,p/9//b}?c;;;7 >wٳg=D"&[>#:WkĴfP2Ԛ]\\oܸ!;m9x+ݻ7jԨ6W"2t\OOfP%%%CoϞ=?x@/W_=z(++CƒeeeɎDGK~ץKΜ9̙3!C5eܹ"zD%%%ǏzɓqA`0d.SSSSSS}}}!99cժU!!!DgP۶m۴iѷ~_x1$$RX=z4((HMMms̑JgϞ=qD]]ݱc-Z]ඨ̙^t?g0۶m9raTT֭[333M6!Fzj777QXXk.oo$G-vdx}+WN2~1c{wU}f}Q\XJEQC1M%MR34K-m4of~+%}rISKpCD@gaf~܏wysϙsJs>纏B>e˖1ݳgϗ_~VٹsAA̙3g͚աC„u֝;wOؔy;|ի\HmTaaAt:]bb"bfi!*&^H$4 %HD"H$rppDRn`7[jZrFVU*F)***)))++S*4P(hΘ|vrrݩ鲳.hF,k2\6M$111.]uVmW1~3gܸqѱ"\.իWnnFquuMHHϰV~ٝNwm۶m񖮥'岲2BT*d&ذa|0r2h LbŊOi5Bs2}'NUFAC>̤96B5 %3.4CUTΝk$&&]vtdՋ-"}=w… ׬YüwY~= >ŋ3Ǐߺu+{&ǏGFF$$$B._vk׮eggӯ46m?%j;j֭{w{q5A׮]pPnjDFF<ӳƏ3UVvLW rהݿ?((hokhEJJJ}|J42DuCXrE"U"p8~p84B<&bJ^gU(P(ȳNSTۿH$brKiK$`G,>fc5fk=z4**j߾}Ft-uɓQFU `#G###A8I';v,99^8sέR@999r\,BL @IIIE"Qǎ;vXyLL9;;ݻ999bgggWWW///777Nvwwtuu=EӍ#[ZASȄzVU޲?.??>xD{*68WNk׮ϟ};v}umٲeu8uWjގY4ǫ1M :99Y5,k͵`N ɓ'oׯ)ݻ799yǎPXC8q5kVZgT>q`o)_~_tEM =SVVfgggZʕ+cbb,]HcJ!!!!!!e'3ϧg䠠 WWWWZZ* ̊Ý;w>}J[C! ^RR(Y[\.cVIKK;w\VV߰P(tuutqqd摝!S^zoٳO&%&&\#KOO?}-]Kӌ5T*d{^dyae2̙3L\7n9rd```px<{7mڴ݉ 2@Y~+Vx-[ODh4"Cqƍ]v޽diT(iiih-[h`2,00B囟ZDJN 2ʭ[\nPPPC\ xzzzzzVlQQM'?z('''777'''11.D"mLnnn......NNN7\O>˖-O,]K}=:11qڵ|>W^ٽ{_`ĉΝ۴i!um6 8qb;{GΙ3G.w-88X(fdd9sf֭׮]{饗ݻf͚ BvܹaÆZU^#Rvttk֮]+F[hOOl޼_ңYf{yyeffn۶V 32򁛮טJk5eK Śku dɒ?pȑofdddI8N:k.K ƌ3gΜ;wqR`SNa֬Y|+jΝ;׷oG[B;vÇ\jȦINNf<ӷk׎N {EFB_n:Dҿ?W\ tlX"..޽{.Xaaa^^ޣGh@977ѣGt$//i. (3NNN-V}ofz***ҥKFF32{B/ Cpذa4ݦ姏>hժUU^2 f.Y>cvUc]W*{PTegg3N>fna;w~[Wa_}Uq3zO;^ʇ01ᚦ^{-;;ĉ.Z)c5ncXs5Yƌ52YSN1c 9s( 3֖qF:Kspp%KzlP_ꫯj9D;::t#Gڵ}@zjXXXzzk ׯ_ѣ1j(K,)t&|ݒq2;`Ǝ gܸq)))999[Ο?_ ,\pɒ%gϞ tl;{nKR;O>-(((((xǏ?~f39;;;;;Ӽ3+;998::X4~m֬Y2o۷˩s}7qUs}ƍWNJJJOOD۷߿ɓv<}Ǐ=w1UW>t5G|^Fyyys=sD"1bDNRRREEE+Wܷo_NNNqq119O?:uÇ;vӧO~bbbLk*cvXz "pMVݸqG}wG5ǶmV3qo8jܯ_9s5J(N6OR9uT~4\yÆ ˗//,,d_G..\CCC=,vqͮ]޹sҵ_~977˖.E1q2ez5OWǂ IDAT봵={bbb!I;K/YFh6:w" j$55bX,ԩS5tr\.?}謷X,6&WW]@&>}zϞ=-ڵ[ll/-]4?[nIII;vضm!$66uAKT*oG>p@gZ3[[[[[[s!fBQQQQ}YRUTT(r\ѣj !ś7o#f27W^v `RR_#z$??DFGdիWqqqii˗=<<,]Ts尰0K݈# /^hBZ)q2;9##sssd&c7dsa\.H\ /Be2ٞ={ƌ8@swհc:) 9ӧOƌ( VW#4*mooooooggGWnȃd21 G&M6mԩNNN.aU۹E^rO>pCn ?PCӡO8?ݻN:uܹ L2iҤ۷?Y'@ 4hБ#G֮]oÆ V<;"|ɒ%꠼ŋvڻwonnnHHի'LЦMKy`0p{ RiCz>::?򲳳wss3!jTeAdS=Çj8p 00E5"C|}<҅'-_2M'ڵ'z+++BHQQ!b@ rURbbbnݐN3#ӦMZ hU.LJ*..ff0dW"8881e'J\X,BV(|Dr%?h*W oֶw޳fz#hmk !yDMj^ڵcǎ?~ܹsf;vhit:J*---++Mzܿ矣G65唔BڵkIlb=D$:^zjii)ٵkW~,]T 24gϞ=|ѣG-]L*3#ߧdNޱc D"___v:Ϗ76NR$ǫ[bbb=,]EF{-UZ%3eRT*rqqRr^jFVWs 쬬mllD"@ \. 7BhC΄o{#Нt?,0Z_bT*NW㕧G=zT te„ SN?ehhĉ/ܩS'KW`|!DP z⥢W*z}S1yR^O UTTrAPBZmyyyIIIsn̩6TJmR{3DX,^~Ks@PQQ1{UVߌ^z饽{Yf…;wnذ\;7QNN!ɉ= 2I.\WTTBV^m銚 )O""""##-]ԅP([d&]MZNlmmriVt:SNt!PkV}+WMzuDPt:RI/K :JIIIyy9^lL/0W\nz4Lf{&и3rfT`W\h 3#U%3U(de*ŰLx-ҥ tܹ%uJhJKK/_|ҥK.eee _|q޼y/RXXkDU5W ҡ'L)bNgћE" sFM 07SXt2zZʄ1݀9Vɕ4:o޼ &xyyB/_\ñM2e޽7o޼y3Ydg}fCҥKmڴdA P˗t 9D :}cΞ=kBlN'ܹ$8s4G 2!6RRRju-]XMn1C x\a"s ]^a.1z}zz:{rlT6WB>^RakXfEaۄfT3ȳkWN:U;" 'M4y޽{sܬ'O^rի?SyyD"䰰^xh:Kh ý{._|ʕK.ݼySնm6,,,66_ׯ_m 0grhXmS#, ك{58،1O>7|s(/Ν;+**u{߿}Ǐ=wi rhh "`ʕ+WdRk֬tE͏H$B,hŊ `'VE ȑ#NHII{nJJʽ{Ξ=G2ϯsyyy?bƆ (8!D$%" $~GV2mڴ^{݈kɓ'O&߸qwM֐dAAAAAAGMӧOG%''߸qC.v=""b…aaaF%?k kDo2ˬJҺo>ׅBayy۴ibcc+oVnCsh{TZO%%%Zl8ꫯ>c.pMzjKW,! tɓ'O;w҅x<_____aÆ1Fϟ?Ctԩ+VܹN{ѹs\.`k׮֪Bzuμ)JOoPZTTpڵG_tr-AT䤤۷o\mǎ'M4~={MyyyIIICM)T}̗sJa>K0>|ӣYU0`-[DݻwO2hci͛7Ϛ5 cű'ӹ-^xܹ.Zp#GXhBBBBCCyڵK.1:::ٓNԩSN<==-X-4Yd[lt!H !ӧOWh7zt7o޼uVRR͛7z@ h߾}T*S+shv%(((88cǎ;vرc9N######.lr4L ̓I]aZҢ"gggS6MNNСCC CXXXwmU۱c믿 ݋du=gΜ{҅@sѨ .ҵ@խ[Ç:thĈ+W2dرc˞={01B@ dg,> C6mV\9gK$""o߾ z Z|痢є$@xvuחFڴiӠ%4Gz>777#####9~ !k׎_CAAA2yw#?xѣG=qӧORСCG9dDҨo dz^T 6rb11k yX^ٳgvgZ_:ujRRRΝlAU8pɓB^8p={BHh,]F˖-2dRP=^틊!"L&Y[[wtK\NȷoNNN>-[j5!D*82N7KkLh E=,]4ӧO7΁lllzٳgO\.OMMer7o?vvv۷owv<<<|\Ç>|affGZ-!!!!jǎiZ41cƌ3t:?sŃN<`0oĈ#Glͮqh 6JҺV.[lGt9uP(.]:a„)d 2@e~WD"QEEEXX޽{1q=! qK.]z҅@S\.DNIIJ5t̖R4$$$$$$&&INNf۶m777JfZbb"ڵ B*+/ܹ/y<[===]]]]]]\\\<<<9@-h4Ǐ?z999yyyyyyYYY_8!ή]v۷j׮M任Nc6otÇXht:#{ "Wݝ=hRSS;w"חi@k׮?4k׮]ܹ33et ξr q2=|3M';99} ‚|q~~>5&bwww_A^^^mڴib񫯾ꫯt .8p?\zѣ_yxF aJRvfL wkbI4Ks\.W,g)a&S@07{{{E|}7]v=p@sy֭?tᡲ?3bj9BO@=! СC .^O;"gggBݻ׹swӂȕD@\.}6N>p@JJ ݹT*eZ&tB*˵k׺wn*<<<<<|r~~'Ob'''GGǶ,찲T*j7͉JO)d)((`Fi@@񮮮2O>......tijx}۷ի>|o߾]v3f 4[ZZJ轎լ^b e҅ʫ/y jOZmg޼y˩ÇK.:tA$''<j===Ϝ9hZ e˖5GvG䲲FEE]|5\%TΌhڬ,28K^^^4Lʁnnnf}]KWPRS4JC'ORRR Iaee%H$T*5Z`OqFy`feee B.+ |v;44rƽm۶|fѾ}y͛7/--mϞ={2eʼyF9v!CBK-V-..&2\aS.ÑH$[[[Pȴvqq%턫ܭ?NݻΝ;_y5kp8KWdcǾ+Vf3!$==}СNNN'OtvvtQ-@ j.Ba0LكH$aVׯwf+Z4ή855UT*` re@dr9mLϟ߲e #J4+MAfff^^^XX 0?PV0L;*3999 ~H$tZ,Z[[;88P5DS@ +\N) ZVU*UqqqiiiQQQQQQiiiqqJV"2(.HڶmD3"gffݻw/r6m^}Չ'ݻ\:]&[*~_5*SNNN" 9Mbܟ(@3?mݺ5m۶Slܸ?ZD iii%%%jȑ#^^^.Ep8&Iy:\.1BQVVFs t>>H4К|WWWWWWS6L(>\.WեJ4== ªTRѨ ޞ$# C0ɳyE13,3_EjtLb0GB魞L;:z};zܨ}ueR&Ap777kkk{{{{{{%ښ2ڴknٵk׮]6n>vؘ@L]hU_,tT*%Яg5n\ͪ?S0ĉ۶m3lذ8KWT5Nr+V,X"@k<`VVB_hZ. rSVVVVPPn\T*ُ*wBO3S!D&F$ѰH}21 Fyy9ϧ(݆iFLǑTUW-b-bqGX Z}4}q۶mJeRR7mT*##&55I'?;v젝E"/2900s7???|&rmڴiӦM^T*KKKlj~w%qu:]zz: Mgw͑ZEVg!4PBaѯ"ɉfL6M0#ֈ4&wwy͛7/11q;woBBB&N8a„&{5!0K1g;TsFo֢ȳ/xZ56UEW]\\,cΈV#-ې!CN:ꫯv UΞ2eʹs̞=۔jw0`@yyZ>yd-]T p(=y$//>Ǐr^^ /2hJ$ooofvف^`; ;Mۏ=}63HgP\.ё&]]]邋'tD.**yf׮]iCJ?_"(00\N[&8LR IDATp૯*TʴLe???dKHHٳh-F8va7'q[4 3 C ֣G=zYqqq˖-[hQttiӢpg#4&&K쪜fff9`f3&L30ºˋN|bf"f" UhۛJկ2aUtBpܹ6l/4ϵ3{)5777m_8@K.]tW^---BήO>~i,{IԩS .LJJ>:DV*--EEE'N0N-tD6JHedd0-k݇I4jq#Dlh rssssskhvLJ>҅vɸ,h6LOOfzY  4dp>&vvvHD5䆵5 {{{s\f&koo\.tilgg'8D"!1ӐQVVpg^t|:!/G};|~oA73g,_ɓ~_mAK*h#""h Y ;v_tQ-ѣmll,]Hs"SRRSRRiะ>$S===i']D[FC;[>|=עx<wϯcǎT7ݻw6lĉ !wǒ%K^{e˖YƆUQQN'v o׮JeSعsɓU*UCl޽[l9uꔳo9k,Ld-^W*P( \N M0 JvC^BQQQEE{XBJJJh$Y&,0`f39`Q>r ! ͜9s~嗒[~0r?fff~g?Ô)Sf̘TM6ݸq_~+V߿v 2:>>~~~;wܹ3X 4hoieeU\\񬭭7o޼rI&-]5ZRLMMeSRRh'XܡCv:sj+p'N$&&ZhRSS[7nݺutQ0_J2-M ̴ h_BX,rY@&tef9ȉ'"## _FS2ϛ7oNKK =z1c:vlp֭`f`0ԡi\.?x}>'N8s̮]v?l"@뒑ѯ_?RYVVr;v,߹s i*ʒ]vׯ߼y^J~~~9JJJRRR޽Kwܹ>pݻd24,,,,""bٲe;vw!y֭:uŋ-]cS!˙t!##JLd@g}t :uڼy VA֭tR=͛7~x&w /2̎2 }h/ae5V$.PMo`%pL3`fi,V`& dU d2k׮?ɓ'2o߾;ydn}]j-SSS WONNNBB™3gΞ=r 0f̘?ZAZBQVV=ڻwoK*p8҅XLQQѕ+W?s݊ HԽ{nݺcv]0ܹs͛tеkWKѣGpp0^zkڵ<'N Μ9~9sE,]cU^^N''%%ѫBӓN[ N'H֮];c K`8z7|{{{۱-@TU*I_/*k0 }X0yGdb#өVC_&KX 0a fÄezþ{31 POvЁp8ONj܉NxǏ;v^/<==9wޥGx񢽽}YYRξ{[>}J{饗8p@Ψ7GEFFrF9rrzO{7nt6mt-**֭?|~ǎ;vMGT*Ս7h(6m*//wppݻw>}\lmx<^LyxxA;;"NvBL&d#Gdr929==رcoߦR)2000^NII)..ٳ օDEEEEE~w˗/_l٬Yϟj%cweRe't w`.;[e,yUba}:>%H$rrrf͔D"7n?`4pcJ _xdz cҥr<%%ȑ#;wD"OO:DGGwҥ:"z' ?zqii)ۿ>},]T+pZI9==ɓ4|񂃃`޽{X@h۷o_j_~… .lڴ飏>={ ߿?i5r:.99`08::A{{{L*0#Z6++N>|zz:!D xyye2ʯkk`KT+Vlܸq6lx7{=___Ktt*2z .;d\. 6L·3&NيD",H8at9wӝX0[=X0sD:y{ƍ.]$''!O?2}WÆ x"}oeeuΝ睔NJJmYXD/''祗^zM!8p 22E.\.h⡖DV:uÇ>m1;iҤ޽{ .  44444t޼ys]xĉ֭p8}|_순rgϖW_2dȋ/H[4#^^^Ǐ?~>[dKTTԐ!CR'AW1DGF H'BrrrhdN޶m[FF%ƴLeooo&ϟ?Dɓ'O8q_~%pѢE 0!zRdV߭v=z2MvɿWqBB`g...fխo1s|>s]fwevvvMkNL_BX,_n}ٹ^/ÑH$|w`F^\\\PPѣG .\f !~2Kaa:t@oܸPׯ_=\,)))22RV;vG.5jIvp{9YTʴL ~~~ sLLLj/ F}y\-w ^Ґ_S4;2n˴ :y򤣣cPPWm/;KXj!/@Kb0m۶t҂E-\eccyfNשS'wc;5y.]2]|yȐ!m۶=uTΝ-]Tլ;"gffnڴi˖-r|۷ofRtƌ3f~Ǟ={Ι3gܸqLfiZ__L:hggWTT٭ J!!!!!!̈Vbϟ?N^^^tr``[=kt钋o=`^%%%̪Jtt(,jU,;K.(kfGu u-+++--}1vܖ-;X}gj/]ԩSyϞ=CV͛ ,8~ÿ+??m9dwcv j~0~۷o~D@Z . 2DkZWWӧO{{{[VvD/6mdoo?k֬ٳgX^DoPjS-ZhѢ˗/[n|ҥK'O$-+oݺE틊2L&EFF2q2N>-[hC8N}&/_ 5;’Ja\R)GK%6D_0CߑYhP(eVٝq !/F\v`;K6*5N9v&ӧɓ';xⰰÇݺut]OZZڲevtȑAkϷn0`B`K_~Ŕ$''ӵ9vبQ8V?bXdDn111`NNmL۶mȠ1777Jf>>>LȥKfΜHbFiWSFKQ (>[jة\77:,//22rqqq+Vի׻T8x={dzb_rrr;=~]]pw+f"@/s\V{Im WG3gL8d͚53gD묖UW_}p>`رÆ _ڶmkjG FDDj "zFJ4cR$P2 (wѣG/ʨ,RTK)..j6Q[R-U.Ԛrw^n }QT"p8T.!ŅgTK^TjK*u%K*5%%4#\.wҤIƍ۴i˷n駟K/@5 W^JnԩF'(OӅ߿_$U>D1bklllJJY.@˱wq Vwq'''K4_?bĈ4?'Lн{}EՂJ.\`ʸ\.Fh D"Q````` {P.Ӗ4|\J...… r< ͳUu%UEN-**`TTTИ>[APmVeG[-!DѨjARUιVB+OQcٹBk3Jx< Ь z+66v͚5oիW\ɞAk"hŊoV ?ܹjժ~3ݽ{[[pZm۶M2E(wȑ#Ei.-E?>l ,`;ZRyy{キmΝ;wEEE˖-ۿN{7?% 7no4a9eZ֎;&Oz}׮]9bq˚2eJAAC,]Hul2cƌ__t-U!י3gnڴxgʕwf6;tаa/1ɩS`=͛7ZUVTTԥK fdΜ9}y? *ДsWR333g`,~sK,د믿Z۩2n۬栦ӦFՎ7Ӊڵt9&𨨨?{l~rss]]]/^HG?;H@!FV-QmhGէjjkZZP82e"3̄DT|:9:!9s#ZBPĮaSw}a͚5?~1mLhWCCCKKKKKK;;;{{{KKK[[[BzvvmK%mK=D"m~ո55ΰSN [7l%Kr%XF\jF<#ڰ-yFn4}KkhI> tW\Y|ybb׬Y*Бm޼СCVVV~9ss8;;ϙ3gܸqyLL !zSFFk>۵k… ib C!@X|) yR[[ lٲZV]]]׭[׫W>쯿255ܹ˷nJ72d'|ҳgOcc㜜Cmڴi9 IDATW\ܧՊ+&Oo޼ɓ'_UgWn-7#knn^TT4o޼w֭۷o~6]u+S;vlD#cDҥ˲e˦NѣҥKÆ psޚOڜ#m{vqky333ݻ޽{ۛ%<+EJ &1PgMgEK[RqTUUD&Jj:DIOaly<^5a%Hǒgga3Zazq9 iwMzzz O7mMS*twa"vʕm۶?...+V1cFӕ۲e>z6""bs>|ٳ^$}eb 8x``{W[[t!ϴcǎ*//-7g;wzpgϞwܡܸqc۶myyyԝIII>mӑ-[\R$ݾ}zD"Q\\\mɿ6&M) Gimm̃m={k>m>qV^/}e˖ё .u҅>W "79Ҷ)$$wϞֳ}^FRM~ !eeeFJKKuuu]]]rssvre2٣G!ۦKj:=+ZJ̰5Lm4h9%=+ܒ͹gZ~zsҦO~@0 K#(J?~F#FFFݺustttttBյ-=7XMMͺun:`_~ݝ^={cƌYr+6(yi 4%nhh)Se2Y=uuubq]7G+Rp={5K.ݶm[y#`ܴiӔJeHHӅ<ӧO3]HSwYY* 7ESΝ{뭷0Z/}BG|~iii۪ \vM\&L_;w[yçNOOwuuUcmQQ)b4}8DnI{mVEE6B]Knɳ{R 1!d…l6{ڵsqvvذa!?677ogu}nIM~hRKm$xEw^reYYٳTVVE>|H_|> %vvvMnw~k׮m7@VRR`ʔ)˗/D ogff&$$4z6?>x𠥥wڵh",/_2dH+ @#f͚T*Y,ƍiVhh*T*_1LjԙE z#_~T*ǏqFCCBKKV+{111<8qDdd+WN8W_5a3+oƃ>vu BsKΫ<)obXw;v,ӵ4ET۷oРAׯW(֭=zɓb1 "/^ΎJmuH$z?|>GF]]]^^:uBB:iaa54ԣGׯϫV:~/tQ-)66v׮]ٳ-ZdkklI'O.h0a±c 駟Bn Eouj iIiiim۶ N:X_5'O$8::>wt駟;w널3gμzZs9{xi:zrEEGfEDDB.9n߾]\\)J !چbDa谢:8nܸ+W/O<)--}޽{"##,Y2bĈ.]k̙7o sD"y@Gf,Yrm++#F,] Suu޽{{ٿ={ȆK X,;v[r9>zĉ. CP>m̙3˭[zw۶cǏo?Bȑ#Gv۶mcٓ'O￿;BoO81..j{?Mnnnpp0!$33+?~|||m8ĉCCCor|Muvv={f̘annϥ#mRڵkz-O=@ȄGfru"zjE"愀r<77LcbbU*Ͷutt{\]]/]>9p. )?344TT{ѣu{RRÇBll,fQPP0f̘;wԛ`ze#FʪrÆ c(xѣG[ZZoLҔ]v}GǏ?~<ӵ4f4?7=VX> 6hnBן?]ԫ988$&&6]OeeWNNzd;wT˷n[X,S0wܽ{)`ӇxQry>H R9{'O&$$3]NST*ٳΝK €5kB ryɓ&M*..L?3335ɩs %Ӏ%@!-Z̝;wx#͛7ϣG>}t3gΜhBBB8pLJrT*cccO}0]ZSrrrB_|?k:###D_nڴ)==ޞbikkrNRR@ hgx! #G/_֭E J\.*eݺu} rwwߴiS`` m_|}ÇBfϞt]-]iaa_k.__[[[3]T%Rb+#Hn߾2]ѡ]Ǎ,++SLʊLJJH$>nL\]]_ҥKXXؖ-[֬Ys8;;3]thϟ?w܅ ?I&уҚe޼y|IܹsG",k^^!DRiqi[/ŋ&Ld 5""ҒŴ 2!d̘1)))_|EPP?cΝ;3]Z͒U(Ȟ#"""""4G|||^R?6~EEE7o޵k;p3n޼7i$B}H$<eggul6UB'Jt!-D"Hd=L'deeB8f:b|hUڟ}٨QfΜ)~שS2]t,UUU.]:wܹs222\իǍ׾=rHDDիWp&**߻w+''GGGNzW4D6m޽X,J?v옾>E kkA}UWWWVVJ$ʊZDRQQ!ryVV֢Ettt⋥Kbz~m\\}̘1mo6{;unݺ緯}y{{{{{RY[[Ad'&&ܼ#phO=X^^N'пɚdww/Gׯwwލ?Zd+WDFFFFF&$$T~~~ݻwoӸ/[>iz˗/#GYfѢE999;w.((\GGǚ5k֭[bJܹsw܉@X;ւfff?cIIJMhH$tZOJo߾Yf1]D&|ׯ֣ۗ,YBG:u ޱcGnF=jԨCb Zx#;wٳϟ&NZ ׳o߾I&O<% !J[ASFFFqq.rBP(j%%%aaaiii idu:퍙|#z+MuVtQdgg?ܹs.\prr9rax<յ$\>o<__ߙ3g>w娨(--'&&vܹk׮Y4D*J]]SN1]6D1bą ryl#G~~͛7O:uٟYGGgРA#G5jT@#Jh6l4i%յׯ]V=Bo$Pkkke2i\LLnϞ=.]>>>>>>L#2 !Ɔi@Y(/Ao*.cwwK޿ѣ}dh/^tҥKy<ްa6mtu?7gs)JǏ{zzTGz1Dfi%y<{tQtuut!GPY&--C9swӧO#""hاOvu0`@l X,~ׯ]vj//#G9ǧH.]zԩ )Z}}SN7ҥKÆ +**255R( mܹsSRR.Mѣ?|u'5SL! 47`_____>}f͚󫫫bab kkk>_VVZ~~>S0hG ŋ. ZJk#Aƺn޼9;;{ԩ%%%G9s^w|ӧOW*/_Zn]qqA>}9d pL/)##ڿJό3vtEqq޽{7mڤ@WWWKKvD622"bSSST`hbbbƍto8ÇGRi9RZZJrtKΝ+H=n߾ЧO'2] =:sLtttLLLVV}||;Tǁe˖Y[[gY9''|\.޽{s$HR:3xEAdhbccǏ/ [ddd.]. ZD"QT̖!JٳyYfXi՛6m6ݯ_'O6ɨD"H$Z|J]3lfffݺuٳalllܒ lr<555_񥥥l6[$1bݺu433c_~ӛ3g ӫ&AdBHmm#uBTZZa p\T*HNNʊڹsgEE]I(:::::: ///P?{ҥK'O_~W,%%%/^ ӊ:uرcgΜifBH```vvvuugJJT*e2L&С̘1CTTA:uSNL-bhhT2_~}iiG}pBuo!hkk?СC-:dX@ Bӧ򂂂;vBlH$4h-.B(..NIIILLK$6=z={ӇZD"=d``P#BH$m;W\! 2BB\P( eeeYYYIII4No74( B]]]x~yiii "r 4p@H?̛7oԩ#Gl&Ś8qիW<< 2QTk׮]n]O6mԡfhQFŽ*?XfͣG͛_XXXh4tK.-Yo}}5$/\eB/^9JÇSSSRRRRSS !^^^} ѣGGK,i>}mSbqyyJR(]%UQQQ/5ZϧSGd2٣G4tGBñNvpp@-hɒ%ӦM>|xhhhƨ~ztt+WnܸQWW'ƌ;d l+ϟp~or֭]v911 ++k׮4L/mP"@G$Ν{AJk׮y1]<ׯ__t[Oj}ɓ.\ZܹsIIIӫX,rϜ9J133swwwrrrppwpppppBLÒJ99J{ IDAT9999kzzzjjD"!V'Nvvv xjS ŷ~;uF#^EEEii)!DtpW^L0@͛cƌ񉈈prrb"xJ2>>>:::&&&::hРA}=(,,\`ٳGO?>!޽{.!$++ښRoLtD%77wȑ*,22޾ o$DnS2o]~Mhho OPmmmRRRBBݻw޽(8N]]]lB խh;ѣGT(yyyp|RI133ҥWKKˮ]q8 @sTWW=yWu(VWW&ν&L#sBjkk׬Y3w\[[FWPwD& BP q׮]S*נ.///))IP>ydaa!!DGGZNvttҥ C111&LӧӧtE,Rݐ!C4 =zCǏ=qF~~~yyzMsss333W ASStt2ɓ'tӧث ÇwڵK.666Eٳ/x  Dfr\*VFEEEcCCƍS&'''GFF&''B|>]_PvwwǫaXXɓN81|p+ƕGEEѶ555'00ё۟:s  ԯIIIJ+;;[RkLtJ758})ST*Ո#;9zxGGG=EEEŪU~C?~[n!wܡJ==={ B???HiddUU*[mmm ݻwkƛ^M&b~ *//H$555bV,rX\VVF}U !G3[XXt ^-[̛7~:"WTTxRDs]rM 1|H$\.L'ӋKKKJV,}}'ON>}̘1Gaj#h(??ƍ׮]uT*urr={+ciii-Zbȑ#_hhD2uTBHbbcxx8*++#ԛ@br@ٽ{EJJZp;0}GP]]Z/^7k֬RicҥK^z;V( WWצ:::uuu4|>SN-[mCzzzZA.4VK@mIIIff:z[YYNԉd###]]]G:::666fX\.f740RfR*bRYYIԨT*%J&>eeeV7|:---###>jwM3mjjjfffnnnbbҜCgÆ 555}YeuGdDJR&]շo^`Ӆ@[fi#dAT(>| rNNN4L^^^<ZΑ#GN](~/?H3g!޽{BPKK+++‚^Ѡs#26lذj*nÆ MMRUUE-r…AAA~mKx$۷i8))\[[U$\R$ ;f޻wo̘1۶mspphj_ͶlJƔkkk%I򪪪BB-N&UUUB5[D.bIch###KKˆjtq+$ȡܻwoӦM?Y눜_VVF# 2lСL BPsL䰰4:y"ܜ/mm={hii͘1C*Μ9^\.Js&=[B=7BR8MO(iRzң8ѡ[uuuȿӻMzzz500ԩޗ1n޼I{deeX,wwwoF$T[~}ZZZ||d=-i@^"p8trþ"}we"BH$b t(X\QQ7μTQQA.k|MӘii}7:U!D,+Jz8=BA=gTӉgmmmg``rzzzFFFFFF<OsX=Ύ㄄jݛ^Fƍ7nܾ}{n^tۻwM<~{Bo߾M\@XyyyFJKKcXΝd(hU-DVT}߁9s6W( M{Ba@@H$Dc©%K>;}}}zAn/ȑ#1117nx7͎bsAd իf xq8/Se+)))&&f߾}N ccc''' (vںuN`` zwd2ӧtɓ'uuu6ԩS#Mj r<|:`%4J3 j=?~899YҦ_i122255555533 ffft%Z@d2ݻwo+==Я_SׯG:JJJFp—<882m4BH~~ӧOwNʚ2eL&֮bD/Adxܹ3zRJէOpeȅ^ttO?ԜMJJJ㓒h8##NOOgϞ"( @ xyyx*ڂ+V̞=[$=we͎FFF?:t(AÓH$LϧDiӖ4T* !4lgg94ذaL&9sq.ڱ'O§OҾͽ貑]ikk|:5KP*bLX,9O&''cߚ=|>)[YYu҅~ 8 \2L} Ν;W^&Mׯ_~,,,.Q('88&qqq,gϞ**;;JRTTThn:q;#T*Շ~sN6A: "߽{wҤI$""Y)U*Urr2muLϹB,--iX( WWWBϟ/7n؜ uDDn߾-HeeeUo$u@ɓtmkkkJeOOO\m6k˖-SL9qѣ.ڴrss ?~\XXrͭiB݋WFqo0--fU*:\TTTXXH>~͛yyy~\.K.]v577ӀɵÒd߿sN\\ܝ;w޽+Jutt<==E"ќ9sၫ`ڸqkע_"ݻw{8GGG_[[R!<^Amb͚5k׮%hiiܹ?cmll^}?N1cF^>Zh޽{@ XrH$ /ڣ~-$$… YSNtȈN)K_D\beeխ[7 :::4ddȐ!ܤSNrfxqď>wrQ\K "dggs\ܼE n"@K***z뭷T*ͅ Aˍ^zON4ƍÆ 633+++rssc@.&̙L7SN!UUU,[[[BD"yuB J^3]Kb4j9(J322h䤤;w>|vrNNN4Lݻwz뭱clқJ&ܻw/111)))---''G.B\]]=<<&Mjkk(4Kse2ÇչӧOl6E(zyyyyy ڔĻw&$$ݻwO*r\///[L$yxxWۧP(fϞgϞɵk222J)))yaϞ= !nLnnn o,==C;|>銠HMbb{lǻF<"ӭ[nݺg0'TTTtrZZZXXr=ٽ{w/// (///!!ݻ4|T* ==={5o<$۩O>ڵk&&&/`SSS+++B "gddt-//b_e\|y„ ڌ3צ>SJeeeeJeJJ [iGGG'''Xlff"ϵ}_5<<\ І 2!dq8---R:JvŋNNNL|L&{LʑYYYcccCCr/ٳ7n܅ ^Bh#jkko߾}ڵk׮ݸqҹsgooC.^K(2])t<W^zRH$$u8><<ҥK}0`z¯(x*mmmWWWHpBP(0g{o߾;v;vw"J=G!B--- ! :}4!6h#Dq/BRiii .d"h+JJJJz*!!A}%==zxxcNJD"PX\\oaaqY$Om6rݶapikksy.]> p8/Sі4o߾jBkP(tss300`]255=uԀf͚uatE|?ׯd2ss}.^gϞ^^^@ۡ+D"zljqqq[n-**ٳgi.* 33}-^X$ӛ͛-Zbĉ_e?bX3ܣGٳgT65#Jjkk?N111T <}z…6B>Os|2^c ɡ----i:^KK#vѣcƌY~ի.WRRrňӣG~-^_~*ڑ]vu7nDGGO2aĈ#F6lث̱o0Tr߽{7''bmmݽ{iӦy{{{yy9;;c7Ǐ~m__ 6⮂=== DG&OL)...//֭!H!,KRхW|W 2QFBϝ;gfftQZz{7;!!駟D%KHD+___WWаk*==_~y=WzE"wd111RT,4ʪ[<5=* !:::4LʞߵkWPPә.T*߿9s|}}E".''''''S[[G3W*={1b߀\.3RSS酰geer@d///oooAM>8p/..>}СC{AmJOO222!NNNRR[[ %ݸqc*ۿEᇎgGjjj#֬Y3d@o6==8pUUU.] ݰaqM6zhlwUǎ3v]x IDAT 9L PT|ABBBTTԫw:r͝0aILLT*4.K[ WUUӵ e߿ܹ BKK맟~Z`A+Q(w\VVFy:[:k.>rJJȑ#BCC=1b@  ֩S'zڮZPF"wd/^6lU9|>}幹YYYrLLLvvJbٶP2 (;880;$[L<۸=՝?ѣ'OkobRٜz\npͦcllb ttt8mLB\zFK.ϛ7~#xӧO89B;U\\Do``űX.]8pN,7z)ʔ&?}7n@ D...< rAAaΟ?4 4GffѣN8KԩfNm̻ P(t!QɄ2ڈ¶nJo4憤ㅆ/o._GFFZYY-[,00֖M`kKԁbHzlrСڶS'!jɒ%K,yappݻ7oz0]L9NMM-))!p\WW׀www@t֝8qbҥׯ:ujpΝO<ׯJ||!CrFFƀ!--AdhgbqiiiIIIIII4UUU4VKS|>kmllx<1}H__b4^Ll#o 4,*:-bqIIINNnl7=[PP9vD)]c' nG "=zD&ӀrXXب&;nC <-֭[V:Ï92qD6Y7M[ 7өSV^taggjժ+W޽{FZ~}uTl[ImmmWWWPDfPf͚EvXSSsUVsΥuuu_t)6##c̙/^$t511Q}|xB&=}ɓ'O>o2.)) iiiՋ:;;5VNji]{ hWL4mZ/O G-,,̬,,,,,,LMM_Pԓ\?;'@=..iV먫:ZZnQ_=QѪAv@aHHyx|Q{1ܽ㢢"X2p%K444esUUUVVV, NX##v(gxbPPиq۱/DfXt:A+Cʻ d/>|81p1|c N'Z&㫌P@'7o^\\ܬYRRR>Ni!o޼Ylӧ}}}?~ܿyW8EEv\3De0DUWWxbӦM.it:~s;ydbׯe J$>2>h˖-xkQbbb$ӧM&umмyQĜ9sfծgΜg'D ?khhhiiikk;~x;;;33= Bb޺uKEEVէO<ٳg<%VPPjcv7j[JEEE%%%EEEeee8y, bd2JLI~ Z.L&Gqd1vYYYUUUaa'Olvuu5:%|2WVV&&&㼼aaaǏ?wZm۶kn۶/kjj"rssmllrrrVqqq$i. ZtKt\.BHYYȈlooߙF8qbذaW^o]NqŋO0ѣ]ckUUՒ%KbbbtnbTvlݬYΝx1Hq[neffgܹwjkkkkACǗ0 ''u6hj}TTzŌ kku9TVV߶mL߅d:ٱBZD"9|BB¿b ===,Yү_6dP~7np8!Cݻc%[*X,b:D9sQ~aÆʻ(6RiAAAzAihhXXXocnt 辊n޼IPk))) D2'''_|ljjZ[[zitDOcccaa!N≒@RPP`28khh(5Օ.`0 GG>+^~fl6(**}vII >Rfff&&&8ljjjjj{9998|\^^}"*** 6T:jԨws=zѣȶ0-[~9 ,8|0ϟ?~'OB^^^ijEEEEEEn݊iKo߾O'''O8ۣFBq\//bm<]+C,wܹ9V,]xܹsO>>'Nܰa1?))W!// !c3vAdt^nn.s\UUP(8iee5tP466622=C @PVV644444|\.7ƿ%%%iii7o,//D!%%%CCCccc kkk+++F2>˗/رcqCNzlĉSNݸqcHLTTTZTPPaϐ77ڊD"ݻ7˕JD:EEE;VU:N ’krVVVBBBAAT*URR211d"lnn@G۽{uD#|FmݺuРA߲eKJJJHHHpp0Bgʕ4Gppplll[*o-[Ď?XRRiӦرgCCC^rFR)Veg"ϟ`KK˚]v#"""BCCQPPaÆ֏l/gltm۶}w.䦲PFAASTsssKKKoogp/Xm7oޜ͛aÆiiiK._zeeeUQQ!Sy[Z8fXyojUUÜ 1]H$zlFN]RR"HB8᜺H^*feeV8ykkk㴄U) 6f+X}Z[[6cǎ޽{ĉ׮]0aׯĉy ,,,߿?tPyr^|)NfX JM'[[[kjjʻdiN:5{X1 ˖-z*Y޵|wܹb _( z{C=mDRT*NhT*ݯT*U-xl˖- _~w%ѣW^bʼn'Μ93`ѣG7n֭iХ;vlٲeo޼ʙ @R[[CD@bt:hʶQFeez>T*:t(}|l'Nl\jt:aXx! i]GGŋRSSttt1Z9dee3+**5ސT*|>ݻG Z}rڏnX "d2D•\m_xpǏˏ?رcm[ݣA߼y u]v-;;[ޅ@EEE%%%nr\)(( B{>|8}aBgϞoh4ÇYټy^`` Bhw] q\7xuFFƋ/222ӳI$ĉmlldé:99999r8>y$<<g]\\ۥ+jyyyRR>...D #((ɩ .++c2-(ݺukr_q渲OWTTxw܌3]Nwf]3NȦB"!_Z 7nH$Wn ;>!YEKNδr؈WWM6}>[*.нTUU⒒ׯ_0&G5j;6553l9ԔcdwwwBp8555{~BD"{B 3,"vGqvvYp!NRe*diiiò3ˉ||<(`nnliifD򸶶!d2===g͚ttkCCC9s̙2eʼyUUKJJ***^U^^N@ſuX]]])<******D򣍚ꈜ7dϟ?3eh4 LWxV lܸqǎC 455RTXŋiӦ<O,CX, w!MYY@7n1hp1`DdNSSSX nٲ寿2eJx <666pDžB!D"< ~)Qc.K d3|}hs+`#fUAAJ$ hmę6 vG5͛7o͚5 瑑+Vs_.]||ӧObɓ'B]77ǏϞ= X,Ν;---cǎ}R基Ŷ077OII\|9b2_+?ժUVZ?=Ѱ@~ڥyIOOw~<R>D mmm###WWױcݰ3fLuuuBwuwwwosrrB/_FijjԐH$ٳ D<}ӧϟ?OKK+((H$ѣ]\\ۥ_}}}}}}???P$|C IDATGϜ9SPP J)W߾}drUUՓ'Op8//O(*++;99OOb[OIIٹs'Lvڮ]Z,cddTPPpȑ3g~֢// x{{#^z5{4&L>XK1rQTTt̙]ޅ^ͲMLpO|RŦ&&&!6G~Ç;Wvv[]w љ1##c͚5!RUU555-((@v 2CwZ.N(%&&>}411bIR&BgMUx<^ff&%</8::ER]ug%FrɓݻGܿ ZQ[[bX,V~~>!]]]"jffu I"9?hhhVVVVVVݺ_]SSΝ;o| [[N. ++!ӻwo2|ڵuM2Ue۶m.))w!Wss˗/q:qWEEw޲dgggS/i֭ǫ-FGqU.ȋ'( KP455)w}p"444444p\#5kll\.??`,oeAwiӦ={|RGGG޵tQ'N3gDRRR ;v,n svv.,,$,\!rJF{no=ztddd[NeލLo۶Q TzM֭ٙ۾};V[JYWWXZZJJ("t$%%%>>>99999bbmm{xxx{{{zzB3g MMMgggqYmm~mѢEJGGq+*..c6RQQݻlC\ssshbD.-..JJJJ666666[.]ZVVfʌTUU|"B9r֭[g͚aÆίO8!B⢢ܜܷpXQQW,TsA}F+++5l/9c9& [d27{v98 v-WWW辬n:Y[[[GGGOOOOOO__~gz={K޵tQ.] 8, njS1bիWXUUՒ%K>|HRw<:rJaaagΜF͚5|(zşbjj:xࠠ Dy;88,_Ū>Zne*̊%Kh4__߱cZ[[X,ۺukDDׯqHAϟ111EEEVVV}ZYY`kkۧO} z={,110g 6662dСC ]chn=z4Bhŗ/_իKԴ4|Nvvv |T}}=-'CCC׷,,,ڕ̟9&&f̙񇁁*VVV!y]v8qb>'Y,JKKJJJ"h8̄PVV[֕eee6kVQQ͆7-Kfb1Bj1#c2L<N'3L&i```0 Kuڵkƍ ){?L *** x9B9w;BС,Xp#G޹sGe͚5KAA޽{VVV.սy󦢢ݜqEEEee%8TPPe0lc555 ו+Wf̘1nܸӧOw!۷oߵkWqqߟ~C4gΜhq„ 6mRQQQQQ/_󛛛7ZZZZuuuWp H$RTooK нPÇ>?,**zIbbbBBBxxH$2d d2[m;ƒ*#>}zС6>}VNNH$RQQqtttss>}W*;uuuwwwwwwbH$*((NKKKMM=ۥR %;991bbb?9R^`$?DAAAN+ l7n?C D"P\\jEEE}}=~D"1 ÝNNN τ)**Vǭ/&Hp_~]QQf߿_VV&;欮~WE_[nw-]ч577#|~ttT*%H߷oGӈ#֯_ꚑqܹpٳ]WfYԩS~ѣGt+Y]]ͮ~f񪫫qԸEΘ`✱ 'So߾+WN<ĉz@ 8p?oweGCMJJ"*Dyyy666 >>atʔ)!>wfGGG??????__~z~~>D277J˖-4h`</^^z1bծvvvt%%%++++++}P()O(8qb˖-<~[tiW`>](fbކ}=\]] OI&Ba+W ##+VVRPP=߻@CCN'WTT<{,BHYYqF***-ڵkך5k455]NGkhhhllmhhhhhr|>566644g|~cc#i}DQQQ,kiieeeΛ7oʕr z9ޓ'O-~-cǎ1 &R-ƕx#qXGG`8880L _BpG ޼ysGz\RYYpBD"Zjl}}}VVVpp0~XTT$lllp(X'd2D"tɷ \'P2hР5k֌9ihh97mll{;wvޭ2`#F?Nޕ~&zܹħOʻΐC>~򥢢WX1`4hРACPǏdOOOooooowPCHt-[k.ZC=:_VVT*%tԄ ޽{.|իWxLv B ãgd,-----}J Fy޽,255\~hѢ#G/A}}=)aS__x<^CC~b8je;RWWWSSҢP(4W^jjj4M]]B455مw)))I &OCmjj ۱cǡC/^aGt|>n̘1֭A~:SZZO:m۶yu-v'jkk|쳲^PT===///]]]?۷o79D}I&"?^]]-;hdjjX,Y,B @OOO$vAdϿsNDDĭ[8ѣ!CP( yǎ%%%wܹsW &L0O>AAA@ X~iz 111G[Κ5k}=@F&.]*))??}t˖- ,wuM:w!ׯ={ӔC7mE @&J233322233333d;R:D"h\6&&988|IC6XbL&H$T[G㏖m_C]]]DDą F?|p+.,**͛o@EE-|#ٻt:N$khhh4>JN4)((C˿|,Y/T744h>}DDD -[2o:uMbUUn$r/kjj\rG544888 >WCC k111QQQYYYjjj>>>'N ~wyDbwl1Z1r8~JJJN'A&i4L~wotPb1ŝ8~e455544uuuDOYoh4MMM<8IL<%;|TzzԩS+**?>~NۮT*uss|2sƍ6Mcf0Gs V\b  r9sXAYYYK Dh_>y'<<|ڰaCdddAAΝ;Ν+AJJJJOOwttw-;N#Bf?bHB'q>:K!r6o߾ٳg˻@б6o|ɂy9jkkcbbsss|}}&??>|J&q"y}KK TWW1CbmoH qu .++999$ֶo߾8HE,gddBL=+|ﷴCtǁ8P-^95uٗO>@Љ*++/_~ܹ%KtܝE%J===_N̴4iۉ97o>sL^^~ٳ>}Q(###2v711)**y/H$Gyyy8qbɲ>@GDׯq2d޽{Jޛj1WUUՒ%KbbbtnڎS7o((( ^xJMMŷ?J$^z!:_RR`0֭[׿6 xbFFuO:wٻwovvvuuڵkgI&]r!-- D"qvv pgR^d2yܸqƍ8tо}m6se˖988ȫl%%3gv2߶m۞={ :$֑ӷo .DMMmٲe?dT*xb n߾=j(yW ={,H\]]F1pN|SLAUVVFEEݻwÛ6mN8֩J܏CwHR",õD\|h,%"ΘlMEKOh+$v)tT*_7!(**ͤ555L&SHAB!6Zt]˗/WUUy1c:;w=zSTT'Xbbb߾},KEEɓ'!UUU===6(e+K'_D"\pa۶m˗/8p𩖅 }[DD;?&FljjRPP =ݻw*BOUUӧO8naa1uɓ'wAIcu2#ӧO766vc:>ԩ˗/ʼnbTڕ'/嗦;w\ti F㔰l\XMMMKKKSSBhhhPT?9;Ԝ9s)BHII'AVVV[vD&Kb޼ysvv5jԚ5k詤RiXXؚ5k FttNѣWo߾'{ "//͛7-:" UUU%^BBr |;"#)tx8 4}t%r:Ν;98#!{-ewFM4I*׫Yfժ RtҤIǏkxɓ---eJjp׮]ĜhlA6lX )J6ؿ?Bhm, Dr5GGG%%EUWWwv+**Bs͵ 6(((رC"Ȼ`ҥfff'OJ\.w<{lΜ9 tر?~wBpնL&sڴiUUUm,rԩL&nڵ+JRX|̙ YYYM6m˚?Z9͞2eիek}ݻﯩqbVvO|tm.oR oR422!_𡎈wcM:uĈͼzjѢE8v7mԕ]OrA.]oMn޽-j ?N8C",,,8P__/zDbff~zy>޽{w駟jdd{=jԨ˗>|ݻ999n Zؘ}ΝC-_|ԨQ{VTTĿWFFFÆ [`Ahh{***]/:IBBB߾}~DNNNƍY__z ٙ'OTUUmnn&8;;]V*Λ7źo>CCwyqpD"y{{?}T|;ixNNBN.jCrG+ģI۷o#߿SwܑJzzz|;w>t 'l'H$Rėpk":]T*d2L}}6 ڑH$ c2z [}v'l=zD"˻b'(**[nj-H$Rlll.\(9h=\.L-Z$bҥLLLd%vSuֵ.7lemӮF |EEE򮥭qqqrTc|ݻ'A t:}.m+**رȻv][|!L8!tn+:~xSS{辿Ș;w*p«WB:::-/ķܸqgdd]!b%%%wolzl% "ϟ󋊊\2pl;P}}ڸ-2̐!C ږmh[ vvv.M^*J_xgSSS~葼kѣG>T*UWW9CQko1 Hׇ[WU^^>w\EEEOO\yӽ%''#]hϞ=|Ν;,YOZbt:~ذafZjݻϝ;]>|>?'''66ٳw^rY fooOӉ=Fsqq_dɮ]\ЃUTT̛7OII.G*J ϟb <==[Xl񰰰!/Jmllp ػϰ&7JWiX. vAłeEʮ]Y MEQTDC>7B`Ν3̙s_z%""befbbsT*u֭ѱ𙴯/! p̘1!'O|yyst… BW\122ǍӦM311z*BhѢE F nH=do ]~=BڵkGE,ϟATTB9 4~w-[LYYyƍ_noo﬛t5?|a9c8===QQQ7rrr,cr~LoNNNI&mڴʕ+,X6>徳<{޽{ݝǍ7dqqq' jff|{;w.$$$))=zT^^πwN%%%ٓL455($$tFKxx8BF340x`ÇӉ~ /H$˗ әw9sϏ~B&Lxunnɓ'Yt9&2j|ͥ;.#W\A"pӧB8իUV}۷oAH=AxBD"FFFO>={ Cmooonnx%1^B&,X@${c{:88=zj\[KNNn͚5/_d/dɒiӦ%Bx" 3Z> tO~F ̙3+++7܁}gy\>gnv[o=jkkB?w ][p^A[5NMMkw[Q:"../qVWW|vvZ 6e({{{.squֱY}X3339W^q9플-ܹϓ'OpZ---<<<>>S߰a9`Ln^.wޙcfa7חQ(u ޻w߱cw 0PԢ>^tg϶566VRRb@VVjƌ/^~ݻO8qgϞťA>{,00ŋ>>>w^~ŋg̘aeee``\!$ dbbbkk;{k8pˡ EEE =|;ÔIJJ߿87o -jjjt:1[2B!e>e\狾~ MMMUTTX3iwww.uuuL?~r!qe_dҙ," /_0333CBB#nBaݻ NVWWGnBiii1&=zT@@ˋh޽#녅D"!$..`@eEΰrn5jB˗/uuu4=K^JDƾ}3yd|6-캌oyb\5v;zO~]iU__O v'OT*@ ܿNd|sss#EazM$MN N>rө?BHRRrӦM>}JLLܸqBŋltȑB̹s璒ܹcll:x cccRXXА#lllQlWQQ9~xNNNtt4[xĉÁ7秪i$7+**"V^ӧXYYuyyuv{_֭9z _g)Fʾ|ի?ׯ?aôt, dlllaaagggool2ww۷8pرc/^vcÇ233hMMMuuuQQQVVVff&-:::"""<<<00xc8}vwwe˖O8xРArrr222ZZZÆ ?8>xիW_|)//[+V HһwﮪwD6lؠ~WǕXrUVVfnY|Itz\\>(N{O_j_d2\__?//߱Ϥ6oެ2|rt:F3sR|͛?}D͛bJ,--B2lЫD"o3>3s"2H$ӦMvÇxy./̳ll IDATfgq~Ď?[Kd]efff!fl;{ȡB1p.7Y>$} *>>!gF$BC ) ؕ+WĚ311Yr%.OᬭB[la^kƍ)'3<7sB( ŋgqqq...zzzv999.#y7[ϵp1oɓܜFr3BhҤI6mrJAA 5#rn8g15660OK__֭{555III_ u떿c۷}UV-Yeʔ)Ǐ044ՕeGƙ  :ԂkCp<;!H$YYY]]]CCC O2m۷o߷o߱co߾\==((hĉ!'NS !!K.?ecch"9so-cǎݰaN{gB---{o_Dw5O}ҤI111x/ `Ŋϟg)c&zzz<34/d񶾾J"Z[[Bݻq^Fkmm;wn@@t9̚5ÇVڲe BݻgΜb^WSS۾}Y߿رC޽s<޳g---ݭ555/^xi #w8;99=zرcEEE=7rޝA.\PIIv8~]i:zh 0~҅m۶צMBBBBT*Wٳ>}@}B!Hn7[wd29==A.XӧOfffܜȡNfRSSt"Lohhx왽=Fcw2rQ]nѿBAA@R}߾}300lǁIIIo߾e1cƕ+W9an^΢97.?tל . zɓ'_~w V[[KZZZB555斖ƗZ }Ʒ[0XJJ 笈#ddd(DB"DDD:, w\z5??sƌN&$$Zd7Xk^jծ]C:.##zjcǎt==+VYfРA'3&66;b]~VgϞ}mll,d!e˖eeemٲeɃN'x_,pMǏ㧸Yygeذa;h4"Ԥ}] `C}Ç/]t%ܲsNooo3fذaÆ C]]]\ 6\z577ǟÇ˗dɒ|ӦMW^-,,\~B+W|2֭}ݙ>}[x(A4mժUAAA t:Ν;K.enf ""BR߃BDDD0ʥ@g ?naBF{666"K=)!!QVVӐ"#!/ vxXp|Ĉ111AAA/^xUPP}z 9'~~c;~ԩSYYY}IBP>}zʕO/\pٲe&&&/_޹s,Yϟ#LX\\?zhFKQQQ]]ͼ<116ee|Pkk+'w&dC"2@rrrv޽sN<' >e # 0aP(-saYWU!$))?f +L Hdddpiii<0BQEHH&e.ӂ $$$B$ σyiP(:::k֬quuUWWtsskiiYlYwwrppyfddӦM:L={,D 0aٳg*));OOϗ/_JKKD䄄WFEE[YYظ|߿􌎎qtt|fw{wȑ#$)((غӾaaaa=w8]+,,ddNDnmmww ~ԩS+WE cXX͛qKpp0s##7oܼy,ڵk!cco,"""  t/cf+Wt+naaaxBBBmmm =BA__֭[nMLL477i<x2 O8~~Є'O;~lܸq222O>]ncJ |QCCԩS߿/e---V>}O<3f **{QF1ZRSSB8977H$+++i3\LHDTTTv@w [[[ڪ8WXX811̙3|ۥd&$$DR\]]WZeaax?]būWN>-''c}[9" IQQΝ;onn޳y\EE%00!ĘĀ8?do;Ǐ?~xg:FQW{ڧ{ͭӧ666k󏑑KtiiiƔBBB ٳ39::>~gɦ}Ǐ}}}DY߿io޼x"Bh…7n`WWo#""bڵ555Æ ͍HLL{'lق{.g.# 111WHHѣGNb飡sҥ ;diinmm PVVV+5ɀ<9޽{xy?߱cBBB&Mz $"ﴶFEE=zUUUVVVG3g_Q㯿mllw TUUݸqERR@ XXX=z477qu[ff&D:tP#>}nmmzj斕+WN8Nh4 ))ӧO WQ_{iA4lbbbfA๣G3ȸq㤥?~ɫؾӹspfe/^̹eCCCSSSiigΜ9|kkw@g>'#bmm+[:}|O+**?/%%u \B_3fLbb)sUrrr"""Bڵd2O1^1dȐw„ o-Y$::z͚5GexXTTTxzz|RZZѣx:SŋoݺWѢEV\fRROJJJvv/^}!CqÇ7440r?^z5***//O__ŅkU=5޽򊋋۽{ݻ\j@(//WUU >}:ct*!!!44IMM:u/SNKJJ•X8p[[[/]xbFٳgKKKUUUoܸk׮Z>}vK Ё7nZkl9;;755lvIIiӦ{;y͛nnn8qmd2L&777466@ 6nܸl2ի'N|aΝ`Ӵ4??ׯKJJn۶]LLAuêU޿>m֭[_z;РAjkk ]N.++,@h?s4mӦMk֬QTTwPrwwp”)S  ޿gȑgϞ3f #8lllΜ9@F>888$$˗/rrrSNwtÇf͊4iRLMMϝ;/dff<(++Hx7V}PMM,d!~t:FUUg#dff677WTT;} h*)) &}`I& 5n]ȶ?Nk{ZSS뛙uŋH$~ w9rSҌBpB8PN6m~uֹ>}?vuu]|y_;<4y]vܹsƍK;.577?|… o޼sΜ9s䓷&N:NOMM_|]UU5x`GGsY[[+߯aƍ, 9''ʜ\\\L&MLLBYYY---!eeF999{ seee"""~|eeD$SSSxX/ ܇@ =zt!__?xJJJsuuu!---n niiyyykȑVVVgΜw duuuqqqqqq>}*..0a„ LMMcRVVVnMQRR~322 ;fF5k_|_SSSEDD,,,?~,..Xe|nؒ%K~5СӓQ={4z !dkk p 6arrrrrr@@@\\uT7|`uVee heeT۷/]$$$$++1|.5jԨQ>\^^euuuc/#FJx_}mlllccçMgmm-""=wٳ%M9Y9;;DeYYY9&q .u > ,``P(0Dv|ҥ/^DFFzyy555vvvw^DP?֪O4iڵvvv=|ǿxQ0h_ťh41cxzzXZZT8פ${bbb,,,SSS񳲲I$""..aND^lnY~˗WXX ,X^ݻwBϟwuuƍlll2tPlK, /^yk 7nܸqFTXX4{lޅ{ >~x~~axx8c"!!!MM/^vkX%%%WWWWWWVVV5jرcǎIofhhhmmpI&q7sZZZ]$ɕ,򴴴ꚚjAVVFKKk, Z[[a'**~̙0`SSSSS-[K.+((f޽{*--=~ÇO4 󗭭Sf=6338qΝ;ǎ=cǎ0ذcaa!$$ܘ2w\P}}}yy!CB3*MD& ^yԩS^tR~ҟv*ګ8Xsss[[lN+))UVVfddB¼h'11H$ZXXXYY7JQQaLfr+V1qrkk+󊒒 r%D"k@_۷ ihhHHHxw޿K  G5zQF6еO>ǿ{\HHhᖖF422555!!!ӧOw, IDAT3j%==Nkhh3fرcƌ177SSSCCP|9C G]nS3f8z(TUU0aBRRyMM͸q w9w{ijj0Zb˗y\AFOvB&'LpYT*u޽? hhxxٳg~ZYY9dGG]DDĚ=Ξ=&&&ɸ F>|/_̙s=|S??!CܹٙTyygTT#GMsJ555̫{ &&ӞIIIݛ3gO``ܹsG'L.mt:}̙;;P>|MAAҰa F{nLLLlllzzȑ#===njyd|8!.7 4h:jժBF#dddX #BBBvܙ~F~ff >|ǁ@066666^jB(66'^p!`jjjfffjjjbbb``krr2KIIiii!ӦM3775j~dddddd jiiٰaÆ {666VWWggg?***k֬4hPuuo) +ve({wt:=$$dٲe3e̢S^^?{4hСC flllhh(,,߰555_|KJJ YQQA  biik׮QF§IVV0..Nggg'&&~)))ӧO!yyySSӧ:th̘1PdŎ;d˗9d!#YګҎ?ܘ211Aeee TUU+++k !r,oDdN[ZZr~ކ O|Νd<7;wyww .0~Ǐqqql/_˭[-Zy/_Ο?wH$JHH5k֓'OM'eeeiii,9CW^!Wy!"""fΜI o%K6o|MD7n XVΎ?|0BC^}=Ғ㨨(͛Ϝ9萑q鿜d#?x`nT*֭[?wΥ"8a„V)>>+&&100ؘúu6lze xK]]}ٳgq-mNXXةSttt  ARSSӿ~VYY>|QVZeff6tП9Cuuu6lYqqqqqqeeeǍzjaaK.P(̥YSRRWdQf!##üy3III|O<9y$B711:tWdff~-55gH 6ty 6DJJ5ݻw>5%%1brrr]]@533[z#LMM555i?x@UUs`SSS---X/ܘRWW/))TTTLOOG577#HD_:yf~cAAA~~~fͺsrrvxȑ%K#>|xÇ7NUU522СCǏ٢oSVV>s̢E7o<B.\`cl@@̙SPPo߾/_|p)|СCF?…i4|2?/_~J :tΜ9ÇT]]ry999qIӃW\TLLKYTFq,--:R_"2˵B9$HD肠׻weeexyyᢀ7nP(۷o bND޸q;qSN-]̛7OQQqҤI{f .{Ǐ/_Lsrr,Xym۶i&ή]^|)A1 T-[>FGG#K$Bnܺu̷*))M6-44޽{{9::***v8TaaaJJѣ;iڵk!WWמRyB3JII ֏=ʺv횉ɔ)SgLv:򹹹-=zt7w[hQkkׯMMMy2fbb"FINP(~~~w 222vauFTTtĈ#F`ht\6;;իW8'RDDD[[*""¯U[[[TTTRR9)))IHշ[v-R@@@NNγgϺ)%%U[[!{A^^ץ̲\SSWr}}=ˆ:LY,M eH$̝;dLv5H$|p֬Ycǎ}-Bhرqqq>dIiiiqqqGcUEEL&'''6=a?{> '))ljW1cƕ+WpV1q[[['TCmmm_ƫq(ʕ+SRR6o޼{nqqq~GK텅̥pb_OUUUuuuEEE.Oill,)))))),,,--e(+**8%wKssegEYXX#Tj@RYҔ9d0dե9Zf~8`: 'OX7(JNNNfffFFFVVVvvv^^^^^ 8GYUUfLkk+#!|M***ZZZZZZjo䠷Q())+W,\߱+( .mǏ---, dBmmm---rrruuu-!ܹyfCC۷oٲ__ ۼy3^FqN333Xtlw׮]+..ޱcݻ}||g@vaaa6m-aaaܬ؃}ĉ,ya޽;wuֵmܸ)aa;w͛8 z Kg?o\xqςLOOG+Wwߺu֭[͟>}3׹G.]Bkkkws_-[=|P__455,=W"##WXQ___š>܌2'ŖJ8,CH$$###**s4yL&455677֒ZxH2F 1'3y?fK}||V^Mii麺*))ꖖK\WWǡrYYYzz:N-IIFpB3/zw;jgg7` <mĈ, (??Ǚ_VV|Z())NVTTg^AQ]]]YYY'jkk+,$$2h kkk%%%uuuuuuMMM.OOeȑ߿wt[aaao߾eeeQ(ŋ b```ddԯS:gggssGr?77*QTTd)L p"r~~>.&##SUUF24U.n3g+Wvرt?[D"q֬Y?}4Bh̙{xxDDD]͍HLLf/^8wСC.\HP}}}Ǐϓ}}}=zt)nV>rvΝ;ߴiJݲe nnnΝuNDvssc{v~AAs , ,)g&Ө믿z. º>KPpgYYYII lbbbUUN1Si#SQ#noƽLXMMMs82~njs5 %ss}Lxlc|BLL _9s"Imذ _7{؟z1cX.%''fBy[[`II BHAA%"ebhcǎ_۶m[re?κ3uuuB܁tqϞ=G47RSS3lذP<۷]]]ddd8l=~>̍Cŧʕ+/_L")e<Ǭ,===99J <0 \`A{{ѣGwU YVg{ SNyyy?~è8Piv:y$B?`9]7G۷oP JYYy+V%&M|^Dg.\XQQqYfh4N hnnniiᐼK&[[[9F K#d# {b4ΥfOfήMΝ[z5,_xȐ!^JJJ:5558#Â⌧ؿNI7t̨W^]^^.--ͧ6ʜEZQQ܂"ɌYYYO v5~&`W__PWWԄO?#Ϙ% O\Wb0q0>XZZ;S%%%999mmm!aaA 2g.\oqFSTTܵkc klAAA!]v7.##CLL3goJ+**+8p`޽߷=zN[[l۶СC+WtwwGΞ=K"BCC'Lp.:th>>>)))"""Ǐ_x17[(**Y߻K~gtt˗555\[!7˚<3s̡鮮;vhkk۳g%Klݺ/0ÇK.EGGIIIijj:::zxx˳ ~5/to޼ibbi&4%$$\z5***//O__?{T\\7G,"::ܹs=8{ҥK{os555T*eӧOڵk\O@@@NN%~Z[[TjOVI,n1BHRR3R~֭9rʕ+_KJJׯ8'deeRCCCg)8ᩨ˗/ 斖)#NU1IIIYYY))) ]v71礲,deeeS>|.'..N"[4D`̼!,,,..+?< ~?b< v{{{]]g ?qbT!f[S;EDD9\FFА9Ŝ%~1113$"g̼p&:::666x"555>:۷,>9rjY՗ T\\L TUUt:@`)!$$] .\9sڵkmmm#qSu IDATxqwwp”)Sy8%K_~ԩM6}P!8򍍍o>w\rrѣ7m>p޺ukٲe ӧSNauuunnnGٲe |#̝;w-ZoaaZ455%$$l޼y}rJR,RPݡ@ [N8YZZJDqvYTW +s8-z&g#a0 Qk8[WI`|$&VVV"RT*f  & Іg'cffIRnj?1Lw#*t*ݺu{왱{ګB"dLOfffNNNNNN}qvvƁcggV{ZNEE---רjtgΜ?~|bbb7wPRRV%j|rgϞhB077!DhS! МtܹsΝ?8p`֭֭9rٳ}](ⱃ(++N9y}||֯_?k,rK.}wkW|X]]MBzBƭ Adѷo߾}gϞߧO`0Ǝ;eʔQF1jݾ}ᅬS>>>cƌ/ԴmUK$ӧO:u<oҤI߿Q:si 2~իWkt-[_~ժU۶m;pӧ/kkƞrNNN_YYիWm-xķ Df,??|I`02al6‚YXXU]l6 2䯿5jԨQ.]DTM?##SaaaAAA^^^AAX,_bqaa!0!daa2h "p ;>ٳ7omԉΝ!!!?{IJjf# P(H$򱅅ެ}Pb23gΜ9sfaaٳgO--ҥK/^>7nڵkn8ё#Gƌcaa/T^^>k֬W?~|ԩ-r.55uŊk֬iڻ=WJDnܸh&Md JZgi4ZʸBPTjZ*rVJaSfffl6WifVkL?oo߾111Ç6l) tڵ&99O> h)RH"q^^^aaannT*%X,GGGP8p@bY 8::5@_o~ĉ63gΌ;|۷9NϞ='$$t /|spYZ</''|.TDg^ti֬Yw}wر#ͩ+W.]bmm=zիWꙙw=|KPaa322/c~#UUU ,ظqcZrx!DLکs Bcw m BL&# RT8BP^zvx<߭[BCC#:777:AdKIRDRTTTXX_TTTPPPPPwո }@@{ggg2!>䓯jʔ)=777k׮uoLL̀ ヒ_xb*++9Rx2 !|>_TDDVXb BqK.m޼y^^^#G9rdpp04v7$HׯGEEd2ooǿǏ=z޼?+///$$"::ϯ^aX">>ѣGt:i- #BjVUYYyu#r`}㘲\.'2zryqq11 [377ǹ7Vbf0xd=޽{C39BRt钚j:HBPPPPXXKK:nkkkgggkkhcc#lllBac'%@ϣG&N8qĦ=9< 5ܥbbb>8OOTDNKKҥKAA'OHXZZ"2@grN:uTV;wd2ׯ_4E*i$SSS\Pn6lݻw}@?߿ʕ+Mn7\IMMMuhB1qDcwa2L&Ʀ'VWW+ʆTb...UkT*ᘛ3 .K,X,&`0|>`2dt`w9ro޼aЦyzzBXJeaaakz1ZXXXTTTZZJ`0lllmmmE";ckk+BP(xFv@ٴgΜ7n\y/^H` ݻՔ///X+;wPqè=TDh4ڨQF͍駟֮]d2?dȐ@10:W^DGG߸q? sss{Nt… gϞEq8.]xxxt҅XF?o>B5K<<$$ڵk0lW\}#FM;8lmmRhΞ=۫W/HdtT*7Rh48hT*V5 1ㅢ"FP(BEEE-t r ܜXp8 gf2xA<`1c 4ƍD$!7nMEEE 9mkJemm-===q1cAӦYxbȑ=z8}tB~i{cbb,,,飷=!!{O|HPz! SRR|}}?1c۷/<RKJJ3gٳWZM1L|V= wmp8m&tr/h4Lhˉ\hrrr F)--U*uʳlѨ4h4lf2M.<ƍ'N{`\]]ye333D677(((@UD6l5A^hB(333***&&!GG~N,?zÇ>|Z633ѣlpB w^h裏>|믿Xwn޼8d,2^KLL|ɓ'tyɓ'{Bvvv0`@޽`0e:߽{ɓ'{7o^޽ ##ݺuv?ڵkŊ;wܷo\|={v1tP>߻w޽{[ q@t2({yy5f---NJl177dɃv4~ڵ+. ,mt:Avܹs]v0vG@{bfffffi沲 RjryUUN-LsUU\.jJ2778WtVTZuq^q=fcewf*pLLL ;x 嚘|3nm~Vb.^8cƌ˗/<=DB ,_k]ջbx<#$2h/$t[lllNAA۷;V`UUUk֬)))!HTRRbkk+"t++|a:"F`8p BHӥb=xbee%ڵnݺvڭ[7d@tϞ={YBB³gҪ---vjffVQQ:yd__[sww_6n/O;vXbœ'O~K$Cر_VK'WUUEDD̛7<*"gdd >\$]t Rxehh˗}AtgϞX,if2LjqUr\V(555Jv9fBQPP<|3nN63Bx!TqgnF㉬oԩS+V1bĉ'&L`жB:dcv:N.+ RN`Jkx<⫋K6_ @gSPP0rHNwI!#=jaa1nܸZĘmOII ؔt:J%^xA,--qF׸ ЎQ(OOOOOϙ3g"***pɓJ!еkݻ〲/<^zs##!7GӉc2ܨs=4OP(;vp8sέ7o{@" 3UUU*J.+Jk8:L^U(8vLWd28LppLJH 01իo,111>>~뀘EnqO"STZJP(ƽAduرx211&/xFD^^^NNN FI{-55-,,Dh4OOO?߿k׮jHܸqg}6lذ3gN0fz_|bŊ;wn۶d~۶mAAA'O޳g[~ I'gdd={wٙH'WVVj4FOվۏ3t?3ft:^VVRP=>qĒ%K3]BA&iFg)8 MRRRSҟfffY2N-S(^ׇFYXXh4"`0L&8Ӥfg ڳ7X ˗;...?6v/`*L&qYYYii\. Z.++#' ˎ"| stt$r8 6Mr8&66v}=w\O88xZfdd#HII:tD",**řWUUh4Q} 2@'bffֳgϞ={[r9LMM{5+ҥk^^^xh̴"|_:::zxx7ͭ> 2dȐ!wx#GϟGM0a֬Y!!!`_~ʕ+wW_xk޹s֭[.]wޯ:nܸf!a:yĈj&/_ ɘ/Lc7npwwo:ujcǎl|\NԽ{g̘aZrΕx͂*Ng#!Õ6q477ap\/\WJ8r&&&u垉fAFRW\jW^m&={ؽ@JJPr3VTJəZ[f,fs\bx<@ `X8j*VWq@rΝ{/00̙3xh IDATd[tǎ6mZ]nܸp `֭eee>EEExZt*J͘a`"tj</ μzjzzFAq8H$|2h4MvvvnnnNNNNNNvv6PUU% Bd[Q:ɜ6mڴiӤR'92b''sΝ;66uoڴ~޽{{ رEo~ݿƍ7n0駟^ro1L'hɿݻO>=hР<سgϊ+-Zk.*/jkk Avɓ={-Bq*=cS95kZ gBr\g67T*Vj322 L\53q""Ec3&A'9277xx#%2䍭5GǏa|SfddS #**jРA쒒kzzN:BDO"2h@ ORSSs8155utttrr"g]\\<hiB|3]]]qҸ=GڮXbŊǎ۷o͛{=k֬YfP$>|O?O9s8889rڵkCm~  5PVV6ydFL_7oĉ"ljjzӧD7Ntr[lYne˖>55ŋV=}5kh6L&_*Z7^zurue2u0rc3zFjD]g"MT&j?2Mka5K,a+//7vw02ZGqIP(*++U*X*-[Q^0C-,,̈U*_'Yz]vرrflѣ]vѣG{oݺa]qqq&&&ĉT*K.`XYYxD9'Cw #7311 㐐*Xs999O<&.|lmmB=^mO@[S]]-J %IQQD"),,īăj@CDhqprr [f͟ᅦ}ӦM[p^aP믛7o\2++?ᅮBCC6lc^k׮]zֽz|adr:ӧgϞeG=z(BϏ\;ǧYBw*?pž={,Yr/>Çӭ[JJJ>cw=3{"81h"L#P2͆MUVVpO&tSML46EFn DZUϜ9F͞=[VڵFs ! =B $C/e%1kjj__/**| :KKKKKZRX,J R/ܹOG}G))) [lرc-ZD)d2s~Ǐ1bDRRRRRR:7KgnݺuҥׯU:!6/_bX,ad\KH'A7.! tbA.k42bOx|)ߢu|W `0X, u  ie;w 2dȑ#GZ('~Jdbb6***Ν;Wv@,GGF2QиTW;;I&$1&Ո&>iDzi"MTFT4yۧ0 92Bqtt1vG4?"K\'(L@\"B!Z QW_uco< %q1*FO?TT}S #**'O)))jZRUVVJ$CP [[ۼ!?IziVzE;Db^@;F I"%"_"U".:D,Rh$b[IY$&῰8/̈?ğxr@󪩩o6n8}#@m.j… ?c=}uo+''ɓO>qㆣc_z/_+++:.t5k֌95h^|dtϗ/_y6cͨřcT Z'd2* L /QYPc8-8&&&8, @ F_8::= >>~ٙNj н{wgKTRTܾ}C6665R4t:N76HQQQP(7^Jqqqx#Q!_sppspp NNNsiEee!C\rť5_ٳgSNU*Uuu5ԴnGJKK/^}vcwh vڵ1c|ǎ3n$c!h6E/F.#jK-Mw\+-I^0FeyZ"xd]9Q:5s=EsXk8&ԕF7B/zU;fwܸr-=}?PYTfeeeffXVVtT 8:: ž={5[qBƭNsLEEEQQQ~~~~~~^^^AAđvvv" 52 !geeٳg…?,==ߟ"vի&M2vG6-(((&&fƍ;|c-0777\FdCuB'N8y$QШBzkB]qƺ,srrjsXWHےdj]@'_W=pk ޖd2٧~zС رQ{3H$ׯ_?|p=DEEֺ+..(JIIa2NNNyyy ++N/R!doooD6@"hh} c ,yΝ;͛_|'~7|3uYf1̪%K<|p߾} O%&& vcoo'GLL̎;8>4Zb811(륓e///Z> r}92<<[nML&KIIIJJJII㒒RG-q%]@QvN;:::::ֺW.\ďT?Ѯkh4۷ݺuZ8A]Adܾa„[vyŋ/qw=((hٮuWND"pWaa!Q}vNNNh^^^^^^ou\zuʕbxݺuaaa*B3եK*`0p*" ڵk{5vGgϞ7o 5jԕ+W[QUVVz}i(J D"Yxkז-[y溦VhF555uM33*,,ӫŸJyxxY\.~IP3ݰaÊ+v޽k׮ϛ7O? 7o\.}v^bccߘMܺukzzgX瀯損;CިW;9))ʕ+YYYꈣɗ/_xy6nhooOh4ϟ?ӧOp} >3j( !H@&A`` yciiiJJʋ/p +WرwppZϞ=`bbo߾=spoJ{ rr%ĉѣǝ;w:zk׮AtB!Ad:}_b]vmĈo\pa=ܼy_~Giii.B(//L& <!p5"@biinݺ5kܱc5kj\)x8C"##'OŋOxB]s~~~>>>5ᘛՋ|Jfff&'''$$<}ĉ~Np8ݻwǹ^z1ehnݺuVgg?n #..{xBVɓ'G %.'::zСFvZH}]/q޽+V<{l…[nm϶ݻ]t7o.YֽOvؖ-[|}}N}k͚52DގjΜ9Vj+D"PvvᓼK>wjzzFڵkEEEQQQuu}NNS\\.pjjj}2Lx J5k̙3\yf/d}0`Y۶mKJJzqR(LO{Y\\?4ÍZ [@@رcGbZ 4hРAjUUӧO;""bݺu}8pBV{͛7?|ҥmg$ZW-,, ܎?~l„ .yyy,anܸ#fܶ IDATZ B*..6vGo%;;{ӦMvwwpQ58qdr['O,XXMLLR>>>:.77700Jh4)$Byc3K@GPƎ{)Tt@??[ny_iӦƎf2YYYzru-[H۔NP URBwݽ{wQQQyyybb'N)dvwʕ'Os֭'ZYY <ヒk{X:ŋ={\|{wvRG rMMMFec?~;W^2vwhAVVV%%%222Lvcǎ%''%&Mr9&**jȐ!NZRRE`0BJ>77tfffS @`bb2yO={6///99ͭ&$$ܹs}ƌ}Y^B$7l@6lvoRRRo߾k. RQQ`hFsҥ?A>EEEǎҥݻ{mgg7{cǎ)5^YYw}ҤI}IIIٵkP(lo?z"@Ev$%%%66v֬Yоyxxܽ{իW!!!h233WZ޽9!!a >}Tʻw6ֽ!r911vˣh^),,@"X,Dn; B0aBBBŸinn4iRdddyyĉӴ]]]333[RSSٳ~zc(((駟m۶<1#|>KHHXfD"7oP(a423 yɓ'ٳg4(;;ܹsMU"7|}N#Fptt\~%ÇOnccc"׬Ys͂;wJI&-Z޽{zjggYfeff:t߿zXeeeϟ?'N;|iLLV' \\\U*UHHHQQ@ 2V=wsrr?rJe܎i4#G̛7իWk׫W/bUզbNRMMMi4nee&&&'O~% ?y[nMnP$+"''';vlƍx+''/tqqYpّ#G p(≠- 4hP߾}:h*++O:y͛7憇 WUU 4fU*UMM [۸gee͚5:gg|cwffeeh p޽{'On#.^T*͛WaW\3fL]{>}ڣGb˗8geejt:]J§@Edm5jJP(wxiӚDr\.M6N2: VZo߾%K^|yڴiFfݖQ(hնЇrrr KHHuss[p"B)))O kDjڵkܮ^wޜ7 c ]1Rw̉'._|gΜYjUjjjDDD޽Ɖ &o rPQQq3g#@-CCC_zel! @[e`Xom߾]ӗ_͝;î^_XN;[]]] ~ :Nd2sIId2֌]>, ƍW>FպÇnnn666Ė$???PuuD"111urrzꕥeQQBrDc}.]2Pxԩw>{gϞqqqQ<|0hZ-eC7vG1::4$$Ȃ" ;@U^^~ɓ'-_ڵk>>>]}e˖sB{1c:Ç-8,JZV%*"O\.DΨhرBĉfffÇ}>|!R>wwwHN> ,bX"hر<0lSՆM>Ί>;;;__ߵkVWW;vlҥz277>}zlllCZ~cϥRiӄBOXXVKcw՟~O>ΝkȷCC!iE :tm4ݻwD"y#FԴϝDqqkf͚e"T*" J;@SQQqҥ3fN:dΝǏ9rdptر?n޼YSS3jԨZt׏آjSRR|}}Bb!T^^noo+" \.Gq8Ï3Fl])**1bDUUUtt4;gΜ~߾}۳gO@@3gƖE"QzzǏ,YB[-f[l9|̙3ݗ,_<""!5ݻ7gΜ{׮]ۭ[ѣGվ}'fggggg_zk">I %"kQD*uꪻԵUEX,Xb @zBs2(53g@f~sO{ ,طoZ>{lbbbZZJa@(`VTT! ',^x׮]߿ٳìr@йsϟ?cV\\)j _jժM6夤Ç߼y'ԬHSSk׮=z^':sN߾}+ :pႁaÈ.=xW^355%" AdT*{ndddTTP(֭ۦMFaeeEti_mϞ=Gэ7:wi>|(//"A.yyy:::|>ё @ h4L&8SKT :"@ӧ.](Djlݺu_~ ܲeKwrrJKKKC޸.\tfB0,..?~9rJ |5>a~~~׮]Do޼5͟YiQQQl6{ΝZ]vX[[FFFjO8s afJIIDݻwa\yxxϟ]_r嫶%ǎ۲eKvvv|||@@a۶mp=-u9ZwZ#mn""",,,.\Ht-uRVVӁV:yСC8}\\ ,//'\YpN81l0ssCfeeY&777!!a޼yc 9>>>55u5Oh4n ҄Dooo|$==]WWð|33btήL"`DBǖeooURRRLLZr+V`lٲ:z(D5jTÕDÙLP($'OD/_|Y툏JbbbXX BOV8رcT.~!44ðE9rf`k5crǎݻw1 kժU]NԺӺit]]| ӧ =V'%%RYff&ao&eqtt)//'T*0ڵkD)++kǎ %$$СCDFѽ{ZxðW^}i/?k_-XCL&s޼yH .˗/뇮_d@PyhDsb----((֭[5O򥣣c~~AmccHt!@۫Wܾ}uBP|3D[n|lϟ߿P;6uԩZg޸qWճg*=A4==]vh9//*55UV3 FVdF0dJ$#2hϞ=իW۶mccckM!#qqq=~x'OvځO^5߉$%%afkkp7ZM"}?>]]jG?իW Z]XXؔvYYY[n ׏j]pV囏b>rg現D͇Z ~nO$CDDD3{|B0lZ>|x3oܸ;R455K.ڃm۶Eyyy&&&d2;;RL L=ST*rh@?0`@voΝW^hvqΝ .>}zٕTh۠7OOϧOV; öo޶m[####GT;իW1 svvuhNu+quun߾S nݺUʝ*Z{.[f_:=== D"QW6[yzz]H-^|piYbbbnn1c.h\]]_ p\A>_DcF"L d0B,7ܩSO ]֭[ϟ? 4hnii_i|QAAMvvvÕD&Nzm۶YZL>}._gҥ;wn׮]ܾ};L>|Kv܉aСCk~hhWܹ9szagg'ObW>x۷酆^reǎvuWfgg:th#md2ٺu `ooOt-t钫kG&J%Hm>Ct!@զM|-NC+))y߿:޳gEˋl8{H$={v3cbb Ϟ=n \uuu9 "K$  S<yyyl6;?? 77 ??! X;(/ixXWW`TڑJ¯eJy<7&&&666ֶNNNh%PT/޳gի׬YAD"߿͛71 ;} vܹ`|ٳ>}jff֦M414ϛ7˃&?A5DGGW:+Wnڴ rbbΜ9ǧ999՚$ 999ܹsۇoڿrK,ٶm[[r>߾}<|Œ3>LRщ^Vpϟgrʥ֝z͐Z2eիWSRR.&~ʔ)ׯvNJk]C?n:w\kZw֭[Eߑnݺuҥʹm =000{ݻw.]E۷GMjjܹ FJ׮]G"##G%( 555={+W Ƅ /^|?ؘjժX{ C;*#2_$޿"%%%]sss{]v}E_;;;###B h4l6 //ӧO l6Ed[6m\]]]]] K48@0jԨs΍9>b0.]Bm?>yq}iҥ 0LDGGϙ3ӧOcGd fϞ#F8p`ԩDBBB^zԩ{k.44tѢE([ݻTjttP( ؽ{w]2FFFϟ??L7oތHRRѣGrss]]]wVʙL/ϟollyH???˩ŋcbb޾} Nl IDATh&uti IKKӧ׵kרT*P3̺43VZZٳϟ<bѣGǎ[Zj=Ç/_u{?~\턂@d+W`>۷5FtĤ~GY/\j:++ OzL&{xxtqΜ9NNN(vl``@t `0 WWAeee(甔m۶n}||-,,|gϞ丸m6f#""n߾--Ztq Î=?`/^DGG;::o>99977WVHuSZ~}.]fΜO0LQPPЪU޼ysYtxʔ)D#-..N:jժһwtFӍi4ZsG\x$Lo>}tÆ DqFPPШQ.]OtEԢe{ZL?~ac.]O??;^Yyŋ/\P/^رR=D"uE{0==}Ȑ!h9//NΥ,0+JPT,d,**999>>Ç=N#:thϔA^|TPPa5/h4˗/ `X KGGٳg;w0,;;s|MΝPXXتU{#H$BP$x< JJJΟ?`0?@t5rՍÇ'Pf~[lٿ?B!JR"TTTT`0h4 Fh>4hlllhhHьL&d2t:.<{NNN>vנ>.]{ϫĠ}?ԩSn޼It!4><{ rsssklf͚QuJecc`+V|iΊ+](J:~qanݺsn:66V,4,>>>''gȐ!/_խY333$dY/_P(477_~?“Ǖj*$$$$$,,,LOOOHHxADDL&sttD={[-VTT4nܸG?3gl]]xq驩999;vlСZ 0UkA䤤|P(AuttdFQb8>>> `SNe0 +HrfmXH333#""֯_?gJ蟘D"rD, B@y/ʏa\F124D;\;vQ//RV=P(N<9oa*=GWW= ( ԤQ-LD"heQws>VnBh4ccc:NѨTjBB9s3xKfԼ$DGGM2BP.]o̘1gϞ,2hh4CReeeH$www__߿.~ݻwM8.###۷o_C[Rxbڃ^o׮zgeeu}Da111D$dD[ 2 ,###::ڵkzzz}ٶm[nڷozR}ӧaŋ݋޳gׯMMMeQT;w\jU>}"""X,VL&Ϛ5kҤIhޞF͞=ɓ' 4//Q˨`]R}i;wP_۷]6222""bF7n\ΝpTQQ}阘Ç[߿1~'\QQ7WH$xY"V$))Iј{e.[u:::xseL3Ng2hdtCCJ}?;v,((ƆBEݺurʠAO~1/P-"OKKC㔔tTJPڷo=hР:xzz]۷oʕNh4ӧOa۷oE"Q.]SSS۶m^9;;k4.Bj]]]!nOD?7oޜ8q"**Ç\ti߾} .DPW^6mJ޽{XXؘ1c 222Lnݺ˗7M~̙k׮-((~1 {ȑ####׮]i&[[[<]-ʃ Ň3|>MO3ޒY T;###J611RT*5fF!f*`0t:J522222Rt:`PTea111Ν#"000**jȐ!d2p[8hT*Bdggl X,W^.\ϥiXGh43gάϟ69t:}ڃ:t_K0NNNr9c& Ad^YYٙ3gN80f̘w҅_[{{sΝ;W(޾}ʕ+˖-[dɠA&Oܯ_?rիoޭ[o߶nݺ)ݩSiӦ ?o遁666nnn5y⅏`rrвD")//':::EEEeeei4dD[&<i]H$.]:yY,֔)SN8?1bĈ'O 5449r zAt?)S|i޽ӧOoϖ;vtww̼uFS՝;w~i򚸤KJJڰaõkЇJV^f͚ݰaCIIIlllLL֭[.]jcc׵knݺu>}'O<}T,{zzѣ.8pɩO>άzBP0 S("H HRXR)J0KRTr+++Cm$\k{o>h92~/%fСgϞ3fۉ.Lo4ofddqzz:ʞ2Lww~iԨQ^^^ b[(EqŋGYǏh痕yyyjccc.p0 S($IR1j;"W 77= 3;x`YYYPPЩSڜ2Q(A 4f:uɓܹ… .;Ro߾f'O|[mqtt:u%K0 ;v옷'Ogl7nuVǎ/_vׯ_j H$Fٵk׼yvƍ7nZNNNKHHX~=144޽{׮]vZ{h>|x_jݽGǏG͉.Qp8ÇuC&5zzz&&&E+**/QrLVf) JP((Čk^`2|avvi]@S1bX,:uAt9!T*Uff۷o>rޮ]]N6]v[666&ޖK$mٲeܹuSHMM>|x sJJJ222nݪ=aX\ d2 ";;;|0T'ɴ;"M#2usγgϚ/3fhժEZ٭XbŊϟ?߽{ɓ/_믿Μ9vpppqqý{||| <|׮]i4U͛ƍݻwyfpp0aeee3g0LWWWWWC^H$Rǎ;vdFѣG>}zÆ Y^ZY))̔JNNFرc߾}ׯ_߽{wsssltRu̳Jf'U*T*K{悂'HR)_$7Wf0T*а}<إKxݙ4iR1cˉ.RT*ϟ333V0{{{ww xxxm̌zEcbڵkFPq84͛7d2+88АJNEEݻgΜY%T*UEEEިdr=2T*@ Rif>_TT2ReԒȈFtPmdt:n``&P(ccch&M&-ZDà#2hhr<;;Ç?~Ŀr]]]ggm̝;m۶nnnFFFD bǎ3f̨{:<22244e$!!ӓdjzyy/lMaa!ka111)..F͏LX,FȄ' +W{.$$d֭{&"P/#rDDDxxWXXƍ]\\. ǁݻ@tEӣG&+e;{K.3իWF7n gX8qb:tԩSϝ;wfĈ#F@/KKKQKڔׯرCRH$GGGwwwwwwwPչn޽p8tOOO__ߙ3gz{{o߾%Ϝ9#,XP<O@o[(ZF͕R@ 8s̾}}z!FH???077gggo?>}/BVǎξ}vCHMMMLLܲe׮hdddddbY˭J|>_&UM0=BaEEŧObL&xjڍԜ`P(xfcccy@믿 ɓdѣ.hD|~Nux<ad2ePWWWWWW'''xli[W|!Cjn FS#rZZZ~j+p8T*U#۷o1 #t:"kdD͈X,^z]ڴi5d+D"o۶m.\ػwaÈHϞ=?ccc>|سgO+Iqq1Dh4ו+W Bviɒ%SNupphP([n믿<<<=zTDrvvvvvU*U^^v+ܷo޸qjVA! 򊊊*}H$hBAsOO!CtTv3fqDVT*^-?]uMo\ sEEj,ɄB!`EEE犊 @WJ0k7i600!L8j\#jyfH4qD6x`-Lh4>@K#qvvvnn.Zrhc`` :"??Ν6lu\ӧO^FBB{IL&Skf2F"Ad[[[>FU'D\~_{9s&\hQtڵk̙~={888]WS_rǏ{ɓ]]Q޿oiiYTTtڵ>`GRM^|9~/^XXX4j%>>}zQQݻO^m)]]]q\^\\fP|6ٳt>bjjjaaannhP(Ң"PRRR\\=GI$u۶mcccӪU+;;;KKKKKKbp!Wg5+//Gmd2S) IDATFk@ \3goNt!gbbRLD"d\.W&U`{0WTTd2@ ◶_kYI`^nzP={(ʰ+W 0@ .+J8 C x8''X,8ӧICCCB ۴iܹsʕ+WLLL*]*>>jԟ~IGGBB(\ZZںukԐ=XT͝;ٳG޹sgVr޽cǎ>}zw1c j"6mtR^z} ٳk.4NE"ޅ |}}CCCܹHod2ڵkm{5KsogggggWR5y-s8L&WPT*"h?|FZ|>Zr\PvOOOO;ꊇ---mll,,,?CcJ[n5kWQVV`0+**~֩Sd.h4fsEEvfL&Bv9;;>WTT& ڍH$)H^xQ5ߜҹsg%/&b& K* W߿?e\~ 1(5 хISSS׮];{Ç@nӡCgώ1j囗׫WDHtѿ``fdd>w=((1șcǎضmۼy^ӠR_T*t2"(P[VVG_.' a:::W% lhX$رc|>?44ݻ:u"" JB@-fyyyh}aTCo8y亯͛4Oӗ<{LPTꚬRҦO Rh([ H$JAd aFٲeѿcǎYXX]QҜÍ]M'MԩSK.uҥER5됐=z]Էp8zQTǏ?>aFFF"ͱ 8q 걧j?ڵk۶m mdU]&jSFɳ»<O$cr1 C)4Bnx&/P .d2 ]~G^~?_rL&0"%>>>##ܹsDG"LLLߞ03E rtY* B\m$"H.x}z۶mYYYC=|/E};bzd2?o<:'**jРAƍ;yd? K)S\paΜ9۶mR #djjڀ-~QtLTVaj]~/8 F_;u4cƌ]ԃ 2Q߽{wOOO JR4(( l O6 BBbqII j_)(`0d1Ef:^[?pDGG+>>ݝ@ AdT*KJJ KJJJJJr9I𴱯/6gƣP(/^|ثW/:wDFtڵ46lXAAիW TDh(@js=~ٳg 2A\r1 2P}1 x Kf]J%\naaaoհ#QFM/odԧN>վ MJ^vwAAA DWZ "4,LV)[j4nժzluC۷/;;_ӧOsssnjS4\]vUOMMussf ꈌ"J BDJh>𡣣h ZjT*W^}ƍtBWYi~…7o޴ifʕÆ {oFFBرSڽ[/_ܿGlO?rʮ]JKKϟgbb2dȐ 6T-*UV]v޽=بΝ;/^\RRcǎFQH$N:tPRRӲe&OliiIt] }577DuO:իW/===Mϝ;w7pK.}[4**jҤI:us}*;"i4tN*vB4@D;;… o[wժU6mBIIIÇyfppp]6lؕ+W𗱱v_ͽqF\\" }}}srr0 +** _܂ ۇϞ=FT XZZ?L&oݺO?~ȑ޽{_xqȐ!ߐڀЧ_ehh".^{nݮ+>>>((ի_{7¶mVX1iҤ{4a~PsQ*7o 1"AR|aخ]rrr-[6|K.mٲ嫶 7P[=zJ4i_ՔZ<ŋ?6338qY~b{zzK$/1 sssKNN;vC&NX})9sw;wntر/޻wZWTTp\ ޭ[f͚Et!4k lH$eFz0 B!P^B*bX.*J>Vy<׼G`F=QČh`FhCCC}}}" @+6wlllbcc{ =@c 2Z>_ZZZVVVVVEEEhH$|>JeX,Qo^tkO=x𠴴tԨQLHHѣGB///|DѰlKKKtJ lffokkP(й ㋡#2h)T*՘1cr .\bŷlٲEajժ}.+8qðɓ'ݻ[ڶnݺ}ŋ3޼yP%K,Xð ˗} ?lܸqRt̙uL6%XyPԱcn޼[n$ Az۵ki==/1 355~%K&OիM6hvCCC_zu>}4~,YYY_t7-,,h4D&Axzh4a*JP2L"n(, @ PT<Op\`d>}BhQ=N(݉h%ѻ(댾BfF֭o߾ݫWhw]SeUp8SiP b,,,,--Y, 1QOFbx̙C >|׮{ĉ.]ݻw3g$ 0Jw/r8nݺݻwȑ-arL&ϺEEE ɓ51 #ɻvر㯿zǏw֭ڙ?0`@ xQH?B1m4WWu}F 0 Ǵ |drcV̨y3\ΥrFy(ʌ3p3J*3>F( ߰:::_=7O?t1c\xQWWKs@!ɸ\vg\Z4L B27ٳkWxwuׯ\_+++6Fl6a֍sX,Wy٦Mmֵkצ߻GVJ%9év]繣w~Pիj7olggGӋop7o~ ?N`$\~޽ȫWr8:̛7oر[&wETOCoqqm[8qԩS,Y~J999{&^7({ð۷#Ghիׯ/^\zU{B[Ujkk2d2ԩS֭۴iS],߹sׯ_;w^tgR)auzzzϞ=޽{~ʕ{ذa>}xxxDGGÝرcǑ#G_޶mlb{Ľ#8΢Ԁ03N9B)gL9qǠRm ٳg͛i&]tD_It: x'Kp 222DXIIIEEG (dSa_~ mNAZZZLL̮]~8'Ϗ't[[[<==|G$TMMM]]=''!TPP@,ҡȔV4M醆+Wܾ}{:>7MLLٴiKM!>p\/eCBB&N-,,.]@5osɒ%'O$f344LNN]i퇿fmmm/_F/QUUѣ痢ɍ1yرmˉ'vY\\ZXXᅬ?=zov3..nժUfff |˗/:::̙3 J ~!ell\[[ !!bxUUR ;#++7o۷/==tڴiWnMs rΝ3gΜ?>--BX[[_^+##O?~;w.^SssI&ZJpA8oop6q k ;vLFF&$$zذaǎ)jkg;vpww߽{7qUϫ}exxg\c,%%Z/5vDFiiixrȟ 0 IDAT}:uUV䄠#rz˗/>|Hv!? v N9~B)g)gJ9͞qw&V.JDfkƁfD(׀#Wٵ,_t͚5JJJ'Х*Jv!*++q ?Ətc7\PȈ7wek\pȑ#,rϟ??|QZ)?}T]]Bh<>>^[[[0˳|B痕#zl|W󍌌\]]Ȯ4荟b̙!򚚚ׯ_GDD<{,66biСzzzUps??\t޼y%%%ٮ'OKsי3x`}}>{l׮]#Gܻw]k@W6t>}֝ շm۶|r%%C-\UV oqqqd f-ٌ#DXg3:oQfx(D2[4ek@bx Dh!vvZ?? 777k]ǫW Kv-@W7tz]]]]]]EEEmmm]]VWWWWW8C7\2~ //Ox\AA۩~q|>ɯ_NHHh5̬S555%%%:"/]IZZ.}R8ʌ{3QfG|>NG x uuu%%%[OF bID{ff"L|J0 3BFi" ..NDO:tPee̙3CBBF+AGdiʺZ:AmmmUUe0xõjV> +*****<1~i4l_ٳgOhh/ZBF:ujذa?hs p|]]݊ uuuRZZST^]]MR9`c4Hv^]ĉ8jnn9޾}R"//okksN{{{uu+M0MM͂CFEEq\>" ɓ9NHH>6mڔ)Sn߾С֭svv{ ֣G{V !# >N>-//?g Zncj=M"\[[[__OD pp&Rx6-MO4.((p8jB ^arrr!kkDWW۷ѣ,uh f@/N×t.[YY7$YHL›*++\.N'4 LzaGj11177777ģGv{…=t.>}rvvVSS nf{ 2JmV i$BH0/ ̸3șTWWo33Bhp76H3~F)))ᳶD^-[e6A|"h7xF o *x oqxJb+Ns8*bs)/CѨTI8%'555FшSKOOwww2eʒ%KZsΩL}jhhhhh(4yyy!h|>J 2ޡO^~9.@JII;v,U;*jffwޥfeex<))}Z[[Ϝ9LEFFfs8crKJJ|ell(s}ύ3}||>~C-]t…jjjD||uuu=zꙛLR\vtdQm7UUU#F(.. SUUE=8D7h"$MĦѷXs0g#-@䕉ߏ5lDhFZp)tKO b#_RED{''tDS\\"?Z׏?...ޣG>}̜9O>fff{&}kMjkkiN;r?|xdd$Q%QCGHE+-@T ėB=|`i{ |!|Bl37|yU2Ӹm$~,fl7܌70]XXZz51?~A3sۨA`ZPJ7)}Kǂ,͚Y SŢ#>K/qy Q?q8}F >uB!FiVy:5{쨨(&ui'''=1e˖ u=55!dllKKKtttt:_XNtDE>mB(((׏B~E8/q^rbbbrrrVVV "'''{xx,\p-A^^~ŋ/ 3gh޽[nݰa޽{n#==}Ȑ!1ہ.@n?~0yP YxDB0,Q#HxJ<(fX \ )^vv6M&^L|:N<u vF {T"M ƚJK 弱W7b@CM[C/$xA $y%^cA^*nYIIIKKo*Ztss{UhhϬ($$"nff0\TT<==O>QT]]]} 2#|GJJJҲ&---''G}&'''''dUBBBۼxbVVϗׯ Z#D.//g2&&&&yyy555BmH߿3+VXb/^xҥÇ[XX̘1cԩFFFm,)))**SYY)SA+1_̮ ǿ_ 4uJ<(oHDw%|KP<R# &:eee5 U7z8FhJJJ4O n+W:uj͚5ow #r:<^x1مS0aB@@'F"ЙbGd@qOOO UUVYߤIEѣGNNN 'sׯ_O:Uh?++K0[fff۳gϋ/pyƍ:u)SE\OJJVVJKK KJJO9...Ə nsDVIIdddddd\pv\YYYWW'_uuuTUUq"YSS?PWWWSSSWW} $11qΜ9YYY׮]u---T*:"7k,[paaaUstv\.BtX~MVvڊ+>ZMbcc߼y/l6;,,l׮] 'UWW !BVVVxdw2hCRȤ_f AdІttt.]Av;ⱪ܂ Iq\|VqEEA 4?e2 o޼yɓ'9N^8p@sss/JKK EJJ !dddDP>}D\ٳ;Պ9999998qӧ׮]۷owWWמ={~ gΜ9wɓ'g̘>5`~ׯKBbbbD^MMM[[[PjWTsSD?(...**z "WRRFGGGKKKGGGSSSOOOCC),k۶m6lؽ{DTUL6xr;|5iKÎsߟG6BBBbرcǎ￟t萹9 6FRRrǏG^~޽{ĝ\\\\]]?_o644ȑ#mT[EEǏ߿ϟ?11BHMM RǍg``;jhh@ԸSmtj}}=l]PPT*UGG0666111111660L__}YXX~8֞Ҹ\`:"?~8$$B]peر/_466&"1Liii{?~ue˖ɵ )((̞=[E>|8zF':;;7Ȑ@/"kkktn&L 2(++o۶m˖-s100 ~z777n>>d2KIIٳ'1rI?~%=zGuhII7n 69**JCC@V__/%%Evfr/_~[Ǝ{m6:Oq+Z\\͛-[4TUUcǎ| s%%%T*U__B\\Ad B "6lٲ5k়۷Bk֬YbBhʕ,LJ۟>}:w1c&O-ׯ_wehh؊իW^/^񒓓?pq9;;[ZZ+vݻw H܅dx<\mmmߺu/_>|xzzzzzܹsgÆ k֬'711mVkd&ݻĤdRVV611155uvv I?v: 555?~n}޽GX,No:`jjWDDٳ8Mb1޽355 r[=w܉'4ZÇGY.t555OR^^hѢ={4v`<oܹ^3*phoݺuUUU8_mܸQWWwܸq&M>|ޯ{jjj\Pn}Bqqq'OISSs… .y9|pcee5sLsssSSSZFNNښp8YYYiiiIIIW^ݷoWTTq}%1ed2}add$Ų{0L???OOOEEEї;866VWWӈ999jjjL&S__,))A8hGd*DaСk֬2778p !KK˨ర~m߾e+={-[pܿԩ-:yd |||^|s3|~RR҃O>8~I&9;;ɵC%SByyy8GP]]oUTT,\ÃR\>}BO>5119|07o߾r***=zgjjJh]'N#UUUo߾QSNX,EEEA ?<|prkYf̘1c PEEEXXأGN8i& ѣG;v4+gXW\}{HHA+kkkkuuUUUͺ@L6|ҥJJJ˗/'бCGdAyyyrrrJJJrr۷oSRRL퉳ߵkĉMMM{\]]%++.+$''GUU5-- !${())խp8Luux}}=BjBzG"v3''A=xgϞdW< qqq4mĉ "'$$?999ƃ 2dȔ)SD\|||``srrTTTf͚!xQzΟ?pBbc6VTT3gΜ9s>|p…/ZYY͟?_U]z~f0PddBl6*;qㆪ*ùyKnj3vQFuƋ+h)SWJJʣG>|8|111Yf9;;/&&&<&DF]nTTݻw @vEͣv֍^pF؜:u u"|>Ϟ=۷o_~;f Y޽{wuViӦyzz8ڄǏsss7mڔ!%%}|| `0.HJJ80((O>4iҞ={FI=mo߾}]vmEE/]F~9s|{ff'*h"ooo#hPmmw[  ܊n޼ v|}}N޿tD1̌߳X,*ڳgO ŋ d+cǎS7knݺ 4ѩ/_>}zrrrV,KKK !TQQQ__'1NX~"s:t,X@vEb0ͻw^```"&&6lذaÆ߽{700gϞ ,?:&cc [n)))=ҥKIIIAAAO<>|xuu5e\nnSΜ9SQQ1f̘/N8QVVhWʞyyy/_tӧmmm-[6}t>  |왑ݻ=<<-^^bكxѬk8qb޽ɮ M09** 6Ed2ȮN x EWWW^NNN^^^222d׮][{Ǝh 33rNN9!BݻwGUTTx<DM( tD!oܸqŋ?~OBLLܹskkk###.L2eʔؿ{Νv9sUȮuٓ`\|y„ L&S^^~ӦMg϶AuҎȅ8uꔂ’%K.]AvQ>)wR;B ͥ{ȑ ڵk˖-W^eEdd8tDn-aaao߾ $r$$$_ u!T__FvtD,+///;;;++ ?~'166ٳȑ#{wر}ӧիY VTT|… N}JupphtjNN%BH__,))Aѣ!D  E'2 HCP6l`ii9w\ s >@pv޽o> MMM+jW\9y?3vիW9~1B(!!a۶m!---ݻ;w>x'a0{9y`W#?>((˗/~~~^^^<OVVvʕ .l"##CC #߿?p@999+$c2~q,+777;;;''x!---33g9Sׯ_Sppps|1BhэN277WTTl8NVV?pGdr< 8Adsrr1jԨE۷oF_VBB¢ERRRݻvZtv***˖-[l˗/;6vX// tvmmmIII111622x?^`> ,ϟ?5kVMM͡C-Z$.]/n;^v7.<<<Nx]웈 aaa'Jv9t׮]9sUpp5E5Clll}}}9\~7BK333 =zeodBkp8EEE_|+((˫ųᄱ!h4R|}}<BCCZ611qŊNŁ\M#~G 2>:^GNs&tm =--y̙ϟ?'(~X,=Ν;wԩȮB&Ξ7oޞ={:TSSCv]b.>Zamm撾+[]n)d BPlʕ+ mmmϟ?^zu= 8 H||G޽eee &LpTKKk̙"TRR2c ---337?0we˖Y[[k̙111O>]SSLJ𩟟[Dy;z(DbM(4hPbb_zEv9 ===z'##CFQ]Gff͛7W^Mv!hȐ!Ν;}/ٵ2AGdйx?~޽˗/wuu֖upppss۳gOhhhyy3;v䒒ݻUVoBF8p`͚5]ǵ'Ontjll,4hPSsrrh4Zfffmm`G䜜~yy9BRAd>OOBCЁ={v͚5;wtrr1bmllȮ !s]v_~ڵ%pijj߿ӦM۽{ҥK7nYϜ9Y\\^~!daaa~Eq̙{?~d=^^^NBegg{qqF hsrrrrr߿1duX" ?rJLLLrrڅiiiՂ3^ߟxqʫ***hy} nnn]߳g" %\^^rʵkv2B("""((ӧOSNr#GLII!FBϞ=#f2dݻw|/s߰aߺu:=Jeo޼)8˗B-JJJb09997olo!߯/77xi??۷o7k M9{?>l0Çv;'6ӎFBB+W$EGG4?#(,,WZ%!!Av-gƍ˖-[`˗/ɮdbٳW?~|ǎ/vuu8pСCgϞ};vϟx}yy9/\rȑUVM>R-} `Xwܙ4iRS3<ѱ999bbb!]]]b˥xuu5>p/:XϷpo۶ ߕ~$ IDATVVVdZ;yѣGFukkkX<ح[{Κ5B]TVZu7oڦ1BLLW^iii"MM﷡%ۿ|ґ pZ:!!ֶስuBB^$66711˗/L&IqN__իW?&>>ؼy[0wɷo9rd߾}nj#x_ubbK] dE|)>fG>}:rHMM͂/*;혮_>c &|QZZ?338!!Zlǎ dqŋQQQ{&ŋ]KeeeiUU1XnTUUutt555555jjjJ)'NLNNoO<3fϟ NeXGlt!CXl/'Rʚiii֭x񢁁Att4'!QT󫫫= AdѱX ??7o :tժU...V8\tz`` UVߟ:.q&L "at:`ӦM׮]tRRRREEEUUUee\~~˗/EKy?xBV+**\.Í=z4n8{3aBۏ? |DYY(++ҥK_zE3gu5/x߻ӒUUU(::ZCCr@?~y.tPl6qYYNc,XJFFFUUUMMM]]]UUUUU[n8[*öO4ǣFjK.yMS_z5hР>4u2ͮ&%%% >}2 ~666 \.֒{wfhgG4iъ+Ν :cǎ˗/1/K^^~ǎ7ovuu1bÇ---ɮ;rJ]xaTTԝ;w\]]߿_QQf#( >`0^ ?"vG? )33رc6m4iٳGE5hJEEEPPЅ 80o< o==k׮߿ŋwܩLv]ȑ#۷oWTTD?z*TUU=|nS4zb555]ǏBk~ [٨qWUU;8jZj"uA!;@q "+?x$7瞜nlѢ޲o߾J[&$$I#<==ӧ~)))!!!/_6[=<ܹsԉZ:QVV&BjWq $Nj]]]lll-īL&S_O||RܰaCvaAAAjpΝȪ^rJ2??Ύ\۷!|̢"Zcx\ P-nݺ~k׮>|&&&Æ 3fLXXXc$IӧO>|7nܞ={=FSN=:rȢEN8aÆoٲdߥK,X '''kkw.Z# !9رc.]aÆW5]v=s,X:~/Riˍ7 <ԩS7oF 0ӊӧwFeee~KIIٸq#N8pٳg7mTcםqvv~ݞ={bbbj^j0ǻZ !bUZb>y#"}Dlcm:u+00W"rml۶JΞ=[Цy ;w1cݻ=p!1"\.D"G,D"<1ג $avvvSõ/YJJʶmkmm]?]UAgƍU힓R(%%%...!B BB{l{}иxcǎ>|ŋZ5jT>} /eeeϟ?~H$;vl>} tdu-[ܹsW_}a{{{/Yd…xŋ׮]>bĈ^zۯX0!!csYf9s_~Kp|"}˅ 4_z5y~;w$yxxxIYXܪUlb7|cJϯvqy# yyyDɓ'ݻb^ӊN\\ܶm/rɥ7H J0a¹sZlk d2ZYIIIiiƚAd=zuD˧O޽^ݫhii/s̉XwԩÇ]/eM[VVV-Wl6NJш_ (affFӵV~W 2w{ :4d|CGGѣG{8~IRtJ$*H$D*|BH$pX"dꥥ"mlaaann󼠞_Ç?q![nj9}SU)dPvvMff&y'33!VT*1DTDFUTi`P4)*/^LHHHII111֭[tttϞ=]]]=&ET>yҥK/^| ڵktttttGEP*۷o_d{`ov矃[$L&;vȑ#O:5h OOϘkkkx^--]tʕcƌټy3QViEhvٳ+W=eGѣ>}:""Bé޼y\իٳgѢE2~Pi`-7b| 80HEBBȱf Rl6o6ldddnnX, vگzȐ!{355566ƏLFo03gΜݻw_~cǎ G =z! pPL(KL*Ⲳ2H4⫔*J(bH$س9Əl&Ŧ mr>رʕ+-ZTN&NŋǏWՠW^ǏAll ]#.;v|ayyH$ϝ;׿Ǐ(J܌p\LFAEdФPe˖x<;wT*uwwhѢG(''''&&&&&&%%b77˗wԉF2d̙3{=u 64uJ5{쯿zРAƄ277dZpB{p€ם/_ޮ])SYf̘1D]R`Pz?FM0A{H VXsΝ;_vQHQ'OdDdrT*Jc͈{.//''Tk&VVV&cDR^^} JKK߾}tR|Ua4B}ƚ*;M;kE߈<w@iӦ߻wG Q sH$*++RL / V _bHN8Flnn8glff7طz"GָӧO/YϺutSZZG+"x<<1Q\\IbdJzé rrrJocǎ) ''___"S*H$?sNbbd2Uǎ駈bi&''3gΜ:ujڴi.]:x`Νwڕ9Svjll <*PTTԎ;ڴicȑ,|??~˗cccbcc7l~PhlQ"uׯ_NұcǡCxTu~k׮ݹsšCbbb="]=z(//F̆FBBZ,{xx,^xժU6*L X,ƏŧE"QX37T*c@j o=rv\y&.l6 9]]/J=|pݣ߿ooozg!F:/Oộ+K^cn(`卬sssccc6d2qaAD---qn?|;aoo߾M\ +WH$ÇWɓ'SNZ:yX,ƫ>}㢢"_AdL/}x*6RW >SShT*w^bb;wΝ[VVfjjںuQ/SqqqJJӧOSRRRRR222T*]DDĚ5k"## d 2$<<|]v}*H&dGe˖|ӧOo\iiSRRҸرC.ϛ7 BrZ Z-Ue<~QEBDZ]iZhzd5v!R#tzw(5\,aaaw܁j$HL&rL0B\W_csM*z|_ jkХQ~W 633366&Db#_%uի{ٿ-x0rc vvvU5P߿Wt:^FEdDbllLPj5F#OqBEdiݻw*--Mσ+tz```pppPP!IiiiFFFffWp8''!dmmܷoߐ`!᳷?wܖ-[~=00eŦK.%ovZYYـ]rmmmdN>|J →GT*J%JhٺuO?tҁ5[nM@y\7]dddddd?u 4#'O}4.5`0W|{BPddff⥖!WW֭[7WvssxgRٳرcCCCo^i 'N8r5%$$k_Pg111۷o7_YfmڴIKHF1̋/ƍO>nGꫯpSii =z1xe˖EFF6XƟP(bbb7ȟk׮]ro@B[|!dcc9QFmQXZKKB[%rGZԥ0Y(/{KtЪ$%%ܹSK'4o^^2***211)//W4cT,?t:ߢn~8|%b8998;;;99:99q8=^(++r\.777>|hܹXCxubccoܸgϞ:]˛6mڌ3vJޮV\2m4/B!,k׎J;5**ڵkDȨYfn  o777?~߿ȑÇ/-ZիWN?h^zʕk׮I=zolu?h |;3-|>`*N"]vڴiM2;:5|~=LnQgyAKrl\NLKK)auHcW#EQ!'LiܵV#Q¥Ajm&76]/22#Fpvv缠ɐH$P"V)^D:G#Kp0M#"L.LmQ#t:qZGs.\8` \V\MR;V,ߵ<9qDΝ}HJJ*--ŷhJNNBi۶GzAl-JMLL+ToB0LU;CXPAddgggggNlQT999yyy\.㥤xb܆N[ZZZXXXZZZZZl6maaA~[& pJR( @  E"?"oǻh4n׮<== Q(Ej*&&m۶OMjzƌ͚55^JKKr\Ą\!$T* IDAT?STTdP?x/^Nܼyݽ}]]]ѣGו+Wonll޳g^zi$ht޽ǩ;w^zA=:~e˖U۲4={.\P@ioPՌ2Q bKT6.EHMPN>c۷oj59sQQ1.TZRL&742T*ãK%%%Db"Ba~~~zz: D\TגL;~liiicc4;88+x/PTTԳgφ_߿_u֋/޺uDכ5kֶm[FS#<!dllo߾ExuH"lݺuƍ C.s(((ʕ+:\ ]rǏ_~ʕu-XDDDo߾C[&=0XB߿wRiVlұc&ڵk.QVZ5LnݺSjhb4b^h+s؀[qTiI޸=AԻ^-[,++kذap@ݐJ]W Ve_k% @c#6Q{K]^j#߿۾};X=z|ZܸqC{9dPjjJ-**rqq!~ZVl>%DFUDV OtB*fC,&u+4j_#[nM6mĈ?C :IJJZ`ի#""*z֭|y"FsιsX"رceeeg611Yt)Q Fu5=zѣU*UJJ۷޽|O>o߾}@_޼ys?/_׼:vӭ[Z ӧO{]f.῭#[}" r8 ʙxZmqkL5QEPi-#ܷS*6֨]Wo*4j`cUU(wivZ].ZE0p<7nٳכ7onw$H"2@rTJRTG-'*nx{[*cD-jL߿MMM?mmm7{c? ꜨKt/Yo'Bt[!{N:uʕӧO<8l0-?~|YdLKKP(͚5SYYY!"2J%~]*e<| '&&&{hh9s޾}k׮b+ǎۥK T|URݽ{wɒ%@ (///((P(x›/^{1cFR(O?$HfΜ`kkkP|r;P(ƍ۳gO-W S6mڴifj:---)))))ѣ+WP(A$+ 7B?)))x1}6mw|򰰰/ݻY,֤Iti\\\c/\YYƍ'Nhoo3 Cn[ׇ]ib ӈ}cUŬ*-%P(޾}[-%+ơ}///-xr-j.Uy)- R^Q#U- E^;6MͿ}ƍwAq[~U0U[?SߓJ}hQկ!l+P(TTU}RT.Wj˭?*n###OOO~WcEKVq\&1}￯?~˽{jis-##N:i*77Ҳ!DTDV999DVT4 BƧ* dm! M̙3 v9|Z-R9rҥ_aaa)\^^xӧO;v.uHV>|xɒ%￟5k1fGu!J5k֬͛7ra ?e˽>ڵ lll|||,s})--ŋ/557(((***$$$44&/--ݺu)St:(JPAd]ܹ.F4 I=]W9s愆VUFKǴ.!B]*mWUV\CU+)??_{ZU^vRSk L d5WZP_ k]K*>T^_U:Qg\|y񱱱+W>:۾}{-mnܸѮ]jj*77`5bKJJ( .aV\\T*AdwF;*"h ]v}Qttt/^[.sν{nRRRU.ݻgbbp-F{%"U?lBOnfJLL=zʕ+]]]5̜9W^xq} 2dȐ!iaa!.IٳM6)J*X[[@Rrrr_z봴O>!Zj:eʔ/9~1H4{l] rP>|;::{,kIG۷wޕ28r]^zifժUw%Ay~+RWJ[-YXPc;w3f̎;>keeeǎ|'Nކ+<C0##!ft:qz+J'*@Edzuv̙3 tm۶߿W{<իW Beffz{{#lmmg̘yoRtٲe[l INN Ypp[;R}޽{ׯ_ݼys _=<<=<<<<< htʲ߽{fdd\{>>>nnn(6BZ6mR@WTTڵk.\տ}|rBںڴ:T1)//>}6mڤcQ%$$±cji{b*iiivڥNXff&S*J Z-J-hT*^KP-w AY[[_|yԨQQQQ'OӧO޽;ol޼9>>sK4FMLLݻw=PCDusx̘17n8x1c#Gtԉhis-ssJ{WCuʕ+Ad ;;;=R)())Q*DrDgΜ9sϊe?ާOÇoذAK?rիWs% BBnӼys*ھ}{TTȑ# @҇O_;vݺu. JzzzzzzFEEJe^^W.^;:888;;:::r8[[[|50R4??????//Ç_e2nd2qUV'9,%%%'Ovuuq"b͚59s@nݚCggg}ԄT*w44gΜ1cźqFxxxvŃjovʕj'޽vСZڔ޸qC{'իWfffT*5//O"2JqW-KKKB WDP("2Q Y?zb 6qDR9qDիN?w\N[\nΝryVV۷oB 2Bh۶mAAA-_@x<ޔ)S.]4s5k֘Uύ RYII .Kǟ>}"dbXL&AiiL& %%%BP( PD!Do///"moop "QVRRnݺS:88WQQCnٲe͛7ǿAVVV.]j׮ݸqO* KJJlw_… aaawСC}Kyy={&N;wRitttQ&wQ*o޼Ad|F, 2B"2}gX2lѝ;wDd^~ݼysAlxחF|XZZruu=zhttthhѣk.]?~<;yfΝkabjP(t2b8P[TTEDo늈D2f2, WILMMt:^niiIPLLL yjM(++#g1J% BbXP6jWH$r\*"|~iiiII W|;*f/9mcc/AUV^-ɾګx#GC <==Ϝ9ӭ[~iŊ x*"x?~|۶m)))W߾}Ν;ǩSjovrP(رc^^BhlmmBEEErSȈ< ¢EJJJfϞ믿߹sXz]FF7TPp8&ˢ"rXTTԬYMֶm[\&M6qqqk׮3.U*)j DRPP!T*% BH # `P(fَCt5ɴ46 ӿf͚__χ ?Spp[4av4i1c=+<htj?sС'O#G߿?88wΝ{6a|V˗/ju6mrss)֎EEExJ Q*!ZQG{Ad-\رc_qN:O T*zJ,WDFY֭[Ǐ}6x<^_~}ᘘjJ6k֬ә2LPTZkY2kluB憰4}>>>#FeX).|]|Y L6}Y,։':t0a„G~n-))iذaO>*   jÇT[XXpcTMŋtҳgO}@֮]9dȐi@X|֭/_^^^ 9rd@&۳gG/]diiVmBy$tvv x`WE""+J)Cs0nݺwرccǎvQ/BX$yzzBE>>>߿ .9sfѢEXV]SN=zx! .0aB6mj;urrQ5W^MJJ=RGqqqׯ@ p@D" 2IRYsփ gϞˌu5p Y*:thԩ:|ruL3 斗Bl̴CMYYYWE")~P(x"2޽;F{#Bh߿1bĽ{rvv&2LsssWII B_K'۷oMNNNLLLNN.--uuu _~}xx … YYY ն|rPPƔk>>TDa})Iaa!Nh߾ԩS#""\\\=Fm6noJ:uԌ3tӧt:S"|;xKQQQY(2 RIFvL_ϟ?u_~EXoogvmݺu-@(kp1>O #ˉbժUBpĉ cȐ!.]^|Jt#UϕORK~!TZZjժ'{,jW IDATʕYYY xa-=P HAdJ$'lٲeHHȠABBBZlY w޽}.- KO>yyy!"foo\.,\b@~(3f۷oӦMgϮ}DD͛ZlٻwoWbƝLBacT*bãP(+F>|ƍ̙zEnZn}y{|6mڴo߾occCj`M-[ {}pQ(x5@P+++KKKKKKKMM_߾}T*MMM[nH/o޼]vնJ BQrrT*@>P8lذSN$!4cƌ䘘d"dbsss|?\VIR~KPnڪUӧ3gٳg֭/_JBB·~qƞ={ֲC_2jժ0 *bN>:nܸ3g|0  vvv\.OOO;R*4300pС-[lժ!T筥ӧO9rDgϞ0`.-ӥR)Bʕ+&&&KNG!d2q7ZH8,ˉn)!|! M\jjaÊ_ޡCw۶m<=ztRR?D"&?\,s8zX33q=zm۶gϞ2@^7nرcqIZvww}?MƏ?hcc3|}pss;sL.]~+V{8B///}UPP͛7oޤرBhC c0o۹sm7oX J:88撫~߿DVDEdPH( N"# B*D"P( b1(HmSJH$؅FeMMMBT*f[YYl Fz/А<8cƌk׮ՠٳg۶mrxbX,vuu%Ԩ "#ڷooaa! rrr>&##[n-[ܽ{wt=׾?>tѣGL8/|M@@#=D"6Q`\粍===}|| DĎKğ>}ڳgu?{_K %//o/***,,tppPb\\W"}q\3"B9sL4ĄfP2Ͷqrrrqqp8ԴG'P8a„kժU .5u)SDFFvo$?&?Ϟ=k׮ݓ'Orrr>s~~~.]O81ym믓&M_N:^p۩cIWM :t0|p}16mڋ/&NآEm{8ŠWh޽{GLb2͛7oѢE׮]NڢE-ZBYfO>]Ν۷/JեÇBx$Qbd2\T.ȥ _ *???######333///77x2ҒḸXYY[ZZZZZܞs\-rdP(: #AjjիW\.1*6effV$`(^x1tPHtu"=\c&Mrرc?ފb L&cX!RŋS:;;|=z0˗/\tiٓ'ONKK[f4eeeu… &&&uҧT*-,,Wٳg߿_-__~%==}vvv@!P@SU^^MrJ%B{?px۷o߰a>}b ]zJ^^^!.ۦMRZZ1qfff2 OejZV) rEd c^$.J^|5NgddаX,///77VZg|]]]*T94lcc.?~xyyyyyyZsnn۷srrT*BD2&hR@SNzj]nںu ۷U"+ J%%%AAAnnnDJ{H$III`ڵ+44t֬Y;z(>9ݻw;wn޼yBBB~=Bͭ:l,X0jԨv{,N?ydvx-]fARb*"N.{.''1twwwssի~]tѥӧOJ@ sDzzwAA)NDW0#@Ed i222JWxxIp`ťQ, ^VVsZZڹs>~255mݺuppppppHHH@@@]4>?k֬GΘ1cӦMu/owݻw/++#Ou={F?+RƍyC~iĉ]t5jT@@?<{:(@ekk{9SS:Ȅ۷]VhϜ96mڴ{8_:DR"2hd2q) QS,=00O>nnn8s rvұ2Bܹs{ֱÇ <==|X,& baee BRT4JF H{=x'Bmڴ9rd6m|}}]\\tGZ F@@@@@yP(}ٓ'OC 2eJjj*J+1ypuu}䉎w 7noݺ59sKV3z谰0 kz08arQc.+pcccGGGggggg簰0WWW3Sn:!ݽ{W+~)--m„ ĿfQQN'>pEdB222b2 2B 2 J(&%%%&&&&&>~XP8::N8M6mڴbOl6 AT'?d2Yf:vldgO:ɓSLYn]ͨmٲ% `ڵ4D&WD~y֭B<Onwر~?cǎUa2۷o޽{lll˖-۳gq?ǎ۷oÇ3:>>>umonnxb}9rgΜо}{}˅Ȱ4Jǂ.G.[PPrDꫯ]\\8Q(ԟ>۷o:V8>y$N߿?|022!q %%%پN"h4q悃x6cwRƍoNLL|Rtww|GbBb9:::::ٓ@p+W2eO8ѻwo333]f͚윜PffJ+((틋BL&!DӍ @r .\yfiiwDDĜ9s"##}wJ2%%%11Ν;6lӷo߮]BV}q…{~v0%g̘o߾Lrq!juaa!éQF9rɒ%:r]t5kVPPО={z]c4:VZdŋWZU`222"""bѢE!!!xI=*zر\B D@@! q9c\58 0X]ۧMLL}z 4m˖-]vMKK#6r!"cUAdH4dooݻwnp;w5kVc>H_ JHHU`+EeWYQEײX@lb]) RCHHB!E>aD\'N>СCP ΍/!!!'NX|yX.,,,޽{/_˗7lٲ .HfST**BL8aqaa!#WSS۷.pƍ d֪Ulmmϟ/TTT~G ]Ϟ=BD9!!TAA!??_WW!$TTT;"tDP( w^RRR.\py7qD__1c(++KNۛbݽ{744?ذa.\8}t ǣի_511{lȑJJJw 8/h \PPFBO?pCͥ}ʕ+VXG˗/߽{7uP֟fE"$[PTTD&ԾC}@޾};sL997oڶ龒BaW" OOӧˋ qڸGKJJqܜhii)**J@wޓ'O%oqʕI&I~۷=z4Bb :)!!ܼZp8555<8yrt:=77!DPT*=+h.{u]]9s̝;ήjbll^YY/._|ʕ .1b|g˖-Ǐ}z…h4kk۷Ϛ5K^^3eb1--;"߿֭wvvvn}1 6l׭[7{Ç2[:x<^aaa~~~ii)_eee n|34 wI$jme$9jz' q\.W ep!T^^kjj***BiiiC cWSSSSSc2 CMMֿ$Q]]jժSNYf޽q!+66Ĥe;.z(h ۷o5k ]Nf@%%%Dii)H"3Uo@:}tJJ۷%_ڵkL&~pĒssRSSӪcccPmmmbb+pwD.,,Dq\"pp^^^ND"}("* />>رcp(q/^xq׮]...Çvu̍76nb<<<6nܨ&N<|`` I$REEEbb5Vqqqu-Z?[;dhhum۶9::=СCqWQQQ^^^aa!~řも<'~!sD`31FĻwF8Ltw.++evvvv\\1L&u7Wƣh4)̊?~ZZիWOnﱮf˖-555[lv!LtҠA&OK8#h750.))RRRRWWg2VVV8aL; >JJJv/t]\2eHb:t_NNnٲeШѣDDD899~sss&%woo%Kl۶MOOO|~ڳgϼydׯ_jyy"MYYŋmxիWݻ~/6l000;5噙yyyٹ9% #D^]]b455;ke{y㉂H<3 wM___WWPGGwRjjjnzAggP|=|y֬YG}ر'N@8zƍ.[… . 2@+q\vCć[).D"xOԙd2Ttk׮EܹSU>~gWy333dBFFFX2laaE*))UVVwD600 !999*Z2hZttÇ/_l2MMMiZbСC<|u8o-_5(>>~ӦMÇ+fwݷok׮͚5KCC˗/***={$SVVD>z޼yC<Ə?nܸ{gϞuss[~~p84-) pG!dY0777777+++//FEE2dnLLLLMM+022i7BNOO?qℛ[;VVV&''wf"~ÏpH.wϟ6mz4 IDATÊ+]NPZZ:86pk# c>>Ǐ?p7lذb xk׮k׮'88xʔ)ҮL&ׯ{vqqٻw̙3544,--TTTocbbwikk˗/Z;s挛իMLL֭[7s̶MB_|ILLLMMŁbU---H0aQAQQ[qg\CBBtrrrggϞ}ӧOϞ=;Jg++ӧkA䀀7o޼{-M<˫v9͆?Yr8R`1~ :39ͮ999&2Ħep$Kyyef̘1qD gϞ?brssSRRo߾|fXqqqCEEEiBGQQOD"**H$? 2 Y~}``EXXѣ]v޽qcǎܹӳCGZ͛7[ly葃3L<ի+ iӦ/Νnu***X_~9}ɓ']]] Pgړ'OiǎHHHHHH||z޽ϟo/eev'EEEsDKl!MMMۧO޽{۷w.;w~-[x{{Kϟz!KQYYڵk/^ܿiеٳ'**j鑑D,FJKKd////--au1*3@%BHAAN1 555:ս{wCGGGGGgРA3+**p Ç mekkkff&k^V\?۷OOOOƚwt;wv!]ܕ+WL3֦ vF˹\xNX-ǫ)*`ZXXLjqb-+A?~;sLs=w܀,,,$_˗iiiDX,< $KJJ[#)***++}91-4D"n8D2ӧ֭Ү##sέYf&L={Çg:7Px޽#G>>׮]STT\|ҥK  %%%4m֬Y:QB999>{H؆ڞ>}zBP(6m֭[m&ňԧOqrrի bjjDtaÆ W^zԩS[lP( :tСCh\@?ܹ|J[|R^^ B͛7U﮴Ahc555qeeeuuuYYp8<ԔUWWWVVKgqtXMMd1 cb10nA&!!}ʕ?c ֍w^֊044422z9J{5Bܯ_?PAABwDƿ|>qE}zll? :Y,ۡ6ȕ+WnذaΝScvvٳgϞ=1dȐgΚ5JJVAL@ HMMD.**ߤVkSÿ, o޼jbb"а/^͜9~4h@bhh8gD BxLZPYYwWDeOBGdu=ydڴiVVV7oބ h3uQFݼy8eQQQAAA999̙3;h_D9&&!$^zE4?.//>8pԪ@@@P($R"H;@ D555555G677WRR:u]F- KKKKKK//k׮M>}C֊Ǐ?{ܲe<<<ڳ檬]v i'˖-srr7ok1dȐ}yyyڊ? Zf3 3 }ob_/1_ Ԭ*ӜN}W|}}O/5?xw^zmܸqڴipx>߿ooQFIRqSD҇N<miiqzxx3ɓ'ڵNUMnoڴ֭[l6ѣ8Hmm˗&L=z'On_b.] S200"kkkx/_ ۷XJJM>].Ap8\.755555o޼yݺuJJJҨa2K.]tiffKO>=`~mɓ'fff;w\dIi+>?l0iNN:'i5w܏?HN"C pּ~+d~q[:::xԸk|9ڵk;6{m?Csx>~XSS!4i$ۉUVV655EⵊKJJăt:E"ߠ+zɓ]]]ϟ?nWZuرiӦ={?޽{w;mڴ`Ç>|=1bX,֝;wÇrx.۬-~'N頠7o|IZa};993ѣG2u(///$$ҥKL&sƌNrtt-v tLLP( D$)))Ç˓nR6mW믿q@#|B6BPCsz{{{{{}СCnnn;vؼy}e>?o<Kfx왙 i9996lXvmϞ=] hɓ' 4k֬ϟc`2uA ֿ!#D3ಲ2P񪪪D"nr6-\Jt*~RZZZs$yNО>|z_e[HLL|۷"N"***rrrzbXɗ/_q8??XXXd2 QqESPP@Z":"# ?N4iBF]zu ,o󏏏̙3333mӃJD>{lpp҂ f̘aggMdtý Fzzz@@}l3Б#G֭[zufmGl$--o͍֦LLL;xݻwݻ?e__#Ft刪qeee~~~aaaMM*NEaa̙3oݺ >|xݺu?ӧaսz˗/Νkkkbgg'۷]#rUUҥK'N(W,wwKJU:66?|># q}BCC[3i```uuu <}TGG!l``,fp Paa!KܣAd`@b w ?3<<<""B*idP(DucxASغuP(tqqٻwJ~~~n$u uǏ_jiׯw޽{ZlgҫW/ׯ_>}DFF&&&HRb,a˖-{ٿիe_TSSvڛ7ojhhxxx̟?n۶͛˗/_~=ʇN<miiq&ߴiӭ[ IDATlѣG% azxx3ɓ'ڵKB/_|ubb<<< 䖛صݻToi Xxw^zmܸx$!~Zl[;HecTTO?4rȻw2DIŋ&&&.mٲ.H߿9s۷o i2%%%Ү D䢆D^>_^^""DޗHaPYYnAtnph8!A"2BHCC/IDhXfK]YXXجYsǏϜ9#>3JIIѣBbH$##{dkkkp~~>D.**RTTTPPBeee)))!|8Ȳ9KN/33s[n8pk?o޼yȑ#]xٳǎkY _9%ŧ+3gΈeffzj<͛ 4B5R[{ڻwǏ/_}ɓ'O<~8==]SSsررc9n_~޽;ݻ~M0!f#J%u̙ݻw_pAG]rSB/޵kW7l`ee!{{{bEbܹ/o?q zͧOrByyylի9BMJJJJJ $Ax>##!3IoٴiӞ={Ǐ]]]޽;~VN٩$G*Soݺ5{lH###iW$3FU>_.B\|nŊ! @[@@\!H,ۚ"7 \D_o^$%RD^}·E <|߾}mRRbTjLLRQQQ^^N\/,,֭ 155ş,޽ ]7a„7oڵKAAX`/Ν;G&]]]mllك```xQnܸq///˗SqSFFF˻^~ 4BL2ړS rI&rkl6ٳg8|GR 6jԨ™l"H=zHJJJII۷/9`P޽RAoUVyyyx !~ʕlذիG"<|p///;;;o߾-[<{L|!!!~~~OZZ͛ȑ#zzz֭#_ҥKe˖ѣÇ0/##:|||fΜm۶O눌rmrr2mall_CwD֭ áY/PDħAYMq<@ggiiy5WWbaQQQvvvs6n܈Q 793H4y۷oן/aguo߾Ȋ\. 7(~W/tww?y$)!ښ|w'OfX]Q$}ׯ^z/_dF9rHGGG|A4iƌB011qԩ;wDM>=88n޼yŊҮiGٶm[VV,,7/^?~:s>~WyoTTTVVqK]]X|D sC?Pg]iӦ=bܸqlEEE?_A:;t萧'ѣG6y8- "7SIT6]vmΜ98E-Zpҥŋ۷o߮]>ljj*Z@KlذիWҮ㩨PQQs.:XD/o07k+ޯk+F F&DoXxL|XtXD"1 <-g: ;;֭[.]ׯߡC} ={vs!HUUU7nܘ2eʌ3B׮]366^tMB^rtt400DT*uĈDWݻ/]ȑ#...nZfǟE#""[ԭAdж,Y븸8g{j$vƍ={||֬YWn0 2^̙3.\'Hvvv֭5jp]v ЀǏ[XXL:ӳΊO>USSsqqٻw/ld -}6~dѣ&™ˇ=Ʌ>|͛7o޼~F6lȐ!C%mڴ͛IIIsMJJӧIvvvUUDRgΜ ܽ{Wڅ43oOud|/,,l„ ]4qu'&&at>}LfIII0L6]]b111W}M<̙3Mjk޽$?:o uBH$5AFv,!nϛ7/77ѣG. %$$ڵ @G$F\FFF_nA?vP'tn!>/{bZT/\>m<]GfBJJJ[Tx!Dězţ^]4x7`:S-DqqW>}Әee &;99}]?z(!!͆Ǐ`0Bqqqs~~z--Љ'"nܸ1}tPVV`0FoMSSs׮]6l`s ߎ?mY8ӑ@VYYyʕ{J% iM6mڴi,J}H$77777#>L&ii\gE---|blkkyWWWkkkKL>[ĵ:s}[n CCCnݺ[/_LJJ yѳgBBBlllmV;FۊoNkvIhkdsTuwlOS}unx&OBׯH$Z|%xtDW^ussr労:W~GϹzr nWZZB?$$Yn %P6N܂2Ϗ '''ݻ#222?} 3L|Ϻ!p8:STT$>PHy ݻw͚5KڅNkΜ97n}9666!!A ͚5ή.Sٳ'#ɽ{...p¾}<ѣ˔޽{+**:!____̙3 .zj<}MY733n={\vڵkݻdrMMMBCC`ehhhb: aj&Tf=|!$GV$%*XcV}wkUtć􍍍}ży@oIFԭ[xvk\. `U*6_v-%%F5Mp|۷o?餋Ob&&&?6}"\xI>r BaZZڗ/_|W[[6`3g;88QGQWW_xq Cyy9ћ9))IOOFUUUGGGkkkBPyy9Dt:!@R&BxT{𡽽}G,_8Ə?akkYQQA&{amm=k,kkk;;;={"i4ڱc.\"H/^P(FFFRR2ߺu[Vv5ƍ ˗/9r%}}}]]]_~aД)SԩS###Kwww:taFFŋB9VVKPN|С?y򤷷w#=G׫W/6%%wDFFFׯ_&!ngeeeDPRRW) J.;"7Jr[ AB{{{GGG???iu 2>rϷׯ_#&A[h&&&qqq=z4ieff=y$99Y5Jɓ+WqㆋkiXϹ}vcٸq={W˻,ԩS6lPRR"B SSSSSSSRRl|ܼo߾o߾l{h{yYv̘1Çǎ[RR2bĈIN8QSS!Pxq)))W^CEZի}}FD{ll8~tb˖-vmt[c 風T*L~Azz;iaaQ^^ٳݻKfY|/_O_l%Y3qě7o<~bԩ8[ѣGi4۷\ѣG%iީ;Oݻ?={6<>~999VVVxHCCb'/M2LR8|,/C~ 2h+<BGGGڅNNWWSWTT:ujĈ}3RRRF!D"L&_~yÇե]H9d2ťNc4mnIZZZAAAھ]s$\WWի[755| ?QUU!aP;EMLy… ݻ7|piӴk׮[ZZJ69h i9ʭ @iȑ#;&~|KIIСX,qVVVVV1c-[3 EK@@^reVH$3f ~233{022666*ݺuEEE bijj9x 5"g, TjUUBH flE ,'AFsss]EUYYYYYwΝs kkkkkk[0رcK.577߷o߼yd7vM6]x!h"iu#߹sɓ'_XXK "JVWW#B!_ :3<6q@x BZZH$鮮|cc\+!lvYY.--VVVK,Y|M6m$ D"5[>|C9C1cF7PƏɓt:?+ԻwﲲM&BڄӧO#"":u^,[l5kǏ[6pp' 6.(( nd(++t޾[nݺuݎ;ȱcJJJv-͟?xL"wbX>} 9 BJJJAd!@@R\.~KML""''f]JJJ RRRH$H$8pxիWC ^uի|999999|83BHMMmС;v8wŋ;ŔN <<'Nܸq#Pxg"GwUU;v dٳgmllG@4222<===== $Z@"666nnnW\v92HUN|~QQQaaann.NO{a!yyymmm---===mmmkkk]]]"m,ig޽ZZZ-Bbbbzzرc9ɺqFFCBB544䈞eeejjj-"DYw~"Hfff .tr gϖv]TJJFQQ||oOUUz!!!R*ڣG !.`DSTTya^X,ѣGm۶u֩SΝ;wѲlwH_*<9ܼ*5̟??44˗ڵUUϧOaJ$.]z~~~ U5:&J/\pȑK./[K.o;yB9r {{xbBB·У Mr)>On @#$H0qQQQJJJ4ظe˖-ZhѢi۶m[habb-yUXX~6'|r5n!+jT*՜[x\.XuD"iNyd2Y*2 BD͜ҥKrkiV#Ν;wfڸm۶(]vMV/]tҥ^^^'[999bdyHHի:"xJ,Jѣӧ2|,kرcǎ}Ç:q֭[ӧk׮ h8/_tҕ+W{wށ6Z3hРw555u ,u-޹sgĈ=/Uj tE, bx1Nq#H-Z Ɩ&&&Ɛ@mVZp 2,!!\vWWסC:QusgΜԩӱcǞ?gϮ[NT~cccb H?,[H3#""~7[[[?˪dQvܩy[[ ,XѣGϟtҖ-[VPd2۷q˗ݺu[bŠA,,,t]]x-[BR;vŋu] hPT*رc3fصkit*++! T* @Qm@ lCPpmmm}||V¸F~۶mqqq_Ӂ\;"gffXoz{{dbffY,lRRRV "0`t_hoA:tK.<~۷o߾}5zJ5?s͚5xEEŋ/Z?JNN^di4\.|D"EGG\w:tСC«W^ti͚5s̱ܹsN{OOD߿{߿_^^aÆb]a=zu!ul͚5Ϟ=7fҥȑ#u]NBT*V搱T*q p8ŏx8\.WWo|JLLYDDלvvvđLR٦Mmoo^zi#!$1@ AdT*I$f_3 Yf :4%%Y׵ԍ.ۿXgU*޽{++GEE}w!ng޽!IY[la0cǎ:߱cǎ;Ǐ=ڻwﯿJP<==>j >RZjѢӧO ݻw ަR{~Ə߭[7Ջ^|tҿٳwٸq#"㿛3̦**)))!!ۿkqqApSNp 4*>zJrqq 9rdϞ=5/u5'ŻvZjfׁfɓ'K,Yzu۶mu] ЍiӦEFF5 qu#27BV BH$?&Zg022"ٙqoC&ҥK:}W6dvZϞ=5#p9##Y`具BeeeP BHdB|2o޼3g躖:PZZڡC,P~~7oJKK5̚5kƍnZZZZZڱck+55ѱgϞ˖-cS={8pٳg!!! .400w״_~ٟ},k׮]vŻ9998)~m۶UVVjժk׮=z޽Y\jR;))x*11ؘ?gϞ=INxk?4hD\~=_Bd2wj͛7wܹsΑ#GbccI$4$Bg%%%|*۫W_Deֻ;v0뺐$JnjewSfc GsFu9^MQiiX0:5{b0Zb+++T1Ϙ46Sd2YTTT߾} 5INN^bt333CCCPffRJ B 2x!!JLJZܵ1, @FPsƍݻ뺜qƬ,KK˹s<ԩSWpQФI"##[n]RRɓ8lW^zٳDݻwk׮\|,\fϙ3MXXXXXBɓ'nݺqƑ#Gd2YvwޣG.]CB?BҥK8Ͽw^'ܱcԩSȢ\tI^d;;={^zJ8qu$uĉ>ܿ@oP˖-ܜN|7o<{T*R...^^^>>>:tnbJ6m8qkK/˻|2\1Ǐh]|^ I$"+,j0Ucddb>p8vvvFa8X /Zqqq999Wٳl6jG֭[파 b\./..Ļ%%%Uo߾E1LD`0T*LV*İ05AdP?qĤ6 <{lqp̙r_~!9sݻwpss 4hGMOOx -Zi&<!tիWsRSS/^L+Wݻ_$ܹsΝcbb$ݻw_~͛7H;xzzjW Bm"|||z@ׯ_G988h۶mhѢe˖ PZ>|pttR\vmddd^P3fxх ++!C 2ᖴϞ=S*d2ťm۶...xCrR߾}디7o޼}/ehhѡC'z{{k[zQX߿?..n۶md \]] &꺜FԡrPXVV=&&&633kӦ s86]5a ˗ϝ;UV_y[k""|___j"h͛78,B%%% CVk\S(0 Adoya]U222B8 "/Yڵk׮]CH޽QB۷oߞBBiiis%rTsΥRgϾr׽!//oرÆ 6lXd߫*短[.\2$$$88W^M=L_t:΂(/^tڵkFFF߿hѢCnذk{͔)Sn߾=yO5=ӧǏۑI^zwRiJJ۷o߾}͛ׯoٲE*m۶mժectB&eeeeeeeffԷo?0-[ĭlgg-aذafffaÆ :~u-vZDDDrr2﷌ E,1rHTZZCx UFX,VtʊHcZ dh0fͲ7oWG(&&&۷Oxzz?B(++KVgee5JsC_ي&+dᔖ" sssLZ֜i$s@D k׮k1{nZZٳg]xY//%K| G贴LKK#bÆ ={vVV$H$nݺ^8p/^xq߾} %00p O~𡠠Znf>}ھ}ɓ'geei%e[lvI& 2:WTT,_<..۷a0LI *IQTYYY)))mFFFbbbNNn@hNջuK$}!///;;gffj{1uThY{̼| K?Bٶm ˶mۼƎ{%w%4KBKKKE"m2&q VH$chhh```hhY,9 6FFFL&>_իgϞ=sѽtZ ʕ+Ν[paTTߠA04*o&DWQ{x!CY_v-,,_cǎˋX"VTT  .++ 8@FYYYR({gqa.kmmfY,o50hʦNگ_|Ο?r5fee) <şr|VhBOO 21C_v#JqC".ȍd>ϩÇ?sf 8pӧOׯ_OR|ԩ]s_ddd@@:z]-ߩSӧwɓ'W^ݴiBH㊊ڼyUry~Bϟ_~=B(44i#V'O|̙xgg/8,,,,,L&]~̙3ׯ7o*]yA-4Tܺubm۶m۶Jڕm9mڴGa=fڵk׀t3CPty\.>|y!\%񆙙q|ʢ|QXXXPPyXk=mll̚sNPpB]RgRRRf̘Z@c3]z{{]*)) 2D"H$BB"DX"⊊ P =-JeXY,nٲ=Ǎ4q:YSLJS/.]Txzz:k,33P__\sL^^[\\lllL"//DO'* rH3|L6 p(|;l" tiJ3fٳ'++kƌ!)SnJ x~j̘1x/V?up8FFF-[!Je7r8Cu-44tĈuRLrrѣv1a„opY===jH$ɮf(˗xXsbĈv-_\׵kѢE Æ {)u9:u]LVRR"Jq2X ۵ܨ4e˖L&`p\bZXXh: bԷcǎ9rZ=۷E")==e˖x1Ì giiIAd OW2L&r|dݻjum6a]W߰XGEEEݼyfoʕAwϞ= mڴܹСCky'Oܹ֭[ƎǏ -_SN7o~H$rrr\-444$.} ;pwFvvvdd̙3۵kWt!):v5&//"bԢE bP(P(,K.#5RT* 2F{Z[[O81###66SiرcG4{ |g?ۍwǎ)wa@|\4hPzzku]'ZjW\9v^tǏ޽{3KTJR5T* }#2?;ի_5jԭ[֬Y>DR-,,4J1e|aP(,+++((@ Pyy\.,++C MEudT]0f[XXT Uk {59/^Xj͛MLLt]Kݸu7nتU+];kk;w2{x oלnbX.⊊ L&RLF 2T.-t:]3Olffd2 5l6d1L a(QFۯX. IDATyܹ#GV=Ntz]EEE``G bqeeBhٲ%1@(lǸ#ree%BB󰍤_I(|kH$]]]駤#G@HP۷o6ѣGMb Mַo߾}VTT={vݽzrpp7n:Mړ'OX,Veef\RY[[]RIT/ `͞F-KO B~477xc496mzуtz.盞ޡCPaaaEEJm߾G4 ***BUVVjB)pcBxB^dQMÇwss4h۞={j 5r|ѢE֭ ۵kW|a#?bĈ#FڵkƍK, ?~|HHH &!99+77W)ӧI$REE-++$GŢqC X,nEנ]z/X˫offu\Pdffn!rww.JJJ&V8\^^~H$p8*JR!W9J]HA蒇ǃ&M:yk({/_5jTJJ>}&}iڵgΜٹs-,,N:qĦ}۷o 7nСóg JDm  =m4'' UKR-X`͚5/^dIoBpܹƍu-ucڵ7nܸs@-߿?00psu9 !4 JR,dJPP(b1N+++xq ?B w644hFX,`fX,"@t:r IOO8pAfϞ]=~!CAy}ee%"gddHnݺiWD9??D"kN BC4³D7B&Hjٲӧ;{/._$66vڵO>~uNZddBXr wލYbE |Vǎ/^hѢ OOO]p|> 2zVB!L< tS/D"8 8.7,,,V;xlO tN" 6|ΝuիW/^زeKէBZByFVZcx<B"l>V'r9BB]  5kV~ fOsM9wٳ֭[7uFn9;;֭;qD\\oTTi4BWWWZM|]߾}'M777gR'Nڵk߾}߿_Jš7oԩS@3w?MÇ̙3G׵jׯ_ u]Nkہ#2 7 JR(T*PWVV8"\^^.KKK H$RT@RD^N__Nh4`@__ST̓t:fk- )@sT*srr޽kddTg>y򤕕էRRRZh&}z۶m'Mh"SSS]½{Ν{l޼J/:>zQF]reƍ[|yF>۷!HNNΩSmFRݟ>}:f̘ZvDFr劁A1}h?vpphRSS̙ӥK]Rj1cH$Ҿ}a֐Cyxx̛7oӦM.|o@STWAaBQZZ*˵\Uq\2, q a:nhhHь( !Fc*p6&MzjBBSݞCm8;;oix-[$ LMMҴnnnDGd<; BT*4L 2NMM=}ܹs5o޼UMӧOk׮۷uE D"^zʔ)ktth ʊ舼zj QF!|||>}*$I暛ߺu+ wUϟ?nѢťK%S*++ǏߦMe˖麖iӦ˗/߸q_zÆ cǎ u9 s8G/ kv,qmll4 $644L&`~, ߯Rm @`_u{椤Tljj*DD˳ v \J\Q(lFJERq.J8$JmFQdСCnݺČ7Y2jժ}i̙3 uE:Ӻu]vXb˖--Zpaddܹsg,;;֖x{Yz5FC>|X("S!kkw%<<ԩSw/_ܹspO6=zDu]KHNN7ou] hF} &<\VsURR-MLp-g8>r֭[0@kpcYx5k߯_:?';vX)))ݻwGcVxݗ lmmBZ9YV3 KP#2b-[o͝;wɒ%'N6mCmn߾aÆsoݺuܸqNM211Ytiddd\\֭[w5cƌYf꺴mgglbhh8~xOYYٓ'OB\.?#>>O>Ç?z(F{I^O><{xWWW]RD"СC;ud]۷{xxL8Ӻy7|RŸO0~09i惉d2nE]#%\1q!cGx*Ə ZpիWٳgȑqcǎծRQQ;"߸q!CZx<5[PPD#2d2.D":"C377?VXqرիW̘1gϞ. gdΝ[nMKK۷oBBB@@jtV^/lܸqڵ[l4iKviipB₲;N{.B nOLLݻw~bbbBCC! 麖:RFP(N:H$ٳ'((ȑ#.~APpVXVBZƹaŁ`ab!$qJ%c/wmq a):NĂ i4R8RPߔJɓ8o߾ѣGKP(j$HW(X'7ɓ6lիWΝ###Kq5 999ڶm[AAaÎ=Z4`lllٲ3gPի/^I"H$R||<˝1c񬞞[RRNO}||{ݣG??'N@ P_3fѣgΜZƊ+_~-M:׫WiӦM:500/\/@hp`"׋S8{n0ZqXs0 _6F;88f!.K0 4i ""ڵk.\~%'NhӦͧ&SRRTj֭B>RM <oKRHD&  >_p8>|@Q(Ld2q@&Dh4Zxxxxx۷nAц 2zΝ;W Эҿ G2ej2\U&Mdɒ͛7X"44TU#L۷wA|%|||nܸann+---LT*E}v@@}ǎn\|yɒ%fSК5kƍwU2r oժháj[bRWX&UTTDXs0klg0n8` šq>Ÿ0\;vlذ !!ZbTǏ7SRRRp{㔔jSRA䢢"Zּp|>BFIR&)H`<K"  J.\8x`Ϟ=MLL:tЀ]W\.?w^jdd4|5k@ /p sttI1r\( ***lvU|||E" 4"Ho_)/^$&&ߺuD"{ 3+}aWWWooO HIIc\[#y<BH+,4'Ad%K(S!2 111111w=z-[fnnҷo^z}Y RffXYY١C1cߝ@֭[rrÇw޽lٲ &48lll}۷olؓ'Okmm}e.rǏG=o޼Ç qrrZn]TTTPP˩K3U8`!J%>XYYYVVVJKK E O} *W_0Y,J_*!dhhHш4Oׇd/_ϟ={-Ю]cvԩSNGu۷/$$SRSSBNNN)))!4.((033Z-Y,T*e22 !VqGd2HEcc#F1BT&%%ݺu+11qݺu ,رcΝ}}}}||t]lP(޼yׯ_jgg3gvV5~ڷoΝm۶͟?ٳ{dr-VXrj;"TlZ]Ǐ_81'Ntpp ѣm'O=~8tP܃0 ܽ{X]9'O>1c>}l _(JqjMId2f |-,,S`h$ + ";;;---555555-----JḻkHw%&&9s1)))'O$j޾0gaaA wLLL"[ }lllJh h8 }۷5kJz%gϞe˖!LMM}||[jUes"J?/^HR*ڵkŋw\eA&N:xӧ?ӺupÕRvd2lvA䌌 PiiiVVVjyqDDDttQj٫W{ 2{Ϟ=whҶn5q?\_zr|РAt:d2YoDڽ{%KV^rL3"T*H*//D\RkWPŬvqW`*.4,kYBfd:W"vqfD"y_hV8%L4M\.}FFFFFNeddTVV"lllBBB\\\ڶm{`8p̬o߾5IIIqrr"H l977w{^remjpvv~E{."FwrrݻwttK#~cǎӧOӧOTTԪUtzڴijX*F.jtttϞ=㥤tѱayyy|+BG:" p d}}}Td2 BHPjD4FFFF}_b1ݻwx!`0---mmm---lll\p8l6^/HD"H$BÇ>|xg-BD"Y[[i}РAfS{{{VfjjzĉǏO<911رcۯ{Ǐ5Dpttd0/^ӦM~n6C۷_lٜ9slebBZ]3j"l6aÆQ(x{{뺜tS/u-]vk.&&fÆ _㕕eee ED*Gzb1n/r]R)BeeeZ"YդIh4bT*kddDPh4^ΎBll6L&k_?9Dڅ|0hҔJe^^5&6rrrp.Fo߾Gun߾}۷wssaLeeeFFsbbX,vXD---B());BH.H$}}}D`0p#gAd>W`ii)1񲳳y<އ^xpaMFFFl6f |@OO!DP~\AWAH$ B! 4hkkkkkk r 9rתU_͛73f̘;w.LNJJ---I$RnnL;wpέ[N'&%bxkhhHшVĚ-zÇs9Ý>|@+L6773ζ_gΜY~}233 Ν;۷oTm繉*D"dP)J RȍdFQMruuuuuL&#Z Gb?₂"^L$hȲ^V\.i7_9̓͛77m}={|qzO.3iɒ%K,!nSSSƧ36;;[,W #߿?sŋw˪"=3fҥKgΜ MT*-***((mģF'1_.]4g5 N'&w1 #/--U(x Rr!Y5T]m?f*r\#6蛺Wd2̙3w1{+W6iuE& 8J8q5S)^z?X{q/ѾWk͛{644ĭ-,,jOpEEE3P*EEEZ!㼼>YF3330`%̸a'OT?c^~M&۶m{͐{1-<!DtD.((@)JcccBߴL&S*2LP(p|AGd+:njjr :A"f̘>j(??ϙ3۷=JR3-윒98//O mQVzSNZ',.. ѣGLLS۷W\xCm۶߿NlG9.,,Z-p8Ad2L..]&;D"D>z8(H3-[D9033355511155u{4z̓'O2D1J͛IP/;u}n{2kN"\nmb<Š۵kgjj}ڿF Xeeeaaaaaa^^! Jl+++|RZ޴iSxxgoq|捍@ HKKýpi[XXZ"L&S"0L@TݓGgϞޭe7o> BB0gg礤$8bnnndd J3f L>rH^ڠR->|i{ R K RUP,E,D^]UXcA5涢P4bh D4ne [D)**ʈt:յC ~d2ux._7o|왙sfffFшdD'CфB!dD"'LL&h8_P(( 4kk+Wlڴiȑnڹsg-RJMM1cFLL̀p N'v:uyLsssP˖-_xݻw_v֭[ Q#%%ԩS ,HHHXxE+B۷+[1E> ,, 䄣2n)_QQo#ЊT]&z:884m  h۷ow=eʔo;رck׮=x`HHea _IվOmllp _~M6ųh988}oFsvvnwEElw\3h``䄿<==}|||||<==2,..nO>Ņؿ=?2eJttĉu=46@/QW(T*Cj@"jְ \&D`\`jjj||ҿDV9&D`zT*{kk׮-Z޽[c#.S%d2YYYYYY[$OX,<}=nǗQ@'^~}%LUh4'Oƕ===D.))+))iٲ%HK?$t:t?xܹzHRyYrUAhնm[ ٳ<^;}4yÇ ZlY=X%FFFSN _r… O8nݺ^zUٳg6m:rn_ch^^^͚5 ;vGpFњ6mڴiq8$6zΝ}z8u@}h4>|(#!ƸᔕCƆVVV666666-Mvɞ"aaao߾}= șcckk["N .DP ~ Ad:=z;wwޕh4ѣGxiP,-55D&hт!jժ7oޤ5Za{{}͞={{ڵ5k*c>p@bbb^^ޥKgl2,===555-----t nnnzr ;;;;;:bqVVVff&.}ҥm۶yNNN? pww׷?xΝѣGώQCH$QQQVVV*@ j+ @ P?fS*"}#RrM>kX/BS%"t:OLAlbaaAP}L&~ O z"**ԩSӦM{6⊊ "T*޿_TTHk "V1KKK:T)wYZZkիW#Ggaaahh(p;wƟG">}ڦM7nT7SDO|ثW/PXVV'/Ңh *"WKJJp k41h(Z֓zFDF;qO?4|ܘSN-Y$..(rL`0***-n ܹs'^j^j׮ݔ)S7~=u޽{׮][zu.]-Zo0TVZ={6,,^?*Ҳu}Yxq֭}}}5e144 -BQ۷O.XN:uҥ]vvTPok֬DW3gD6mڴׯ߹svT*P(Dj@y<^P(TT!HT*Bbl@&D`B G!Yh4D B, !DPp?Da`SSS CV ‚|rZs/kPA䲲2LLjgYķ\2 BYj9smFRϝ;7jԨe˖͝;ʭtF &ڵҥK_zᑞd25kF^|yYYٽ{ӧO>>ܼysddF!@T*L6`]vM>nx۷qիW~~~:u?~pppbtҥKP(=zѣ}vܹsݺukRZԩSN233.]zyG8??u֭YFc-.Ko"x<{}QQZ:88I۶mˮ+zY|-vܲeZiѢӫW\]]<OPB5WD/g|LVz14x=zlݺ8c!JxƁ?0a‹/5kvԩ={7Nn|||prEΝ; ;VCqK.]zrM6QQQ}С\P{...#F1bBݸqիvZt]>}ۿss:\.'$$Ž|r̘1M6W7nܘ*0*cL~CP,IڶmKn!6ف_uZx4mСׯ_{xxTٳʎp8!FchhH'ȥǎqX*"G4M.\ TZZH4vĉ^zgddm۶5D"ߪL!#jŋCBB._L~!T\\|Ǐ_k޽S&UcC^xqՔ &P5*,, '>۷oǎeee#F8~ ߶ϟGFF>| zR'lasW"D6V#־'|f͚8qħOA˗1G IDAT @UTT`q_ɫx ZI&MTc2 g eLлwBCCB^jժU= rcYPXZZi%\."2 2=۷sα}:tӧ+#Я_5k矅)233ϟ?ߠϟuT*k׮dSN8q͛L&3""bڵzғ{… y<ӧ?inn>dȐcvܹ _zk׮_`ڴi%%%-[ܷoXBD  ]Z?Hr\./J%1.mBEYXX_fff[ jӟ(K%'W|9*z֭[-YD4Ad|VZM ,,,l6YfxA.(6<<))ٳ$##ظK.W^ <ʞ rIIT**Ē%N0223 2'O*! ߯Ǐ9sˡ&L8|0[qII 1((!ryٲennn999YYY-Zhs\h4VW*7 E~~~ppA~wzرpfN:u'N?~f>|8NryRRoݺf͚I&bߕP .@Pb\.G)JHL&J!F62"\&5MșERߠg0WV"[?,?9XLJ=+VXjUxxxu=OƨT*ëB,7722fw5wkݺu~~~?C7d24M${xxTٳI&*ñ*++Ӛv, BL&S*t|g\YVWW^ 4۷o8G8ر+WBCC~/S( AϞ=oݺfx]vܹ7n7͛7sFVM6l0DRQQ***$@ J@$IRbÇGEEm߾'@ `i0T}rqq?n:eʔիW/_UV?yB3fƍ۶mL&0`@~~u=/8BPR!?C*#•72"\&UF 7X,\{N'U .GUބH W q"a q,X !!a7owDS*@ x<*Q\C%P(21vpp *& f :gΜIHH/_J$PׯB UcggWRRR"2. ) ɔd \}|Ǐ#"":vxIN:%$$DFF:88[vvvZaaagϞm߾=^U*߿Ϟ=4iݻ;RQQQ5t(//_n۝-[F. @Hj߾S GZd2Ν;ebR9lذݻ͚5p;8/%%+aJ+L D"RDDI0L200`Xh4 r9Qʅ/Q嗜JĮ`My_3337ZXX+G܄V*&62" Ν`0Ct}rt= |T*&Z*Jٱl| -dWBtyС섄Ç9j !+ggg|+X \Ʌ>{Ɔܓ"WTTt:A"j=yh~yM>}ս#;z̩Suڕoaaa``|>Ŋ3gF^~aNAl۶m 8p>11Ç#G HJJjӦ'N8sLn>kC"K~\A,׾n[}nLMMBt:xRV#.[PVV^}9s9sFc˅W@d\.$Vh4%9@3֊Lu?7kl៵UvvFի^}G= BZAf͚bXP \.P( WD "jZ 4*Z=o޼;wnݺuΜ95w^z###>}Fھ|RsQQBD b#"rll7m4|\R!E]p*::zر"hʕ.\PTӧO_d _gϞ-]cǎZT(˖-x"޽;j.--s4hڵkɟ'O>|YYYNNN:t?9֭w^$拷ŕѣG.\HX]\\>| I׭[wȑѣGz,5ۇzĉ[jvZ~ՠ bw޽{w !sΜ9w q)Dvڽ}!T\\!ϟ}vb5''''''!!6 ښG. pƝdfflٲgϞEFFaaa_x:5ᠵ9SbiiyÇD"@i4|7 #r\,FHT*BJ6r}|^bW\uh4b|FFFŋnnn8)nmmbpO&ILF슜577s-DbW&&&4}FEE=zZ蕊 X Ad `1牉qۤT*gY;::F$&?4///"O\{A=WtZ L&yږ&hlllFwS*T*dI DVX-ܳgZneffرcF;;bZ3sXzzcΝ7l@͛G|*((_=rΝpB=O!#ܹsԩ?z"ܺup6mXXX}رc?sllݻwLJJ=,@پ}{WXm6]EEAhL>|}Vc6 ё  &|Ǐ'&&&''סpZZ NKB*{!K$\ل\㡏qc"2b``@Pp9dcnDo\zz!C$ɃZlY6oȑ#@WTT1͛/&W<|QCC+Vz |(ŋʶmFn}6yӦMqqq/(( n ,3gBhܹr<&&&))AW-ѡsϟÇ[h:x4kyRRsQ* .xboڴen߾/?:6gLLL~#F\rƆ\з_۷o;v޽B{{ 6̜9sذa:tp/P(JKKW0fiiI+!!R Љ%KѣaqqzW_~h "! ?UWDH$ C&Q(*jhhP(p"R(x~('H(bNr|@P7b2_x" -55588T֭[WْW^گ_?>4~[>B y'GTޖX]r%ùqƍ7B eРAyϟynn֡Hܵ3Xڵ+x'KNj8'ToT*Zݶm[]ݿ޳gwhʔ)ǎ6m_Ky "ZƏD"H$*//B|#HBP(Zfff,?j֬# ȓ+JNNG-.W߼yCPݫ\XXD'AjȈ\h4&>Nd2\iEEBB@oF)...,,,**z]QQQaaa~~~QQQ~~T*`0ȉaHċ+_, >Jٙ5D">}cccccc*tƍׯ_8qիWLLL޼yӭ[7FycccvzիWmёHU-Z%^9 NZ+juxx \\\LMM9C UV>IJJqݻwZnrʚ7k}ܭe| bquwFYYY8bVҥK.\@U6]y[--ZhQjjj6m\'G斚zҥyY{NHPo]O޺~:B|O˾}vѣzRo^hъ+"""js-8p0K+KRG262h4%f NMW>ˍ6lp밭H$z葱1N!]]rE䒒sss.5-4˸"T*e0"2*zRŋׯ_lWHHH&M\\\pa޵111111qttχpׯ_ggg_pBfhOV?+uҥI&YXXܿe˖϶mΛ7oaaa KPΝ;ݬY!Cl߾D…رcWXe˖/n?zyܹ͛7/\!t۷W3..022̙3۶mCDD]y[BPPЌ3:w⒗wQׯ|ᩩqqqFFFϟߺukνj>gg= E꧊~_~KxM޽!Ϝ93k֬+Wz,B9d ƴ|t1.%C|**Nl6*vqqiѢU[|).tݴiӲe˜kT*񩲳T*@ήT롒@ Ad*dt:()Zs}d/^HMMqX*d`\9''';;;+++''˗![[ۀ6m4k֌< sbx޼y6lY,V=/ n߼ye2ٻwlmm>|عscƌ U+{ϝ;T 0/knINN:K_ ?cƌ={ܞ?ʾ"۷D̙3wM ,زeK{A˖-o-lʔ)`0νʍ5Ntt/B6\>yOR&LpBZZZӦMu=W\\baaqƍ5 ݻݻw?ydTTǂB˗/|rjj@)jװ\yoFFF5W#rO6lذ?˗L&;aaaǎ-NNN_`Aί_xi`` n5kVzz%8qsʔ)yyyW^sNݙL 8uVT:pGҥK_"2߽{޽{GFFRd08a9iҤ???:a6 wwwwwPSSSO>?k4ss֭[w%$$cǎ &{رc Vh۶m0`̙3rȑ# :u ڲe˹spKKzO>}˗/ wމ'z8.\8v͛7[h1o<ղc,wcǎ=y$::6lAdٳg;w޽{٩SC~͟>}}= ]8psϽj>u1/9'TߔO4ҥKϟ&SgϞ)))Bеk#F 8P2UP+ްX,Bƕ1\X,*Ÿ[bSSSSSS .H?233 Dqb3338 "W\ILLxbR "99JhD"//*!Hd盛#p% Jd2qEdBؐ+IEd"ׯݻǹT*߿{- 14{lccJDBaZZZjjyСkPrΝ0@E/ך?444%%T(ޣG$) IDATɼynϞ=k۶-BÇ 4zDP~6=ywn۶MߞYV[s J * \e^CCøMBBBuoӦM6m>wv#OLL$Vg̘rssj^X阙mܸqƍu'>uzɓ'Ǐ/--rJHHS wNo޼oй;vx{{Y DK'644!k-7h|`cƌ8p`p=>OP rNNB 2N!Z8ZZ!rNBJdJRT*! ;wmvȐ! B$^zuw9sf~~~{8p`pp\|rrrƏ?R(=ܶmBo߾Wչ-[$ 8m۶6l8s B"cWn߾ԩS}}}稨(oDWO>˖-kݺuzzzBBѣGB&LWqg͚5{޽kלu=GN!k V^=o޼cv0\.W?_oϗH$bX( \xY(P!\#6]1D 66VRm۶{x񢛛[nn.4''ݽǸήrTY\\Hj5BHѐ'bԯ܋/&''߻w!2a„.]zt_+}Ͽ{Ǐ7olmmݯ_zLaÆ7={׷;f̘ϟ?J?~VE92##ʊi]L&|JKK FQQĉgΜ7mڴ0m_W|sׯ_~:sC]}nܸqϞ=,ãGDAAA= @ͦO~Q*q\}<3f7< R|ɓ'ϙ3o߾:! JT*#ɤR)#U6*illlee`0t:fxΎX&/h56pf͚%H=%5ڵkWXXزeK1;;cǎmG! r!$pZ&j*"PoD"QBB={֤I:ErԨQFRTo>rȄ 4̀Nڳg,RTǎ[p!F;{u5ƊD"{^d֭[B qX 9>k׮]d ^nҤI\\ܪUΞ={ȑ~6lԨQAAA ?^L&KNN>~xJJ F駟vmn/..ӧAJJ  6?~͚57o4@ 2@pAb\oDDtH$+BXXX-,,L 233Ul6^033377UMMM)~$%%;vԩS_r_"$%%Y&..GD{vvرc*??ŅX}=BgɃS H$8jL!j-azRM/gyݻO<)J|~z\իW^7oNHH8rH޽}}}'L0exTgׯ__pazz̙3׭[;v?t萩i a׷k?@U:+666U(ʎ;fϞnff6~;vѣ۷o ۷onݘLfÏVPPpՔk׮>}:t(""{߫W/CC۷o;88z8|5,--׭[7s1c7U*܃| \Յ%I͟V[333fx̬I&01dX,dGKM~~~.\011pPԝ;wkĉFj 2RK.}2.\ݧU&i4e堰Vbs@}Ypw>};>|pʕ{>ʊ(rd25BZJKKȸ"2"WTTPT;F !#T*y=y:`˖-{Jcǎ;wn͑Gόǎ;zK.LJ,]422ȕ۷ cݓ'O֓HȈ`qܚ+ YYY͝;wʕ:,h;rʂ1c,[lٲegϞh33k׮a綶F5jZNMMsW^]VVfbbҮ]N:uرcǎx*%īW}jΝ;=W^NNNnaaa۷?wy @ifڴi ,߿#g! nd2PH^J"ˉD"D"|D"J|>_,rX,SSSaffb =d08.`0,,,LLL d2MMMY, @#۵kמ={Ξ=k'}sDFFĴlْhw~~>bXDKJJlmm=\.N&r99L䏉xUOG سgϺu … g̘aeeAz@R֭[ծ]7vUCd޽7nK,!_ 'x<۷kN5jʕ7oh=zhO>;vBs9y$B͛_x *ڶm۶m.X@ddd{: ㏄#F4B(==}޼y7o2dȑ#G/:wŋNNNnzDP7o޼yS" ?~{-,,DY[[n: u-Z; =+++x9KOOd>>>[ kӦMvLLLt=R}qڵCzr駟f͚5eʔƜ%h4FR)Ǔdu[a4Ғ`t Z--;)33sܸq#FX`ﭠݻŋǏ'>߿u幸[޿k</j###:.JBJ+JZMR̩ѣYf=|Ĉk֬qss@j޼ybb˗,Y2nܸ-[WLvM>=&&N׃Ʌ SSS=zTkСCg̘ѹsg+"J׬YuVOO+WV3w;v\z 2dZZZKҦ]ti֭*J6m/@1xPjݻw/_,++Cknԩ-[U|rTTTDD! @}4iΝ{F;(χrP(JKK J5Ĉ ̴l66bИ۷{=x Q?K:ub?77W$vFC.%??_WY咃L&OOPv\" "}!.]wnݺ_h< ;rȪU|||F]/xǎ{JSN]d rի111M69}e˪/HBiiiӧO|rC0%%e֬Yeee[l1cFuW#F%%$&&ϟST"\PP Hjٔػ︦'!;e%,2U Ѻ>jځՏljRVRAQTe$$$!d8_>a#{O=$Br_}JKGE >_QQ0B&d2QT&+"k4DtFAdӧO;_`d^Bd2y…fڰaâE~#GuP^^Ν;l[o TܼySP̙3'..`0L6-))A=z|woDؓ'OΜ9sͿL&\a0Dh,**z!.pɓW۝tMvwwwqqqvvt:^h]r\(Db"s\XX(L&B׏|ws_ڵ+&&@7:o޼5kL<}Vh嶶p" zBu=zX|~  |sеTVVN2N={ʘlooOR;FT*H$b#!`0p< **CGm$[nK`5ss+P%h;@VS((E 唕=Z\|ɩo ߿? xÇbBeeFquu%ZD"B_,Jd!*bj5T*FV5 d2L&  Sd;$K+;;{*ܹs]=5:sz6p۷o-\… dXmtTWWwo&==}ذa[TTLy=I&"FM".]D!cǎ۷oO>=--ǔ.^8%%e7nWzFaޮj%IiiX,5tBaii۷WX=q^o8::vvv'NWYYYYY)FEED"1o$d2Q }FFF:;;;99:::{A;woM<@wn۶mѢEL& rW!4JUՍn6{-X,rXxo0.9LlXt/tc<;vl^[Jûs!F+ qÊ<+"jP(B P~7x#(((!!wt@BѶo>bĈ燇'&&wxN7{8'D"QNNΝ;KKKI$Raa/^ؒ 2B駟W^o߾V҅ ^{54~Vkh_aSոȮy(V"mڴ .DDDttVZuk=z%ɺH`)zLjJRjZh4 BRiZTh F!ot|\ښd pf2x`ڲX,&i p.]陔㼭ɓ .D=zȼ"r^^_SǖH$ 2,*"AdXP(,d 2FjFBXYYNR r_^zl IDAT{ˎĉҦM6tФѳ9xѣGF})S0 vB:tߣG{EFF?~CH$QEsϞ=o_ ^㏷n:{쯿5dz{{{{{7AdpDHe0l60L6_lFR!$bt|aq —3-F\.G) ^L&\%ZTt:VRB g% OG&mmmy<vrr3n;88=UTVVN2/^캷е0?|A\P)#BjVz\hIi4TJ:ǣ,h4?{xxt6mmmM81Lӭmll ܬ dڲeƍ|q8p ::gϞ`DϏlR{{{:Nb'' |JD"Ad"RDh4Je4qENRA?ɴbŊ={l߾}mz._}QRRRNNN#M&SD#{_~>>>֭>}z'?~yyy:.44?B:"xgOedd|77n(--upp߿u Btr NjڴiŬ:>N[~.Fw^;;z}ƍӧGDD>}Oqh~z||SZY>lwiׯ_ .--uss#w1bĦM憇1 q.\xy楥>Lrw柾7U{!D{d2 |x֢mϷ£G-[v;v,_?kdeeݾ}{˖-x7;;*ʲf*"Z,j*|~eeE9Zȕ!2LTDUuh4T*%H8 AdZbž}?>gΜ9|o߾;vć;w̘1#99y„ -9vO&v/^xOݙn,.....NJJr ^Y^P 4!$/m~˿k?U/8r/\O:5))W^i9PNNN||KKK÷o>k֬nV8^VVjgg믓HF+jexС'O?>##@L.\k͛F&{ъ%~q]%^h喇qآ-FA9%%%%͙3'44ӭ.%v=`Ǐϛ7*" H [7 763L|bآZJڵk͛FW >=rH{~>|h2"˼ rMM B;wXk0\r>@~-p 6Kx> 姟~={inݺQ3!>}wr3fػwatuюn4 D&LϥRihh;w=J,NNNǎ7n֭[[UWWhѢSN}h4ݻwƍ7nJm޼9!!?OZqXFS]]]__/4P(<{s$B4?Saz}BBƍ,Yv;yZ^p!r޽ӧ^^^T&.++3DNNNnݲ3o7\UUj,K(2̺:NGAx\xq՛7o;wn;zڴi} m#H$-90//!x>u㏉'Fv!yUڨ( SG 4ށ(J||#LrdN>CGGǨ͛79e?WUU2d2ٳѣbqS_5jΝOEA.'N9s KnΝ;rȢE:z:֯_qEK҆g=-B|Fz0"<==[^'JܿP(5kVrrr;O{(GGG[^^^SSӯ_?C~~_StrwwwFH%E᭚2Wwd!Ff "&ʊD"AEdrYpicB&F^'M6d~"l2^|J->h4N:u֭Zm7Bb2j*/?B#::СCfӶ=zpppHMM ß}bq```ScĈ xc6M"L0xTwIHH}h4:Rjqb]VRJNJxWRiZTht:]>} (*jmmmmmMѸ\.s8^zذX,:t:Ųb޼yIIILJS|g+++?#B7ވh󦦦~DKff&D2R?uԦF ͍hd|>_,f_zV(zVmmm8LTD<ש! òed:z"gРA׮]5jn9zh랢O>ׯ_?|}oT*U)JkkkÇ;wwZq===333ϝ;b rOpOOfng̘1eʔN%'''''߸qCwܹ3g4hPzBRd2!ˇ?~aY(?a7o, _}k׮5q̆@rrrNZ__ʕvtx~pmmV%vu:\.ǻ Br-nI#x<l!ht:?~|塡4fh4[[[\Bsuu}η ?~СCwZ~ҥKqH%++ӓ]^_PPLEbyY,#$ŊR JqEdGGGDN!CEd믿~?'Ni&zԩ;v)^ׯd2yƌAAAwݲe 8p`122~/VZ:qĞ={ܹ̏s'B1cƩSve#2mڴf֞_˗7`ҥK.]JII)**bXFڽ{ĉ?%jjj =!"'P('OXz}III%Hɓ'ѷnݲԝ:iҤ~C觟~ZxqXXXBBt!FQ.jJx9vEFh={/~x'v-mL֭[۶m{ 666;,}999h4FGGo߾}ԨQ$=Q]]}ȑ۷!LjfZ="B,KXTD6"!t:L AdR+ @SFchhӧH?zLA[n˖-=2LQQQgϞm |WǞ={bm E@@@QQw믉Ox˖-on8ܞ.Dwڵ/>\.z_uҥ2+=zĈu\2rȒWWכ7o6L( O0aɒ%E<<xkfO>>x2d_5y䌌 rss2BW^gϞ>|رcN8)d@KdddL6F]v|-3hZJզarxUk wӧO3gNee=BH"ܿ?''h4\.7$$dܸq֭ ny(+((>|8j޽{Μ9G?Ӊp)Jy><:\rذa'NtC&I& uuuV?S# T*Vԉp֖Fr8ppP.JlAS6nxر]vmذU 2`իW]vƍ :>dȐqv΋gzz,ڳ[Ν8FͼQ$["4\SSCk2(FaAdt@vڈ#,h;cǎ5L˖-~UUB% `ԨQ+W ivžx"f0r9O`.''wm***FtiӦEDD={|jjj.\x…/"&&s~k !pH NDRqDƆBx<سgφ,Ns\*jccwq{E3_fͶm/^*0CT[[{՛7o޸q#++W^φyMٲe I[gffFGG555}ijDh,BpĈEEEqFc2T*BWD>pw&D/?=6ѽkwWpz]XXHYZQIIhĻAdWWWHhJ5nW(|RL钓˗####""RRR|~"@7:{l_%"(qXVKRaND&mmmq`-I<+V|w6mڷoߋAdYEEEFFFzzSSSU*@ 6lٳ޿Γ}7n\zի*"r^^BϯB ajZk3#XD6L&!Hgu2L'OtD@7p:Bx" { B"gffLBϙ3ѣGׯ_!q%''+ti&?퉨"ả(MQ`0nlc2|ҥK}ݖ"֪222ܹKKK,X0lذbmۆaўm0̃T*ռCmmRtvv7\]]B!BN[YYH$\D G 2yDmET M ZϞ=+**:z_YY!t:Ey۶mCr/7w߽|KK4޺ukĉC IJJ h⊊|ͫWYO?psC??L9Rin> mO?tԩ-&)P jTVVVPPy8''߿ъ-nAd'''Ddy`0l6Z!deeBD/R(=rڔP(tqq!vi4K R:EZZҥK:xRRҚ5koꫯ6ځfooƌ3⋘}.`ѢE}ي+TjGÑ,TKN?~8y?t:6ݧO~{lZZگ_qYYYÇ7oə4iR36 " B^oXL&o'j bAd+++H"(^E;O2[+++;z"puuYtmXd/++"⒒7?rvv9s/^jժfhcǎƦ8pʌ0L^råK 3.Ef C336,*lcc[^~>-رcرc{r%H***p__x x].Ix}]`yn? 8qbçt:݃+%?Zy-|><\SSckk+WUU!T*_z"FTD/gggw,f2Zq4 V޽{~ *88mDF <<`0 VVVN:544t߾}OD裏F1k֬ӧO_ |o߾{mٲi )6T*u:ݳvh\d2֖JZ[[/ͦhG p\++:XYY5ڡ(@W4pI&[n̘1ϝx r7VYYYlD"BDB|E\\\pёKx4kId2JDBaZZZYYD"!St'''ggg^zoty:ѣGѣGzFW_۷o===;O]~gk4@%77! EEd\ VD&nDEdNG#3ȝ?@ sNGtgAAA=O*уm*ߪT*{尰0ԘZv&ɓ-_qĈ7n܈>|_Hk֬;vl^^wGh-6lQHW0mٲ%(((111::F r7PSSSPPPXXh;.**|Nj^Iήc'D"7#&(t pND}||̿^zG FQQ^'Hƍvwwo͛njl6_~DKNNuvvnjRhY,h4?Z̃RD"L&"2qB'qEd ;<@4nܸÇbEStqk9NxxxGO1hh9,, !TXXwSRRxfƌʺq-O흚lٲǯ\r4F^W( e2Y  ~0?Z-[vѳg3gL2g#?wJآCk&SS]vп3fEEEZ^C jO>>>>>~~~^^^НD¢t:BcyrjjjSӃ۷1BAdP8h D`Ly/`e2+"L&y*"رci4tҎ ~g??ϯ-5 "k4ثW/PAAB$""=_WرcӧO_dIRRǡ0v1L2 Gm ! T*ET*VJ%7d\WWg~wP(8L"\.B Gq\b [[[f `0pvRRR֬Y[omڴ%xtbd0@ ׯ|}}{Q9cvNW\\\PP@ړqd ?]:?X0m\\\ǠP(x}Czyy1|zʕ+gϞ=hР>ܼ%''߿ad2xJ%I"M \__gF#L 2x)̟?Ν/~&SeeeLL̕+Wx<^TTԦM̟5'Ny[>|<`\)x1Svnݺeeeտ&ҿ(ۊjF")&qё"~w.;lذFywV^`XtttPPмy qXȣ?zT*K.?o4:gvL& DI$Ţ8pLܧ%/A+pJ !P(z=!RpCPaaaPubS([[[[[[rmmm>l֭KII=ztZZZHHHG@dFQ& rmm`J9^Ձ#x<^3\Ev{̛7/..n֬Y;D}޽jZ ӯ_s}' 6*ڻw޽{O8h-((={/4o߾ xRWWWRR" JJJʊ q)\L Ző#Gl7SUU%+++#WTTmXlOZ.N4bL&dW2Q_4eP(!LÉd'''`oo&PVV֚5kRRR^y[nx?%[X >ņ%ϺS"HVgdd۷% BgϞAAAJLLL@@?虂0`"ܹ ?ZZ&BcP!tK^BBBP}TO?vwwoOvvN8p RUU%HZͼRkjjzA !lZ8J +++ ^5k֚5k&N >#ٳH YfƌNھ}yǏ#ze˖ݻ:##/϶<|=PNNNNNNbb{f[OǘӧO3L8:.777===--~^8p`dddDDDhh(Nw98>bBa rUUUnnk׎9hK,QTU&B͛7oҥ!!!/޹s'P*bX(D"PX/HDd2QiX{{{;;)~>t:N?BfF.9oQQQ!H233qyG.+3wvvvrrruuutt=W\\y+!!aƌD. Lt:RCuuuƢ0^1%&b*Je9Z[[ST<ƆFl2666x=3Ábo|' ,xPCB<(WE=k!,{쉷u=^bj5TT"hPeY"gϞիWw\^ӧB+W\|9B>j~!aذaO~ѣGǍ7}ӧk׮ }Qrrݻ}.^|Ȩ(Tj`````ŋB**+++-----mk׮ :t!C:t1y!GGG " ͻOVVV6ƍk8w}믿^xտO?ݻaÆk׮޽ tuxA.%TSpp \\\pG!tCh4JH$b1~e'kGѫWF+w-۷o駟7|pγ:VRZ^i%96Z3x 7Su+!G=zomQJ!bl^TWW_tŋ/^,**Raaa111aaa=AZy„ xǩo߾qƾ}t:ǘ1cƌ3j(N!.+E"EX,B\.;88;;;9;;@ڸS{]K$>`k999=zhnHDb'h4JRbZ!hpb ` ^:nnnqqqqqqƍ 鼐'O &OLDEE7n(HRRRRRRB$)**СCz1D5㬬6l@4n>|jO6=lذaÆׯ_~=))iǎ&{ȑFkwU8bD&NNNAd\ܹsC'++>Xnȑ#bd2>?~|TTΝ;k6w-RÇ>| 7@ĉ|>]Zj4\Z$+++ggg3U'7nLII8p௿:iҤ* $ ֡Mun h#|>޲eŋ[^/a A6hn޼yŔL++!C#G 5k޼y!Z3h 3fѣa=NL&H$xgq6!DRFK0L^hho|h>}4Ǐ,Yb" mllX,VUUExF$ pXѰ캺:&iD&LSޥtok֬9wܬYnݺ w-7oSRR^qp6l())i>]Ņ 6o޼k.??g=m7o^r寿:x D =zᒕ9|H8;;7D LHHXjUAϟGl~ .oŊk׮nP( l)DIII~~~~~~^^K$N򊈈x\|?pBhhS! xAu:thϞ=6lh!jNw`݃ZNNN>ydRRR]]]߾}nj1[LСCT*/_r-[X)S̞={„ 6ίR,WTT DRYY)HFee%ﴲ+L::::::zyy GGG'''@AWq[nݾ}>8pycNN3Gd2LaX^^\UUe0̟u 2rdS! ^.VVVg͚u|8..nҥT*բہvu#Fì<ʺ{ݻw<ϯO>&Lp_}7TΝ۵k.`OOP(;_9… ƍki:h4uuu8[WWhT*VU*:`0r(L&T*5 :NTi8bt5J%<gϞT*ښNX,f4XYYq\2r) Fہagglٲݻwƶ/{Ih?ɓgΜQT;w:uj+P]Y[[O2YVVvٓ'OQQQf;vlWY RY]]]]]]UUUjjj+++,b|{{^z <ǎ|ކ'=Lnݺ^{m4 sS B @,#,*"r9ncjdT*?Didyresvv>sLDD;wuoљ;wP(3f8uԮ]; 8pٲeÆ suu-))9vB-Ȑ!A|~FFŋ݋0aB˧NڷoCܹsΝzѵk׮\}UVyyyM8qҤI#Fxi[OAFFyKmm-D***B{…ݽ{͛gΜItoFP=3gl(t:_~c-fCBBBBB^_XX}'Nlٲd2q8@K ߿['ݻw,kҥgΜj3Z qaB- cQ IDATr&I&F\ ⠰ B9`rT*< lƇa\'@[X~曆+/5JV"W/ɓ'pqqYlٲeN<9u=z,]tɒ%#ϡNxqee%Nc8L&4׳gO;;;±aBBٳgS{ gggȅd2("QLԁgXj`t:|}`0dy @gΜx}u,B9KwѿIF_ʕ+wA<>pׯ#G"\>aMјb"##N:eʔmѥׯGDDD"O~իr9b?jZ&g-_K={fBѣGׯOHH ڰaôidɓ'ΝK/qƏ?O*n޽{GAfyT^mm{4Z- :t Ԋjӧa_\R)Iqn5}1t9=ZXXؒ÷lrȑGĺ7n|g)))^`A^:zR߯vZ3T/SM5VKJJju癧cǎ}"hرqqq=)\Jcbfgڦ}i^!-dϱe9qDrlQEPEh_fi~24ݦ\N眙z\*Brrr***8^JTTTDutkv횇͛7]\\\˫C yT/888$$˗/666NNNza7#""ϟfBwqvvlnn.((HLLDѣGR(---wwwC9 :kTTɓ9x Ϟ=Ν;q{ɓ'󍍍w\Qt-yyy...\.R!2ܿoٲ֭[ׯ__t 8a777##)FZammd2srr ***OqďӧǏ%$$,--7h hDHQQG=zぁRRRvvvCUTT=yQQQL&966vܸq Ϊ;$1̟GapsÝs+V8p̙3 ,hsa6lpΝ#FDEEw k-pNׯ_ um000ذaÚ5k]vر=z۶m. ~ 6][[[:_AjX]JJJ?4MYYYOOR+~ KP]]_y{{'JKK;w.۷oO \\\-VH!deeL&,%%ߒlllrf.+>ťpwwt /]妿QWWtkcccccuۼkkk{Y yŸq455o޼)Y Ν;w\uΝݻwmVVV")3 [ZZJHH|" *))WH8٧O謬;wz{{/w̙˗/Sʊb%%%ݾ}>|-fgΜ9hР+((tq۷o_/_RSS?~|޽}H;;;;;;ӧ=;w޽355]v?:^}}}UUUG%fXBBBQQ;|xJJJ.G)))/////{߾}0C@0S]]Ma0t8XHPSS322R@իWs8,\VV׷o_޽{ _e\GG'55=S rII BB0L*b$$$[RRRAdAd[sss{^ -BQQQ4hЕ+W!IP8NjjjlllDDĎ;ͧN:uTCCCQZmBR_zE~uss… sssW\1yd#\n||}||>...>>666^^^G0`|BՉtrr=zرc[+++zKRRRTɓaQЙX,VCCYWW?LfSShnnrt:]cRRR222 G~qBh4}}})))IIIEEE %%%2LH$2LVRRcܴXZZn|xxKM&|I6 c}$vDDԩS*֭[!.tmIIIϟ_zE]USSd2p6]SSSWWd2 N't:'Lfmm֤pzXAA411hic]ȥKN~vvv hInjjZ~ta8@+R绋L$iƌcƌ?#Ν;%NLfCCCMM SDoll$_>+**b֭[7<+^_wΜ9cǎm*O>޽;j~?VXX-..n9.!$++Rl6[QQD"x<.K4K9"144|%KgϞ @HOO9sϟ/^H4[6lHHHܷo߶mF !ջwÇYYYyyyG)((+&&aaa\B#**J|[[;w/^pݻ }۶m#Go V|===kkkǏ9vX hjjb2nG_Z7n'FڻB~vycʕvvv B r]xqѢE7nܼykiׯ_^^Bݻw 7f̘xb|!22RL2-[v:22ɓ'q~ΤzO6MJJ]_ss3p8\.tPuu5'b0 8f0Dk;RRRSTeee|K___FFKKKh4"wj lۿÇ5e0-JKKl@D"ikkRSYYI,_UUE"@xSFFwD&:"ʆ9rѢEaaaÇuQ@nڧO.WXRRr̘1cƌa .\7oŋ'M4k,{{;%~u޽{TTT߿_^^տ!C&XT>,Dss3Nwttu떁ë#""\]]*h4/_\p㹹uuuoxt~\ZF}GPlmmGqAd6-}M~~y|}}6 Yzgttݻ8~x||¼yf͚Μ9~֭0vҥK۷o5k.BիǎSVV޶mݻwnݚ{@А!CVZecck׮T.={ݻw{yy}eӦM߻w/ |+Wvʭ!d.\@-X`ѢEFFFϟ?߷o_;0B(&&&$$8!!!6l~{9ekhh`1o555fͲu9ZtWSSrkkk9hnnf2MMMuuux-| _.Óq`1~ͽ+++KKK())+`222x\ZZ+))qO?M֭[ŋ'w9Γ'Ov?I&-,,jDVUU...Xd2T*Bd.+##r9Á 2)S <9r3E] ɹVWW_pԩS'O444^xqWZGAIHH$%% <8""BKK&"mmZr[.g{d2Æ #ʋ-Zxf%VġTq[Yf͚5O>ݷolٲaoo.t%Kxzz bp~gQ"̵kB+W\lBh卍k֬!8}D]iЅ[m )(( 9z֞={? 9oDsss=f̘sί-3pyoTqЫW/OOϭ[ah4N y=Ύjjj^˗}QSS+//os[۷öAmm풒;xJe2...>َkr/Gbr @ssD•ivvv<W/^#b &q6Or˳AѪ\`1pڵwމQ]6"_0~{#X0$7Uӽ!iiiaNH$eee=T*)-P(=*))ߴc֬YSUU?a^^^ʕ+BVܾ}ӧ?|?8m4&w^SStێ /--fkjj:88$%%UVV+**֪lڴ'@4!wwÇoݺuغu3_^EEۏ9sܹiӦgee7o8p`Сs]x1LP)(( ZzEEEׯ߲e ]i&|0--M#ꔔUVՅ,X@|&KŇ޽{WZvډ'9sco*.|@waD3x2hjx+CYYks! F/p!Svn//LRRrܹq&%6 ۣTGGGU17 !܌j5|Ç䘘kkM6}XMKKKE]oG9 .Fdm -/"]˭(Z`DxX`2 A|D|Aϥ8׋©_iiiuuu2#OgTE~8!D\"#> ` ?pڴiÆ ۲e˷~BSLbXXX#>:6440LJ]]D"?l6o@XuD:ݻf͚m۶͝;wǎ[n8q"\%ܹJJJ3g|YdǏqѽ{M}.\tO|剌 Fj9Οs5L&f3Z6]WWWSSf 2O?A3j=3-g"RFSE+))rD'9!FS+ S&wt ,رcիWN, ahh+QWCC8???<˿YjjjDDDPP1xP^{}IHHpG޿ 622#T=z葑b <'w?##_-[U#*_VTT5j 1"hIU^Z;~?$G} c^7"/˿S"{_N3fZTT@==z$?b}:V G...B_qbq\Ͻuq~ꡡs]vU\\\;/N߿N/_|-sĉ={9sȑ#cǎ:tK*eeV/ٓD" 0|PWW1cFfffBBBWCw'?cccUUU}}} ƦMbcc9… ׮]KG~ECC2 `ljjZ~ta8vyyoRRF0a¶mrQQQ>|uuu|}} ۬Eb IDATq "co9gHHȥK޼ycbb-6qk;mϑgddL>Y :,,F/NWWW_l6fWWWX,6]SSSWWf|RfѐUWWo%ҷ/A$6eDTbDpG#p8>} 6{x_ s(///''@PT2FSS hkk;ѣBrL=233L]0 ++|8N9\:Ù4i҃>>11ŋ8::hjj"$%%H߿4z¬,Wrnݺ}YFFĉq@WPTTi&yyywމ"m\}={p{*tRO>x{{KII]x!djj:~xnvv6Bӧ^R.DEŋ999^^^!555+W 2L&͛7Y!C˗kii!nf=[nmu]j[j/_d2W\>KKK߿cǎڵk͚5>>>^^^NNN366nىJ^^nӧOݼyC߿[8Nn[}WVVӝ\غt/DMtuu}||_>l0qWlʔ)&&&VsܣG?r۹}!߯_|9m4+++yyyUUU??/_x<;i$}Tܵ=~TI&ijj!LLLjkkW^mbbB<|΍xbŽzRŋyvNZ^^G*GGG t)SPǏFV^mii)0lٲ_ӧO'O!gΜ9vXUdrSSp8!iiaÆ988 Facc#))I chh8wcǂ+ ;tPII㽽nj#p3gμ|qŊǏo˗/ (((S6#TTT4O6HPPК5klmm?.[[[<{.5׮]9r^;Vx666xQbxFTBG;۷oŊxݻ#G*..npEI6wڞ#O/_:uϟ= ʊKKK KKKʸ\.^RFFF@/[UUU555uuuApXyyF|蕕W֭[+JHH\tiҤIM#fL'## //?hРt99H>J-Zt1gg۷o`HII9s&Ls玱~}l6ӧwܱ 2W^ǎ>|11miݻ7bĈR b0&&ݝbv=,,ǏW555d2AVVѣSL9{,B7x"R$r.o@V^bŊ7n={KAAaʔ)3g߿͎WQQ:uSE] Z~}hhhHHHhh(#0,k׮ݽ{WQQ-))͙3B$O> As 0n8J`˗x"AB6B&LXf ~b[8Lu_7mTZZ"H&LWUU6+q<[3gB?x8m6Tl9;;s8/_B/_|˗/yyyϟ 2$ICCCCC;qc ]]]< c$$$455Bpܲ2/+++***)))--}ݻw L&~ݻu릯߭[nݺikk dU/`ԩvzl6BB. !&H$<#Y rNNNׯ~MddsBgu]5k֜={#,,LLMکS>~֭[?ɶ1--mܹ^nnn< !TPP*''W\\,-!T^^_!\.áP(;NCC$Dq_Y\bDĥOJJã"**ܹs6551cƴiD$@T8Njjs碣&L,>߂%%7\2<<<(((((h֬Y6maOWW7!!YfÇGM:5""D"m޼-[[[\.4VVV/$$$Z!ҷ7nr;wח/--R{>$&&&''X[[oڴI\H໏փ|Cb]19A&I$R,8~ӧ|6sII \!##CD3 [1L&?T++V`X8\RRR\\iii_ZZZ__rn݌uF|]ydd-|Q]?KMM b,b2Ld2kjjl6욚&ft-GQQ۷>>>V5(UlЛ!dooS'= *Bvv[EEEBBPedd4kO x***낂JAd 2}V|BtajjjK,Ydϝ;ثWqƹ <¯:>>>..UUU'NTRRui]J]lٜ9s|x}}{Qoo%KXZZ~={3L33ct :{}||D][~Yz葖2=z!e333!=z WWWD&Wxl6FAd2 )LMMm޼yƍAAA4yܸqGVUUu7o޼yѣGd2aƍ366uiK8qbƍ ,䛣F+,,\zʕ+ϟG{fSӧ͛7?q'޸q޽{1bիWٳj*PTTThhhKKJJzzzFGG߿![K۷E?]]]322ݯ]o߾;~8zzz'N1c[?#O 7ovqq֭k7o޼~CNNNii)BD"8F+=V644lҏ|=ztjOӦM۴iӽ{AD &_ǯluRRRjjjrrr4xC[}[%%%uuu.]M (ʊ+-ZtܹC8qbСnnn4ϡx;s9ɿ?k׮={699wއ1cƯםO˖-?oS~Ǐ0@`ׯ_8//F`˗/fff< 2*../T*Bl\ ~ƌL&O;|MNNnnn2dȐ!C핕E]]ז2vQFSozݡjjjs w,,,^~maa1eʔϟ~jll466nhh(//?uꔷ7UPP8tٳ;qѥK^zUl'LW qرE=J,֣G6;2 ++ЙRRR233-Ze˖qVVVeeeˉ&^xڲ2<POOD"ijj UQQF/) &Dcܠ :" b_|INNNIIIIIy=D233ӧ5\+$eٳgeee222stttpp4hP{BzÇ'OHRlmmMښݽ{wΜ9vv'y'$$:ƍm6sՎ]ѢE;|mQs>{?,//r tZUSSiiiRRRvvv. ӧ7o޼}իWϞ=ϗ077۷o~kii)>hHh %''w8%rϟ?׭[7!a.$qT*fɒ%߽+W\z,|ԨQ#Gtttwd2߾}KP *KRR֭[ݻg7o\VVߊd*))EGG Y7##555%544+++֢P(G5kB%>>jii]VQQqҥuuuÇҥKKPpdPjj;|}}3f̘1!TRRɓׯԐH$CCCJƹd---QI?~UUUݻ,]0X8p`jj[-[ֳgM6-[sɪ*<%v޽K.k۷o"[XX:ܖ-[cffk׮3g]d~zkk7oDFF;w!4{lQđnݺѣÆ s玞+ϟ޽{޽lI߾}W^ݷo>}W- ccccccsYYh+**3`#F8::vf'Lf;!q=&Lyf˗/ϟ?pBcc8A]w}>>>>>>>>LLLLHH8p 6`@+O<99'CINN LLLMMM ?|F >}[a_t7;v...?i_/^:d##&555{{{ >/`iiYZZZ^^ pcǎ}}||v8}tP/%%#GׯHn766VTT3Cr1 4а q6$3f͛7 rSSB>tH #FsΊ{%&&^xq$gϞǡdssZ 8Λ7o|ԨQ}f{yy9ro߾?uYYYCȰns###FRKJJD&:"3 )))ii銊 p8\.{%H$IRRI| 2vrr"F?~H4.((xEaa!N h4EEEeeeeee%%%%%%EEE]ét:?t:$ZZZ8-=t% Vzlٲ^z͟?_xCoxb)))m޽ B)Ȯ],,,qVMMM ׯ_w 2fddt̙-[8p`ӦM7ntww6mȑ#җGzNǷt #|SSSMMMCBBfΜ)VYx"***::://wԨQ. ӧO>}:B(333!!!11qݺuǏwY,ɬeX,N555uuu,`} "''$//OP{ann +''HP(2B >\FFsAD)AxssW=z;w8pIJJʪwVVV=m*,,|UffzٻwoOOAYYYOgAD/^x̙ǏtNS$ee޽{ gdd9srr B# 2Œ\T*O777d2">[ĥĜt^z%0b󋊊pᢢ" rnJG%$$ZYp85ķRZ%jjjݺuo g7nȑ#wڵtӧOq㆟ߡC\.n`0Μ9n: uMMM'O8anaa͛/CT +W={ECCcӧO^ >oܸq۷oxzzn޼ѱx򈈈SN~pʔ)&Mu] =T.V"b""">fd2Ϟ=px<8&8yr۷o_|SVV)a*j``P( rrrJJJ8 9 T*!>>bERRvҥƷofg߾}eee!SSӞ={ڌk2( ?~fgg߿BijjZYY3f͚5z?<@466޹s111!WW 68;;wfod=Y\ǏG"YFF l%%%D~e#2.R|bR]B133333 ֶg d2F#0/ ++{xx̝;o˖-?r1Ç+VرcSPPЭ[7ХKϟ钒x]v[!daaٳ88QRPP?ϟ?GDD;w.44h̘1G:t(But۷o߹sqww~s>}ڷo?#!!1qC9889'L^~=00PU|SNKVV͍dFGG:urrrs?K6lk"'iiikkkKKK333pH4!!!$!!ѽ{wJ655ѣ/$yyy8spBzzz֓'OիUS@;q8GEE]zN8p߾}SL'0y<^rrڵk߾}اO q8Ar2BQJJ qԸXD]XYYn۶mIII'Ol9O{TTTL2eܸqqvv6"O4 _UPP zzz7oްaɓ O>Muٳׯ߾}ΣGS hhhxyJ6lǎ-7mݺu޼yJJJ. AQQQÇO>^r!?L6 ?t-n$//t Yr֭[l2gΜkptt ჱ1 MMQF5a2d2W GodqURR7111559s)n 'xbݻ~zllliiͺu뼼 DURVVVEExzz:!|\.8::s8---w+++ 2knn_) Œ H x2oB@gٺuĉΝۿйs~x}f|>wttxɉ:;;[XXPK"H BB*X,GFF3HV@TUU8qQ(scŋY,q㜜`2˟L"XXX'**_~{HІ«!!:F3}={̝;>*%f[$/^f _4[Aر=BO0>h 'x5F'<5JBM=z$H|||RRRCn޼m۶2.tR|6Ξk{zo_sss}||.]:bb UUUNSRRVZo~{b4u:ݡC233󝜜z޷oWaO8sK ?Co{75}6F_lw۵kWzzԩSS]FȘ2eZP(p8޽{7kd8\ZZJdUsss+++E"NëYXX8;;;88xh۶m#F4u:+V? {LFՊD "J,b|%!6rq(ƀ5&X6PWW'd2^J2*1BL&D8qpprި@ݻW\Q(AAA111`s:/Zh…ZfӦM~[;wnNNp޽^xqٲe555^yf###Ξ=R,˫jjjd2ْ%Kss}痔T6J֭[s:tܹs׬YF7n/6L!#|||\rYX_cǎӧO[YY}&M{O:k.m{믫V4ifѣ۷ogX_|ŀ|s>󜜜zŊ XW+VtR{zL{޽믿=ztYYʕ+/\vZoKKK9rNq"zҷ8p!4}3gѣ͛ׯ{gdng5mNpc3333÷^z߿_.|H߿xxxwy 0`ժUUUUj ܾ(cSrz"lX{̀mllX,`2,N[XXXYYtkkksss:f 666d2N "HL&`H$jߋVzBT*z}mm-BQRiں:,JU*Uccc]]JJR&d2Vl,np"m38Ιе<|ĉgϞvZCCGbb9sUUUP[ǏryXXsɓ=z4k7BaQJ$"I\VV( Jh!KKˊ l;K?j?Ӏϟ/&Hƌ3`㙎}}}KJJ8п]RXqroVPPЛDnw%fŸ$;wN<~zVK&i{NWRRGI$eppp>}![|͔)SA !t1Ђ >Cмy󚚚>cb!4}o"۟Ay-YW^~=1Mرc9NBBck֬YnG}T^^E涳ʹgŋϟrww_lم >|:~N?CFӬ6RxttcǞ477O裿5 Y۷tq~5k,))- K2c) _YSSzAƏxB3tyyy#Fſ jhh8pL&f^x`++O?~RSSr9IoO?}}}_YYoz`aap~Kkܞ-=h4fff$ x}hcwޝ5kիW ?w,b{>Ulp󆷭/vi#F HG1l,..ȸzjFFƃH$R^ӧ2 __1cp8UVUVV; ,T* BH*"pFYr "6.NP(4 O 301B_Ég2lccG 7\.qիW"##bbb?0`߬}ѢEΝ}v/jL&s޽ƍ3l_l٩SrrrvU4GF-]/tww}P(ŃΝ;Zp  PRRRPP؈37L~~~oߞ>}z||ŋK|CWR%'']z8"H F2loЊ+:'~F FHHHHHѢ銋qۧO^tLV#tMvwwwvvvrr]hL&%%%D渨,!v IDAT?{l( "4޾1gBFi4!\Ot:]JJʗ_~bii)|v{$ZlJfݗCBB233?~|g^t㡡+W|uH7 yw_iiq%-wwwww#d2˗O>}?b'%%%%%&zΝ8 :nC?|0333###33ӧ$?::zўKvZzzS999ZSZZV9P(ӧOgRN#εB QҒxhnYub0{={Ggii9y7o^pVVVΆjd2yŊ'O>z(m29x`QՖ}) q9;;>|'''wd;\^QQQQQQ^^^YY쑘2`yppaÈ:dfׯ6mZ7Hyzz޾}ɓ}n Wؿ5kƽ{"z ;sݻL u4Bܞ=GNNɓ' aN)))狅M;Ç߾}{ݺuT*uȑްa ;v@«ÙYΜ93{lTDъ/_{G~7 .D:thƍvvJJJNNκŭ~ر7[ٹhΝǏrO"""fΜRZZo>e:3 v~yw'N8vظv5jԨQ}]ffӧO>k.:2lذfÉв &Ƿn*))A999ׯ_wӧOGDD˫w9VXXfmmm:Q*:995{V,#8Jr^+"766j"VqQdLP( lz)o8HtUV z-gg粲2d[[[\1EdY6o޼siӦuX( \d0+2oM`X,/`L&dX,:<ɔI*FLP(d2L&dR/4{$B!33NNN\.Y@}t'Ol-+VZRzĉ)))K.]z5xmﱵ6driӦڵd不o޼6⛹`}F} xZ:vwӶm,Xa:2DgΜ9y_U__1|wyϯz۩gg紴 xgG$111p_~E  >|8}NM81$$Dӝ|m=7u{l+++/_bRRRbcc㓟W/?. Pȷo.^XRR3jԨU7o K~ ]|˗w8LJucǎ"oѣG]t}V>>.\"\$ sss߿?zH.dOO#G{yyu,ӧBq=| m:ƃ(A܍d2SD]:m۶mΜ9k֬裏p DZz̙3}Ϟ=K₃޽kJh4mf#:4`aÆ]tva. j 6d bff֫W/St96g-]t߾}ׯ&M4fSj;%-gΜ9saKttQ^;M ZgC'\r_~aȐ!W\~} ɱ6m:.]hѢ)S$%%u\onOV?}ѣGyyyx!pg̘`\RT/_n;wju>}rBa\}ٙL& B'''J${{{XXX mu:BL;w%K.\%Kh>@N6h?}tttyhhŋ _d2Ba{x7ol6?^Ǜ J"VLl˗/_t_| ˗/5kKNNƍ.]lٲEV!6-J)ǫ$trrx뭷/\`ڛz+Z-BHVG-ׯ_~lr~mƍ˖-xO>6lX|||XXX7HQk1\8uu>~&;;nܸjCBBfΜ9j(???SXXXL0a„ ׬Y7mڴ#FtSw]z=di4B8+**B4G~~~Ç6u#G7nDDD=VPP`kkk\wAhh L&"R477ollO JL&"kZAdVMvqĉmnݺ{/###$$2..!R򂂂 "K$b.B_qqqIIIs!.f̘'B'O?mR 4hΝ;޽{ݻwW^z!d2 0|.WgNMMMw޽u?syL䔐0{xUwgΜٱcĉΝ;a„ 8pңGhLOR>ɓ’|xjjk$;wdggggg_~,888"""---""ߟBiʕ+V8ƍ⼼>46m޵=5455LرcS7nNAAA~5d2LZSSRJ"" D"1" J655+NDZdK.VVV[߾}KJJNJ7++NԴ1DgyaXȑ#~_~%11=e2a !ZPyt:DR]]-Hrss .\pa޼y={ ٳF{}M*>xQ,H$e˖'44` LNHHHHHvƍWXaÆEYYYwںt BQ\\\򿊋+**p<庻/55]\\xnC;v,55g_F۠Ro`+ѱ UUUSljP333KKr\BhZLjׄjL9W VkHPM͜9sfܸqYYY,k׮QFsܾ}RķZB%%%m-,,Brgǿۛ6m9s'n,AE$BUUL!z׮]鮮Ȧ<Ӑ L...իWkRt: 2BF/k֬3gέ[mfffb]Z:u*Bh4͞P( @jjj*// RZJ%*pl#www cD ^Fx^FR5eƌCqFͽw^nn?X\\k9880b^;@ #BD( 4JfX, 0^XذL2Yj2V&Bà3r6s!RϞ=}ԝ;w,,,f͚uI}vv6"h4>_\\v,,,"#H$rg̘QYY~HTQoѣReee?%X,6Xl؂4`08kXXboo=b0 f333Flٲ6^HRƌ[JeKpO;;;Hvwwwqqquuusssuu|7E$0Aqqqqq1i?L& 4h̙x5u;56tk׮'M|rA(--~M>}8;;Sl8;6l0dӧO+**۞899pu'@hª*kkkX,FjbaaP(T*T*$`h4,> 2]J[ IDATzqAA~DqTSzH$AYYP(,//+WBT*յų,uݿĈrܹsmG޽{K.;~;C"铝MVRRvDƦL陚?z'O<==z=!V[^^nbX,yb"HqSS38LH$qȘul5#6]QQfȑ ,ݻ l`6655>~ɓ'O>Elmm]]]]\\(|8݌J"xd"X@x8plڞwiL&s̙ӧO?rȊ+<==Gz=z?z^PsNJP(գ***+++DBh8j6p@ggg|lggg#OAAAnnMZ|ʕ+ #221n/--5BaAdXLCFPT\TJBtV JJJr~iiNP(nnn}0a/"'v$x'Dyyy7oܿD"A1LPl3芎?>yd//gvH-w4(&&fҤIsMHHuܞ>}vDF޽{wڴiqqq˖-[bE:Doꬬcǎ JJj~j555ƏDclXfJyb{{{J&/joo_QQaee5s̹svxޑFSfR/q[n9rR!T*x|>ɉ::::88|>> UWWWVVD"PXUU%BaeeeeeeMM ^`(\ td2yԨQÆ V^tRݒVD 㲲򊊊j3wqq۷/.#<ѣGZ٫W/~={# ͂ȵ "2L Dt":H߾}4ի̙3=<<=vftrDcuuu^^^^^^ii;w֮]+H$%L.ϟ?OKK۰aC>|pBЪU9W_}+WDbHHHk "#=s9rdݺuIII/y,K:thnn.F̬5upCDIcLf*&igggkk}||QW2fX:犎NKK<==\x<F͜9sʔ);w\jݻ.\` ^dDPCDB(D"D988jfM{wNMM533kqvn`ذa] PUUex d捍$RTz"2BQzVɹ|K222 Æ իWHHu3.*****hu͛7oݺw BoLLLLLLll rҥ't3gwf󛚚pq~9s"##d7 ׀ڨxXZZڠA'$$| ի/^xCݓSRRbcc[jEEEǏsΖ-[f̘ۏ?vuu%F%K̟?ٸ899tx޷oߒ%Kv5b܁޽[WWcJMMT*ŏ-1!ҤDb//HB*o}j Y&“'O2~q( &immmeed2---LTYZZG*RVVV G^yHNGzÏV1~DϲqRTPl1544(B ,0b,ީn׀m۶m޼yqbbѣ׭[hsP0R)3P[[ pX*T*Ұ{3D>'q̗D":8L<==__"!C xL-,,}b6\S T[[[YY)qcXCĂ&p|>qss߿?9$ šC-[ ~~~*((0333_ !"z}AUUU/t&% d2$H8lii)Jq-dJhJAEd^G8q?v횙Y||ڵkd2B7n8w܉'6olcc2x`[[[SMj7m'ܺuUɓ'=z0l2eʪU֮]`Zc7olcSfffŐH';ׯӧ[oxAOw7se;; Swls _H& c/㊤ij0 xL&d:::s}\-R*666* L& aNP2B#SMMMr\VN""BDGlFE"q= `*PGk!lauLbx!j5zVYpo. \dzzzԆApKKK&ieeeeeennnaaaccC&_/_;w׌3VZB'4D"AEd`LVbDSxY"TVVJdMMM^hee,kR 87fn=࿰1kJ=6QY <|Hi!h666/s\t555xgQp}x< !g :'O644BFFFttt;VPPi...h|>?33!d\YI$.H$cP(t:RjD̬WGng?_N^Fnn޽{?^XXrpD177JKKO/!6(Jⷱ5cFCcHz\.3p80tQ{MLLlmRyiڹǏQ(2lTh 2 `ooP(BxsccFAd|ABi4"Б8w[n7.%%%22Flrf͚5kV}}ӧ;x $''O<9))Su߿An@Ob&Lh,65zOOOohhعsԩS_B![YY7n׮]rG}sqq9sk"֬YsԩqرÆ 9rdBB$GI$SwxFvpo,Fo\/t#б<8k֬ӧ{{{' ,".!"qqq1Q绹DDDVҵ7msH$v&D" ;Mvwwi]]mmmuusC555Px={8dLMxh܁,--yV=zh?~}>}zݿ駉'.]S]kxx`}|KKK\ɓ{?~M:w﮵9JٳV6lXh^&,**:v„ ?w|||DDB`>} X,n lmm믿ZPУ̂@}w@+J*&hDh4zR*%L5 eժUӧOwww_n?дDn bӧO=zTRR2778p… ===r Cl6O>}!H$t"JM~߿>}(H!&gĽ<ںk׮xXn;f}ƍW=m u4dgg#$IYYv 2>mdkjjp)%Xb2ɍL&fP$&&.[,))i̘1.]$| Fddy.^zݻGDDYN`Jŋ6lȈذaC][AIIm]ﺺ)3 IDATٓJ+N:rȷovE^ O?q'$$z"ŘF :t###}}R4#77k׮^ÇǎhNBhÇSRRd2U~.\igg>ݻwĚԔ_~Jz{{\2Fr@PUU%vs%x++.]Ԛ*p8,K/zgdduЯٳ-N#JetlCC^z.\E@WÇ/333=zpL|֭[ +O8zj `.kjjڲ=n<:>hРA! EzzǏ?~|]vr333JtuuW%Ux<s\???:nccCBK,9tйsM޸W P(sADKxm۔J%Fswwٳ'՟̀}w:<znLj3Db\SSF I܈01L^>/u鐐o&99:ֆ r 7o޼p7D"Q݇nݺ 8?76`YF(޻w/&&ʕ+7oG8q!>i<O$D".|@ D\.W,ו-ޛF\bYZZ&&^f@ ۷~[腻wZXXSfdd wVvv6O^^=LnSZZP L&`0 26&*"BEd4qoܹs&Mڽ{{_}՜9s<<Lj58^gSN:Z3611ѽ @>}ĉ}}}/]D\ E]JwܹpիWwjggL&sѣGF_v… _~% 0aBHHJw7A#87<O( 1+D"X,D"PXnl6Fc0&&&4 Dž3L[hvޭT*-ZT؁>.իW,YYYxZd* X,ǵ+++ !1R"\MPnDz;k,Tz퐐}w/+mӀ^xnݺ_vѣT3KKK۱cDž ,--.\8{lwboȑ⊊ "g|r//[n1Dc0ׯ&+++555##O<9}4&X,H֭[N\\\\\\,,,OC\\wwhd2P( UUUxA(r\@y<T]]ACCCƉa6mnnW֚-p8z-ٳg&M2dȑ#GO^{*J" ޿ѣGVVVmܸq񶶶mCddddddaa_u…P33sX>/JqX"qbXs% WWW4bt:h4SSSF8Nx#6fX4 >'EEE۶m۰aCd2كo߮322v\)**ۖT*"P(R)"!DӉR$PRT۶m]}@m;=}R7o2dȌ3sEKT׮]ԩӮ]fΜY4@z`Ed߆ H$RRR. :tΝ; rEEăڜfo޼䄄<'q4SNΎP%3a}N)J"%K|uqX A >c0 dd22aX-4?>@foܸ}Yf%&&XXX;88㈳owFj1sYX,xA"TUU r400`X'qJ'655zrmN.@{6c WWב#G9ڵkuM0A&\xӧ#B2eSCC &L0!11qժU}>}]LMMݵOH.ոx0-k7qX}zD X,&iddDdlŢFFFJ; 8%C&͞=;((h 6mTPVVVX,+LR9~'g;L&!LLL X,&H2 ׺R* E>4Z[yyA\n||g rO?tƍtLPx{?_|٥KUV;V}o%$$YYYcժUO.\gjjqF^5|PLz\.wРAo-B޽iɒ%vj5wޝ:u޽=zĉ?.Pdr||H$R;v֭ݻWXQPPPvemmmmm뫱*'''777;;0333111//QFQTkkkGGGKKKKKK+++~Zbcch˄BX,BP"j<')~vx? *D p8ƸD1255upp(W"U/Qlddh OJJ:thPP۷͵P("7J\\ӕJӧ'O {7o""""> ;s̲e˼Μ9N5'P(U{lIIIf}_VTq9䂂Jݦ‚loo=\&`!X,"D"2L"2h*** "j=n />p@Ӷ]z͛gƍwÇرc\B޸q#...00!$훝*... X-_s璓SSSLGi-Zdmm=uT۷p©S>|hcc3y3gt4*cLѣAVXiӦiӦCY^^^o ¼Q.++{mbbbYYYYY/Tmmmm1غ-{7|^NUUUSJR |TZDR W'j!`?p8Ă9Fp8)!BΝ;C9r[R(|7СC ,3fѣGf .TÇ={6##!6mڴ9sxQW o߾.]X"D]f ݴi` B];W*ϟOLL|7opBu}uռR*qRP43D"M:us >uԄ ZR8>U?.Kqa>P(p3\EX=[''qqDdD&&&D8P(6*w Z{9yիׯ_`㤤$T:daddVVV"!j}6ZTTdmmM,))AB*b0bL&l",Hp8 2h|~pp0Njk2B?ܴiӌ3m۶ &]޽{.AǏ_rNϘ1cinJj2B(00pҥ';;[n]fB{ζ[lٸq.^m۶Fgb'YYY{[4iB1c?ܚnPMM}7n䰰1$$M]5Aݻ9s9>>^=ZdɱcV\YZZڄ@tssu5BHP8rqqqiiiYYYIIIYYYEEEZZZOd)J]erK}uaa!hIO\LH;R\SS#ju ERMLL4&*Ԙ%͈5y޽ _ܻwOc)`^_}\p!22~Z~R .TÇ߾}XuuG3j'77!TRRkbf-ճ%Kݻhݻwޝ;wNuռhwiM/_^zW_}E&/.R9'Vtb%ֳ V D"x/ X0gX666,(gqch Ǜ-??JMqq ḠB!BA\?V"CEdЮ)ɓ''''wر5x+W6me˖}w!''իW߻wիWlx ̙3<5o߾s%K/_\_hBhRT*=w?~Gh)S∈WWӧ'@ y+Wn޼) <_~67[U'%%iEnmm"!(M=i&***ˉhree%^(**z%~ ϢH`2LMM^I8 CƚӧGt8S%o02L*''l6R)Aa܌`hl*өS7ڵkOI$Bc>999sYpaM!# .>|,kΜ93fPT'N8v͛7=4X\|СCgƍw矟?m۶5k֠(N#bTسgOnn.qK.iٳyEFFvܹӧvFu׼RLty>3oӦM<oƌ""zч/^B$ 熉1^IRM9%+=QFd'NnܹҨdddU6(++k<{{ZGǕ/ LX,C$ B166P(R*"vmÆ x.4f̘&o;m4bLj( ³Ι3ɇ۷G=Yee%^x=BhԨQ[aaaA@ ޹~͙3͛7^^^zCYYիWbbb EPPЖ-[j[瓆Ⱥ\:;vLw̘1;|0n pDe2zFR|@PSS\ĄH-/T*.bjjJ&O<٥K8cccR_hB8_\FHP >1b9c\n/i$?E-t7oljJ*"bݦ;vwG`3g ͛G|///О={Μ9{Y.ܻk׮Yf5&M2dիWq,KEX=U3h"\tRwʕ'N6lرcC IDAT}uռhw,ы;w^|yĈ742p x <<͛7lٳ76@^4iRggg...'K@ t:]#ldd$ d2D"DѣG6o/HZxD2T*kV7n)t~ITnٲёdjl=ݖ-[MV]Rx֭[7o|!J 9|Q[m277)))}hGԥ2;CCC\_YMR)%x<"\@MNN@ d<OTVUUΡCֺg<_$~083D,IPl6BxC!|'Pl}(q.곅xH #T*E^bQ q284x+.\7E v(6 9y{pBRRAYwܙ1cFn^H!4cƌ={8Q"==!4wܹskX|iz5jR<"YYY=zt|ڵ%%%111111!v1UUtF/fΜyʕ!C/nڵ[ݸqd8PԼ֊999!\ (//"V%%%˲2? bJbXDEd.K R)@RSS3mڴ!Co߾<))){~ NVWWKR'r (*88x!RZT=ժ;Eᔳ[x:K5eZU_v ^_OHڈ/RK#NS_)m[[[?'u N JY("!RYMÙ:>]-}݄NR\w@?}ErwԯN߽huZ F\/4{HhjF>.;ٳgbbwqߏZvmdm3bkkthJu̙ŋ;;;?{:ĵqFpppJ_{N.w]-<6FPW444 _݋D"D d2'! گ={m5bĈ7RTGGNj//{3fd2yܸq^^^͛o߾c92uT+++288;v,Ο?w^SRRvi``0nܸ/޽[A{ hRkZ֭[/^O< bX`m3L} ́}?)h#Fh4SSSFMLL`Xٳg\.'IzxB߽h@N>}vb%.Pk.DGzg͚Uk/#ׯ_ׯk??soo[nEcf錾5l}6222..n;w(S?Ho߾F1##Bzvv3??֊ B=rt:RXP(lvyy9L666JT*_+t eqܭ[.YD}z5jO?t޽fժU[[wW^v߄5j=TT3gμ|#G9R놋-:~xvvE-Zo~WA{ ;A9;;/^5+Hsٽ{ڵkϞ={S*<AAA>|x>F 6(J2nJJJ^~䰰$l*T*""ɸ\nUUbX\UU%b1ǫu%q'b?qR;Ko(bl%HQ <OT"t8b]9lB{J3\.]`dh4bt:011hᘚr> [H#Gx{{ׯ_dB۳gČ5*$$Dj͚5WnTV`)S$$$>|!4uTЩSp&xʔ)M>7|VUUyyyуJfgg?xǗhuvhhwihh~ʕ]viiru HGGSN!233uC]5/]5Kg"''̙3[lwGGyy֭[<صkDLJJJLL,// 8pAwvjxP(dX7n1bD׬YuIoo/_:eʔuI/"++Ç}A%Jveee9cbYcfyW%⊼xNh4\FlEјL&~p"D"H@, B> rϺ?Z斖4j\jѣݻw$I&??g75  VVVwmgvv6(oETÇ ԍ1٫f5k6mT OJuڵPDI=>tG+yܹs=JH… 5JfZ^i񶑑955d6X,}լ>|G&9::>V'Hh4ѣG7СC+W,//777߲eKdd$355|2tRYYٿ&F =JHHx1);BQZZZRRRXX8g3KTJlbhhCfOWӁ ɴ+^՗Lv:~g@V\y<`ߝwﯿ0a^qdLfoo?rQF9::xxx\|^8|0z ܦM6w\osŋ۷o:v8pӧKEzzXWW5V/\'444((hԨQ]ty nPWuwr񸸸WW?^w=5/]}Lg*<<<##ݺuwwO۳g{bŊZX*C_uaNfΜfw_nذ>|8gXmN:5w\X^6o0s{DGG?~?ǍwyW^M0޽{111D4kCřѣGٳgwGJN6ܹsuU*UfffϞ=P(/e`@ )Ճȩ* 433zjg͚u2"w:99A UWWqiiiAAAIIIiiR-ԳݺuNZZZe$vZYYYYYXP%2bMVVZ[[tNlݺu/^BDFVT2 !TPPpĉ#G0xzz;wni,--ϝ;F;,;7mbٳ3gj}L&jEU=>t=G+mll+sm۶m۶5vuM;wܹs[>3ӦM# D/_>rH||Ν;gΜՎ^x?rFm% {Rp82BښCU^^ĤR)\x!6-HB H."Ձn!m+s '''OOO}w__lnݺ5==!$HSSS矔G&;wrJoo>}r1 | ;;;PIII@@?baaѱcGbM׮]\2lذٳg?~Kmbcc ^|jjjrss򲳳Bnnn~~>~"H8?Ý=z+ 4; KLT||iiiaaaqqqIIIZZݻw /W---:tءC:ڶyZ Dwwڄ555!PRH${ݺ})$$d^^^/_*==]TD.,,H$DYaD $I"0 !b$JdcccCV! ڋ䠠 }|K׮]k``;\NRwޣGѣG 71 _^:++(700Ν;#G,//x񢱱qw}swG>m"(339999998m[\\P;88R8s dAD&/j]CE"N''O.^XTTT#:tܹC0&ChKKKۓD"X,D" BH$ y3*XTRTJ 2h_Ҿ }E|wQ?OGPw~]__ѣG{zzzzzv 7݃ȖaÆ?xOHH7 6lï]3(OQbbL& wG>233BAA~.}}}'L36-NwܹsoI|!2b!DR;u;O~T ogR0˭D@ D".f8j,bq]e NgXl6F1 cffF9hd͚5uu@R3fH$_~ӧ/X2o-۱cGJJP(tss9rU^ QjjN>>3f@{߿֭2|7n@&66[nKNN˗/^~ݻ̒Drppܹs.]FѩS'wT[%%%IJJ:ydUUBcǎoG=LLLZpʜN_|t:#wP(|>.E\UU%Hr= +kݭF366655%LMMWֺcrON\.22ԩS8rO>fSS&z Zh.AAA}/^I P_YQQN~ɓ'pǎq"GnnnP5;tݻӧOGDDTTTԕ?C,kMH$655uqq?CܨӘ{ @ %kfllի.\sD1b/PUU{݌ C#FhΝ o­f_3d:ڬښx'233B!"I$B" @K1j苹yEEp9 `ӦMEEERl㓔UOj|N SRR֮]Maa'O?~/_!,,,z5m4wwwwwO%sssss~kT*UNNNzz˗/ܹgDbhhح[7>}x{{C%T*y<^qabEHϛ7UN=.NH$|tΜ9sوں</**… f2001bDhh!CZY׮]yB:tѣG >P7o$''?URRR^^^WEwBѱcG62ƆXSTTԂeeeS*#jjjJ%]AdK 2hLLLH$@rTXXXdggؘGPe˖5mnݞ={6uT??~m֬YmIsJ7e >~IAAa=3|pwww$D"999999s*߿|իW/^طo_NNBqww۷O=ԓ`k!SSZ ;::{sR۷+WBIDPh4 %^+3f̘1ҥK/_DC2d'dp IDATHPPFe>KB0...:::&&իWt:=00p8!mBɓ'III>000ٳg``5ƅ aZZBӳw333ΝsD.++STKKKZ`r:H$r7ƧJ*"vsW߽lYXX<}Tx.ٳg||cEE_|L&ʕ+ׯ={vrr`۷x>{wލ}۷oB]tYlO^0(Q(WWWWWpZzr֫W/__ࠠOz߭u-WO>8p`JJ]jj͛[4ddd5%tGDDDDDH$Ę} 4*‚ MNN~QllÇ wXX؁oddmRH$]zݵk׶=wܗ_~fmmmaaVEEԩB(''F:μ!^B! _QJ$B! ڗ^z=\߽hqx&JՌ{Ӡs]  ޽{cbbRRRB}@@@婩wG>[ȱ.gfffoo_EH$Һu,XP\\@ϕB^|;"bbbbbb޾}KӃO޷o>}p8}w FIL~=%߻wo݆8}{lOœ'Obbb={T*CBB e칸L8!TZZ}ΝC]f80$$dر: XZZnrAbt:ֶhԨQgϞ]tٲ0a„ B>|abbgѣ;MvԤ<}W"7nTIԕ~^SSC<<<֭[٦Ο?Կ&lVWͯL*ڱcGPNN^V\\Ll6B_-2LX>L&Cj! ڗ-[zzz/]5kmll3`ccGر&\t~ZD"llmm]ZZRH$R@@Jzɲe˚pn̘1_}վ}gvttԽZ6@[ٳ_x1;;cǎAAA .:thp++)SL2!rʅ =D"H$H$ |H$D\./xjH$j?Nh&&&L&N3L6t:KX,ͦt:t#i4H$_~s :̈rŋIII=sξ}d2a׮]======{΋U(w\S?HBB$A(ZZ'Z^ZUۺGQ뮊QTp2e!Fq_,QqssrIy'O<}455U.kkkۻ3_~<;d\󸨨!dll>>M6D{JGI$,f0"2VTuuu!|"*"w7'D&.\nݺKr8Mwmߺu9r*Ǐ߻w&&&} D8f~PXXBN8teDSx3BҶnݺbŊ;v_>44_]͛7~;233mv _ڵkPMt3XBӽx?4mԨQFH$N:x;wt3MvzDEdPQQ """4ݵϚ+G$=}ٳg8$z<Bġd;;;+++ Z윜,9NKKV(!SSS;;;WWׯY !yKKKic"vcaX1̤#Gں|sdCz ūRrss-,,lVTTHYYB 2,//Gq8LpAd&9mڴiӦ={,***""bݺuk׮;TENWDZZZ eС/_ rGchh8h $ IMMݷoD" B‚dj͐H$8m5BL&nvvv6̽y&333+++++ oz*''GPH$sss;;~}8sKuIUUUYݺukniFFƘ1cB" =q!k$K$|=`0,c6)J<)3rW\|)SXZZj;_~666^x1cN:i&9s愅ؔ=zh۶m=y!ٳgϞ;wɓ'[hqƍO<9}tBBBu׮].\tG8$ [ӘQ(H:wJIJJڵkѢE˗/7oފ+,X/ի]R>>'N3gΝ;g͚ա{P={v'){.Bhʔ)Uq߾}֭[bEXX޽{w~uV2Jd>C 5kT*23*&榾D=l}՜ ܀ BPhlllddoMLL)|蝑T*DmAAAaaaaa!. p88W_ uTWWM1ۉD"XYYY[[رf9qL&>}=ӧ={l!T*uA5DD]DoD3j~mm-n8lgj[DDmө={!pBТEd2ْ%KgϞ8t萣!CF=z֟ҥ{~ULLLTTTNNΊ+v6Cmڴiȑo۶---mʕD0_n݂ 4ݗظAd bjj:xѣ^&Ad㹹vڲeƍٳg7w: Ju尰0Mw=9s}{zzuwq뀭7GύQ[222ڷo_xx3<_jS\.?BB!4hРw2Dӽ>}4/q4977ȪD"R1 SSSCCCgdd{Аm$h%%%"H}KRܘL&|e9w$Jsrr^ ,((l88p5޶Jmk޽!!!D߿̙GD{FD^jH$qƍ7B$iȑ700h)B}ӧBY|9mBazzŋq`!xb--k׮hJĉy<ի5ݗς)^5,--|pÆ +x222d2D666nr[]]]EE!TR6H$)4]__CPZ-M; 2h~~~ ,pttTfܻw/==ܹs7n܈=wU>쀓&MZ|9l}ccsƍa:%Kܹs`|嗞PӱVnUVV֡Cu{O1&` ܿ/5jɓabwu@лwoMw>|ׯi/_~666^x1cN:i&{z̙3NR:}5WkÆ SNEΜ9{n~gݺuIII6mZbB'22͍dgg=z~ZbElllkz@kΈ8p`ӦMƍ{Un޼e%Nz'UTD&Bs )++{Qk0j3R/LskrZ7\[Rud;wD͞=O hG\.E xh? ظ*H$233jfs8F9Je0zzzT*bRT.l2LRD"tF"8Χ}/QQQRjjjjkkU*^NPTVV򺺺ʺ*TZ[[+s+**b1ۧmƸ:5?P@#$I^^^qqq^^H$㢢")({ǏB-B!kaad3HR+"=DYR1Lbuttr9DP@ٸqgBBB5ݝdmmx9|fݻw$77˗/p!j{)"2B诿BṘnݺa}}GGGZL&{xxxyyyzz:88@6deek4|zjH>yyyyyyEEE;v,+((($$$00t4W^4hLtGZ>}zH!#Ξ=W-d9sx+B(**?l}yѢEK.%om۶M> ϟDZ͛7oݺ#FRRRZZsFl~-Brٲe7o|9~ܹs']p\.lϞ=q!GG!C=zP(""H)U7pQ]\7h)pvBXmKÝi_,+m GǬJ%r E M[H/Jq36mbbblllbbobbbjjjaaabb ׯ_=zt߾}<*11ظLkkkB!~|^I.aM"\VV[ z"B9vX~zOJtBBBnݪ+0m߾]{XXYnn#GB<ׂ + =zt;v uN[&<<|׮]?L&1bB޺u+B(88cΝd<=gPii߿_RTOOo߾^2t;7+++X\VV֚,--'O Çӛ={ٳ_~}3g΄ 2$$$dСPJ#H$wٿ;Z2ŋ]&'_9R= {0uԨ("rFR3ЬYf͚KުT*KիW T*Xs=og$X___vh!Ç r _jH$qƍ7B$iȑW[}Fp^N6Χkg^^^IIIH#`EQQQ]]Juwwzꕝ;:1Zũ&eR+.KRLK7>.N`X EGGZpaf&FlN>ܣiiiݻw999AAAM6kD{x<^II BHPߊ, ') T*m%@aX׮]yfg.\?^p!4o<\{ь3xbW~6K.]~ŋ/^Lݻʕ+2;qM6T񂃃\. 6 |}}!d2B6..33𒒒sΝ={vΜ9uuunnnC:tgG%u\.:t;Z:::<///OѤԣGr!xw.rJR9bĈ71LH$ ?>#JeǬ]\\ݻ~ܹ7nƞ;wÖTj<` /oxhӵ\///Mx0\TuёB@3R cHTTTDp իW`` .rAac J_p$11q=:dBE䂂6+L&Ϙ^;fEdX+cc555ꕦCCC mmm### spp`0?q;=zhx^vך6u]t)00̌bgƍu 0a𪖖7|sSN߹s'44ȨgϞ ,8}tiii{S377P(ez0OOg͚SVVv>}>|x|>رKOOH>gW^իWgYHHȶm Pg^xsy8@|QB988|y{:p)T֭[H$Re4=ck 'U@D"i1޽#._Χg&l7t… cƌtG:BATDF"h4ZnRRR49aTVVfdd]t۶m[l٬YBBB||| MyK.s87wtңG^~][[[^^۷o>}zǎ?ԩS )d͊R* ,#W^$I~~-n)͛lYPP`uyy9Lp8[zS"HT*T*P(-ܱz> ׯ>1=C\nnn-,޽{ٳ`Æ 6lXkZvd*jVZxƍ߫ȓј1cНT*ݻwݻo߾:kk{{{: B633 Ӊ'( F#xxx>}ZPhp ]]ÇO>od2_|gcc~>\W_igڵΝ1bĥKpe+$$$11q֭ZZZcƌ9uL8ݻsҤI#GLĉ?ͻ~]]]ttto߾}$3gϑǏ7Ξ[#F$%%mݺU[[;$$ٳ۶mk4++k޽&MzEx¼rss9x3jz|?eʔP???Mx0Ϟ=h'T*߼yS^^歲Ҳ2\ۘ+Ɉgh4| k``  |>.r>FEEŖ-[.\p> ?VTU*^ԍźd2Y"tT_֒H$:^[[K&r6%w 2 L D":uKv5sLMwtPdڴi/^l۟w޺u+66_::99 8p=X)s\<.ނ"KKKss󀀀~ |q^{{jjjbccܹsjSSS???OOOOOOggY 4իWݻw}43}}hWWWMwUUU9;;ggg{͛zF**00ի 8lذ/fj(nxiŊ6lhrXJR]paĈ;8;{-={#5k־}t:^ !s8xofDDĖ-[opw~F} m{}[ :=""ԩStI+&NrJҥK/_cMw7\l7͛ 'jiib2DX `׶aÆ͛7geeA6lPRRǏ>><<͛l6{Ĉ7n$"H$˗x!ԣGɓ'Ϛ5ilݺu_~͛SRR233Tߔ)SBAAAϟ?z? r+{gl 6۷os8#F۷̌ha:~ܹj+%$$n޽{}||Ǝ'gV7>,_|\SS[geeE, 4K&⊊ X\^^^~ 1 rfff~bi5য়~1)dPRRRsBiii:::LPܴ҂{{{nIIJ}H$L&S*!kkkh4ZYYL&ё*"˗NJR:4p@Mwtr|ݺu?c~N8Auu{…s|??@<7TUUTTTXZZFFF~wg>}v^Xiii<ϟ+ ##޽{B}Fs)Mwཌྷ=ٳt:{ %""bܹ<&,,lC r劦^˽{޹s{ԉ{7nD-\0>>ׯ}C)((觟~jI&UTT\x!4۷o7$22owLINN&H*jڵk׮ Zxq~4A Nc2\.WOO[[!c6r9G.@ܹ_9s#S/^X|ys RSS}||vVVU H$266&#pA.\"jjjp澶Bh4L`0qEdJ*"wϓѥK=y۷Wp\r]뗔X[[/\p…eee׮]9pM,--'UUU۷ogX=zr'Ƚz:|Bi]5hРA剉IIIϟ߲eRhNNN={tvvٳJtG>^SR&rϟ?7t~U=ZTp^~{ǎorG:B:RyΝ'NH$;wx{{[l)((@YZZd-֖dx{eeeo޼755EjwBJblqJѴqX-&imm~GhHyH$ӧOPO>U(Ġg**55u̙nfff߾}lY\\P(BqY$1 L&ë֒d**ɸ\L&ѩ'HmD"M<9(((22r„ ˦M˓dr={뎓D700?~JeBB˗cbbKR:z._ƛd=<88JZhBD"߽{w^zIt{{r]D󔔔/^$''߼y377!`ooogggkkknnVwy MwC0)roHғ'O(//u?s̙ž={.]t,V |()) 璏9frwwtttԃt:d2q1I,TWWZTZYYYSS7jkkqtD"H$H$aXL&'9) VXϟ_UUu?TRRͶiѢ*[[[Rinڠ"2L˶H$===DBq=:2T*K\!ѾwX9}}DDDYӦM{t'$8v7o,^8228dwwwww+W?̙33f@ 8p̘1#Gŋ&?ܹsl63dȐ%@&ٷo_bŋ)))Ϟ=~tttEEBFD-N'|>T .L2EQފꚚQ]]q% MIIiPDK.]xQOO/ `nn3*by6;§ry\\ܵk׮]CB6vw^qqq)]ǙѭA6޶R8x*\^QQ!+++e2T*555嵵555FEEEmmmuuu"b0FcXBo0 fuuut:ޠhN㧴Ɲ9sǎ$.BvvvZ++&[Zԃ%%%!DrR3Ǹ.6 2_]x?ǔ)S֬Ycff~w%K̙3gάqɓ'###g͚0v ήݻM>~z tRbO```DDD||BJ#))i„ ]aٸB˗iiiiii'OwScccӭ[7 ׯ_i#:555UUUD(-% J`\8F"4yX:f NgXzzz C ~A of/_q?Q]]mcc3h WWnݺ}y2ɓ' ׮]'MLLϟﯾ{p88p ֑#Gb^^^x3+m bee|"!$Je2X,V(0Nr\,p?TQQP(b1NZ8˥ht:p8l6[(lF1 EF;Z/!!aʔ)s m&&&0GSSSY,BPO ) H$x\.'$I=LєJJh:Vo >|xʕghhEz~6P__۷4ÇnnnÇ>|Bɓ'ٳfH>|x)ݣG{TS~333wرe5a{ann~5DF+))]{a@C쩯zUFF=}tVVV]]BNw- Py \]x@Q^&qpMMMee%ıc\D o4yXmmm&btuuuuuq (]]]kkk:uQ^vT V߿Ç vd >}:Q5228w^<䡆XTyeff>x+NJJc~~~ׯKıc"rӧOq(9::zժUZZZ}ׯgJf0555ݻAd t.}q XPTVV*JXRUaEEJJRP0NWVV1[>/p8ZZZ88եRl6[KKfST]]]&rq c:N)))߹sgP&%''7 --{x;++F5WoԂ"D"h* _) \t==:tӧ ϟd*j}͍6(! 6B"R*/0i4Z}}=~iDLwȸ`z`|qUqzAayv b"BH$<`h8D7fX qc)ƕ?{T_>jԨ6.l66)--mx;++ں *"㩒DL&K2Ot:]&P2pxիW;v,Y2iҤA[O8qȑw~6mg2B 8pvJHH8|ۗ,Y0v)Sp-q8SS@GR͛7ۀSpŅL&'&&B}iiixqpꜜPMHH8wׯe2BBCCCccc>/ q:X B-Ƨv--!Cù!a\X[W"j 9c\ 7NWr8Fe2&&&T*ba܀F1 ===]۞oKà ,}4W>33ٳgs*((h|7oH$R 2(..Fok!:N4 2fǎ?k׎92rH]]qM<ͭuڙT*=}#Gn޼Nwޮ4zݻw͛7_zرc[lY~!Cf̘1|;>}wÄ&ݑH &]z5^ŋ!!!.d:884RD"QAAH$/...((sNQQQaaauu5nIs1e#####6)u%?HƣUUUDDAXP4w"\dRT1Ņ`3d2x|s:/7o}㛙ryjjjJJӧOSRRfggT*M#3 DRz*##TR={ pvvٳQx<ѣG*..rʕ+W6nijj:dȐ!C|, jDѣVrrr=8C@=QSE.^WAR*¸ 1N ԍ Յ񵞹\:+su RŸ}q3f6_&4..lRff\.'șÇoq@\!$Hp`D">z[?`AdJ>| 9w^gg &΁Wͪ}?^YYxĉ`*uT*uĈ#FJΝ;tرcy<ɓ z풒KΟ?ɩO81i$Ë/ک7$ Bk p:9??_$eeeݻwϴƘL&ox{ !BJRRH$&ɈD-GFFFFF>y۷ocee5lذ`??q]t)&&ڵkW^W H11qĉ'~СCںu׌3ƍaZӧϊ+JJJ|ҥKtڵk[holl~8''뛛YZZ Q bꈴqNNNNN%CFFFÇǁnݺjemmp… \vǎ۱c 5jOGnQ]]]yy91])99!`0jkk Eۮ @ST*H$***/-----DL|[UUUYYdbQT===AR2t:‚Jcԗa 1u%K̟??**S|CJB955U(I999Jʪɖ B"2" L2 Go! BRe2[+ʎvֱzsqqڶmۣG.]t~MWW7 ((hȐ!cP(bbb.]@l33˗/[ݻo-Z4~ٳgGӽkw)11Gyg&,,lqqqvvvsvۥikkYTI咒|7''x?p8l6%P:/&2UUUDVX"bTWŷ1%6[ܬ/ŵ-.(effFPp%*|KċMfkq2''o$$$>}y%B@ 066644kk?ʊIKqqqAAAQQQQQћ7op3F0Y|>OV*sܹ 888$$d7/((P"8p!d2U*T*eX"VST8jLDzܒFx<@ |~y<bXzzz,wVt555ӧO?s̞={f̘+++@\4;,Ps B+"" 6.ϋS*J2S__* |Bd2o߾}]fMAA~RF=>|x۷o߽{"((hڵ~~~8"H 0`;}͞={„ rdz;} //o߾moo_UUQ.L&|<e5kVYYX,x ogffrst2U+ ]]]6d2qtf"x'U8:aF*8(*//' DaXLDVdUk-NlPCؠhx[|K+"hԨQ/_ t'…ZhڣG4ٲ rII Bic5Rė-x-Aoȸxcc3gΜ9S*޺u gr|||aMH$G޾}\.{ժU>>>`d2gϞ={޽{ѢE@r޽=ZZZʧ[ƍ/_Bׯ5Ri.엜Lo.&Dp8 CWWWOO9a08dvYWW?Nø@SL0N"p a"\YYP(L0jSpa\MSZFZ8p8$H$kkk[el6BOdXDYbwѱjnPL"(=D, *ct:GyjQ8 .Z EEYdd@  z+9sΕ#fϹqBݻuVrrrdddCCz#I*555%%%555555++KPXYYm޼ g'P(-֥Kش!#FX~۷t钑ѡg'rٮ]2b0 ]H$!溺:@ b@ H$uuuyyy^Շ4ZV5qԔL&]+=.y_U0B׿ס^"0] u&8ȋ_L0n!KKK*CT1 帻0#p߫بjɓ'fMM cM333: #~Ơ D"D"BH$8[LU UL&ԔpmN133  gΜڰa?PBH$R׮]UȎVVV= BD}mhh/$@?Zi-M*P~?V4%5ajjg _(ڹ_A*5K jW5jܥK.\0}t//3gtҥCʁ4@(q\|377B%%%jTTTH$,L?ߙ T*d$D"!"u cĈ#F@קF r>m}}}VVVvvq!ԥK//K:|$͗,Y믿Θ1cҥ . 'v2X|>zz[lׯߡC\\\222::Ν;7v؎{ %xvA-,HD"n5 l"VWWd\"AA"|%8GA|,PjC"2H @gGW;޾/BKG3q}EXuoF*mUp>B07i0@wI$Lڕ/_WcbHL[d2t1Nǯ K몭ىՏw=|(%~o%aqBZ"l1q !٭PTpj\-R}LLLM6mڴ+W=z4"""<<|ܸq3g;vOﷳW]O:!`x:&@  U,[%Y~'> ki9W5_$|Ӫo͔`}O,_Qcdmm Qcdݑcƌ9vX'C*Ҁt7|l% B-78vŸ/#294ߔJD48BR\.i@e2YAAAB|cٸ3D"5jԨQ/_ܳg7n8cƌã3+YvD"!HϘ1cZ@//%K3G-_\ۅhc{83>G4X)]DtL*J|G큭9ÑDtnz" ׭Rx!TR{ִ >JTݢZ1>25t%jTӷj%\ R ~5bKY5_OVóh⏄hE=!3D2'8@?gt4t:]&jkk%jV(J$ڢ"U]]~K"N_@s@o{qӽf Ĩ  8 @Zё= ܑN;::ԪAp###:d2L!P>64m„ &LpÇL4s1chSKff~?~b-TZQQիr|իWeee^jߡBL&S-k)T{STccc633#$~ﰪ1놆~<DJ/ KKˮ]+VVV:Ǣ j @ y>}zڵVs\>|-==dyyy:"uDRE"J_[B!JR#\.׵s}wsB3g^R^^YYY/^!t:a֖fٱlkkkոU'khhx<nX\\\\\o]tqvvvqq pvvv7{۷o߰aѣGw9bĈ.00~ҥ;vnذAdК5k㏬, Rŋ4mȑ.dNI'j f"V!B=bU[ǐT38 HISj"A$w UsWTWMWMb&UC=zSFh8RK`6ߟ:u*ByA߾};m<8p/^puu_kjj4KJJ\]]U*J#Qmm-q$>S.?8,JЭj)VVVVVVC !(⢢"WRRZRRRUUP(<n,ux$iNב\.U x9~L|'l6~}%&&n޼yĈ^^^~mhhh}ϟ1cƜ9s\p!%l8U IDAT%k066߂P&-Ν Tk| {1x,ZTt+E5MhɮPӾԨ1QP(8>eiiZ蘛+矣G8qbΝϧOi'q\GG 0@Th4*{@Eaa!x|>PRT+++[[[2ڵ+ы.0==V昕J%H=zݩT*ZYY٫d2/***--ŝJJJJ5^eXAAA8d۬:BTnݺu!!!111y|>---""BØ^zyyy!GGǖ7\QQ“0Ad X, 644( |f2=Q"Md0QZZCBPC#7‘eܐg̺u놯ZXX>ֺ6! xF9rȧOn۶m֬YϏxXRԩS9ݻB عsg;4gΜثWN8 tH$z_mJV֡]:{IIIqqq~~;}/˫rJeNN^biiigg3 "/TcDbs8~&D'] ވD"YZZjd>O4~ѩS u֭[7"ܭ[7.H+..&2Ǹ11i%p8C7Y,7̙sݻwuSSS5wD~a\===;;fGr>fUz >b1%^fuBNr\.Qs4&YurT>|xŊ۶m駟v_7nk׮}٣GC YlY^^ Ze˖n̘1j9EIJJdAAA.ѣG=zϟ߼]\\̛7bd2933"#?~ Adؘed6mooMMTnnnM絛/_O>pBii\.Ǐuqqqqq?׻v|_DRWJJJ*++v, w2fٞxFD| ^KLL S(׮]>|haañ@ (..&:"gffٵt購\.vDRD"᳃p/db^D?Az\"HzzzDC6zaÆ}mݺ5::zo?Kѵk׶m۶sNt>}h4ݻwD`2şѣGaعsOѢR֭[ׯ_?~w9)S  uV "kT*}ӧOׯJgϞfNNsKKJJBBFFF ,pxSD|$ RחdD!lmm7nܸlٲl޼y˖-_|Ŋ+۷¼SN>}…B*}>봷:uԒ%K~tL&x˵]]y+V=zt߾}#F/'OLc%Æ ov AQ__庹M6}{Bqrrrrr;v,P(feetrff e2AϞ={ѻw]jkk󉐱jԸ355޽;rtt%Ǝ]]]]CCCuuuCCC]]P(lhhD!H$@P(0Puu5BH"1DU555J50̦iEbRVg2!ccc2l``۲QTCCCcccfdddddDR߾M={vd2ZrST޸q#22RØtaooo4;όR=\[QQT 2 JY"AdBdd2ϰc@>(FFFgޱc/rСŋ/Ys1czq|}}/]ԾlmmI$o6{lGGǯ}ݻUUU!!!.n111YhѢEݻo߾ȅ ~WjRooSN) 8iCCX,~'H$Ǐ߻w޽{>.]xzz:4<I7)'>f466&! ]vɒ%w^fͶm֮]矿oܸqMy R*<=zt[Kp87n@X ""B ^ܹsue˖-;{m\\\|}}-[>j)JJJ DF_~Peeד)h-1c/_|Çܹk.TJ" "p88m_JJJ^z%H4bzǃFhvwd7u"f[[[:T}WPPtҸ]vkn8pcϟ??>K<---M#@ @3rDGd "ugddlٲ>lÆ -޸qɓ[:w̙1::ǧ\.ݻ "+J7(NOOoʕm]Movvvhh ~P(wٲe˄ zdɒiӦ(nw|믿feeedd(ʞ={1"00pРAT*UBH&x2|>q"ɴurrqԵk׮FFF}"*c&:j؈QzS?M׮]mll8kt:xB5kꚜd"d,HTXXHtD&+..VD"  jwDS(cq:"}-\pŊSL TvڵO?4"""""B|}}޽ێ28N}}WKDFFfddx{{{ .}x :bĈ庸UPennnnnH$ȸ}pd###Hݻ{޽u0(..w''#FhݺukO.z⅋ q,5;;ͭUUUuuujA䲲2ՎD`|D"#$ BQ r"!Ǐ6lO?R(GX.knn~ LfӹP=z4sL~iR}b|}}]ettŋ3gFEEmݺuذa?tФE Go%%%vp;wlܸիl6;22r̙)JmfjѩbPznzBD;u"lvDDDDDDAA#Gݻe˖#GYfРAڮ%_||tssի׸q\\\\]](7&Px8]v zѫW/CCC-(++[nuvرɓ'ٳgk޳gOfNNĉ[\\\^i"!pY*644X3!W__OP^ #=| ƦM$IVVJmI$Ґ!CnݺՎvҥrsիW>c@'Lx8S6& 5?d7nܨ'ਖ਼믎5jȐ!rĉyyyVҵ2xK$Is?-95k233%kiի?;f̘GuC|ٳgL:g>}tNN@ w^LL̷~r! :Ͱaϟ}eWQQq5k888ܽ{߿qϞ=/^0@|ry顡i0//OϟAdHTZZ`Dp8LV]]^qn2222R "K$}}}2wB G믿JvrrZÇ_v m.]TVV6{D qFaagLLL;%774i ѣ/\r5 Brɓ'{5kI.\}{ ӧO_~ҤIݻwx"YXX ><22X,~ّ#GFhѢ={ZXX7oެv:M,/zڻw˟>}:k,?֭[T*uA/_"[L T "@QUUT* 2LmJ%LdD[BBB?~|ՙ3gSL?~hh(HOuXSSSCBBϟ?xZqYssCjq=~xРA'O^xL&kv=y$ܼ`RuMOOׯ=z>>>ڮu0yC\ymW@صkׄ =<쳧Oz{{>?vEtǏvvv=zX~=Jݶmۋ/*++֭[7zh333mW @;Q(/_qqq}9y򤟟w mWMٳfb_vmII͛͵]W$''kNӉqvv-Xܬb 2n!AdBATDOOqBAd2g!>>RtҤIL&޽{MH$3g$ɛ7oְnz,:Խ{w _ussx<==si={}4OP]]ݩꞂs熇ڮEK~~>ޱcGQQ?ڷoߕ+WLfddxb##o?J%B(>>~޽vJKK+//?~xϞ=SSSlقwbXٳ˗+5kִr5"e˖[n oߎڱcGaa!;v9sF&g&}5رc/OmN>{,7v}Z$Q~Ϟ=H۵ٳnݺuϟ~+맧i4;w'|Ǐg̘q̙;99Zɓ'.3g:;;_vm׮] Cۥ۷=l n899ilGdFtD x###@ @* JJRwDƗ:2tD" h.\x3g0Ç7{r[ѣG566уD" 6,..N*c=#N:UUH?t:jw(b 7Zt/^k&~GՅÇGƪ.Iwooo|NNN4^rk[E<>::Zm5奶Q"FF + AFFF߿JZWto}hQ}}͒%K]P(]6yd2lhh8uxDE=o8Bsivvv$)002LEYf 2ìmFd۷o0d޽K"""LMMgϞo[MT:::"J)Slmm\͖-[u6qI&!_gفTTTݹs֭[AAAo?qׯ7V^WWWkk7ntUAPΝ~5ssiӦ'*++'NB|tcccΝv?P( p)mTCgϞm``B4E~5a/^@͜9SuٳBVRA#==!4o<}}}2L&Bx+W 4ԩS999ZY7nd|>S[ްu 4ի?ܹsBBB*++[{略wRVPYf%$$hڣq޽...Ç/++;|pyy'pmWpPmWjѩbPz7mjhhSu"};wxxx,Zf/^LۥRydzX;v̙38))iҤId2YսBqҥѣGkʈbٹ"H 4L""%;ID"J%B100hllަP(@30GMNNѣG+շo{ >?11ܜD" :ƍK.m}FFFbm H%%%=<R. –VPWW׾2LMM[:^N&u333|n422Gt:NH;v444| ,B/سg)3Nwqq9tPUUO>f]EhGIW2Ljڵk EppO?p|>joo"‚'VnM K=<<޽}٫W&''={sݺu\T1mbl7 Boڴl֬Y'O}ϯYFUhB"|}}}}}nz111 ,Xr{AիW'O_&kkkB*+ }}}**JqYOO;tɓ'G|A[^r%00pĉM.]*J[m;"ak׮]z[>~e˖9rcǎ%ՃNpԩq}H?9!%H$ V H$:qaT*L&DgBA5Jm'a"ݔL&׌/LLL%DgSSS:L,t:̌N0 o AOOo߾}zzz!!!n"ߺuk||ԩS)B Ν3gvk"GGGe)))/^\d ^r9=zsѣGnJ,}c":… /_M@9qBh3f011v9XYY-[,22СCk׮=xMϟkp‘#G._LP&Ls΁jwl6ђ4Edsrrdr[li`zDӑJL&CGdڗ|رW9Rmk֬YjK)$$$%%%**J__O>U̘1Ν;C}g#GL3ڽ݅ &%%-Z ??֭[Ç߶m"ĉK*o[SvpppjjjTTB 9sLtt[[ۼ~>k/[`9Naa#GB/_l'wRV? Ud2׮];x^zi" ӵ-1007oޔ)S֯_pA tB}#GD"Q@@@LL̤ILK(/_9riiil[[[*bCC.].,--JjAdX`0D"RRzDӣR2Lzڪ> @ D"8pl#ՖKQ|Xt_]dɘ1c;o߾=bĈKnڴXp-Z|V$...44T.\MMMBB¹s.]$\G5x`'競HTYYYQQQYYիJ՜1q(D\迋;+t:wL&NxV'kjjB!ёZ$I$X, 8AN[7Ol@K.]^pvaW^\2z蔔//cǎ͙3gϞ=/,,;S!믿cofM]`={O<122jǾzbE" 6xutD"O\e˖pZQQ~M33 &lڴ Jr߾}ǎ{BG3gΜ;wn+&֮]_y˗/ܹŋ^r˗/'L?~gϞ;wYYY\ǧ7}MּEb;gϞqܕ+WN4XCyy9~^" ʼn'޽{L}c.^T*]jkjjvIs5fk.ݿƌ %11^BqӧOϜ9s666.cXZZ=z{PPqfѼyrss]79r$++X"HJ<=f͚USS`oojժnݺS{UUU% <==zqС䝃 2o̱P(cLa NښiXL8lbbbjjjaaf91~bgϞE?~6>sG}Zrِ|ףACCq"N2dȑ#GtwwMTX,ֱcǴ]xgryyy9/))b3ƙcT' J͞6]g:T*m_Q[+FFFM]tڵk׮]Y,̀6IOO0`y֪>>>dzpٳ曕+Wjuԩ)S8,JmllƍpFpƍ&&&?jp Ι9y)gld"--m֭Ϟ=ͥRC5kBxѣG]֫WHUְŖJU[XVV~-SS` .DM6={D,VSRR8p͂ggg___??֧Re.\*6͙~_M1ZQ]]ŋdWWWm()S\zC v9kzâEvލ*_}ծ] ݻWm׃p;+'H7okMɋ^+HCHHH3f BH(w /^XmZՇkWNi8SEu_5[sDDDъqƕ>zk׮ZDPܽ{y<^=&OJ6mZyyaL&ȑ#~)^߳gOճ̟??++Ēnݺ!^| a03gx!GGZTr sNNNUU333ujVX"ZYYYYYYEEE<055p8fffjP}Hw@ GpYP(<UڲlGGGggg.#3̂RXXxرѣG5/^)))WWW7oެeX~~~Y)))p8qqq!!!ڮAmm-+///-----řr IRUͦE---^ @Kr9BP[1i45ڵ+dkkkkkk6meesF5Ǐ>}JLL5jԽ{|||G)J/^A5K.;r*`dd4hР:~q__ίt(]5j˗X֭[3gKLL!{a޽_uRRСC]K6mڴf6w}'qqq[l)++C_mݻ`&9wٳg+C߿_,۷/,,썛]VVVg`?SSM6 2b]vmƍϟ?߰aÚ5kBK.ӧi~~ѣG7o痜ܚlfxudd)S֭[wƍaÆ.6lXnӧOoٲ6VneeUQQ_.XɩwiNn76nܸz,F[A~Zjhh}ٳgKKK{5yɓ'u~15w/^ܽ{W>}<Gxի#""ZzHPPÇ%>]B!D:;;߿?((hԨQ~~~NNN!%J̬ʊ\ hFmmϳ333q8++K$!t=qO#KJJqf˂|8'q4>vԩSϞ=nWP(+**o۽{;w> F|dwMJJyǏR)2d>qYg]v^Fi(??_),,,..cH$dXVVV666x!$(rǿrq;O֖tΎٱX,њ1c˗?; : 11oILL{{mm'8xh|>ٲe4iRLLLֽ{_U۵h흒dd֭˖-C_m rΝKn۶xTdd;Lf\b000ڵks!^v-00FEE1敕fkGGGGFFi>}n4**jҥ-m~аI&9s&00ݽW^F}U]yӽZ[[E8}}Nyyy/_tkkk=<<&M4y={vNFTr8^Zȃ~WBL&#^zeiiyQFOOϱcǪ?wĉi4D"K?t3g|nnn!O> C,gddr8 @ꔔײr~nݜ]\\pfr8VCCCNN9+++33!`0<<<ӫWV6:ETٳgɒ%Ǐ777{xx̙3gnݺ͝;_1U>x֭[>8pؿ\ݑ#G]G˗8m\XXXVVQT"ioookk[1喙:N.+++--%?~QsrrL"xxxp܋/"ܹ3d};#]5kֶm:o>|xiRR?SPP@P==='X7{⅍MffСCϹz>y>t:km۶mXɓ'CBB[ZZ [g͚u 6P( 1W__?|~gϞyxx|#<==!C̜9sȐ!fff=rh#,--}gϞ={p,--hee%˾ ޺ukӦM6m꼫zzz? \\\\\\ݻ'a \D" ɓ'Nl޼Y^^|Ĉ0laaRR^^q5iwAAao?{(//Ϫ#M36t0`/r`"f333t:;dddԿ@U#""gϞcǎD2|E>|↸\n}}=)aQ__flvCC|l0jfE+"AUUL&x"HRC T*HMMMp/RTT K.3fLSSɓ'ǏXں )RSSߟpͽBCCݮ{H55;w.^KS `͚z5DSmlٲE x{{޽[__D"UWWlw,( PTTmR?RbI 7o$$$%$$nݺ7d3D[[W[[[K,~O?5 ԴkwΜAaDb~6CCǏ[U殪"X VUUp8D"`())AAss3bDbKK 7Qz   dfdd?}ikk/X`Ȑ!C 5B BXYY͞= }vņ>}8888;;;::>~ɒ%K.mllܺE)++;:::::‡O<̼r 666C %e%t+Ws AAAyyy/^ͻwI999==?0FI$e JJJm~+ÇuuuEEEdeeeeeemmMPH9rWZߵk۷}||n޼|>łꚚഘV_\-SSS***4 ӧFm℄/mYQQ\2 KGG:u%  sWnQވk׮EFF 4رcgVVVvNx266JLL\f l~ #""߽''';wn_)w̙onQFFFى!!!! õp88p??ggg?g {C:#\.>>>WB͛{up񒒒Ǝ+ɽ['OŠbRVAj6@$KKKq8 "ã xC@ PRRr4eJ@AAz>ohh8p@iwP[XXXXX̙3r333߿oáѣG;::zxx :GnF:YlYTTTpp={ɓ'-,,o߾dɒǏǓ:::>>>ԏP(|yffϳ+++[[[ZXXX[[wj$t%tYAA0/^ & 5774iH_EEEEnISSSSSsѢ?~M'gffFFFlL$[[[[YY^DUV_3gܾ}{]]˼y|}}>|u>yd 1I,''GRۆi4 7ň\,,K 1lvСcN>[UU ;~z\\۷Ə$~!3449rݻnj#}'O322N< w`&lٲ˗Z[[+))segg]zu֭:tz[aaa8ڵk{SfϞ!>6lXPP~IIITTݻw߾:3Rqi&9mڴ[<{ɓ'=zIYY oݺ^f?x=_TZZ'[[[%??L"eeeKAdyyy,, 92'H CAA`>&:WVVniiQRRيr3AA*//OLLqƝ;wLLLŌgggݿNGGg„ 'NtssC#IENNqq#G͛[?vؚ5k'N8{m۶Htt… pBݑ@ xϳ?ŋ %[XXdiw޸qK}AA鮰8JKKjjje7-E ݚP(,..~e^^˗/_|ի&gfffee5taÆ١JG 0L% 1D"544geeZYY xAb8NNNN(hEb5%d2RRRǏyFW^^|ƍ7o|ӧ HϐcǎK/_fmllt:ֲlٲ?|. ===Z-8~DI2p͛7ܹY;Px ooo]vajm7-~_h#ɴH(00x<9r;Vŝv?m{+p٠ǏcFFF$;ՏtFZ?>z蠠 ;А<~իW|>O>ÇWSSn$qqqs̩jPbb7 8pΜ9[l"QQQG.**1cדH3fܻw/"""11Xy...Ϟ=8vXssҏ  HRSSoܸXTTD&Ǎ7aOOOMKF1qD???iwWhjjڱcǾ} ) |>ذazzz.r`XYg#,Yŋܗ/_MMMMLLD~d79|͛kjjPEdAA1L>,//pC7zE"|~QQQ^^ޫWrrrhu_HgÇ'Nq㆘" |>@Ç۔)Svܹsٳgæ&t1լ{ĉߋ6ߟdlܸPYY?eʔ'OܹÇ3gwwС}wM@ (**-((/(((((`0`bbbjjjjjjff"""AAYWRRrΝwfffLLL}6x`iAQSS#zkxGOLuRAb&pg$f0VVV111---־{˗7lذwﺺE%%%;:\SSӃRRRG:bĈ#FHO`0=zw|>cԨQ|g;ŋ===Ϟ= |⅍MNNY|y^^^ZZh#@:tnի&MzUPPЫW.\{n ԩSǎ~XJ}k \AAcϟҚ9sYd (((((QQQK.]rɓ͛ޔT[[|˗/ϙ3gȑ#_r_wwwX,lܢI^^~:e2o߾-*****z 'h4cccccc KKK8!#>yd۶m  KMMMIIIII),,$CR @CC >,**{8pDT*%s>H!n;7$ǶfUTTrKR#""<<<|}}Nz777++'O&%%9;; rǦ***nnnnnnwݻwSRRm&''gff6|pJD{yyy=|Ê+ƌ#g͚ejj}Knmر7n˻xbTT`W/bŊѣG.]T__O" fgggmmmccciiUC/^fee=}_ɓ'9rȑ4MD1bD^^ޮ]֮]{_5 g|# &'''''888L: w$߿zꬬ,Ҳo>]]]ccc##ӧ]\zujjjY___TTdmm᪤V Nhhh DD"j2v@ܬrBI\FAA~% `ɪ"۷oCBBݩnѣG!!!?^|]d7էN~9.]ݻV&??͛7t:gee%</''Ç?oiipp0AHHS^^[PPp8٠A>j(,1 RTTTgϞ--+V,\O>ٴgeeq8sss//Nd'yzo_~СCC 0`q~zF;rZZZ[lYzu3gdd8::s>euuu(/^ܿ^%!ƍW\}6lp lٲeʕQQQ@ߟAA8<< K,ILL422v 0`۶m۶mػwˀ/_oDݻիW'&&:;;?{llݺٳO>eC,KRԲCCCCCCUX%^pзo_, ' ttt:dKVVV~~ٳg;c  Hmmmtt_xall} 0eqۂO>455mll<==ׯ_ommmaa~D ccSNm޼رc{ߦO>k,WWn:Niw>rFŝ;w7==QڝBORRpǏkkkc)d@~~?+bDTUU BMMMf\pzcc#H$r8ee喖+QAAuϟ??p@lll߾}ׯ_dIFHAdCVVց~/_|ҥL&s˖-Ǐׯ_5qDih4ںu~_̜Fobjjjjjڪh2QNII @ utttuuttt555ŋFC AA;+** ?w”)S=(Sf:ڼy{mΟ?,^t.______ Λ7ݭ5n =zeӦMcOdkk+:,uuuunn˗/aH499 `hhCɦFvǼF&:o㇙B:zzzӧO?HN__Ϟ=[n>q544L2}tyyyiwAdHQQQ\\\\\\NN֜9sV\/~!+3XK IDAT3sȐ!CP/Z긭b@~DH$6M"D" ---XY^^^ H?*++s\Vp!  6m>sQe.**j޽ǎۿaÆ+V+ 7~)44_~Kׯ߹sgXX rUUՈ#W=Dj`0JKK+++KKK+**_xQYY k嵴ƔG$Q>…y桜  SMM͖-[Μ9}EQ(iw AZ;v߿^rpܹYf)++ًww 4o޼y|k׮ݾ}`nZY{9t营ŋ/]~Ǐر!11q„ cၵp8‚N>p೰(CCC@zUUUg H&&&s155Ŷ t <͛?Bxxx̙3]b H'ϟ?ONNrJffɓ8䄾 &yƍJ8ӧO̙=,--733HII UEj :l,&H$B!HԤD"\.,*"#  ȷ_cbb,--ݥ#ܹ344ѣ۷o߻wuBBBP ~7o̚5kΝ>i1cܾ};//m:S]]t**JR477\rYYYEE)ggg߸qd2 %kkkkkkkjj}b]AAdB\\ܒ%K9(gy&` U---O>}͛7:u… vz>}\ti̘1\zubbe```yyWhhQe.F"DkkkEæt:=)) Fs:::ZZZ𧮮U___VVV]]]^^JÇO>K 8peeeOfrrrG5kք ?S7())INNNNNs·tuuǍwc^(!!A(N6M?~XTT4l0%?? >\\\*eee fŠȢq8\SS*CAA466۷OII)<<<((%S{0"~3gnٲ%444&&f޽xs޽lnju[h۷׬Ys/--.)++}YxiƔ+++jjj a)flNWWWuuu------ mmm uuu ^@A7xK,9ʕ+RbjkkSSSi4ώ;Z  O}z񌌌R kkБ#GJovU(*߷9u ٝ?~ʔ)Ta[[ۄ{yy9r>66v֬Y#ՇڪdhrII U˫p6"N`% Cmmmuu4Y^^^__gԄrCCѣGkhh71I B<.....NNNF 2s̙ғ=zÇɅ$yӦM" .7FI2gτBa r߾}Em[]] `-, "c\"2vl.czF  Hs޽Ґ_~L&KGHyůz3f8p ׮]^|9eʔ[vӳ>}RSS )))nnnmgMOOwtt!?`;::N0aĈ555ՕgyyyGЀ +# ]ӧ߼yҥKǏvw~ͶXˏ;>=BgRRRǏ(IγbŊG栠'Ny=w ]\jj$="aYP-ڵKt7o[dY,5PZlٟ E>%fW>tC{s)ɛ{owҲf͚Ǐ_pA\wt:ѣGfͺxbXXXiiiTTT~~>*|xS]]]SSS[[[[[ XR@444`("L&h4lNmq8d2L&æ!kjjl @^^^MM &#֚Ⱦկ!&&&ӦM={^ A:|ѣG?~\SS1rH"bTVV/**jƌ̿sӧO:Yjƌ\.իÆ :~8lYhQYYiVVVpp3Ann;9ӧ)S޺u맟~ _fMPPPzzz^^7΄*"#  2Çk׮}v~#Y[[ߩ/677߽{%K$,խq?#//oҤI111ӧ ZreNNNۓ/mmmi!T*5==ٳ狟uuuUUUX@ǏGt~6ܷo_uuuчݱ  =Lddkג]\\ݗu!:/L<9>>~޽3}Ơ%F$.HZŜ2wŋ]\\WB~G陞~…'O݋ =uꔴ|3  Dh]^)) JUQQT*UYYH$*++dL  E^^^YYִRrrrx<^EEENNJv ! B!X,8]WWblv}}}SSɄ2 &[Jbm8agga{É_M!+((|<ӧ7nذAOO} ӧO999^Fڰaȑ#P:{l>}Z #Fffh9d@~~ێwTUU"Zfr8UUպ:"fǦ&EEEyyyu   DAdAADnݺ`/~Grqq߃]vy1W;uÇ̜9… VVVTpuuMJJ*)) _[=;EEECCCit Q/^=zh4k777 ^}XPP'50 D۷/Ri4Jm&BA߿= v`ڵV^_!&&dɒ[[[cbb$"^zÆ CXE8<<|eƌׯ_}Y ΆiHE_~ nܸ޽{^O%$$ntժU<oݺu+wppvڻw""",--Ǎ7i$IΊt{+r$7ԩM6I/=L5jԂ 222 ??wѣGKHА0}LbQr,`0 lf86 |V—;Ӷd =JYkhh(++c9Vᄴt6ݻ****++D+<<<6n`X=zӧOϜ9!666 RUU x<^aaann.>.++P(WW 62t/ܹss̑| `іׯ_7NRŭFUUU***El!H D"aAFEEE |.v/|JIx34  tkӦM;y$Bvzo$#x`55v7@_z%zvUUcnH( "pƏłjpv{{kkkx<=iBSSӯm~򜜜e˖=|̙3}%y<+r$}"wڤI\" ƍ?cccg͚eiiiddl4t0q=Qr}}=˅ۮ'psXVdfabFׯ߼yx<^O?i&1w~ˆ999SSS333}}}T5(uuu577+((r,RRRƎkxh߮ ]];w30 vuoo-L ._,zqee˗'O ޼yuҥw1 lٲbʕaaawqss;|pppE^|O2FAAz)S?~ܹ̑p>++kʕ'Oްa{Fd0==ԩS/_رcŋX[svv:tիW/^| g߿oll,!?ƍ\.;9y555Eƺ:J|SL&Fd*JP(X FӒ"BA{ٲeKrrߔ)SE+))zHw\o5PL'P__زe@ ޽{>Dnu[7iw,Y aVG?zA=x͛7 )))iii [n ux2+r$7JJJF)^...>>>L&3::Ν;gϞEEEb*#x(ԋ lt:Umc8 kkk;99r_Ep8\KK͛G%'4>o߾-((Qdd$}*..o߾|xUlll,--xK>|)dӧO y2@``` XQQ ׿l6Lp8uuu,/// IVeIDFAA#//履~""21rUVĴ=]|)22ԩS_vpp8ԩSeCP(?x ::ĉK.Ş-**BAn*""S|px<^GGGB!QnYf0?~,b1 6j%JJJd2L&qH o  =cXxĉM DqVVVbb5k`Eg077Ȉ޷o<899ܹs 3g|SϿu022NLL -a 8wlb??mgg?DCV!;\r].vƍgϞH/7eʔK?~666[lqww  HwJJJDqYY(((`xzhppuݱW{ɱӧO>}$_fld2l6ł-uuutee%.X8D H$L&J<ORD"e<O"( '4 w0AÑꪍW\Ϙ1cŊ Me_VVVXXɓ8 :FF,7{l@TTv-[|:[[[kkk%%%:~ܹl77W߿ݺuC}SϿut;;;;,, ]v-<<>> ۱B193H$SSS2No=x`֭IJ:^RRRYY)Xd0؜0-.B$>|Cmm-F/TQQRX\___~(ep׬Yyf  jjj[n -L IDAT Ν;'fm۶ƾ~Zq֭gϞccc#֨r̙9444&&ƍ{ݻw?>|888ߟ`IPAARwޝ8q_DDBgo^ZbѣGa؅O͛7===%I]֪GG~)99TxyK˗/?vXۗ5`3`t!##\$ 2N7%%Ef is8WFFF޻wBL>= @4Kp8>}DFFΘ1#77wԨQ/̴Mo( ۟:uJ\n}}=hlllhh`0 ,444p8\.x,΀mKAAL&8F)**FUUUEEE10, 7| l6FatRzz@pvv>sL;tx*|n ?i2{}}ǎ[n]IIIBbF555ׯ_f1 .\֭[iiiŰ*++'$$̞={ׯ߾} prr*++{W^-D9sfccct 2  u={5T5* UV͝;WWWW] VHHȴiJKKnz޽1cܹsM={vѢEaܹSN{]`o*j֭Rt:=::zϞ=NNNiii;vl޼YGG_~ݻJtU׀mJo߾i&1}lEEE...zzzwޕs[<ݻW^r&LFemm ~߉'p8ʕ+Y,Y;hРtGGGihiiaMMM0\WWl6L_얖8'bx< v7 onL&t8n#Ѱ:sg1A[28ɄL&Fxf(g|mx?#,f% BPII&??_AA!$$dҥ_Bz'N7En˗z#FHG`X-r… X,H8p Bj1<Kc0s撱Z>|c0QZ,) @өD UTTT2HTUUUVV@a=/uхG8<؇L&f777buuu`wT( F"ݰ5B@',--#""$_*..nL&ìԼ}ݍ[D"ɓ'ӧOǥK/_>x\}MMMqqѣGWXCBB&M$ /_m{)J  HoQQQ3jԨ..z 6|߲KHHpƍ{$ Øyjggggg놨}Y  xڵkWZXz5]~k]IBB "K[FhѢh)rwܹrJBBǏk׮3g,;YfͪU,,,P ۉ266z7R8ɰl\~|Xii) a?m$`e >0D$sckl~3>ZѰG# ȗ*f*S /쵝<.fE~bH2[Gtm>QG1" ӧ:+y}ڋ_-V;`֯_С/YQQQ(zxx,X[YYGٵk… {((رc7nhkkwŨ(/kllz'222 viӦFdLt͛7-ZDPqppG/A:ުqQQQyy9<Г744!!C`c)++bhiiYVEaTp8Q]^alCWg%|i_<H~#H$IYYY5a* 3RIW#7o\zz//,))quumXQQ!//;y'`0566O x +x< @AdAA+¹sK.]3g6 cՒ,3]hwoƏ 0>}EEE &`O]Jʥŋ^^^cƌY`Tb._|Ν~)<<EڵLOOgϞׯ__|S]C Ͽp… ^^n喇|7V찳|=òq0i`i}l ?g#nIe*΢Ygى7 ri{jVXZTqپVVL!vhl+Xd-^fpez?CV}*CAaZB#X{'HKJXyH 9ڎZZZ,Y2{l 4 ?{ܹsOnddԵjA8E[Ny[, ,AF_p!..xyyݿ)Ə`{))) < ؂ JJJZ6,++ 0dk=AdSSSSSSű $vj7N\ _;꜃xg 1Yx(GLx,/:Qx<ަMͿigϞ 2wqMo žaPD N@bu[GbDu[GݫuVU.P2T$@~^ܟ'0D'9WB udGRSS MLLZXJ*[ZZʍ#ƌJ 2ދAR) .B 2G5 2,HD"ݻwM.+U&Z_5kH$ѣGo޼BSSԴ5(ȑ#-[?4#w}6**͛?۴i!OOO55{4?Jz*{n|| SRR2}tEfD萍!!B")J$'vȶE2MXnB,#Dݯ l.adՄ&wH4ٵE6ɚRe_l[V{RsI}`,Y@{=rO3X?L]'Cbr{eӽmQ'#~ B7nw}\F͙3gΜ9O>=yWWױc0~A_III={ U`=PB111%%%˴iӘLK-133V6Ў;vG=*MRRRTTe'|p +!M֑6o3۷oD"ѣGn޼yƍ7ohhh /Qiuε؀:L?[S:H$sΝ;w?.{`'CՁ"H . Zvv횕g [MMMVVظq`xzzN8?_KI&}~z/RSS?xG*--J\.W6,-=n ! PyX@ P(T£ߕ Yr/BߨQUUU-,,._}Č3>|xI2<~x''mM999:u*q!CDFF+V@?~Ϟ=K;6))iǎT*u/_޵kx%cƌiƍmrTqGGGWWWٍ5j߾}>>>p4hl>..NMMΝ;'O:s J^zu۶m.?H$R;:0+V|wz5PT;>|/cbbv:t!C899ڶm"^xxΝߛ.ZhȐ!_ ,==}...t:&L𥫮&rDkkkw6f28/ž}nݺ >|ΎH$gny< bff&;X\\ݱrI$ 㜱X,투&%BH*PvD&uC@IOOwppt;~r2!$'';;;ˎFDDNnrVؘT* qFVdɒ}-{ƍѣG utt%&,\Ą ,8x 1˗(&kcRRR>UD<(//0`# +iӧ8Vtf={󵵵 ͏>!ss+V̟?Z%'Jsrrz1>kD211 cC\ݻw eGzeb$;;k׮O>ha;vٳE&\reĉR4&&8a',Yr??/N4LJb8pBVWWWUU]j՞={l2cƌ79rm{tDkΝ=z7n }rJDDDfffϞ='O|rm+$G=}WH$sHHH شiNvZqq1qVt??ׯ={6&&b;]`<{lɒ%=z͛qk ݻN߸q4h޽Ĺ`Y5:: IIIOٳgǏ_f DѣG߾}۫W/:VQQqʕOfdd{NEEmܹnnn={b cƌ~zQQ7ߨ*4bq^^\oc6]TTBd2 ]\\1ɤh.@ %ɣG֬Y#;J"[^6//K.r:::ؘr Bx<РRT,t@/q/d :z##͛7/^Xѵ ׿kk?sss_zϟ?ׯǀ<<<`Qs=Jmlcccnno=ztdd1c>vt{{b0<tD֭[ 'OVt!5eʔІ$$$<_={ׯߌ3<<}!t}D"׻zذa6l9r8@|ŋZZZFRt!_!###??????| ---55555޾}@ PQQ֭Ųٳg׮]v`0[9_offffddqQQBHOO>((bVUUq8bxB6 h4]]]&ijj*{nll } /_oΟ?ޣG={YfҤI=zPt5{loc VVVL&bcCCC%l O>}޼yBF=z`899~2 J=)YSSZGG,!$544L*]W _wﺹ)rt%u_dI||BB͛7驩iii EEEd2_~qvvӧ40^^^ׯ/,,qttl3VWW߲eܹs{zٳgw@*>|xԩpRB`aaaaa1rH|S$y&--˗iii'Oş46H·ofggQWWRSSիY0L|jn+<O6,Y(...//|]]bʺfffǮ~xϞ=nnn/4i+P]]M䌉18ѱŽL&|E=x 66O*^vgee988`ee "r\--ZppGd 2BH#l_OkwQ IDATzj޼y]|={b222lllFݻwoGGnݺ)aӝ;w[>XGGk׮1c֭[7mrq~N8BBh4{{{s ofeeHGJJJ^^^mm-giiiaaaee)_>O|8FXZZZZZb+&MvVfQQQBOњ)&&&poڸq˩T۷wɓ~mΜ9+V3gܹs---],tT#ǎ2D211 c8LMMOq? Vb575T&ʏ=p8eee!Bge --_e˖XǓ人yG9y䯿zȑCEDD̙3g_S⼼ʉh*+FFFJ؂KݻdիWX"444$$͛G1cƒ%KM6gΜTD"Q~~\o㒒B@Rw;677WUUUt:H4o޼3gΜ:u3wo=zYݻ0f-,,>+# FEE(,,;R:"kkkWTTUTT#2\sp=ICC4Rf}J͖-[BCCO8s΃3&88XdN8ﯯB@[RUUn|PL gYYYII z699D!:ae]]]?(|MRiuu5NVlX, h4ell3Do###ޯ/d2-x1e"\Ll{D.RFDDıc7jԨǏ;v =s &(v@ׯ_68nee䱩eyxoΝ;1Bn0554>@kjj eq‚ݍȅrAd :MMM R]]BW v(tڧ-ty={v׮]6lؼy`߯CvvoܸB@RUU577777oaMVTTTVV|߬&:bT*q:ե:::uuOx555<glr+a0:::D_m &mkhh(>:NәLKss8NrIIIzzG8NiiT*œq[:+}2g͚ikkMPm۶ZXX]vŋM:uԨQ_٣U DX{|<_ ^OuuuPFsO$|hqJW(2oixqUUUw媩"73 ;;;OOω'GWWWq &<}2dg*--mӦMriii .l6 2J-//755555p"H$T󵴴A JT _ dcckEr_ RtY׮]笄F͞=￿{C&MdddϞ=ҲJӧMMM ~p*IK"( '444t隚ZZZt:444p _đ)|WD\.W,\MMD"9^!SGl1x<.Vd:::=z)J> _k fKJJ 17T&YMMoذaÆ /_ҥ˽{~+VS(??????sgώ7NWWwҤI~'DRzSWWW__r*lhh|~]]S9G ~a_B&&&͵o u5Bbͩci4>Fo uuu]]]Auuu <#ؘc[[[___ۘdB$++k̘1山§OH$///$abCCÒ&\.W]]V#rCC*DGd@)#2\^x*7~O^f@ عsgdd7otuu,Y&E*o[хׯ{{{߽{W$}$iذaÆ x޽{7ndɒ &bǧOeJ8py>￿vZJJJљ;w?fZzYf̘amm&ݻWTTw)Љh43׃R8,\K`}8q"<<ߟܥKдK.]tiݺu ӧOkÇFUUU8X,V·:e˖ݻwoኮ Ӛ-Y~}JJɏ?~Ν;¾[33U^!w"76n>|puu+Wx{{S(p7ۯ_>55'7nhdd?zC6ׂ2&&&::ҥK'NhpmN ^Qr LqK.nnnzzz[з(*޿߸uUUUEEŻwR:::1esss###sssccc|SD"Q~~\o㒒B|J!c___\iІfϞ_m۶mŊmrlbA䢢`# ;q"p7P! :+++OOCAݼy!?/]P{nݺUյK.m۶Mхx .[D"7&,,,6o޼iӦ'O9sfɒ%:tnjŕÉܷo ZBUfXxcf?zᔖ &#˖ cP(֭:t̙3\nDDą 222NZXX;hР{{{-hnnaÆ 6\ǏC =z?qX_$Jݻ׭[]v͙3ͣխ!JKJJrssKKK%; f뛘0L###522 䴵Z&D2εOaagJJJFFFfff&&&fff8lbbҥK82ׯ_68nee䱢JӦM#ɱ>>>mH$___񤤤3g~pq6ݸqrqq DVWWxLMd2D"tPCC~'qYKK"+= Y|ĉ߼yӽ{wE6***,Yr}]]pٟJ$?~ӧo޼133suu]dBhMϔ0aI?[0h͛uuu| Bo߾.\h͑d/////[^|ӓ'O?~말o_SԠ E|.:s{MMvVNLLp88f-tV666n28陒:mڴiӦmڴIMM-888>>gժUÆ ζutt<{QVVvaÆݺu+**jŊsε8qbDDDBB LH$BP(Ο?ŋ'O477o!X\\\CCC"8|lbbBک69Q(ccccc444yƗ ޽Ժ/kk.]0Odee6 3 ;;;OOω'c[G_7>o߾9sl߾޽{%rrrZ9''ںxQQ>Ivo-D"P(xo&W455h4@ PQQ ѣ?|UE\n߾}sssB^Z|={YYYYYYΝ𱓓Sff&>뵵?jN.]^zue#2Bŋ⏪Diر#88XCCCѵ9s!^]]љ={ٳsrrΞ={ܹ;wZYYM2ѱ r>}:00PSSSхNMMd2x1e](...// 0]]]t2qe…?۷7o}l޼Ç͛7322 VTT:u5k֭[7};wDEE?~޽T*U,geeɖ-J=zܹsܹ@g'33g B!BHEE5u&mh4+++|o>P Dqoc& c5233O~ر'7"J[DfٍMqX 7H&`0ȪBL&# :! s#F Pzzzzzk^x8__W^mٲE =!_ڵ }EAUVikkXBх?G7ܤRibb!C:kk배 .8qb˖-cƌ>}KUEHMMPt!t:d5!ɲGdFRϟUXXjgghѢ/644E"ڵkqcЦMRRRΝ;9nܸqI$gϞ5wBj5rdd[[ANNΛ7od H$Enݺv:dȐ RSSnP7]XX(JI$nI(|fmmmee6aͷ2553K(n۶-<<%))[nŒ ?]Vbb\Il6{r&Ǔ "744T*wD :"k၁sMNNnt$988w^l@ Xr%1ի٧NbXÇ;Z!C/...o߾wޝw  ?-[c'sϟ?O|ȑ#&L7MLL͟?ޑAdʕ+CBB?~|y1o߾'N3fL׮];$%t^z 4Hх_ UUU1k׮fəo޼Rimm-@,߼yd2L&;99F͑J=z8pԩSO}/^|+BFFFvvvvvvC%ھOzQ( ///qo>HTSSSVVp9455Zy0dEGGϟ?'NlmhƇ%''2Bf7֌p8Ȫ|>5Dx!:":`g*fBH``lyڵeee!xQ}}Vn/ruuuuuP(W&[9GKKŋ7n}6ݻmmmld.KJJfΜ9iҤI&)4P9s&ܞ?H$ݻcbbΟ?e˖~b3յv%rgϞ]n 64d2LE :taaa***?sL\\\˧SnnnfffܺP(DӧOz={JKK_x/^&HVVV'OfX8sl9t:1((ƭSSS?uttݻŢhPv555>|C,--{_ZKFnPKKq`Ckkky<>5 BIR6 "CGdЩ:uj?K IDATiG{~qVVֵkע}}?"5s"""Fq,E"]]],XI@hii߿H~~˗/Ν;WSSs7N+***88xՋ/=jJJJ*))OIIH$ "l2lee%7^PP```j#2˥RzzzmhhH$*** !YEEE(d@'?fffN2ի#GTt9bرIII;vRǏ|]d'-X"??̙3V_~/۷iBBݻw݋}Z3!4x+WxǏ/] (T*?իWwr@222>|%7F"RX 7n̝;W 5jԨQ'999>>~͊.tl[bqJR!i~m- !$H^zM6b{{Ç6ۛN+^#Y&222000**{-333$CCCKMbfffjjjrڨ rmmfUUn^^\Gd>툌wVBGd߿7ʕ+FRt9mҥǎ]tҥKB .UJ Ytss[nGAݻw޽{# :zh NE:u˾.d666#F֭ϕ3LXhѢED"Q\\\TT_vZMḾ3LEٖ=jgg7p@Et:d2ȄH ={=zȑ#UUUۨL@ mfBqܸ۷oܹFP2Rt@\n݅  5ܺukĈroB}irrrlll8::R(sAdQYY___jjjDGdP"pJlL>zhPPP``e0Ϟ= 266[bŞ={d'$&&.\W^NNN-p¯'$$̝;Gt:СCO<=z5sBo``wq]cc:^}} N:u%ϰ'O\nnnϟ?> JڲeKBBBjjjXXXmmEt2`gϞ}nBRWW̞=+hWEEEN BQQQmy`nn?ܾ}妥EDDx{{C /:t֭[SRR:yf{{{ K>}F'%33sҤI=zx۷SRRBob !ܚ&ȅT*ȈBȎ 2rzzzuuubZJDǏϝ;f7XTZΝٹO#9!oooooNS~ƍ{mttsNSSә3g6>}>TCBB޿;w;v,,,LOOoȐ!C:t.\hhh]<|>// B&E"$Bp8>/)ҥKtbaabllLt'ZgSZr+Qb@311>}%IrrrTTԹskeeΚ5ɴE(**ڲeÇLcǦMF*,z2|x̓Dg ۷o? w9gΝ;}mk˗"5bqnnn7&vvv\.X.$zCC/LR% nSLaXƍcXǎ5j+JD m߾=((ȑ#.ݫ1w&#ӿ ,;;;;;EDOPCB 8p2]Щhݻw]vP*^paÆ ^bXζVRgѧO>}DDD\~GvrrZj! @IҨ[>~~~~pdX|˗7QRRWRXX( 7Ad.x%\.`H1"'9xbX]]wDhB+'.@g׿ )"۷k׮gB PSSs hhh47T__?--# T*{Æ O>}իWcccVX{EW*رcݺu2d HIIoX,N>vZ%:ĉݻocc3i$/_*. =z4JzI@@2B>BؚC}rrrBMuuuwDa08^/A"hhh 腌QŽJ+W=z9u EWZٙJ&$$,Y9|)6oެO?<>==cJR;v$&&Pnj|REWΞ=;k,+T*ݷoBӧ.]ruuUtQbʔ)z_bTz!CCCCCC#GHVD"H$Xj*'''?3j(kkk~zFKHHwﮮneeӏKE޽ZZZk(// 211ٳʕ+E"/kBJGV.HE9;;khh}7qqqS-/d rP(\lӧB\.w嶶VVV[liZ䃿OXdjjdg|sssGyʕ+?PtE+**ZfM.]f̘amm}???-uڵ^zuڵ?~ܿ֬f7>lAAEIII 2\[[K"i4DbX"hjj787YNJJJL7n۷o]Pk׮"H倏PPP@w .߿loܸܷo_퀭iqtaԨQ5O>|>_vAY$Xq[  ߿… exr?jaK.ma`+7͛G,KPRRR ޼y% mE۷o޽!77>8I$[B̟?G@0aJ577߸qcII+jD"ҥ˪UD"]~5 qrrj<>mڴ#GHWHMM̙3477J4ɓR8ٳ^*//G;w!qFI@Y/zxbtO<3fP(_jՠA֮]{=@9mܸtbҤp߾}{|||uuuLLKKKCBBFaÆڎ)ѣ~~~YԩSW\ SU={2ݻwڵʕ+~۷o31%%%99yٲe#GZ/nڴy!~ŋΝիWrr֭[">>>WaaammmZZ?,JZYl;~֭[srrA!oߎ{|bwqo+W"͛R[[wOO>_p!;;{ҤIb755A~"-B rOI$\:8 /ZYY+Wr8EWQbb/BhȐ!ϟ?J\.ݻ+Wtqq! eɒ%/^'rȠP(Nj ڻ/H$JLLܳgĉ aG}e;sPlll{fڵKх|gggЎ;"ozyy!VX!ԲeB>>>^[DD!CBǏFqqq'Nڵ+F#"8Қʥ n;wM0117#Q"FF?-|ر!__ѣWҤl&k>s 䈳,/TSE*"lقov={ z o߾F*=yAd2\rׯ]TY¢7k֬mzX,VmmmmvVVV"bbb?pЭ[RiNN~~SNhBŋB7nTQQH׆ |\wܩzN]]]EflrĉnݺEDD4yiEEE\\Ǐdװaô;lXPP˗/SSS[s-ӻyQ:/Nvvv\\\||gϒpqqpww۷/l ͚5穩m6|}]LEEEѵ|:]]7o7oŋR)))}100&p7={)!@ihh޾}{ԨQ Vdѵf HB"p%7ٽ{6qha/^Xp'OGony&79BH,S#d2Y,"f*ׯ3F__?77Js5`dddmmA9;;;;;O:!Ғs{DچotWCCӧ7m˭8$ q!$JB^[[.MԡDҳflRG&Y,622, y1 R$J500Y3}Gyy9)aEKr9BHϿ[BqF[[[QSSnny DP!/v.6痞^TTt… .w]o'( \N\ԭp;ĕ+a91b͟W!;v0J;;)St}X*jkk,BzAmذaԩ?ݻG ѵWWvZZZH$j !$BL&S,o_@l>W;]}M,--wڵaÆ?c۶mqqq#Gꫯ4mR{-**IJJ0jb„ &L@=}۷o>rH\\!C"#####CBB؋L&7oȑ#pszg酆Rrffmp} ࠠ@"еB%^;O,KRH$ RiSS@ J'q\X&bS.D"gOB!zL:==Du~.m?utt O8ԅ#:NӍtAۥ7sΝ~);;;!!aҥxŋUWHKK;qjG"p qÇg̘ O>Emݺ9tPZ=j1'''!!aɒ%xNBBBu( c0=؅+VXxʕW~bHHHFFFJJʰa{^vD"}7'Nܺu+ҳ|w@mmmQQ]T*߿̙gVTT,YdNNNnZ>}aE"Q"-_L&S([ZZRjY*Ռi4L&#T*"~dΞ={ƌgϞݾ}~8ty=Nkuxo޼yӧOر[-5ivlllNN֭[)رcm۶[ᔔ8pO?577 8p޼yᶶeeeǏG=58rȌ~ ߍ߲e-|C'xxx<~!dff6s7зHT.[lܸqn wL&KJJpŋ+**\\\f̘1yET'N[ݻw &***266VIH$D"Q"xbȨ 2N*1B7+":i @wދ/R(O>}С;L$?رcIIIӧO?͛ceeezzzZZZzzzvv6Drww233{s~UWW0`7n:tҞy9%?x ;;[ H$''+WN!B"A5gLL!DU"/ק"/NgXL&N3 /}D8ϗH$D(E"T*B/ r-0lbbbfffjjjk۷B׮]5jԂ lO݉D"___.K̙?޽{B8T*?k׮{ȑ#4̋Q:]vF JKbccUg^zÆ Ħ^r]wΚ:S x{{Kg͚uA:{ -Zk׮lÃlٲW憻~".ݑmݧ p N81m4O?f͚NFֶx$$$|nM,_z… /_cƌ3f;233CBBrrrv>󲲲[ni%Kܹs޽{޽{ݺu455m7|D||ĉ ƶm ##QF!ܹ_xܹssss9/a1/Zۆ7nq^D"]re'Oh=<a !t|Mnnny<éR, p&cccՊx#NonnVLP4lAd_h49r'N8~Ν;>?8((j$eMMM/_tR]]]ttCƎO> QF"޽{RѣG[ZZAZvGw!. A7Bvrr;v,q.9--ٳg E__%OOO hjj™ڊZBS͆EX,v_. bnnnnnmmmD mSRRB'jXh4KKK+++ kkkN6777bխ9xtuuWZj*:6v:GjܹsΝ۳w1쳟ɓ';\ۮnMb5LKK˳g͛^^@ØLf\\\\\\w7خt^qƍ7ƨsMЮGu̙ӯH$͛7fddiE/X[[>|̙Ç711v\~/åuuuϞ=ZMLfttttt4B秥>}Z&2᪗AgbiFy+q8 RRR2p^o͎yٳg :T\\`0p4^iiixeee<≲r\k!D"p~;}||mllLH^G&qWS(8_[[[YYY]]]SSѣ7oVTT533p8vvvvvvvvvVVV} Y;ddum޼}_ 视f?3fh]sΑH9sh-=ظo߾]v577-hnA ܼy֭[yyyzzz˗/1bzwOBBBUUUgA䌌 Phh&d%%%xvx<˗; "D"ظ$Jd2DhP*,,,,,lϞ=yyy /_>z(L1bСCf~G %%%l6{Ĉ裏LMMݺW`ٸl6BH"ddd:uI#no_vZ@gϞ=]WWᔔn+577o .hޓ&M‹ryYYY~~~AAA~~3g֮]BPlmm===q:9((.DR6.++ƅh!C8.%3}:::׷$ N'WWWWUU}fff|||UURg\\\ C)JL';vq3f̛7_Wo};t]ඟqƍ7T焇O0=<G`̘1...mLdff:t?՝;wŋ5L"ܹs֭[nzB((((&&f֭aaa="##;Nu]WWW As\\榾EFFYZZ[$X,ѷظdX**t:YOO85@P /^\zŋ?Y6tСC8n4T__0~={W6lذaⴴW~~~8G.?~8''޽{Jr/v{bN>!TUU}d2'''J vcC*Μ9s9:#FMٳgfP(Q1///???//`Ϟ=!CCC777WWWwwwWWW777777_ {4//ѣGO<)**zyMM BD"q877#G:;;%q JjjjsαcB ry?L&#];[f L& נU(/^9s&Ś5k֘1cBCCixۉ|{|r;w^r͛l6{ܸq۷o캲5Bxɽ{p饧OR(:,,LmԲG2O>år'OpkEEE,LF"H$zA( 2FVDh E.d/ ^G&.]P(pe~~@8;;W񚛛>|ѣf]]]OOϨovСcǼFÊW\)p(yРA}雰aÆ+WvzI@C<?-EDDDDDsjkkq&ӧ{ii)mccɮ 0`3|>ѣGyyy>!dcc;n8tvveueaaaaa:A5y1Hljdooo8/J%f<:>nf5334Ȉ022KU'Է:tn:+++o߾[YYYY[[ر#88' bW\ILLÇoF#=h<33 SN v Ryر &t!+H4\TT^!BrDOOR\Y$Q(bnDV*$IOOظJd2**9){GGG?Bޞ;w...NTX,??):ggw_cccQQ<~X.t3gmj*TqF@`ff6dȐРN\?-[_sS F|k80`^ܦ|ά,(((.....Ͽqyzzzyydd2пTVV⛅999yyy6i FFFn)8AsJeii)&??۷oonnP( JoL&{e\%ܰj8I5UYM\Bd2ŋx"ʊ2ٹכ xs޽f͚#Fhu BQXXWYYIRBBB>0nu֭[O<9ydg+ܻwdj>4naa|pԗD"+++XklloYP---4"S( @eff6|ÇB0777''ѣGEEE !2loouF&UWWxϟTWWP(nnnÇ_bE@@{<{#tСCq\];55Ν;w^bB  4h_?w444L4)&&櫯z9::TUUFބ/^̬P(>>>} @$!&&ikk+..+((ݹsgii)L8p`HH}||ࢊF kwmdʸpKADwmmmx<"nxxtzRRSRR'-,,BCCnwJ}999bL&{xx~!!!ewm۶EEEvBZZ5STTԮ^V^^nnn^]]P\&&&H"Bޛ"2Ŋ B\9sѣG!p쬭mlllmmX,fنoާT*@( WYYY^^^]]kH$3n8spp*Q]{!HvɆ&6d|&O͜9ST:tW#%%%D~;~W:_heFFF!!!!!!3y<^aa!ɓSNq\\w|n޼y֭BW\9p@@&]]]]]]ǎ] ֬YS__Oq> 22K"paΒzzz w&~'Ӵ"2LV(JR'Hs/8?rȑ#G"$Ƀݻw۷\B2wHI޻w/;;[& 4hŊ 1皛bDpeeeii)ѣGҰ*b o``O!p)vjkk xZ,d2ǭϢP(666C hpd2WZ*..NKKJLLܼyBprr G"߿ҥK7o455 ZYYh4.+]hmmݳgϗ_~٧Iڪvomm-..~-)))))ILLr|>`ee.`kk )566&%%%&&&&&GFFN>=$$$88fk^`nnCL...ơ۷o/ 'CBBr[EUgv$144԰pKql… pW…;{lĈ^!~z/^Jл EIIInnn^^ޣG>|6###??˗v8b;X,~IAAAAAdGGGOOѣG{xxxzzzyyw״gGGVxImmmTT|B0jr}:BH,?XO?T__`0p(9** dMEEE˗/_padddomD"x ¹s-Z톼Juwwwwwo7XիW+++ nãO̼pBbbbVVB;v`TydҤI7n\~_X,VTTǍgee @ VbM STpĐ~u_{:L0tB0K޸3 BSL9sBhiix</99y׮]!p,,,ho3R) ojjqvv6m @  Eyyϟ? ?y#Zi IDATTTR(OOɓ'{yy{xxe;f͚. E%%%1` YTT⢾9…  "H."PMMMĈ:b!---zzz'TD@`Œry^^ݻw322^sN2=xzxx#x<ʕ+Ϝ9p|ի7lؠZD")JgQ([[4888>>^P/R(xxJ vuuussssssttxd2YqqqQQQaa!>.//W*$Ḻ}~~~>>>D{gDqqqKK BNyxx|ڱ+|zvA&̙:aaa| mll:r$9R)Y,X,nmm511Aqf&qJBEd!?o>>%% gP_}wٲeW^755t###KKKŽt:]__01Tۯ .Dy游8F"n:}WzJ577W`MGG'&& g =x@R)ɓ?C<BHWWD;::;88<㕾TRR'q333яvuuő}6>O9&b< !dee+L8톿w^xm۶+WvR}%K4lQQQ}8Ip-mjjR(L&S,WDnjjB/DEd",戠Dm211+++^ _K[m۶s%%%k"O>͂vv톼%d2 jH*VUUWVVVUU߸q_AYYYY[[+Ĵ] R\\m۶Ç޽;""}Q}JL_k׮v+ȑ#-v+,6f̘1cƈ#Gh/?p̙˗744d\Ϙ1ɓQQQnrss+,,tvvNLL\~Cpzx7nY7ZBも**6w~]㲯D޽{!kkkKp8 2RYSSS]]]QQQYYYZZr\niiiee%.N/pz]S<<<>cK~c]]?]ɓZK](**T_RR+"D"2L=,JB!D)L&wbemmmmm=j(2===---++R;<<<,,,$$ $))iժUqqqC y777OMM}[mk!}Nomm KKKy<z }e+++sss2_ o=tgΜE5={L:;w!t)S1yZ1??߾}e~u}p~n+..?יLٳ?~յ>[l={F1k֬M6m޽T*ȑ#SL|~ @@R+**ڭP("""v)6j7 7\.W"tuu-,,lll,--Okkkb'oBājMgBη_llKPy2N5dLLTUU&((h666vvv666 w^p!b ͷ\XX8zh\.WGGG&1 nKDO X,&ȸ~LZ[[ \.C@.kk &L0!$˟>}%/VVVD䰰0cc^oyyILt^٠: 7q۷bҤInHFR#""-jjj*++#eee<(L&-,,^d▪y9s̜9sT]f͚BgGiiiӧWI\//k׮7oށN:vںe˖u֑d?|GhK.;w9^y7onhhM6rG---D*ww)SO6ٳxp DxvB!{VTTԔߺu\T9011155t:  TZ______[['Tb/pÉ OuIB&00pԨQ}&AO?Hŋwڭ["""4_*777E\.ҲF2R "bBhnnV "d:P(d2LR"2^5}tPcccK(O```@@@@@/q-dܸqƻw666*J#455߿}ct```᡾!~ZP-Fs9ru[hQRRѣv+(:u!iӦ͜9Sïx5\^|9??_&EW^ݹsǏbcck>ۗ}||V^=x`[=*۷={6//mƍ#P[[K466v 6.KՃӮogW(ONOO{ӧOmll-ZS{\_kr^y;f]4g,--}e2YPPК5k_`hhϞ=1U'&&vQ64999+++==Ç2lРA_}UXX_leppp||<>o(V^r:bXϩBbGql'G ,)Qz!d``cF]/Ac^xQWWW__OBDӓȸs8sss`0:>O|4u/Q㖖)FFFffffffS_~e߾}]ߎikk~w}JR. !bjj*HJ%qWEGGF[ODf vLz_E!fffbX$bP/EcBUCCC###"jlee?&{z ::Z(fddtѯ!t޽м=..+JSXX֭[5>o TDw=fP(>|0+++++ȑ#VR(l6+%˗_z599BRcǎߐB }}}WWSJUUUDeS]a׮]c23gϔJѣG:NNN.,,h 8q!C y@dff}I&uo&ryusE"QEED"H$DMd===}}}Ft:F300R B0L]]]HBxՉC.C>& <_&֦&E"477KRT,HZZZ4usss>}^V555mhh%?սrPAA1ʪ;R*x@,_vmȑajl9%u_Gb:SSS\XD"ᖨﴰpT8t߹sXч211ku"hrYEڸqH$ҹs'NR0aB~~g.\|r W>]5|RSScllDGGÉ[[H%,>CZ]$dXw.b ,V ĄyeeeD?&e@'Zx}"߉;?O^{&_Jgeq$fʦ&ⓥCD`!P(&~ِJvӯD8(תg2xܵfQpg>fݦT*GQZZ]Bȑ#~滰]pz/^\t)66`;vܹs>>>!!!}ںu sСn{ԄcNNNBB’%K𜄄vqQb1]+VXbENNN``+W^DW6+\soywo٥Kl٢톀Q*֭J_AAIII)mmm;v#O:H$b߻w/B0R*}ѵk=qȑ ϋQvwx[JT^t)66Vuի7l@l-Wu{쬩3wyy9t֬Yİ΋-ڵkW6\l+ju.{vszww}Ӿ}-[9]?RTTY]]d2E"7mԮgH$zjbbbbbbqq1?Ç[ZZjTTTxatt˗T&Os5ъ+nݺ諯=z]WMMM o۶mŇZt)]OPpŋ~W\ػ︦IIr *j8Z[֪?Zmk[[-VA{Q7By岪$G^'(9oߴ>`?ڮR()))^^^'Nx9y<KII_Z֒K6nhXBB0"""222******"""88?&En޼7߄3<=====el&eZhd2ZV*F4oR'ɎdNe"d2\.]Ʒt:D"Y;vl۶B9R.}^~y=L k֬6mچ rssb1F INN={6Bh„ Gٽ{ٳgcbbL[o CQa7o.YҥKgĉIII?P($O !pC{DһwÇ'&&N>F?.~vƍK^vmʕ+W)nӧOFww~!11ĉ'O5k֐H$2f͚KR(vͣG߽{f=zرcǎ vz|Qyy9x111:{\O?p7|sÆ tImbbbxxΝ;BCCϟ|wO8!|}N8>:qĸq>쳟!tѣG#>lZMfZy<ުUv9jԨ[~# b8@0aB2B|5*J3LBqhrll,>_̙3RȠgR>>>ZBipvH*VZVmllJ:llltUlYp8^^^^^^l6Gl6F+ݹ\;ތFlwwwheEӛ"r6Moܸ/2dȊ+}.#"cƌ뗛o߾]v!8{\ںukffA?f8bs_ܢqر BѫWԯzȑ,NWPP8˓^"gwRpdYR566rCC,|NjoV ^h4§Et:`0LwwwCRq2Jp8L&`tfCpǚqB0~;ihhz`0KMBZd2Y,FRVh4⹛YNg4V+EL&Vd$8f#_ڻws aaa pS=rӧO>}q͈#OydWWs1v{8pq„ /_1bnOJJ|{קO>FqСofjjjLL Τj r770x|Zv۲e˂ 'OP(ƍ{Guuu&44*(JfC/t?F9# k@ w;vl@@s;NP{8q'Hh4ZxxN qk؆ {})l6nv{CC\CClh48VfJF\ZZj6q+hTTf'pI$+(3N9dČOX,28⌛I$~8L @@w*{ߋA=H#d$~×joz=x'x}=xo'N8qDk;P(v=55uܹcǎڹsM/_>p)S$%% 2F.\q۷o766g„ +V#R*.\8{Cryll_~&nAׇ) OOv}ћ7oNMMsYVDR߿_ZZj2B, MJJ { tt{SNh4ϟOLLlӟz)" FxxxBBP.Dk` |4d2QThD]6a„G`׿ƌ3f̘OMuSOTs-ԕdWWs1t=rSN:u͛Vo߾ .>}zTTGک8..`0fuI&A'ɡT*vxl6O6 E"0HxWgvA=ztH22`0,KAdNgZlvCCLGZ "fh4D&v;4"p3gL2e̘1{8]kAq܄ǕFO,I$qL';W6nܘ0///::Żx<]||ؐbjtV%t:7"4l6#{Ј 9?~@)"ţG&V644L\xD"9,, s?~<##8NeOJ=AO"twN&9&P |WCmOA@Drs ѶIw S( ;fQ:E͢Ds@vN[,f4###,X0k,<]?rssׯ_~yxUJ޾}jJ,_vƍׯ_ᅪF#NNN^vѣ+))~zpBg :f+++#L&DGG6l֬Yx>:S\zwMM͞={fΜ8ߟzG}`~~ܹs[K"VVVSi9ǮlV%.jZboqJAd׼Гl>O??͛['p8ͻZ-N.--ׯ_/++ 4-,,% W_<3vHccֺ~[nҌKhaZF͂WicZD===_|W^yeРAFKNNyƬ7o޸qܢ>>-$Z-qT\jD"l6܅LшSf@#2O.VVU*J"R&DrűXF$'D\.fX,׼3f\ti/ӔlʞM+J'N"L&WDDc%d={l۶[:@7vPtw>3:6啔~X[[K"ccc⢣ )ʼ\|[SSdOOOVW]]}-????????k<&vk7ѣ'OҦNZcBDDD8f,t7|;w;{Z6mڣ>fD.++4iT* iq `XFᕍDL&Ad| ẅ́x\UUU2R*d2T3jZT11wLʌNnÃfP2p8|>_( @ S3? &Nh2233 _grz=>.b+WvޭB E(CNۀYf9z$??~  &L0)Jsss޽{Ǐoڴd2;&&&66O>"H$Eq>>66V 4 ?<zcR1/ɪ'J6I'7,ww Ο?ߺu륗^:x`ttbtШQn߾ݷo_f{嗏?n۷oގ)cl6DRTTuuu!W^y%""ǎ]06~w}W&Xbٲe{ѣ楗^jc򢣣[޽{vZfXvfDP(t:!؈k~ 2!T*o߾ݳZ %44w#G|Wp`ׯzш낂?!ٷoxQgB3gٳg?w}ihX,وHT*7KT奧[,Fx<\[o2{ IDAT{aznN:4L/))o, CCC(xng4D"H$xB8pܫWvL۱p C}}}b8##C*y0:$$c6m۶m˗/9~8QKo̙sA;̛7Ϲ<4MNNN~~~^^^~~X,I$Rhhhtt͛ީ]ĉV})SN:իW]voFټ_~Żtz``\.nqJfT}tƳԜ8qbΝxJ*))i^rZ7"d2P`` BHl6 2NQcc2Ѓ2#f R233/]tҥ[nY,7xs`@@l6f9hl6[aaat#FHJJ9rd]R'V]pݻ/^~nqnUV#k{|ȑ#SRRS:W~~/XVVw3f8{8 7Qd2⚬WTUvGd6m;v r(ccQ԰5L8P(r9Q=ᐨc*d___ǰ2<%bpڵRYcjC1}`qx!??_&֚fb{\F2sCFEEE/r~~_|pB2x˗̙r333ar0cjUR HkEGGĴ. T*fAdLP 2NAdhD9*++322Ϟ=k0"""|ͤ>PÇ>|Vtŋ7mg}655uԨQzbfyƍk֬۷ovvvhhGpqul6y@d2DQF2L.bX|y\^ZZjB4M x<>P(t̓TjPPPPPPQiMMM]]ݝ;w Ds-FP5\.`xxxp8bw)F766jNz=!cc.d29ѫ- [,&ο.`0 o>A7)+JXT*RFnY ®(fmܸ>2dH^^^2Z #׮]c]66Y,FjF#~{4 v`0z eV5LVOքNk+ 4< "T*r8777:+K"l6Fyzz2L׬sM ??/ܿO3nܸ訨(@NVojƌKi:۷oܸnݺ=<'''>>{KJJfϞ-HBakdjP(B8$lX `ho 9 5… bb;v۶m< v2?yw,T&e2YMMb!rŔJه?ٳg[_ݳgf6mڬY2555555xJ1&Yp±3FX,rDT{O_=7ch4g&+++v_T*f___???I ݻwp8??ׯ_LL믿M{n߾ׯߦMf͚瑕J޽{7nؾ漼%Kxoqq1BH$I$泯j5$c_$F&F#J0hDi4~mǎ3g| a{ .\Pӝ:uj׮] ,XxԩS̙(… .T(۷o;wn+}:t>h"H$5h4VVVd2\.۷ކN|>7`^zyځNV#$nkxK"LXjZ?#Zo&(n1L|gB!6hnD~hPk1 *4ᇠT*ǔry^^^ffR"r fee˖?s֭k ۚŋ={t pP(q#ɾer]1dӾ߳JұCZRwuuu~~>vr8->"6bӧO%@װZΝۻwoZZj>}-[ ޽B̞=}/((0kbwwCJfI$SyD"OFFL&|K5 2~vwy!>yǏ@̀+[ B"HҊ Tz5<_-B-00бDY  @<z$wwwsXӐfeX,+**Oo߾=--Be>n:NG"233q9:pM555 RPF F ! }/we$!svH$( |[YYyƍ BAh@@ǎ cQk׮۷ "!!a͚5gv\??d2l!!! <88FdRI>j$ _3INqd;x\z~_uAAArr͛NO Sy{{]vڵkڵ'N|뭷=n>###Ϝ93j(g=3h͟yΝ;vt34全ZUUUǔ˥RիW|G|')))w@Lų⳼r\,^rE&1e|OoBHHxTv_}vK/}}}СC߿j*TYYYx1:<Y =M1Lb޽{E"d2>D@M'ccce0$ݻr_Z!FcQQQAAAnnnAAAyy9BJFEEgر111"5gX.^9rD*=s3glqD}ٸq =dggׯ{/HB5"k4b4 V!D6LD#2pW_߿gٲeχZnjĈ#F}W_}{mܸqѢE ,:\~7޸qƬY֭[Zⳛj{f"ТK.]v… ?ga[,xJLvyLVWWa28 P]hѢ˗{ss߿O"._|^KBebA,gddHRLlO\sZd900D"u~|~ɓ(l.(({;w򊊊, BGFF>sQQQt3nl6K${Ǐd2 #G;LٳǎȨ۷ܹs'OFZY\rҥ/>N'Nڽ%%%ӦM+//wss -nRB8WjZOOO` F#N'!N&q} 2p]+Wܿ\\O? /skvZ~-[6nܸiӦ˗Pd(''gժUC 믿 uZNg}6bĈ$g5`0TTT4)={V*VUU3T*5000((:* />l֭ W^}Ȟ3ͶdТEZL!# -^jz"$ f#r-*堠 6h|'oԩSyAu ŝ;wrrrܹs|DP"""bccgΜkkY,V>5xҸD"H4~xbZtҽ{J{޽o߾]vQ}8q@wa6^zԩӧOgeeHÇ\rɮ< =׷h4UUU"H,v[V#lN +ȸbtdpJ.{+d={bbbN<9zhgt0>駟Xbk֬Y~;[oA<++?sLbb˗Gu Tj`ТSN9rDt:1j~nd2J*޿ܹsrRf0 Ǽr@@P($jjj6l߳X^{Çr67< Fxxxxxx(&ʎIJ܎bx߿clJf:t^lɓ]۬oݺuʕ+W\~]P |||뗜dɒ$]b=쳎k C^^ !Ct "?Kf3.BF5oD 2E6lR_~… !ڃyzz.[l̙~+ٳ~gyrUV;vlO{Q(hD~$+W2dHjjnnn<%$$4`04(WUUeeeeddTVVzM $"˸POt[(++/zzz~Nj-x=,^!s.@7)|LVSScXtztr2{U[[ۚoݺ5m4E-Ya^]ee%N_zfȐ!K,0`@||<tzBB;wܾ}ڵk6mR 6l%`O Fsտ7nj</))髯JIIi#k{I&=NCCC[L&I$6~Fd0&>8J%Bd2AdFdf+; <Ο?pBT[o{0"88xΝK.}Ǐ?cƌ*00:7nܽ{wHHȯ:sL2Au^zh4g۸~cOgE6ڧpTEE\.'n*++ jśq8@ ].]uִ 6̝;W vɓ;c`0 >^'ɎeXT*v;ޘFy{{XW3j!{ IDATO?tg~뭷ܹsO>}tYY{dɒC Kh())v3337ol6RRRRRR=d:v,Knn͛7oܸq\/&&&FDD8{qԩǏ?3~`999kbPHۘVRQ(F+ A`0 pJ[ vҥv0a©S' wر .k}ϟ4u 6=z422r/^ڱ [<Zz#G;֘wYVB{e2YUUBd%%%sNoQ "y @jjjٳ}_u>2g޽{;r`q.BAPTUUjB$6P(~~~t:]Y,ŲcǎqoѣF_u3gܾ}L&6W^yZK^z5k,^}6رf 0 %%eO=,,KaaaNNέ[n޼t:֯_#G.]tȑcl3<~[D$!nDf2o"T*x#[,=O?sܹs=gJNNO,Yr;vșV={g}ŋm\#L0٣6233O8q)g"|>z"\]]- EQQх ]b?-&vs@544?{,y饗~>}<>\"Jy<^bbbGp8FPO.]*++#z0?رc>>>F1:::%%?NJJ `0Ç>|Uϟ?̙Çg/3<)|Е CccZFL&V8SX,RDYVht\IYL5l6IA^A( $W>0!Annh$QQQ 5k֠A벅k׮ܝ;w>~T*X,۷okDFF BFYRjrJV燗qY``0D#chh\to۶f;{D=>0W`0>)Sqqq?Ӕ)S=8_?o9rլV{.]tk֬ILLLIIq@D-k6q:IXP.WUUz $CNBnrz 6x۷o/^xԩ˗/_fM껝m۶ܹSϜ9s޽sH$t/_>uԙ3g=www@ Z@Vd25gU(rΝ;8;KBT*'p2^x~~~~~~.{t :dxbzzzFFFii)3f̮]&Mԁ/_...:ujGnm]kПP(>>ׯ]eggsܐ"!f!Zfkjjv{AdVKRqٱhYnnq<<<^1AX,/2lذ7xpϞ=q;N{۷geeŭ[n̙Ow%H9O>IJJ5jڃbXcJR&)J\N,\rE.KR<.BNs\.y<^󅀀2% :LEEi0fϞگ_#&O{- BN[,뽽ϟ?6ݜ•d.B\j0xL&ӆ v]^^]g!>od׮][n]~1cVZSO9{tr= +++r9ĘL&?::'cН47k4`ZU*N6 ^c͸:ZTZV\)]]]Sx-> Xf2 ˋb3a0 {Y,F V\.H$RB*bFb0"H$ |x֭Ç7nڵk=41*** ђ^rww 0` !c8ׁpqXtz^R>fZhz}ccZfJ7+--zx`Rl6`xxx3^x .UN{xxl:NEUUUrBd _?F"CBBƍ'z@ ww=㫫ۿ]_f͚`^z9{\.͛ r(իW'''''';{  Fxxxxxxk&eO!6D4ż2ǃNVϝ;wԩ'Ob//1cƼ;&L| fD.x:׷dɒUV}7wNJJrJ b-[̚5f;{8mqwwkŊ={_~ m,݋n/--sNaaaAAAAAAaa!np8"(<d' a 'Qk4Nt 8T*zNZ`0p`h!btɤg㚺I  {(PЂ{[qPہZuW[uyWZZ[@ DPCe+d$?_/F@Ez+49s  F322200hL&X3~ڝ@ ruuuDfWTTBby&immmnnor2em^lmmmll6 &N}v٠L&KOO_jUK zƆNNYZZl\4!DR#2͈ 1c;ڵk֭[كg̘q֭'eӧO######?<,*****cbb *,,DUTTdggdma͚5˗ӯ_#G7.**J O K.]p!!!x޼yYUUY)HxtHKKK3ǾЕ({ݻw/!!_~SLOGER;7n(I&uND6mB ;w.ܘ Э[B˗/MSC xM.^8tPMZ񒓓322233ѣW\5],: `|߆X,H$G"vD$q\D" y_" DmO3dG&I"TY,#$x{خA(JR'9q .K, U7bllleeennnmmknnncccnnnaa'$T*/_^qTD'ׯ]jz \.ɬB*AdHDߏtz}}=DP(8,#2Orr)S;2?wԣGw]ٳ{5kVII?%o͜9ŋDUJqFoooUXX/l۶-66!tȑBM6͘1ڵk{}->Ll>*;;#GN4)::ZKd_uuuÇ?š34]z˅mߏ3fȐ!.tz8Ff>.aeWʪ~K<) XӧZޣ%''GEEEEE=~X$ٍ?믿5jo(ϑ#GkkkΝɓ'WZghm #)SN]t);;!`K1Qxz;w=z4;;iԩ~-1RǶ WYYItԩw9V#qBqʕ=z^ϯ+R̍\.b:'NDž?f͚#333>}ɓOfgg+ rJOOO777 JAL|q[r* XRRP(JeVORq!`0tuuqFU3k|Ŀ5Ʃ9&!\%)>&KJRP27Lb}0Ld2D߾}Y,:dwx݇X,6mZrrrlle{mٳg,ɩ^~#FP)g``ߎD"vDH$B:VYY٠A\]]o߾a={|wM70h&~zСu9;v->|G'NTX[ hӧLIIy Ĥ!_h߾}7oVݔ-n$5uO<944Nhhh 555 9sYBW\Yp!%N9f=x`t- D"p8񴪪XhcccmmmVp .C$=|޽{wʢC7nܸq<==5ῦW^4 {ȯ^:gΜ۷Oy|>}W\+>-T*'NxF+ܼy-oxUV-]`1oadmY7Xt{Yw P(|ɓ r@hqׯ˗'M4z%KtޅBattjjjy3gup%Ǐ!ܪm۶7R@{h4b͛7](pX2~jaaqZ</..ѣG>LJJd&M:|СC @hh(DrQQҥKW^)dԆN:u]CCåK)ϟ9s֭[!!!˗/o?ۄ O>}oXK1qA>xk$Ç)<5r۷9ݝD"i@@d/hdz899y<^vv;ZZ 77WT~^j.HRX~FAd]]]T*HRqY&im-- t%%%%~?M 4ɓ'>=z4s݅G~7b\w-@OOa`09sZϟ?y񜈈ZFN֑~\؋E ܾ}Ν;FFFƍ_'M:6/hmcǎck> :NmllZZWUUUUUUUYYz ?Ńb:::fff&&&&&&&LMM MsЩ)Q$S IDATʬr 2|ɓ'/ڹzINNB:tѼ{iy+VG~Cҩh :|0mFeff"-[l2ܓBT≜c5 j+((h)SSY6 PMM9ΣG>))) bРA--CR>}JRՌ*NJJ$(L&d2BFAd=|":f̘g$+W9ִiӞ?~A]]]<_ZF֑TS| 5ἫgϞOO}׬YoYg_V@ $6_v} ??)Sl۶r<==ѣGQQQ111<fы/=zt޽۫`0`t!K.oݺ?t-h; bccӖkbӲ򲲲zi4 kkk&,--q$N b>bP(rbX$0 A{cCCǏ8C m nJ߫#MO?khhkƏ$dG~#31\allLLܔ$ʙqqqZ&HD[ +cҞ d2Y]]é{ OGX߅E"X,rBP,񣞞n`0pF7M[Ҋ8M$ni4懷AH-'\B@5RL4fVÖJg{iC0L:bX,1-bC hb8999>>>...99D" :t…>>>...HcaaAt)>l۶m֭E6o޼SN!ϟ?p&x޼y/222򫯾p8^^^ ÇgϞMII1um){ԩ)))ӛ6mZXXءC- 8088xСBmBKjb4ŋ?  ŵkN>ݫW5k,ZCtbccMֿ0SSӏ䠠 5 dggϘ1!TPP>Ło$v :::8[#2"d2JC$YXX_k__looE└O!\\\ PRRһw[n}駚EK;;;?hXN555ՍbL[8CuT'4555UUU5555555559cb!w=wt:wFFFt:`xF^ouuu"H$x<#59nWl@-*۝$%%%&&&$$$%% :7x`??׉,Y$++122rĉcn7'-,,߿|Gaa!1 T*'N{nn޼ٖ@[: m۶ݻw7{vTT޸qcԩ3*VtR3\n߼yCtٲe!!!t:ߩZzuڸf 8t~#x_ioo`0X}H1"G]WW[#˯\gϞ/~ӦM;wG([YYiiiyΝ &4L&3008{{n:b0᦮_8y䌌 OOpйs+z„ F(((X|yAAkX,֝;wv㓓ckkh8-h5ܾ}["̞=[Ӆ.kܹ[lH$4Mu~}}}zzz[ ށC |bN:bĈզ=]rroSTTԷo!C >|̙mBKDZb:Ù6mZ~~~ll,ߛR ݹsg~~Y/qǎ۷o>d2߿^~-\]]bqYYx<*Z]]M& LPH|Sq5Mx\`Dw^߾}5]NgŋUV=yd޼y[nurrtE4@&mܸ{ݸqcqӦMwMMMmi3fWW/^xzz'v؁Ś0aj߾}kAAB9??„ ɳgϖ}qppI=zh -h5$'')lQ[XXmll'Oѯ_?+111Nt!L&o5]@cp¶RFin*333 /@G/)))...))),,o޼~"ttt,,,,,,p¢Gx&$A#ɸձ Eee%WVVUTT477ٳ]^lmm{իW/kk%K(ׯ_xŋ/_T*>>>'O566tpm'''XsH8;;'''/]tذa-_,,,4]xǏߺuWFF˗ŋ5]W7f7o|…ӧt?۲eɓ'4](--={vJJիW?ǏմCFegg#ZlddTQQajjJiiYP pGd "J#+Vhkp\2455t9yQQQ .DKUVV8p`ӦM7 h E ^WW+bNԲ.266fXL&?UhD"ׯ󋊊pڸRD4s={ĭdq減tED. p:G]V^^)-^zӧW^d2c_P{D8y&H$Epcu֧~otE4,,,,,lݺu˖-۸qcof]{DfEFFFFF:t̙3?An9s"<<|ԩzyB>pgfݺuk=zHII鰾R499yٲej7:盙YzdK  RT(t:!T__ojjT*U*R(Lp >D" [mo Gt!@[nk-/[NӅ&dbbbbbbqӤ2rJJJx<r<7LVM'qD"K.zx.f͚3֚-A__O>}i#T/**"2ʅb!DP޽{k5݊T"y'˙Lǀ<==5RYY{S?iӦM0ԩS_.\j*wwwMMLL?.\]]'MeՑҎ?~33ݻw/_J|ͱc̙믿2LMW@3r֭[۷x'Nh%,(((P!pD,R)BϑH$8TtG<!?>Qyy444DDDlذAӅh'N߿`h@mwb%HTgά6BьߢOiff;@WUTTׯ_lNϞ=cAP(^ fٯT<~… !ۻwowwwH,+++;;?fffVWWH$//szxxxxxw|mˣGH$҈#Ο?O5]_n]ppqӧO9r…}{ݰ#Fё{lAv…XOO'N̟?JjΪz̙O>=zW_}۪;wӧOϝ;p{BBy7bJ2''/@DrWH4\[[r#2F) tDhhoڝD"ZΝ; ,t!ZjΝ={\r mY$ʸr]]bprssBFp[eը2`0  1000A @wPWW'\.BGӧOIGGǎ@Wbiiiii9dՙ555 .azwuumEEE9998yUWWٳg~|||,Xп777]=z˗rph˖-[lYll#GǏ?{SB TW޻wOGG' 66vᚮsKOO $IIIt94&99y̙R422׷~R>*((PAb5A䆆}}}#2"CGdЭ1 2/ֲX,MWԹ|aMV233/\p nߩ\.x&er8"",d2YM1 JfX8d2 |hq722jc,11ٳg)))%%%!333www// |l| 1GTefffdddff޻wDOOť~N$윜x› ߿~tLJábR Aýu\nxxիW/^0uԱcIНFFF޸q֭[r|ܸq!!!sz*|nK.:th۶mCt钅Fʈ_jBVǫrB8, [DJDGd?FQTL@7B@3gMWZ$""":BԶm\\\]4L666~bX"bpZp8eee󫪪nFtNLL,--sww8q[5u JGGnҤIx\.JMM=zhQQLvss8pANR*8s' DիW߾} dɒ~Ds/kũ|D"qtttQ `H6nܸcǎ1u@ DFFFEEeee>رcӦM355tu]Dhh%KΝoimGohhJx0.P(E"Bᠷg[WWT*۸#CCæd2A2 ===]]]ܟȈL&S(|$H"xw .,((8}… 5Uƛ7oToUm*''¢z#366655 M;"H%,Hh4LP#2ּRSS5]GdiwlGz9BoÆ Æ S]&::رcϞ=C 4믿=zt=BW<==5]hэ7d2̙35]6JNNr Lt-Еt:nllb@ \.Wn, !&81QAtttX,VK͘ t!Nǝqh_B;@G+..~tttbbb^^Bi6m8p.:2ܷo߾}N6 ϩ$n-غukuu5N߿?˕d88bccԷo߱c-ӇJjt >>>UUU!Rj2SS˗/_\"GEEEFF=zTWWԨQ~~~~~~t%uuuO333===--IIIΘ1߿+KNN 駟mۦZ@p******pڸfWWWD"ba*ӱfffL&G̷L_" iaa.l]41999ȫOXYYX[[[XXGT?~ڵ{גQpcccO~l|Wj~~~^Zx}޽x޽{_|i& \zu񇉉[$I:} tmJ*nܸ?W?BP(&Oy bH$rnD"1Lܞ`X,===Vhx&B100mLN?p9t1W\vZaaa޽GzqƵץ\@caa1o޼y!"##իW:?={6񱥧 jjjdr߾}5]QWfbbBlhhHMM}ӧOݻwQ<NiiiZZZZZZjjjZZZnnnCC3ᡵ .|=AŅEEEl6% ^LOOG]\\,,,,,,p%]dcc㶟-qzyUUUii)nό7naaammmmmmeeգGKK=zwFV߸q;$b[VV7|p5p8 WWWPAAmD"L& qȸَȸUX,V, Usu{ǵ~3g:;;kQUUzꘘcc]vTP\r%>>>!!!77GWYCT}V9s!LwBhѢE3-Ze'N qqqׯ__D"Y5LLT݁L& Yh8*'O|Mdd 5QT*"ӊ  HT.xN==I9+CTUUs̙9szg]U#UU/DH}b-9&  .\vÇr\Tj $ɝ;w-[lذ! `ѢESLnIvv6΂ B銺 ]]]!$J:tQmbbҧO8K}}WrssrrrrsskkkB'Nܼy{~O#BCC,YwرCӵN1ft:UQMZXXXZZj~JSO@ xMeej !!`޾eee!/ 67.++Id!C,rqqAG6q.Kh4qדjFx<:.e2Fp8T*@o6,,Lӵ>?hРBPEEEvvvt9B<}˗//_ɼƎO?1LGyyy'oϟ⅃C@@֭[%KKKBO6}M6m߾q8WAA4V(vh4Zx'SSSI]ݼyfXBӅh믿ni@S Nr-=d8VWW>?U#nǚqF^f2$XGGbdzf08"@d|޽koo~׺:tY2?~|ذa8% L^~uW^*>.g}gڵk7lذh" ʺ: q|"k B"촴L| BL&P3$݃!Hpg r9BgϞ^^^gׯd7,YsNMX,{ Q+"xjll)ΝKO3n`0\\\约N؄/^㫁4^'ggg# \~~0""bʔ)0@}l:ngg*((7nmr\P]]ȀgJ҆"LP+++BT*EAd===ԑ#G mll6m4cƌk׮ݻWuK.!VXܧOC៶djj*B(333333<<<55"~!? M7 ~uV&qFbgccTC,7WDEELMd񴙙N:5fMuC&nݪBBkj4ZxRxqf( R7oR@ d744Scccd2uuuqKf"4гjbH$Hc!̇ ..^|gnc۷9sgϞ;w\tꍣh+W N81o޼'#Ξ= mv`0-ZhѢÇoذaΝ;vXd M/_" rrܸqq @VlllHH@ ?nήwD(>N_x`ggg8-5k֬ =zRSSsssyyy BGGW^Ο|… @޽a|$鮮KҒ<>󝦦8,<<<~+Vڵ],qrr"H2ՋɂB LP"d#BN?TV__OPpL&+J"k„ ˗/OII!>XwR ֬YZvT*ݼy3СC^~}y77 &L>}mŘ1c;W^ݺuEEE۶m;z(^@&!Ο?w@PDDġCoN{={ʕ+.N;88P"6VV,թRdY5llllnn?\QQQTTU^:v؁p mԎf#x<\\BuuuJ?~KSVVF,r-ڼ!0@3nҌbX:::8ӌ/<3bMqK\7&.ZVd2/^^: `ɒ%j>_zuŊ رc˖-#F@ܺu !|NWZpaJ⺹ܹs۶mOt΂TXXHP0o޼Q*-uڀ`x{{{{{άR ޽{8glllmmmmmmcccee{aaaѳgOb8Љ7oްRⱬbpO>={6P733l-}RݜL&NKKKKKKMMMKK0L|SÝޢ隮t2 ѱѭhrh:2A====,tv[n={胟haNN7441"H9!)"^DGdڋ˝N<Z>H~~>BhĜ@ ?fB:::gΜi?xE/L!kk/_nڴ B6mݰaý{=zTVVaJө/Zh֬Yf;h666666.)9NVVVYYYmm-ި ae¢]<Ǝ?nmmgie˖>}k o;jhhDe@!\}qf0B*p6V"HRPĺx@P pWf6ČS4 _e"WA3z&Du&6S{ ;T*V͛7i4ӃƌhhhXb_رS_ZzuLLq``]-T*O:uҥl .]Ƥ^aoff&G{ΝGfggWWW;99M:oŗBIIIk\\\II-[^ySKU*?322l٢z;qee%өS޽_<1Q=8qBqʕ=z^ϯץwZ qy­rZ=-Q_5ſUHHի oo?tQ^II T_ZZӴOr澾s\M...&l6Ž200ٳ[xZkc]L&b٪qB$ baaѫW/8ԟĺɓ'lEѤR)BQ투_x' ߸qi4]{rppx͛7ׯ_DDD4]o߾|7|}"={!ԧObWllk׈?!D 04~;wnʔ)2ΝC7* bɒ%^JHHh"ɸKK6444Tp8SI$RKaO?ٽ'OhK~wRJrƍcƌ t-h4NS"z6 fxn`&5 hL&C8U+v2=qӚ՝۷oذaaaa#G|ʑ#G mll6m4cƌk׮ݻWuSNݽ{pҥAAAJgΜuVHH۸?sAAAnɓ,k׮]Æ Ν;SRRݻm67nb CCCm۶ƶFڲGٳ{5kVII?pÇߍvT*V͖f.]BX"88O>555IIIjتjnmy9m?MTN;v !|#G~ZRbMM Md)J"ڗhq&bg ER FFFS1^IAyoHT`N3 Zi?xz>/ % q\|*wqMdOwREEXw]{A}m ʂ  H^B$dbhoCDqw& g7a## HxO>S[ZZΛ7耗H!C 2~~~MMM F;vq㌌ZG4ٳjkkۻwWM+""ť1, fkkk(J}}}ǎT* 1LAGd:vٳgrWWcǎ  'N``aaimmVPPpVVVV9r$BymxxSB̙3DǎCM4 سgO``͛WX:{lPPG/n`X˖- v9?'i4򒒒rEEdLgTTT( PNNwi4کS<==OL;w܉OHHv!=@4x&%3 IDAT{F_Έg38/!iĖ&<yh"Q1:$HQQQ^2661_e B_ںf:yfb7BK.=z(^{GT+o=b6mZv-BHCCcϟ?OOOǟ fիL 8<]o:(((;;ڵkSN%.uwG߰.x1oo{]xqǎܮeʔqݾ}RUSSSTTTxxxxxxjjۨQ,--;+@1hРiӦM6 !d2_~#G_^YYaܸqƍP(.TsN:U__bŊ[R?~dS))) r]]???"AbP[[q/=[LDD=lԣNmmm/כ`˗_{Y~#QOf{nΝo7<66zxxN.]pJ^ںbŊ޽{mAOuZR%%%T*E;zڵkuf}}}ׯsBֱ~J@@djii͛7oʔ)7nܘ5kVW#ddd3fdd/UUUS޿{LZQQ.mzz>BgxG, /544999@y;xX=v bU&)((H"p%wtCONN^|˗/'O|\GQ:"s.Nx׾ԩSI$R@@ dž 6y{h4111 ܏>N*Q&wLw^CF r;BBBenLL4Ég>>>))ojЙ3g;޽{p[;w<|իWt:'2a:0XhFFF3fw2TVV־}ܹ#''iӦEӳ˗/OMMB >ȑ#]oyƍW\qtt3f n10}t&7zh GUVVٕl޼jʕ'NLJJ233KHH>|x/~ս:">we5y/^D\\ϥ\\\:XBB•+W"##:f[[3fpo^x1***??_VVVKK? geeu̙ZiӦ[Oׂ-\pNGYXXxyy믽qZSSۓ'On߾ )D&dr3T,vMMMh˳lN*//?t  3f̘4i!^///>/|<)dK.ԸПֆϲʾT{$Z>nivabcc#""^xljjk׮oQr8?རw ]EVSS#xdh z@9sf͚5Νݽ{/_tqqYtKHaÆ 6lÆ ͱǎԜ9s̙3l3^BBݻ=z}ř3gӗO>=zt)dPrrBbeffىoQSVVNII! """5Od2uD 2k|||W^UUU]dINN]T(((aB9|.AINNNݶ_i]qqԩS?}1fnvZAAj*ZUUU\\MRٴ1AAAYYYX`Hב岲]vРAܮb!!!:5{iӦI\]]?1EZZZ!!!֭k߿>@__?&&ϏϵkB߼_33Ww: ##!t1mmm={䄦fRRRHHڵk񚐐vc FCC7bС7nܸqcRR?~m׿G^9&$$ɵ7FEEE ذaӧ:yfnz xbdd$L'LO"{{{{{Ç'$$ܾ}ú1}y٠ mm{޼yŊXjU޿`PQQQcc#1R B+---85!L&CΑH}xxx$%%ݸqCVVE^+))iiiq9111111UUՖ ,\ѣlmmdZ  ^d8kÇlܸۅRvss>}Fݻ788%44¦511رcӦMwމ'[dΝř`77oWXARMMM򢢢^doox 6 nݺ_]\\;&((tvcTUUsss/^8w\EEEΏZAA/B(;;'W6+s}7Yf;۵c &n?Yf%''ߺuv9TUU?~ի&Mwvvi(?'ssssssooׯ_߾}{uuuݴiS;ׯ?>==Ν;XGeee]ͭ6lB(33!IAd&$""܌½t~#׮HݶdIII:ujcc+W ~*t:}Ν>>>fͺt/9sfÆ ߿*x9GӧO/]ȕ+WFഴ4{{{YYٛ7oUMLL5˗/?w2Ś8qbXXX':99HnwtstzYb=|Ņ}mq%2LN> `_>|] w.ڭϯ_޽{/R/_,))v]Sx;BsLxDii'Ο?/**h"OOO555nՉ)].*uN9rϯFNh7o;w.))e۶m#GvQ.""ŋ<Xx11Qwԩ;wVUU t7((huuubbb.\غu+Jv㖖qqqaaaNNN~~~fB޽Ν;w{nCCųgϪoܸN beebŊOZYYa K.4iҲeˎ=o?yedd8qbժU\\t%KJ !$.....4F"E,?WDDԲROs]|=<}~v9kh4=:lذk׮A~ѣVPPv-:;Sˁf͊377 zK[[۹svֶve˖Wyzz^pɓ'ܮ >>>w1bĩSFTcc㴴 6?~L&u@v-///gϺrRSSg͚u֍7~FiotDNyggϞussji=zꊊ3g]Ϟ= jnnvtt8q"Ke .\Xpa#Nw=ФIݾbEEŇlllB˗/OOO7o^]]]DDĄ ?}tܹ]vUTTXZZhhh۷޾7',Ziݺu...SN=rȐ!C]k_~}EEϊ+O?ӏrd2L&p2rNNR$L&ϟ?GZ}oʕnnn'N8}+++n~o߾455!@G/\caaÔPD"xR\\wvQ0oذ!&&fѢEG` 2NK. )u=B-Y&&PQQx۶mwv[yG]h|;wl۶mԩˉ8pp☰v/ѿ񶶶[nƾz*##cРA#Fd;}/NXJ2[[[>&""/̘1JNO>'''-[lܹ?=1bDɄaÆ! FnnNokkhrrr +AdmmmF&ї1^d!!!pdĉ6mr劗ז-[,11q#FvEDz9B=3#RYYY^fOeH-pǫW_[1Ҳd_yĆ}}}ggm۶Eb_PȧOmݺu޼y z gS'O\SSRD+oo?~ёdɒm۶۷۵|U}}ȑ#B>|gb&NF |5k֜9s} 5x‚l~~~~~~hhhdd-'w۷~b֭믿YYYYYYjzseׯ_ñb؄ ݻ{n0eʔ'N$',ѣGݎLMMeX&&&&Ibc 2BJDr 2N':"C1cƄ |||?~ڵ;vHsgСAAASLvEm޼YGGgŊ.lذAGGgʕ]rCCCב嬬,b̀qYijzٳϞ={_}v))_uǏvg8vvvvvv}^?ȼ]|Ƿ6sƌzzzܮncccuuuJr.X,֓'O_xaff7c MDoe˖QFٳۅt寿SQQٴiӴiݻ> Xx X,ֵk._ѣK.-Yݹs ,x^pAZZz666 ӧKJJ޹s'BvÆ Ç;|Ν;_xIpGի޿{aa]?ン'O,(( vn,hi]l͛KzzzjkkWUU}aPppɓ'{;vt}{9e^SL=znݺl2Hc?~TQQquu=y򤭭?vvvvdJJB(33D"qDC)))III#2^hmmʼn%(((`0xw- ={xzz8p`ӦMvZdʕ+U}khh;wݝgO@¢p&{>}[E\\\\\\MM]ZfO-Oe ŋ#"">}~&mmqۛ2ە~ :={vРA+VP(.k. 2|999&Lr( ?cZ~իBk֬7o&ܸq!tңG5'O޸q 5knJ<č{?̚5KAA8|ȑcǎ[xXjj*M6]!}ϟOtL&sÆ 7nmm}5CCCGGǩSN:w<;vbs߾} ꈈ"uuS^|ʊPÇw;2%%333]EQ(zAAA0F_,J1ĉfEHHWcI?-?~xVVի]1vseee9;;o޼ښEd2 CnCZZZLMM޽Zi嚚N?WXX=,--Ҝ0z q?TyyyK6RRRܮrrr^~͛))c:88888@cჁ_aa vٳ]3 #88ŋϞ=SWW_t֚ٳgeee,ϒ fdd]8'//_UUlbbB<ݻwfff  w;bXxALL!,,ɩ] r]~#1B^^od H$\Iǝfff~mCONN^|˗/'O|ѐbַo>y$,,,..o&Lptt455Ggfmmv;r̘1gϞE-[Ǐ>-@[ IDATիWG^n֭[z==9s^ZZZٳ+V_)?~3))cǎycժUwnDĒ%KܹsqzzzNHHH`q˜N/c#G"Лllllll>|xu3f̘1ښD߿pIIٳg9rZ <***v!e6mv!>>СC k?TIIɧO233q򸨨!$++kllldd4k,cccCCC!!j*ZRRB$''|N[,*** }XVRR>ʽ͛!ؕ˹W𺶶gϞ655M81 7T7lذe˖iӦYYYqruuMLL|aܹbbb. 7oΝ;722Ǝ)?N-777w_R<KL&3###555%%K%%%]2 O?\ }NH*,ϵ{aO'w,!˵Yf@b޽{~޼y...o%$$<~̌tw$#,_ܹs!|b&NNNN!!!\=c:fvyNv,Ç...+mvAbSVq]k5223YK.䦦&߰N9iyybfJJ7)+]]]߿?x`b޾} ((hbb2rHKKK[[[MMZ@R^^?cƌn޽ƍYYY!rÇDWZu̙ݻw=~!SŁ cΜ9~~~nnn3gܵkפIΞ=+!!ѣׯ5󹹹p- {;vرcGll͛7={(++;999;;7EP!!!/^`0#GZ`\***V^)dvk֬0an999x]$,//70444449s&^.1ŋBENN'544>xoW /////6Vd2?~'^VSSyTYb'}rZZZLL ^&'_)\}СCmmmJ%%.V&++ʕ+nݼy9sԸ]2>>{͚5ٳvEzˡCpD#H?oܸy<<<Ѿ}O~ȑԜaaauucΟ?!|}??O]9{䄔T||WTTݥK8@&8? W\:t1clmm9 tN:E&>|X__/:u ~ñbRZZڬYjkk###N!ST唔:caa1m4 SSSNz(==]ZZyĉܮp ,xizz:q BW\YlYRRk߅.XEEd.e999aaan}tznnOsrr+DzVSS 8LP`!0ic p!laaq ||'Jݬ%$$ MLL̤E NNN"""N=o޼]v!닻#w_~&LPUU|2BHUU566622]zݻvܹrJ33-[ 4(::[FFFKKݻiii%8і-[' tuuuuuۭR8JKKB EIIiРA***JJJ Bh FUUUiiiYYYqqqyyyQQQyyyqqqiiiiiiuu5&""BnjI~m FkN&" T* c2226T˪7p ~ĉ:bC?⠠/^ 9::޾}{ҤI#'NrJCC+V[NYYuo4~۷"ݹ]ON_~;w8qbRRRPPP^^NtСC-[fdddll gϞ]xDG䬬,wT]]wD׌ũTLSS(Je wDdee̙3gΜ֤/^l۶MTT| v{q`~$22ѣ ۶mv]?K.?222zzz ~&Y__҂<677԰X,|zR!6GN7&455uk;@RR_HHus( ?㓒oHKK IHH@ְR_hh'O***Lr_~dddHV!!!]]] '''993f̜9ֶYvvvvvvv}GNF|۷o~֎;.\(//c'$$ZKuDVUUmjjoWtx.:؂ȍWWg0~uuuIII߿ )--E󫫫|jc0EEEٙ_TTT 544tttƏqF333===x_tre˖9::rrƓ'OrQjun=܌/ݲ, 800J=n!;;/㏝^q(1112,)))!!A&%%%dT 8/%!!ϏWxS?x.6GܫD_qT*Fl1544hN",,.c lLbNN;+'$$Pϟ?.E 񂢢"'Syf>>>x B!d:jnneSSSpJ࿖eGq|I$4תGb:?A׉gTLL !f))))))IIIX 􍴴Gƶ{zz:99YXXx(5&&&?~tUUU˗/\ ..ncc3wӧr<p턄ӧ>} eeeǿ} /gff"8"t999U__D'T*UVVw 2~&$$555~CRRƆXSWW;c/_vGDDDUUUEEe*** RSSoJII+m4xϥx\:tPccS~|xDkkܹsUTT|||] oINN>zsFvrB"%ew=}ALԄOri4{F566h4jjj鍍 7B_`!#N6//]B8܌ٯuB!]a8Ljhhp pdGw1Sܚ=...N&%$$$$$DEEĤj:HXXXEE_PXNOO/..`2xŔJ!!ӧzyyϢP(CqqqQQQ|[> 1ʊr\YYYZZZEmm-9NBB]VKK `,,,,))s222DTzXuKK D碢t"^WTTTPP&7hyyyǏrʄ } }}}~~~{-Q$HMMM?~iӦ9883RϞ=Ζquu=x𠽽=܊|}}gϞ:;;{Ĉaff'mmme2 mmmAd111T]]-##'D 2,((`0x膤91 V__{ _,iܱ kEM%V܍7䨫kڠAFxv] os7oDGGmҥ֋/v-p=}-Ld؃eee rLr DDD#$ZDhc؈ϯhocccyy9ցD#7 #&!!C#dOc-EwQԎ1咒*ZZZJY,ֻwƍG?\RRRTTXUUUiiiqqq*+++//}1~~~/MLLIII~$N۞RTҵ8]^^Nľsd%%%eeeBNbI%%%EDDDDD677M8qԨQu?^vX,~~~%%5kֈ=}t˖-ƍsppURR곂3MMMJ_ IDATo߾%H7nȑ#aYۏ?޼y񉉉,?:tNo,7؃jYY٦&4} ")pYHH @?B(oWWWG\,++#/j_t$"BBBC vqx^~m۶{5۵K.%%%%&&/v8Z|Z[;8>Zߥ4dsssuu5J_l<,䔔"""'NiP/+))AUTT)((Õvq!aaaEEEUUU2VRR"z3"eeecQQQ\\ϟʈʃ RTTjԨ#G:8%%%>>͛7o޼677;vۭ~) ӹΙ3 444+++BMMM/*LXdrcc#Db"rsL&sΜ9igƍ;vv!tpjݶ[.))!;c[&ɐK<|pժU۷oBF 9 DFF\~}J1e|jKJJJ%gdȲ/(Mt:=''Ǐ_@??96>%%%---###//cpSN}7q4ˀG p߬|||:::NNN7o611100*õk8J||<1b~&,, Buuu,DYPPPJJF [ZZm"?"@{YYYK,qww={6k9=q pߏk3yeYYY Ho^p/_zC w\Sg?6dp8)ŁUq/h EkJQ" ep +H 2HHBurrrΕCϹo555_$UUU+++++V*K&) #V:+tVZQ$tHt˗/{zznڴ) ×Ar܌gϞQTѣCBBpK;B566}Xbbb>|2222dÇYoQ.󴴴gϞ# k.oooGGGtXCCCVVVFFFzzzvvׯB˨Q֮];h ''/y?رcԩ\,,,,,,]qrÇ89*''glllӡx/TYYYVVVVVVZZZr<k;v DEE껔vvvm?ƍLwl6ؘFq8999"|eeeX{!C@wB'Mdmm)ZdNAA޽{5]R= .h4ѿم6Wns |%"hݺuزe˖-[>~!q5;ew+ vV~eUUUmmmSSJtrȲq[c4 wk+Ș>}UBBBӖWGPp8555++K  2$$$ťTTTLP(YYYiii{쩭%HnnnÆ ùvBaaaaffgϞ={D& 2o<V__+2+t>~P($ ruu!m~3g.\8[,9,!)..~-4-++c2x}}}K655566666622۷^p8 R]]]UUU]]p츪 ϐH~7UUUW\9tP6l0nAAA+-[qGd q/ ޽*rdKSSnʕҮ++Jj?J nY2L\9xի++cǎwq?8G6rtddӧt:Z,ㅕцxf~455uh4w9o޼u۷,񥥥JJJ :thHHСC[ L111111w߾}?}422R XZZ;e:˗YYY999GQQFxzzÇh1*.. Æ ܾ}_o6hРA5OӉhriiiyy) JxxeeeCCCSSSCCCccc}}}======֯]h4*Z[[Kjkk񏲲BX,!0'---<]v7((ODϞ=۾};1'??ѢEm_ÇB, X,gf%$ +V奦JwXԮ2Ū(//D_L1-9S[[[NY|̙ѓ'On㳬?|D_֯IUUʪo}}=VWW㉚*ZPP +**џ[jjjjjj:ydTTԄ ֮];~x0lذŋ3fZ݋9sB\.7++ gO<)||| F%%%'' <~#@֦$''?<++b7؈#֭[[Iظ|FᦹrrrTl61e2L&vL&`_62Z*8|Lg)++cǎ5`hh]jkkvݮwNQQ:t(PZZ,!Բ#7ox}3d$zAw%RRR>}Z\\>|x``+^18IGYYY4N7{+PVSSRVVTWWWQQ!0mmmeeeuuuMMe0 ,1Lt:p8L&`0L:`04JIjjj5s8pL&1B(--MMM-,,DBAOL&S]MtDp8!X#dCٰaÞ={|}}]-6lXHHk)hWe.+\hv"t EEE|"esf2}|/sȑ#=377WTT,))@)++*FFFFFFx;wpB!B=:44ۻ@hhhL<7eyիWҦL2soȠ>|/^444#G~w#F2dD0ioL-1MdmJJJ\.xl6f7[P>RPP+!mmm999fm|,YP(cGd2B!ǝb1nL1k)mKKKɔ61e9L z$q~C<{l;RƧWVV"BL&_!AÇ P(ĿD1#,l@z󃂂֮]+Zdў={_x!///Z@Բq۟.˒=95558 A -鲞,] cǎ-\kPRRׯ۷o9WcOPSS2ɄSMY@@5k`(fffW^zuUUՕ+W.]ߧOe˖-]~YPԧO&''fff ]]Ç^^^X6,m3X,o2 HC{!rqnc%mL544TUU пyh"8L{n@*"""bo[PPs EAA"[XXbuuu[I$|@CDޮȑ#ҮElݺu۶mҮzvu\FqvN|6ܖ2@ ߻w_\\aW5`/_w!RMM Ԥr|˗O5n{Jv4\^LZwur.+;u"f͚5k֔9s;woÇ.LhIII=z˗/baÖ.]:l0{{݋fIڅPyyΝ;nދ LfAA/B)((pttl*++vL&!pD""MMMDy<^D JKKGշoXZ$ /^~zie**$,zvY___q%%% ,HOOߵkך5kp`"D.[—<4??ذa˗/:ujqݺu+<<\U|?iӦÇ{yy}]:>|0//!8vح[z{{㾘̖-[LLL֬Y'DaÆsW\5DYSSg!999>;" D@7P]]㣥u]2\ i߾}999_xunp2@jjj?n윙9`/__%z2 AfBBBQFH&-/^D_s!.5JJJ3gΜ9sfrrrhhyOhP(䄄=uԽ{6L]]]@/u/vӳggll2̊ ''!`$;" 2D__` AˎD tP(ӓv9˗?͛] 媪Fg˭g! +V;tPPPPgSp-,,B? O:U$={=@'E-[uzѹs6nzܹ#FH(x<ޓ'O=z)'M4zhooo]]]iX,駟<<Dx!==#|>"s\" AdfO0ɓ'mZ*Bpɒ%7nv-dZKJJruuu\600Fg 3&..ʪW>`99BDVSSww^zu@@@TTlvl y葎Δ)S~f #G?!dɒ6&bMMM7osNAA@ 8pFьC=}944Tg+o-RbqDD˗mmmCCC5߾};>9>k|j"ŋɩ&&&!!!CmNaaanݪ3f́+R؍P(p1]INNnܹ~~~K.7nܙ3gfΜ)@xս{=zpǎO?y{{G?ҁ횚RRRK)((477oJ*++6͂ȍ!===#''po* IDAT|>7{X[[K-kt:.%JvJ.]ڰaCCCѣG.\x Bs7}]t)88x[nv-b<==KKKBՅ,KrXw=bΓ'O|X[[~9rqbbm۲wްaviiٳg𤤤TL[8qbΝ3gάزeÇكF]lbh2ZYB˗/߿]]]FFƟ=qFDD7޽{?${9e|a1]o cfff.@ HJJJHHHHHVRR9rdhhɓ;?N',X`oo/y(.O<100';::} UUUL& "ɱX,ݻwKDGG#֯_fڵk|MΝ;Z|9B\]]BΝk{yڵ?q7?.\|wǏy&βڵk޽?Cee%S!ʛi7[!daa×/_nܸ!5k455mذ;1"::۷Nrtt l[7nWRTݻ%GxNݽ{T*wӦMǏёvudϞ=KKKp'O >\qAAoPYY'>YUUNAd|d 2':"J @***|||D"ѓ'Oͥ]:~ӧOSRR#qv?\hv@O" ?yfH/Z\]];& [Nߟ?,*))AM43eɈjaa!B(((HYϏ m y!tҥK&vbOݛ0aH$j>f>E5jBJ޽kɓ'+ʷlBRq_Xܔ)S;ƕx/w_uJ1R|PUUծ BP[[ oYCCFIi4"s\#2"+**655!"W^I;܅SڵLP(D544&C\.- !P{}pUmmmJ3D=---WNCC} B$I]]cebիWªuͦr_~moo/''Jm\򩿫pppCC3رLCCJwo*~[/-+8p`rrׯoܸt WW-[txs&SŴ12B $%%ݾ};&&o߾ӧOp @:{쐐kvx%EEE4mȑ|cWBPttth4LnY2rqGd)H$'HAtт) 3fXZZҮ!d'2ldd_fs\d2\nCCr'q\X l|b-[H`0:Pd&%ee)=rBhܹ3gL9s:ݕ+WǯZN:;;HǏ8q";;{ܸqׯ_߽{ B/^ܿ*oRvv޽{N6[ݻwG;wAGpp#Ϝ9zmroN)F*Ν;ciҫ[nűX,775kL<tw'Ntwwva)))+)((hnGɔ™般p8b"㉖eRa=H$ ݵkƍo#.55{׮]D$"H21NfkPUUmY&"w-RWW+˰Xں:VWWWWWN7Y!DwqG^<UUU455UUU544pX*ÉzpL&ёbq\6d28AN?#h@YWWW___OOO_pGTTTyWڵkR,fL&3&&fǎǏl6 11QfY,Kii)1gʕ7Bb߽{=q„ 111eZF&÷o(X,}P<~g+oַR%g2 ''JѥKFEE+UB!!!?3ׯ_gϞ >t1277WCCK.;v  ƍ7.] ''7z耀ɓ'%wٳgϚ5_BF/Y$<<3~xKK˨dذa!˝;w=ztjٲeeee[lYjUPPF?~}ttt6[[l~:" }q_,&9{G.YD:5w\kJ|}iKTUU466ŨT*nVJPQQQ@:ek<+**t:Fk}YCCe:YWWWOO3Hii;N!8nujll|ee>}=DVUUUTT0Ld2t:_x&`Xxyb("d---𴶶^߾}k{nʔ)T*^^^.X~=8v@cy}mrII S[[l +K&tLCCByq渦BPԚ"U, oׇHG/d`````ЖB!B%{OTQQ122266644۷/N'@O;v8Idd… e2pJ6v9ɓ'Νͯ/\ 9e+VXcoe;w7K4[g+oVR422|2q788!diiIܹsΝ;ۻr77777O{ݻwoˇڻ㠠 yyOH^bEGG_r%>>^IIiԩ/_k>ذaòe"##|ZZZĜr֮#P{+++Df2ģl6[MMM( 2gUUUBxE @ו B466eqd5Ƌ6?lLDBѵo޼ :O111ׯ_߾}MMMMMMkeeeii g\zӧ'%%KnիQQQׯ_o{ࣾx[/NJ6RMMMbL+ xt#2~r***BP(*++Cn~իWÎ>>>8kbbbff&yEcWT*BP(ʒ'OTTTp8M~\ w??Q^`ԩS] z/ .,7 .5[G}}}Y>۷%%%eeeeee8m\^^^]]*++ÇV8s ?e  p8N~=ڧ_zO"g׿~"11q޽qqq/_6mLE1.77ᨫK٢n:kkKnذg\B%1bƌ_op'Bٽ{c nܸaADǏ_tիt:}ԨQӦMù.=XEEŔ)S(Jbb#:k"Ç3D #df!Be***;=eee>Ad啗D"sss;;;OOϠ ;;;;;;mmmKNN}g[3}$vvv/^|aܸq3srr\]]۵J99FDfXcl_(UUUh$ ]Y Ad{DW^㬬>(**:88 <888ǎ{Ʃt------ɓ'3ꊊ_xg "''gmmCnnn ae_ll󍌌  rÇ'&&&%%AK$H8ӱ˭gtzMMP(|:)W5wQWXQVVfQ,???//o߾R!999SSN0(D"YYYYYY|Jri:RTT477ǿMNNNNNNpv3uuuǎ;x`uuu``ѣG=<<]繸}:))iܹs{9q޽>},\pٲe. @cÆ{{{_rӏT $gfgg/^]P(L&lY(jiiH$AOuD&"E(fgg?~8))ӧ)S B(n۶~ SmWTTaÆÇKYс +Kvb&4 8ȲdHh4ZRRǏ׮]xκt>///???77O0 K`` HZ[[(=t ߄ $OfX!sssHvvvvrrrppT_UFF}y_p4eNEEeҥK.MJJڿppppp7|3k,vF @bn޼yҥM0!))[uX"͛;vHOO7nܙ3g! 655u!!!_cHѣGKN 4]P(&&&DYXf !r X,n%,˟F! z>>Ǐ?}b5_:tCJĉ'Nɓ'><}4dz#GHZPTTdɒ̃Kϙ3%<<\ڵtc_\+\WWJpe'fEN>RWW555KXXط~?7ӟ?_QQsvvvuu rttttt즭(Ftuuuuu Beee4 IDATߏhllTRRwrrrwwpssy<˗gϞ . w#xeeeR½u ƍ7.]pBEE &?y(J||۷cccBOTTԔ)S=Wu( vwwvEZbbbPPDzWj0$#93''G,sDeei^^Բ#2'!D"x-]KK˗/_|HOOt Y_BNN‚T[(|2''eee =3fAAcΜ9C'N#@"޿ۅL&ϟ?uuu׮]~zpp0qtt?~Q444]&_~Q|||BB˗/Լ<8uT]]]iWeff_>))iҤIW\0`+t3f9r]fə&&&ZUeeMSSS 2͂Ȫ!tD@ o߾}֭4%%qٳgNNN>Bƍ7nBᤧ'&&޾}d2ϯO>ҮKKK[dIEE.] zэppzAJNP(z "HJJJd2Y[[[GGOsd2, !TZZuֹs熄899G|x̙k׮ɓ'v766v!ݛ ScK.mݺUNN~Ȑ!8訠 zhPEEE!#F^zر+ݺukժU|>ԩS=t^bŊqqqW_Xŋ]Bcmmm%Y,!bɎȪx .544|,CFD]]]O:inn2ǜ#rʕ+WX{EGGoܸq'O^`l.W^]YY+Wb9sb3gkmkX,gO}D"X]TT4f̘{Ĉ6j֬Y>|x˖-ZZZGoe0̙WVVs100v]!;̙3gΜCjjjjjjrr˗444<<<ܜ]\\I$Kxܬ 6ߺӦM6lذa{r 6l8rȼy o腄BTUUo߾=a„.۷o ###%gx7kU\.}d2B͂L&hYEEN,Ӑùz3g}Y-,,~+WrrrΝ;w>)Bz>} ›rrrRRR={v%%%;;;0_Q(ܜ⦦&%%%{{N6m...VC={D._<}ti@ =znݺ/_Y&_[\\L9r̜>ٮU!Bd2ݻwE^#P(lDxDR#G}7gϞ @vL~X(bY N^_1..ʕ+ׯ0a‚ &OЕdgg_~ڵkFFF5jTvI  )**˗?kjjB}߿y{MqqW^zUTTT\\ߦM o<q 6xyy%$$Ojk׮MLL;wnLLIWnݻcǎmv;##C[[ZUYY"DRWWhGd%Dnjje"B$ t}]pAGGgժUK.eO?O?= ,شiի-[}X,>s̏?tرs_Շ']Vڵ` pJ6uVxxh'OHKEE% f_zɓׯ_`>upp9s3zp(rnnرc]KB"\]]]]]9T*577D+++B 8lgggiiinnnnn"A7XZZZVV;9~Uii)>Z355suu5kր\\\ ]2h/Z… 7oN:տ? p8<ؿnnn§ؘfG!Dr:"Ο?L//+WL8v<==Ϟ=o߾'NdȐYf uKzzz@@NffuT͛}}/\PAX'''SSSə aaa][YY9AdI\ fUUUY,dɸA |%%%"A7nLII v횃+F]]=88xW^ 0`eáovvƍ&M3OOOiWCmذa۶mK@455-_ɓ~bjkkCBB=z3eʔ~bȑ#ϟ/,,D988-YSbMMM7osNAA@ 8pFB(##СCO>000pvv 6lX+o-RbqDD˗mmmCCC5߾};>Dr4D/&'' :d%I϶-;S$knnmNEEE,^ɓӧOoJ>| EKKk̘1{ew/l6{{SSS7f3 M.//'T*U$MMM 􌌌ehhe<FjkkT4P( xayyyCCC/200ׯw#1 ٳK.={,ޣ622222R$]VD"իWWXl~NNP(ly8Yeee'Nhdjjjl$//$jjjF 2Dr$ ^Wڴib999I"UϜ9s׮]۸qcTTԊ+n݊ mϓ'O:;;߹sg„ Үw}nܸQڵnI,Ϟ=;666&&X,OORPuuuaa!Œ\@,ݻwɓ'Oܸq#&&Ydڵk###%>|uʹ4|.Ãx'ܹ#ooTgx:33sڴixzok׮]z, dZ]]:[PFFwqM@ $7""C*:vi먣.|ܶ*uUΪ" d@&?痲¸899W#'u[Oo!zs8ۨd>#(P(R!PDm߾="".22_.2{ɓ'8pǛaϟ?߷o_ddͅ fϞK-,Y"HN> o,~ڵk?~kP7n |rXX‟ݻ4mѢEAAArܹsN}/dɒ6诿ڳgOPPBի?N߽{Ƀvڕm6иq֯_?b:^XXx}m۶ѣGm;sLXX؜9sw{ /**"zʕFDfhe/^D}7˖-}mrrC޶;RlLs[vKo o[9djjjGE-Yd񆆆M'OdzX,kkk??'=S||zzz:*UUUSSSSSӖr"ؗ(hs]]]:ARt:BҢh EGGGSSB0 <@WWWEEBvt:D"QT ^ l6[. P(l6BD"Tr2D|>D:' 9~.p8Rt: #Ficܝ7u@ XhџrJe;߿ʕ+/q'zݰaOII9rd{?p86meefQ 2SUUB|>_SSS 4 "D" DMn޼r8G.Y+;w\l?{S=zJuu!\~ͰQFEFFN:UEEEu5ǎ100Pv->BF]v !nݺիW#֬Y#CBB.\@}7?^ pBۃk֬ٴiĉC}Wxͼy |}}plt .)) x˗/Xy#m9"qƵk"l7EFF*tdm|Z߹kΝ;4iҤYfu`ᝢ._|ɭ[5UUU OL HͦY[x'qnii)Dl6h!pvYUUUGGm|i9L&kikS8*4fmhhHPvvxt+''ߟfzyy)]K,_rnnn/^ Q1\~fgINNOۻC&AdE :СC/VvQO&]~}߾}ׯ_6m )))ׯ߱cDGM6XF*>+(((<n8 eٛ;!윑@@?$YBP  X,Bge2b1nEt9qG^nDGGGUUU]]]KK ǩ!F'UTT#2tϛ7oѸI$_jkk?feՌ™3g6ZrrrF2LdeeE4Bn^&Dbbb+X|]???e7kJV;vlzzΝ;.]zƍ_~8ls~7oL6ɓ' gܹƍ۲ek~II Q9P" Nx۷d3fݻB[[Ĥ%}}}bL&&rh+;>|x||7o"##=zcǎnmvᝢ/_9v(*66VMMmԨQ!gg爈eWH.?~<88x֬YOx}T*ӧ###WXx|իdAQr7X IDATrT*uwwoLJe6cccC H$DGdYUU(L, 2L F.۷oذaBnD".]]zhhSSSX#--mɒ%۷oʊrב .p8 2sC΅uMbMTT >s!;|\WWWЙ3gA \7qFJJʚ5klv|||XX!!^SÇ/--QvQЏTWWϘ1cڵvtO :u*? nړSf}e{wd2B, >Ѵ#H$R "kiif;"d2D}.Dۺu޽{oܸA|0a„4oo .(/^tuu۷o_IIɑ#G Ш?Lٵ^z;69dqY D"6 " B"󵴴Aӎ E,#pGDF( @ѹwk:{γ Dfw9bHMM]zʴi޽+ɔ]THG*w|!55Uٵ|.kmmx:SuCׯ_?ped2}}v`֭+--E=~X.9ry&~8||+Wr%K|}}ry``i\w 3gN^fԌ7.//'NcH$$JlK%7;1c踹]z]o߃XINN^p!C455Oo%TWWg&&&6mJ=I$sVR[WPUU=|ÇluUUU>|kTTsssoܸGffW_}_ʮNNN)))Vz+ 4o<###;;GDD( Hw===<==7o~Iͮ]RSSϟ쬭7bĈഴ4ԩSfϞ=`//Cٳ]mٳg 2dÆ 1fϞ=7nn|rGGG---+V?{鿣ᇿ͇jIII^^^V P(ʮ055%ƜJ, <6bggsqtu)ST*ܹsΝ]CRŋϟߴ켼uD.,,b!p8q/VǍ:"D$ok۷o}||>|xݮӆoرcx 70h%~yPaoߞk֬k׮5Z=OkiDzqBuuu΅+V?~KkiĀ˗8q3p mmVjR_|>V{ ۷o={P(_~e;8n\\>Kd'NܹS&]vҥ. te˖O&M] #?~ȑ#}+SbŪUlB\dɛ7obccXU|>_~ٽ{D"ٴiӪU{ZJJJnܸq7gΜ3f(8qbvv}Mϟ_hi=|>_[[;**j%%% %22،b0l6dZZZ"&Nhddt…ϝ;W&4mfff?޳gə3g:w p8ݓB&_{ 233ksϜ96gΜ;v8p-AӧO_vMSS3((h#FHKKkin\.o67nGA ϟ?o߾m۶=z!QXXhjjqƀ˗/kpb'[nm.5o|?|…z???<˗//]#+ez=@p՟~ɓ'd2YSS?VSSP(:ޤ[jM E]]]KKD"t*JwmmmPPJ{{7n($諪?~q@hѢ[+($55_xAR?SN͘1Couy77)dPBBkf)..VVV!rBC |>>&BH "H__uD 2&R>x왕Uwz͚56ms7nܸvZ-[bcc8usB_~%FXPW|/֭[z5Bh͚5b8$${ 4Idd֭[?%t.,Y2x ( .GGGKI&;vlxz,w{nLLLw!o޼?tӧ탂Ν;p@eW׵ZONy+={v[ 52/^?Nzڵ*B744B@ HBL&S.M"KR|=dWMM Or34L&2Lhݬ8 AR$4N'HIfC]u?t޽;eʔWjkkB~~>Bhڴi& {@hsZhQNNΒ%K\\\еwܹ~;wx<LMM Ь7o~w;vkŋ/^plڴm̙ޣF—@o~ŋSNݼy z^Z^^>tM6vW <O,b'8zQ"ol6!rR@  wJuuuo"8$^%3ѭd&D_Ge< C7[>7I䡛>~Ν;;88 ^zd2Yrrrdd \]].\zΝ;w܉DӧO7nܾ}[(Ι39c2l63#2>R%igbYMM%u t(}|uAL&H$ąᚚfx>)}z^@#۷od3fػwveeI{P{&%%u`N͍y'OTUUǏo߾3g7oΞ?~PPК5k] wF=zǏ[l՝0ak4s-Y]=j~$11޽{{T*>|efϞBKKKKKsٔbf%3͖8 57JBg"ppW0#teVP([nݺu+zVQQVZH!]ѻ8u]hƍK\nLḼ[n޼YQQaee/((111>|YAA9v옟ZgΜmjorrT*Xdl,-uDnhhDVRv-dzg&Lל;wsӧO;vc+4ʜmmmSN)>ŋ7o kw;!.7[[!ϟwuuBCCɓ'߿qƫWLrҥI&@rgΜ9dȐ'N(d;>ydzzztt֭[mmm'N2h eW Bb8===%%޽{111wŊ>>>BdrF4ecD"Ѿ}F8E9iVWWW[[+ 񳈮Mw:Fݠqf *h4uuu]]]hjjjt:Bh4:W~ D(>}4&&iѢE3g1b>eee<Q(QFyxxM~޽{={fbbbnnށ=ZYY!lbVOmǷV B"DǏ"=)S={{nuuu ˗/8psgϪ޽{k̙/^8x L|Çw;O[;۷o͚5_};eRŋ8./:}'O3x:r|#D$e"(!!e>ӧ^㯾>±~ɓVVVSNmvGƍױ=3LWWW7%r\*J43Adccc)<O?F툌{ 2΅ 8p5kvGzSSSݩyf[s[aSrƍM׷UV=zsoܸ1cƌVXWW\XXH X|9w\lُ?H 8p`FFn~lm}O:WCCŋ;DѣZ ______##N,t;vܽ{h.@T__󤤤ϟH${{{QFyxx8::ËFiiiIIIUUUjjjp?rUUU 4(88kvM޽ŋn.-p|Y(x<ry<^ovҘ*JnQf0 000Br˗G\tVCݠ ,KWWwѣGc&88xMJX;\zuf͚qcǎ޽8qbHHȾ}BbB[СCRD"{077_noMM7|!KAGdе~'\vZeW\ }ܹsqHSNo$iĈ7nl{{ReeexԩSΟ?''3g]V4-))iժU3fػw/"q#GP7n}G!lns]vkvc.z//]v 2j۶m 2êl߾FEEfСÆ stt'Zbefffff|&Hvvv[lpqqYzz4`YR۩t4&X,VmmmmmmӅwl ^ %WżSUU5`ooݻwO0ѱLt  ---T:dȐѣG=W}}ŋݚr:熆2+++rpp@q8pGdsss!H$J |v[#kr?<<\ٵvh{g666 ,h=D.322333R'>Kaaו+Wp|_|%4tP[[[[[[bsss_~ǥ 6lСÆ srrQvy ڼysΰugm=D"y/WWWWVV$ PaMMM%"@zǏ?c24mܸq󱋋 $p8/^HNNʒH$fffnnnnnn#GtwwWv=ј1c.]]vtcAAMRR)S_˗B%%%֭;{lMM BF/\FEDD}Br۶m~@JLL,,,믕]/](,333wrrrFFX,R...!!!ƍ#4Ù6ms ɓ'$''˗/_<{laa!Yȶ :}ryIIInnn^^^܏B8:::yСfff.Ձ(JWmllB6L&[bpLXx^P*M[[@RiZZ?y򤪪Bϟ?d2ćd"h4رcgΜgϞ#G(Ҟ.)))!!a- xرc;ٕd"xkEEB?!\NRP;"D"Ŏ~TmBnݲvvvVv!ӏKϘ1cƾ}BaZZZzzzZZZVVVCCҥKܜԾA$͘1ٳg Id2iܹxP(l|QqqT*EDR_=BeeeQQQqqqQQQaaaaa!$ BT*[l^fTVV֑#G[oYmeeE&ݻވ`0!C4@ ҲZnj]v=z„ n?ްaCvd2pȘb1 % ~odxvD"HTT*mhhP("HMMD"k+{gzЅ={*@_APƌ2336lؤI6l0l0{{~>:fҥpc7?444:tPŕb 77͛7yyyiiiL&O9`KKK kkk3&&&p1"H3L&p!dllliiiii9}t84h̘ԗH/kҥ$SXFRp׷AqqqYYYqqqIIIYYYQQѣGJKK+++q+cceSSS333 v'>>w>NNND^^^aaa^^^p 4l6ggggggHR+++{{{+V8::6 |[n]z999^^^?ɴ@IҺ:#<$555H#2D٫nQWWw~ ￟|򉲫}q/zu666'O0`]n޽ϟq㆓k@Շ ҴQ%K&RRR\RQQ!Bd2Ą۷eeeUUUUUUeeeDU "v?vX?Dcwĉ7##-ZX[[㩓AסRvvvvvvM7r"^ZQQ!H0CCCp{9BH"dff>{,111)));;!4tP//+Vxyy-7s‚<"y\^^RWWspp booNNN- 9rdd2zG.㏬\.W{oߪ2zD]B/P(?DMMcu""HB@gkkrp-[DDD; ` >zX\ZZZTTTRRB8+++qWFd=zzzxP:EmmmMM$D"pn8YYYY^^NMMM}-Jd}}}Ű2%D.lbXw3RxG裏>™c71-\j*SSӍ7)Ư^Ҫ@Ǩ6P]]]^^&''a0666666&&&xrWJLLɿr رc?׻(Fݗㅲ2<GR;t萩ܹs[ ˣ/_C}!6m9Q.㩫D"|w8,BS ZPF"6Ї͘1/<<\ٵ7>y7ULVWW}6##?dDZL&7M'k JԤĂ&BW ڡN s\>l@ b"d8p8vBt:W¢fZZZJyWyflll011!:ҁBMM g͗P\\\TTd2 Bjjjqq1/ q𲹹9 q8ϟgggK{xx̝;յEA*VVVyryyymm-?c8;;Ϙ1(zΫWN:?gvvvYYo!H ߍ:"+yy<@ d@R:" r>UK"^vsl|"*6=}t33 .(/}0@(h!///\ERutth4JѡRZZZZZZjjj'LhwG%HR)?e2?qbDb1˭uuuB(tbļQ&]$..nÆ aaamqeeL&555z\^^^d2q4w(LJJb2L677W&eSSSYQQŋwrssbȑ#G9i$GGÇh4eW T*TUUռ[!>BfffÇ8plddz{ݹsÇ>x`6//!dkkb4MEExkP(T|󵴴b"WUU!(JCCtD*./^tc{k2ܻw޽{o߾ 633:{D^YYg!t:Wq,]]]2[gQT 3聖.]wnp'q[j G `!="?LpSD@*Ο?N-mj CVQQA wѨT*----,,,(( KKKT*hL8pr^%d2YAAAfffVV˗/D"ӨQ.] chԅfsXjkk;88xyy|RtÆ 3gneXCCCll޽{;|wㆆ#!B E#2@5r˗/+(;;{̘1xU,#####̙3O>uppPbm+--M(N0AGG+Rf*2/%xX w"2͸bNfƙ|={6**jĈʮө1e;wLHHxq{xrgg.( JƍS\PTTsL&0##eeeQ6JV(+L@! 3 /_:lذ(f@ X,V5l6*!F7RT]t:N[YY8doЯ>uuu>P^^ޠA2n6,˥R=l<OSS 2544p8oH Ќ3ݛ_W[+2fժUl6qӦM^^^W^ZYR]rm %7b$6An-e'P|M+~Wǵk6n6uTeܽ{O?ށtuuN/ =jjj "%bMf27n())H$!"QVzܼ\8;;ϖ`bb7899999wv|| B,xF8bpS-=KK-=OVw "M>Xn7RabN< ^bOݟ(<qmi@ he&`?4 ?344Dژ`p/x/&nݺ`{{GFGG[XX 2͵l6&;³3ZZZDYUUU]]?fff.\ r7KIIAEDD7nM}T*?f͚ծgQT*ډMەi&XF۶S2g_AAAׯWv-YΝ;bŊ{~7 F#6tM&[nnv 2=Ymm-9*++B$lȐ!cǎ]d̛Oxzܮ/s\.[__/ q:O/1NpPlQfJ8%xf7)@蹫9-$HmINHSŗfMM/95wo<(?Q8Y/?h4ccc< !%ēh4"p:::xnҎjZ'Jϟogg{~CFe6rnniii D {7 "CGdߩ^z׮]6m3WWWZÇ ߿ѩL&?rrrVZG!7mvF?4i̘1]}nZJet]f"ٜh{HL3ى@~~ɓ?裓'Ov>7ܹsg֭A!ɖƍS\/q4ŋxVӀb<Х8u]RRR\\3ǵ!UUUKKK[[ÇϚ5vРA86X\SSۚ*bxk#8IѴ 466VSSqXW9N%YH![lOJO|uf/vni'uuuc"k{{u,m\ݴbi#"4 o%RZZZ k+wz/^4>hѢK&hX,333!Dv&›0+@> B2D_[hݻ9}ve <<< Bٍ.WGDD߼y͛K.7|k__NYpݻwW^yf///P\\ܞ={TTT>$GH$3gδRv-H6vۛi+M6E[̭lbs̱gfO>` ;&HTZZZZZd2LŌ2BiP600011144400000hKN@ 8g9.*****"ehVVVG āckkkҲ-+++...+++)))---++9joaaꪭ`06NhI,t۷oڴ߿/H&OÕ B"fp88^_TT277'LLL|>LFD8 5}[.XZ|BSSӍ7\|9,,LqŋB|Ͳelmm߾}|!qtPffffffdddzzz,r+c=|_|1… #F+y;w e dsg4sfܰѦZ͛7e[<<<;|ļ<-~bpzq!CZZZBH#D"/544#2!bŊ'N[ʕ+ʮC]v !nݺիW#֬Y#CBB׮];wӤIf͚5k֬gӦM#Gͽ}vxx8ܶmۑ#G>F.~Dq999}7Fii]V\9p@e ;k/ڭ%V&t:in ٳgSL:t˗;fc"^\.ZP^^^]]͛zŧPT\2?t:Nuuuq+b6r9O6h}UUUUUDCC~񆆆r_,555^yիWb2dȄ  UUU&L044$7o\bŇ477Fl6_HrrF#x< LFtDƟB!DAwO9uԢE]GM6XDޱcGeeettttt4BD":uJOOODnݺX1?~q!BCCϟf-[ցsd }};w* pF}7EEE?vrrޡMDYq%4S2'N7 ]!22r޼y3f8|g]h2.$Ȩ1bX,\RRppuuu---Ú4L&S(MMMK&q8kD"Ǚc#HMwB&qZ`䴾Attt pshSSS3X,VZZZzzzFFFvvׯkkkBvvvvvvg϶2d4~B(Λ7>::Z1ۊS~qlllDQGJ$D툌bX]]wDSBtǯ[nʕNNNGVv9]ho޼~Qddˎ;:/b֭EEE#"""&N?CBB|pWWyYYY(^Jf?;wX[[Yn277>|:77wРAxXxB6 2Y]]:"ۻwoff̙3,--]Nؼx͛xMTTTaްaÆ RSSGqΝϜ9mטׯ_7&B999_D"$&&=uɓ?D#//bB(.DUUUU||"FȍbXUUiAdMTUU/]4f̘O>$66 z3gxd29 ˇVl2OOO "ܙ8//=zʕ+=<}taaիW^Z|' .l m4-BٳgϞ=k]ZK[>|8>>>;;;00PqNt/r9s|嗡ʮtyǎ/]Kouf-m"͊Z~K[)R JU`z%5M4*QK1֛bILC( (R{qo/(.}͞9;|s8l2oڞFc5… ㏃~]q"(ϟ?|Ç>CYXXΛ7oȐ!ڟY3fpqqv횅E^{M]]ѣGO\+~ZfJ%eL$r\瘨ZUDO'@CBB4ݣ155ʕ+SSS&Ok. r^^?FCBBf͚?xرct:O>...111|򉞞^,**oSSSBFwttTVNZdILL̏?zgϮXb֭+Vt_G$ԲfdsEEEFXצ!LPǚ~'OnjjJKK 颣@R|XBa>}Bbx {{'1ʕ+Çt:WmQM簀G6!GGÇu3gܼyuvz+66vڵ6mt_@fui掔pnαԒiƫLLLK"frvv~A& ]HlCCC__Ey{{0@5t\KKKllljj?<wKJJzd2?e21áR` \2z]Y$H$MZZZ\\\DDw}xbMh)`cǎIor޽sΙ3gݚ aa3ͪxϗH$差R: %N:駟7.!!=+R x ɓ'y<|~ꚁ 3gp87n9r$99Y*N6=;S^^L\YX,9l0Mwǎ/ ?q]l~kUYu˕dK-fHѠ@Bʕ+uܹ1P(pU򸰰Q(KxzzXzj$ݻe??r…QFYXXg?J 999yaau+T-xȑ#---s?,˗- A* !$JVDD ETJ$2,JV qZD"N0!>>~޼yڳgOXXD"o.Ɏ=h"H2O>^u$(^x1~x{{K.zൺ.L3lhf ś;d)J>}pz%>f͚ǏO:رc_@DpܹsRt޼y→r]]]gggd 6M"q]]\.W󍍍|>qT$b1B@"h'hoos%%%mذaذaߺu+8z[n_pܹ۷oWHN=z4Jy&Ftw"٬=4U`s9Z2͸XR*!#B|>_"WB#r9Pb1Mh7MKR2R!\ G%rqqqgϞ5k{d2@Q*%%%yyy8|\VV&~!Ch4E";vlΝl6/\~=QBB¤I2(玎Y,fMLLURd0d DH$ _D" ~"-5qSN}Wܹs ???M T*x߿?f̘\Mw t_~{z4lV\n'ӌ 6&q#zt655H \.B>fB@ `J<?d2.q?* /! d2٣G޽{ݪ*===#G6SGGGH$:tо}D"њ5kV\WTTj[yyjj7d2bq @ 066&B!Զ"2LH$Yg7 ]paLLٳg<z'·c=:vw}W__?s.((Hӝ]822寿2Q;iOdj#ps,d#NG$BRb1L+x? BkH B6Bᛒ/~MLLpBXYYM[R]C$n&g7v1Lf BJf0s9l\^QQT*qb' >&&&x0!FhWn97'O+**nmg^6`Xw޽sNvvvnn.dz ?9tЀBJgϞݶm[ee 6mE߬ղr///D"r8?fK㺺:RT 2DDD*"6m˪U`ٳg_.]gkS EEE 8ի6tK663ɬhHdD-f ҧO<r_|ܬ3&|~qU3^hFFFx!J555544411b_M!~wub'Y,@ HrB!p8x-NkVVV.}_@㚚?~ƌ7o޴tB!dee{Q(YYYYYY L&^:((F}ވd"V sMNNvss)SN͝;W]#?>qD)/Y,Q`xDFamXLPP5 Ç7nܸ~iӦ}cƌA L&ٳ wuww߽{ =~8**ի@rNjqgCD޴iVT ;&M}+JV xŋ?f2/_lKĤm:Ҳo߾}Vd UVVv]5:L&Hx<++ׇ9F`0p渱(cO&UmӢVVVT*Uh5.Vr(555-DR/P(666}d;;;kkkH\i/^`޽{-,,4ݣQUUegg=Eqq1N啖*J??Kh@H$nnn"VǏL:::u@ tr}}=Q .0 o``qFQWW{A,ڵk߾}VVVI4ݣ7z $InnnfffVVVVVͶ ^xqHHH``Oˋ/}ڿՂ\L&bXDE人:333P @ 8LTD&Rt!C|8s`kknT%H$ȧDF 500pvvvuuuwww{ r#))iƍeeeW%=Zӽrܹ+ ;rHOOOMwx---.]:w?k֬}k?R_x,--0`jKKK .b8|Rzzz5pNDb!( AH$V 2Uttt:tЭ[ի|~^{ T*rNNN&Lضm[DDc]tiܹ111ǏR@fuifUM6ofFuEEE?~gB$c񮮮8F \\\\\\ڮjhhxΝ;Nb2!==='''gРAĝcЎ;wlذ!;;;&&ʕ+tނfx{{k#Tee[+**?l0Ԣ!99)))}ѩS&Ma/_njjZbYZZd2 jEdacc$@ Ad\B"2Znŋ/iii8>(G vxgddss͛7i@.\߿D"i;(`vL3~*ٌWx}:nT*q8++`#GƆ>\,)ʼk׮ytdrTTԉ'&OLR5ݻO4I]!Jeii%KTJZ ưaLMMU7bL&uttp"L&K$hM IDAT &L0aB߹s'###==}bxȐ!~h.RPP_PPPPPPVVP(v㣣nmq+V|gB@%;i&9\.oXj4Gͽ~AAAQQы/B V?+kYXXXXX 6hQ*t:͛D>>>AAAl%򒒒ݻw'&&6,==}ȑQ'@ ,--hl6;77ssskkk q H$RG#ryEEEQQQIIIaaÇtwPPСC oرˁ/_8q{i0pvr|8ˊ%+ʤ={dggO81===44TӝzGSNt/\.OKKKOOĕCCC?9T*}Jcc#W\.W"O9D"p8j;OȈL&x-N-h4KRMLL E.F4 EIIsrrܹSRR?tЙ3g5jV۷oggggggw4F !cxF]]BD"VDV*BPWWTD'IH&% SSo}}}_____߅ R-++KJJz9ή_~666"kkkkkk_xQWWWSSSSS٨-,, 0j(| *1 sνu1i$Mw@3aGmWJ6U<O*sw4mRI RK.\`oo| vv?L&3---%%%%%(<<|C $*z4kk &%+**p(95585t])YP\re)))Իc2DYYY)))YYYB!""b޽QQQ... x˗)Q*6L&LMM300033# zWsw$ΫP(p-\sϪUl6[P 8QE\|Dt:]T2L|8LrR){S122244433kx_h4*jjjJR{1_MMM^^޽{rrr/_NIIS(ӦM;vlHHsqqqqq3gB֭[7ob833S5|G-]444Mx<^UUUeeemm-f0jȘBXZZZZZ?T9[ޙN;W]X,U*^`|>_$\ Xhjj <_ؾv.IMo25ryYYÇ>| ~%B-88x֬Y#F顣oݺuΝ;w}o "lkkpT ! H$CGD"BL&Ad @ovP($`&f ӧOqhdXsss"lnn޿L,--qfzAYcccttt}}}VVC$ԲfdsEEEFT*tצ!LP,,,:O% B(333--F.Y_yyygΜpBUUSxxʕ+njcmmݙ =cbbbbbB=uVJJʿ+WΜ9sΜ9Z_D}}@`d777MwJ= >qAA@ ߿رc!| ^ɓ'Ϟ=㪪&J.(('!d2L&]\bdrUX,y<ŢEEEE ǗUmp;NIQQӧO?~\TT$tuuK?????n]ՔJ-["##վO3CJd23 [[[.Hl  bD"dDCEd> jd2Dd4j„ \.7%%K 'ղ+6-Y,`%ŒdG"|@ qIf& .x߶U蘙㹆MMMLMMӧmw"BL&ȑ#2dȊ+>c>_~...sΝ5kZNQw%ۉkV=ת7H$oBPKIx<ީS.\!˕J62x]V$]~7n\v,X0i$IP(RRR;v /bٲelrQFi6|DRQ"1&&&r/T zL4&''m ߌQW5JBfU3\.W&f=99yњ+455=~x˖-@(GfddX,yEDDxyy_z@,?|===΂.allllllccөWU6xT6bp^f֖mG䎇xt:Nqss3BD"ۻyyyXYYi]qƍWTzBTmliiAb?ú:B)x2_ D"<_$DD"!JR&i"x}…SN_/CGG~]O˗/%"Hb!k.V Й3gB}LL QI]x!tĉyOV# ,Xk׮믷m۶hѢ,J_5))8..nٲen&IIIcƌtGZ8555555==&""baaa0#P(O>{ݻc\ѢEC quuU8@[rԲjpS:pL&nSՔMMMMLLp:ظղXpH;/^0Lspp/>(ׯh]4FLWWU 8jWD  8LBRTTBEd=ۖ-[m۶nݺ]vAл ?uttH$Bpuu177߱cǒ%K& RKҬ,?AǏ7mڴ|cǎ%&&w>|xĦ#G̝;WㅙZRRҨQz߬R())))))999<iܸq?cHHLbݻwo߾}\cdd8nܸ-[:::j`jjjjjک)l6r,/|vWdsss bhhH(!^P(8LPLLLLMM)jbT__(Klll5TOvԩ3gtNQQ{>,`0\.J%6 CP?>HDd2fT2lٲeVZ1NWWWPEEE͚5+%%ϷmSJ̾VSSʕ+ͧL}V(ʣG&&&>y!xKědڵkDiׯ>|ɓ'/__fΜu=wɓ' lٲ*[└;vh#nT*SRRrrr^|ioo?~PMwhR|gdd;;۷]Cfddddddkk2xxY$X,P(*++E"P(d2xbu}}}GR d2J577eCCCqM^yllDr|D"a2bX p8DpX,n2f2!J5W0d;;}ں_~n_CCüyM6k֬.:DII[5`+"/UEd===DBE"QG% LH$! Gɿۜ9s4P(7x M6~xss9s$'''%%?^}|/\.wСUUU'OT*oܸAdfffff^|9))s|}jˏ9B\74hPvv5kΝ8{l`\p&&&ӦM;x`XXX2Xrr2Ǜ:u;yyyYYY鍍GޱcGTTBݼyիɍVVV~Q<==5;Fqwl6[$|#x<˕dL&S.s8NxB.X,TpʙCX;B__!D"&f9Qxbfi'8~mjjj``HS(333'''___"jLT1]A*N:ɓ]wVrB---4 l'''P]]B[UD666l-B" "2vʔ)շn tw&˗/}:s?"~6OZommC;;6mB(**j?Ӌ/:u&`w o_>s|}vݻw>\GGdΜ9/^dOb(ʱc4q8WZ!D&¶lْ.4;-jjj80|pDRgϞKM :8qD"=zKR\\zaCe|MwI #PII =<#~DeVX,Np-ƶT*W.Hrrrn߾r}B5!bϞ=էOٳgϝ;wذa=$N:{y6mڴe˖.=xVY/믿B666>={9r$11qĈ555x(ƍoܸOo޼rvv^l/_r IDAT^VVvc:99{zz>|pȐ!]ޓ7P(W>|͛7o 3F200tG4TS!LmJPL}2233C1 [[[<Ԕؘ b,P(D"sXL&?P(\`ŋ:rJMw-2mڴ/YC\\\󓒒֬Y[O N>7'OD 8w֭'N,\"[~NGgg炂իW㖤VKRnSʸ_~eAA_ ?#P-'pիyyy~m;۰7n5j۶mƍswwﶮ wٹs;L&{ p8l, /o*KRq\5kff0H$^%{l6[Pkq<硕J%BJ/KҊ ~)J533333 4FjzJ2??޽ׯ_ַow'ݸ\~ر7111/OVV̙3ϟwn8\IIk/[ZZ|||B"H,^xaoorI$<ǑqH$zmY*", CWQQ1sWFGGk;hm۶]|y׮]{6mZ~~}f̘q…nuQJHHw>+nݺd4*##ĉ޽{BgΜ9tPz#vۓ'O.((طoi.]VWVV;v?9 Z|yhhCuuuBBB/Tgw\7;ݿs΍h9333=====ѣGJrȐ!&L8|phhҬ~;<<\GP466644466655555| ?%Ǫ/!D̬_~^^^8gZ3d2,mSdbXu?D"Juo߾De: 766;vQQQ...8 ߷oߑ#GR3<Nr'N0aѣGgג)Sm'*"^DyȑDuB >obbfpjEd\&Y"ȤVK;wn<6*..ӧObb\+V!|HTFGG߸q Ǐԑ[zx6mڴcǎޖR*W^˗/?rȸq_龀^"33sӦM !dcc3lذ[D"'' ޽[}oWWWWZZZVVVVVVYYx-F#NNNvvvxkB/t:f6ǀ %cXw>t萝͛@l7+99R .̝;㵚LC$^reҤI7n裏lvss˽{.\v}bѣG1116mjnn}ѣWZfTjwnwޝ3gDJMM:t@Ϡg8pΝ;cbb>`Mw tc-**B .t@' #Gdee!LMMhoogΜaX};ZDӧeee\.!dbbc1Zյ*UUU`vjjѣG|>BJzxxxxxxzz0/øAT |r7ύPRR*b2!sss555}!B嶊|ccc>> ![XL,dD_vDL&۱cǍ믿ZZZjG0۶m۸qcBBgOOϸ9s8;;kw]D"WNy֭[nΚ5뎨 6AK(SNo}5v%TA" Js]`-Z^! kEQ ۪DQ@lY@:"̕A0͙V###___--H$yyyiiV**-- 7rχl7\nQQQVVVVVVNNNvvvII BHXXXKK`ы-iQ2LtPPPPPP0B\;v̙2.1Ő!C|o.\0%%eŊp}={|);;PCCBh!ܽXHHBtYYYL&SFFb ]>jii 2%;;{Μ9O7oЇIJJ.]tҥ)))gϞ ټyӈ#DELѣgdd0 ###www-OER=zw*++KJJH:U"""rrrRRR2228,%%'eddedd䤥! ޱcхL˗/rssY,żyLMMqZ܃L&c紴h2_pa׮]<D"XXX\aàer/j*mm/^XYY]O<x"!Yfu/DhBnjj͒ DPwDp8"""@:thӦMOȑ#GyфWOǏ?~.5~5?TZ{nS 5|mmmiii>|/-,,<== | ͦRT*Ow:,h]gIII)))IIIP,URR Qb uuukQ?}4%%ٳgYYYmmmrrrfff֋-233333 @BB>}:Csrrrrr233srrϦ&QQQKKQF9rЙWa˗/pႯo``` g3L!3"33΋:tDVPP@Q(N}ٳgϞ=[feБѣGxԴcǎ 4hԨQF=zuoC؏L6BܹsՕr#<<|ժUs9y$Qܼ<.knnyJ%H8-[[[uDnkkr  2g7nrwޅ|kBBBׯoiiIII'N+++KϟzE^^y׮]ƍPo瘿d =lLR{sK~>]*))544hhh٭]g)C ͛bX/_LMMMIIٷo_UUرcǍ⢭Mt?7o޸())edd@j1X"22k֬!lyQCCnFt0Lp!$!!p#2ԬZڵk;pcpvvyD/^<<22r֭BBB#F566!LKKKKK{͇lٲÇ3}Njɼ}v͛7<<<~W/od2߿T$''cC~j%'66666׷I__''Loٳg111222D3fHNN2e 䘘ty*"h4eeeߎȝx=Dss3BHRR 2AIHHBGdr޺u'O7EFF ?}mݺNXXXWWаOjRyyyyyy>|x@@$ѕ [2nBmLR;D"}IKfeew7o0`˗.]khnnyΝf;;s纸1BT`hhhhhjժTJ>qℤIfΜ?vvЍI&988\r2c*++_h4ڳg,,,.䘛wH#2F2dɬAd:.''`0l6[DDD\\;"s8>@dɒ/^رcݺu}f=hTTTS s޽|M}}}SSS###======:ݻwo߾-((x5NWVV" `fffjj믿E_1+**~aKf&!&&~[2 yy.}l6"۷wޟ~im]EE@o$**jooooocǎk׮]|yԩ'O5kք .HKKsuu6mZdd$SFFԩS%%%=z4dAl.544(**F Lnkkcٝ;" $ !fխ8,!!AdڻwoHHСCmll#d2L&mmmٹgϞ---x!UUUN-UTT}|>XBBtܸq榦D >Gw]\\I/%s=lɌl}dƍְvPbbbBB²e˖-[UgϞ=y۷oGcOOOUUUoooooW^|MOOoٲe ,wc5**9(22ڵkJJJDB LRqcFP(!2ԄpGf|{~ $$$Z[[! ;~ujkk7m75111119s&!1DPpLII 'd,##CW)//P(奥 ߕ¿>Ǐd2bO}T^^^``Ν;?5^K;dmkkrݒV$Sԃ۷G=xѣGϟ=t/[:TޫH*u۷l6ԉ 4'??;w \rđ?Ommc/qƖpO?0LLmEDDD0($$UyyyqqqYYYɁTZZZBBӯ&lٲ?3((ϯm ;;! rCCh  EZZZII !$_1? :"@1L:؈zD1 |v:%H$!,,,//(///''$UV=}tɒ%۷oWWW'"W&))ijjjjj*8ᔔ}833s&xg{ϙ<~ڸ O!55is'OdҥK߿Yf F@=t-[] :ȑ#AAA.\ [z͛SGx<󛛛]Q DsssCCC}}=~|_KB222 222;d9#-..@8DBѺB^^^YYYIIIIIi& {ԩS߿_]dgg4h].mhhPTTD1Lyy<}ˎ?,YBB%$$8!TWWWUUUVVR(* 3t:Jq7P뒱.HKKPL|cMMMk#*ZUUUUUݻJX]]]YYYWWd2x.jiicƂOzkXkk+}1eC? SUUU ߿?w\55gϞ[s|2Bollx#2LF3:"KIIl {܌$JED_z]AAAAAAaaaEEBB d2YQQQUUUAAAAAA0(̟}?Jwrcc#Fh8/:NrssJ?t2Ni=}tIDDDUUU0 KL'fΜyy;''յE\.466"]vDaXC+l6_:HJJBGd&|<.((_g23vgan)++%-PEEEEELP=zTVVNĉdM644Ӄǻ~qqŋw g=!..R555޽㷞}‚ͮ7 ++*** O3[x4JRTڿTjݿ3JJJi17| s纻{yy]K/`0 {zzN2СC.]9s&QEvvՏ=RTTյ,--'L0h 555t?]UQQQ]]]QQQSS_~WVV畔ġdK?w ]NX,ׯ\JѸ\.LB !DӅob`0H$d8i!,..Ad*_/Ɓ]2RQQQQQ0۷o޼yB$ĤO|\zu/Ƿf ?6*"Oi4s-&**9̟(%%%--G@of NgX,Fl-懌Ǹ YYY~_m2em[l ޅd1Bxʔ)'N>|GAt]kjj---EUWWyx|8iYYYYYݻw=zؓ; VZZ7mڴk׮ x%Kϟ񢢢N:u޽'O.[rݻϟ~zxx®]_ze뭭JKKϟ?w-[$%%zE,22288xƌ e۶m!!!8+Z^^hLLLx<~[.f/^D-_KOO>===7oFIIIhh/bQF9sf鞞NNND߿C ƎpUFFdر1m@"""ZZZZZZcƌd2o߾y555!d8}Geee͝;8<<|ҥ!fj%$$'?ՇB!8(Ib0f$%r ܮ ܑZJJJWW HIIJKKH$yyy8z!!!?ѵ:&M;wn4?t萳)SU=w 5kBk׮p87npBhsBCC/\ ڵk7o7 I$,+++8`H$%_A,$$$!!"&&&,,p9({SU0̸G=~8++][[j?t"p^x'O$''/KGvpppqq7HZZ7bĈ]v7qdPK`<KBL&gLpgg;L?0BxF&!)))IIIPCh((--stt|2$d2yڵDEEEϿ{~~!9r}}}VV9UVVV8=FFF-$`} Bmmm!|J[rI&ٳL&Ԩn"?(;-,,>}ZXXx͸7oZZZn۶7zU1DTT )dнd;~n5:dȐÇoٲں˛~ ?moo:uD"YYY\=#&?v옃ׯP0z 0`h  ENN_`L;wDa2, gonns8ޛ;8rp$ŭYfȐ! 󓖖x}~~ɓ'7DDDlll֭[w֭իWM882)S96&&&%%RZZZ&NہObii[f;wܺuKp?^pfTTBo B(??!tcccYYY!!SN}R基ŞQi w-? BF~l]ߜJ***R(2455oy1L"l<" 2tDAQQѹsF wwyodƍeeeݻsΎ;6o`ӧájooqFHHHZZ7&M_;|6`d 铼޽Nt-ᑑqQQiӦ]vý̙;w.B(::g̙]bEll7J433/--}qddWƎ{_!}R基Ş=iҤW^8p@LLƍ0fРA%%%'N;wJýdryyytt4Bk7L>}_}bq>ڶl2k,kEEEEBBӧO⊋Ĝ܂FOQ)} vZbkkkoo?n8kkk8%PԀ8|Tߒ!%%6"t~ddH$JY455]v̙3jjjf͚={6|W$zattݻwĦM`'''|J h677'(^* 888&&fD҇͞=;66611ԔZdnn^ZZʟbŊcǎ!pȁ㹺>x ܹӓxLĖ-[ve_&M_G+Rg666VTT.]ɓRRR, YzÇ?c]L__ߐ;:~8Nvvg/)(YYY?/;v .̟??77АZ\nJJ۷333 njIN)))ϟ?p8ZZZο˄ dee.hkk ߾}}c}ѪU^x3glkkAM4I^^jȐ!'N@9;; ~ 377HHH:tBHKKkʕ6lعsŋ]vEGG[YY޽ۿ/"Lt|20o's\g//;￝GNt- 9s&X,֭[/VWW|ͱcf̘)d-HJJ3fΝOa„XƇNlrȑŋ;vOD+W̚5+//? rW 6'N%4𣫯OHH믿nݺEuuuOOѣGCOիu=yd۷oWWW'/.''truuŷ0556mھ}?`E6ou#G̚5k֭!YYٰE>}455uƍzzz-o@Gd7YFUUoĉEEE ,㽟ގ;ڜ qm߾L&?7o2@Ν;y;wB k?}/\`hhJtQ?>))ӧ+WF !Z[[Ϝ9chhxʕK.}?3sLbcc6l@t!>r䈃v``իWݻOЫH$ӧO{.%%e̙o߶2d͛333.7P(˗/744|ٳgSSSGItQ_˗/ͻ#Dh!&j\.b82BᴷKKK#ZZZ WVRR2gΜaÆeee:u4 @YY.))?BBBhhh8t萅(ӧj9rd޼yAAA{!IQQѣǏ_fmVUUEt]|ΝD۷L8NGFF\xq Ҥ$KKˍ7jjj.X %%S__qF33ؐOOwTZZZW444 pq.w8f0!L&!$))ppfNAd aٻw622<>n8 DڸqcNNMtQ_$/// @__ƌ4ȑ#aaaÆ #4>>>>w%QRRr EEEoooUU)S\t g&!jjjVRRRyfIII@@ v۷o0a3m۶իWˋ ZZZlmmS__ "STN'x)NG vDEDDBAdײ rطDHbbUPPЪU֬Y'._hgϞ$nnnfͪ&OSYYy+++SSӨӧgeex':>Yf޽7o& jjj"##ϟ{/^p\k{rϟ? WRRZp!;sLMMܾu ѣGϞ=۶mх… .\(--ݱcѥ .Zٳgfff;L޶mΤcK,սtҶmJJJ r]]oDnjjB툌{$8bPȸ2kmm "^ncǎͮ] ?33w&&&|(<<߳JFFF;v!!!DW'XS9r$&&Ϗr~h$iwyѣG544BCCmmmUUUgϞ 𩪪Ξ=;k,#F>|L&{۳fo!00ΎBW1t eff&%%yzzB(_"hذagΜP(W>zoUUu}}avvիW}}}RZZ^7c녅B4 !TUUſC1ȸ#2A 2ĻEĮ]6uTDrvvڹsիoܸq ꨮ͛׮]KHHvuur劻;Z>[mmĉ!{())-^x\.7333666..oii1669r@r_~'%%eoobaa?ۓ'O={Ft!Kq+Wl۶l~~~Dիoӷe'N8pɓ>>>֭Mmoo{nXXXBBˆ#_>yϟ>~}}BJQ(_/PGdN 2 |__cǎ͘1#""|zW񏒒ڳgٳN:AtQ!TXXx…W~Z^^~ҤI111...?* ]]]RRRo<",,lmmmmmqF6ٳ+W455ٍ1NYYzԴϟ755M>=44tѸI!B{urr9r$х/r7͟??>>~РADW@o!##n:ooG=zGLL>G}}ӧ?NP&NLtQg͚z%%%}r]v!lfffnnnaaann/* @̜l 2dȨQGmbb[%..޽{ D>Bzĉ/^hbbBtEF֭[hQppۣ>O?]';q͛7Լ,XJtQG I$R]]?$###S`HHH477 .1bD}}}JJ A䢢;鉿}'66v͚5C 155 Ӌ|3 =ztܸqɐBDDD,,,VX]TTݻ7o.YDJJڵkg666YxqXXX|||EEUEEEE|||hhEllldeeMLLfϞ#%%t[nUWW}ܹs^^^BZ[[WZ5u1c] \.7$$(33}6)s΍3FMMMIIix> wtt8pO<)UGHHHHHuڵ:::666ΝC555[nȐ!{~I[[ƍgϞ]WWa@ g NG޿MGGGVVvذa;vhii~joolj/^/_|r׮]׮]ݻnnnxdŊǎauyLpf-WxxxEVVV=5\ȑ#.s=rttD555z{{=zTm~O=$8޽{]k׆unnn>}4WxgΜvǛ>}>b"Ν;Ϟ=[\\_~eŋVUUQT400ҥK_?pC.\sƧNZnNG)++ܹsŊ͛9sAFFշz_ tD[\\ >|S\kK.˔?/oӧƍkhh+oii۰aL򪩩ټys^^^aa -5*111..RD1bҥK9DRq;99566FGGϝ;wذad2yܸqK.ݵkWtttrr2B#?y$:::00pɒ%cǎ%ɸovF''J*tKB h+Vr_tYsss SȧOqㆴWBBF{B<|ڵkeddݻwIrݻ+**zZGG۷oWTT0M6x-[aaa =\RRcO=$$zE333 FYYYLLѣ?u_kVVfpӦMd2~=}t]]ݙ3g |tX}}=JxMMM,--ltss3pVG6KKKϙ3;l]re]~0?޽{=ܺLJ>n>w/$$t6!oo.ڇb 1:::MMMMhiiY[[744|333CCCC\֭[t:T uuuSSBkknn͍ٳg… utt%tttϟm۶z V}}}nnnlllddm͛䤭-**;f̘ ٳyyy---Dς Ծ>8BhwW+8sڵ!GGG݈Oӻcmm_p6ltybØc""##g {xAAATOOO===w Z[[wo|/>>NP($͛7孭 ,vvvd29 >wޮ#wڅnj_|c >|544{jkkiQQ .x<55Ќ PAAWySߔKGGɓ'GGGSʚ5kϟ)8׃̘1Bl۶-11~76ӧ,Y"--=|OOOkk̠ |7뭭JKKϟ?w^GGǤ$Ю]l٢7mڴk׮WWW 57y%G7}k΃ JHH<8C_NLLLLLLJJSUUuvv7nַ(tֶ}{N:522۫JKKTSSPSSTQQ4h~TUUUVV۝f555߿Gikkkkkkhh {OO] MMM'ONMM=uٳ. 8.##ʪ"ee,sssL+++eeZ-*jooW5t#,,ď_o``***{x~)?uuj^|!`0>7j_~mdd>x͍v^!H:eS7mkkWz{w3mLS+)JiGD(/,!d^%\۵ݵ+d-J QRHҦf_ǝ|dZgyϙhΜ},YsOOӧOwdze|ӓFݻwW^-벲7n\z566BxyyM6~*--&11M;vcǎE!KJJ&O6mo+lٲ4ww Ж-[_o޼ݡC;w::::;;gggkJ. io~W\nݺ=w͚5VBueÆ QQQ<Yf=zquuuuumXz|їx֭[+V V\׮]+Djn$,, eÇ7oŋ9/^xyll;vy[[[@Z |N7>>>333,,,22ٳgaaa7onkh:/_qwwwKsﱢݻW^PRR7n͛7GـOJ||0L2bDgGMM !o '<!$T! ZP(9s&D~謲Ō?ϝ1chLjyBkkix5B(++ !4vXCA_nAFeGW\3f̐!C̙Ss@QQQBBϟ?p:uԿk<`2`tbh4a8•L&YUU`08C45gk"za5՝~B5?T*@ 3NZh D"HZZZ$IMMJN _ZQQQXXXTTT\\\RR%%%>|)XDGCCCKKK,J"455Dbb\.-r+**h4F 8|#Z)WTTęo]]];;;544R2|  *++۷OֵzhC )++5m=bccϜ9sȑ]xqϞ=mP=իW۷o#_铐3dSVVg+wptt8{ٳY_nӧOnݺ5O`ff/Y4GLUSRRzDFijj޹sgҤI#FxaVM"''Ν;oߎ!Æ ;w'܊DJ}eB ?~`XAd:nnn.AfAdjUTTi81 2h uiI5/i4\[i4xzt)D&7jԨ+WZj\.7)))>>>...99PMM7ovrr@*+++**+ᔗlCX,Ywp IDAT*++d2gpUUUuttj&qE[$֊BsQBx-tT^^h4WSH1s9ꬬ,P3"C2L"( BQSSSWWR8r g~`[nnذ7t2&rrr#?OPDH¿J$|wJOٌgѣCO/ryy9~ Nx ֝Y+)mSSS(|,M;h'6o|޽Ǐh*خ]ʺZ̜9366QPP0aÛ7ov#__߸'N Op6rAAAJJJ&L ݿ?4z脄۷ }ɓӧOF]dIDDҥKlmmUTTcbbΞ=ҰRYwww޽Bի߂ׯkdF 1izT/^<`cc . |Ҁ7+":a///_rTUU},I&...<H/>> ׭ !L&S(J%>1LCCCl6[",*++D Myyyݻw_n]``` _V_~ O<͵ ۢ>giРAK.9XTTT*++ OMMM)}v|`{ꅿMS޳gr Ys#zJJJYm-GYYYSSիW CGG_~߿޽܏  FYYYIIIYYYiiiYYYYYxX\¢.ȋpK]]D"ihhP(D&qX&É 6g 0LQc8A.zk}k (w:GkgϦhgΜ 򢲲'qfTbx<#n,\. {ܤpZ4CqQiQgQZ]]H$BqȑӧOϟ?~~~êUN83'''YR;PyݚBQ=z$ѣ񕯚~uhw52sW|ׯ߹sF>,Q0B(00pǎ^vZϮZֽٻwzxxQ0 XdɱcD.HsĤJ[Qc$L&s</111Y(..훉ɸq<==]]]a_>~xذau|K^^я?p"NS([|7'rS|F @}~ҥB/111Zm !D n߾}鐐>={Y?'N\tÇ!++3f̟?pj:tD"ݽ{` <СCN=7oܹsgFFիqVdǎ$),,do۶mĉ{IMM"&&&nnnSfnjs/>ykժUS(W^GEEQT|UD.HsĤQQMNN>sLtttNNˠA&MԈٌd;w\.o߾UQQ0`GLJJJHTJR'Y,h=Ñ舌D"&r(y޸qc„ -Mnuc6:Ӕ$Mna999o޼i]\cX߾}+...,,,,,ęo߾ }ڭ['N FihhxY/_<{,77wܹON!Wpp M5׶uZ'6~/ RMna6mݻwddСCeUZ<////777///;;/X,<@ pnNJH&[=L ||qqqAAߋ޿ɓo߾#cdddllܥKΝ;wܹso۷o- _`]d]f8|B YFFFʺڝ9s;wn߯(ILL]vuss={СC[ >>~Ҍ,++1wD655=b%WaXd2bnXpp\'D"WQQi#2hFl6[WWZ|k Gӻojjz%Y2f|ƹ߿gvD"Q411122­dqZ9fq:}NNNaa!WQQ166?8ܹsnݺuYQQQ֯ /_ G}aÆ2Hu9 Yݳ^xxǛ7oAzzW^|V]]mjj6x`777cccYƱX,--/N<ׯ_}|7nLq"?''g9BHEEԴk׮cbbKLL\būW,Xch ǏMpUH!˝ׯ˺ک1ctyŪIIIt:]EE8pklG^xQYY)DGd ~JBT*UF(2Ll6D":"Z#QGd"f䤫+BLDn%ƎKWWWYM,'''55?~KQQB@ ub]v1JE,933333}| ]%%%V1[vvGnaa!Pk׮Yf9rȣ{˺HVRcB Fy9oo?o߾ߩpUVVݻSϞ=y'r@pڵUVmذL&˺(->>>eee>4h wgggYW@fdd$l IEEErr.xK.Gt:@P(58.Df٢x9"BrY, m~ii_h8|7eee[[޽{5FWWWe~ܥK1c5YYYo߾=tPNNS>}lmmU[ݻw>>>=12((h˖- x葱+`0srt"VllP(}z-q] tuunj#jCQQQWVVƉCӧtJNMMݴiSXXX߾}>}&k׆;ѣG&&&4 CClYW@ڡCY=}tȐ!RfX<OD]t 2gr8$uDVTTTRRxDHEE m܏?lll/^ h9N+\.>PQQ! %VxZ7Bӫ%VVVVSM JbeUUSijj|DҥKuM%"" >`VIIɻwm&2(pt**`@j "xsh4Z͎jjj8|l6!;wD 2۷ٵ)?/+afXϟ?˺VD"՚ά/R'[xpyyyaaacL&7ֆ@\xܹs߿7333${)ij[Dȶ>gϞ/r8VrLd2CBBBCCcbbBak~. >|ƍׯ=zYƍjRVWWvzȑ#]]]e]C믣G>|x֬YեK#Fڵ NOҌx/>} . Ǐ~~~.իD:wDAdR(jrqXW=\Fe˖-r4Qk{"##Bu!5 h% ^kIb|~-['5ha{>|xdee={VQQqĉG8p A}j;w]3s˺楪:~3sy{{̚5 wj%Μ9sر &>}QEh9\.{ڸqҥKd+ `nnnΝ;Ab<@"##Ǐ/2EEE5NKKCu]|NԩA&"CGdjrʨ#FȺ9)5… w9s6m$mհaFhѢ7ohZ%%%ZZZ۷oT \z5>>ŋ>}ԩS޽Q~Ӯ]vޭ2{l6nd27m$Bڻ1cH2---_H|F_bp@ /l6BOL">//s Q.]\\\?Ady왚ĉe]KS*++qƞ={d]d\+I`coʖqVSS[UUUG}dgg#!>`=33333ʕ+ ;88ddd :?uR/,^bٲe5hO>|ĉhjj_LT N_`0( &H8 ի'M'/9," ǏOPZxѣf͒u! v7Vn%5tl f Puu5^䬬?sƍݻw={̙3ůVUU-\ܹs˗/ߺuUg!Pxĉ˗/!dee5cƌyS_ê6mt޽JÇ:ÇR ?C4!))յ]~}~(:I?pSSS-,,֯_-Bqqh;vPVVFڋg WKYR6(k&(akO:?wܞ={;w%g9s… eeeÇ#m߻wٳgUTT/_l2mmmYdsǏ>}e]NrlCC5kL0!44t._ZpŋuVVVKo"޾}[1!!!QQQr4Q~eddL4kΜ9'TDBijjDf2#hڧD{ 25| ڰj;;; [nɺ&`0elҥGE} rZg_C8coo_tdiƈ|a[jM]TTdaa*@Jr)F [X+I݈f#srDz@:k,ooouuu_~}э٩V(5ѣGO=ztxx4Ylّ#G ٢ IDAT?~\bcBBVs!::zРATjtKܹS|G鶶,Yr1T#,N:6^u=Mi_$ʣիWߗ/_{L%HGٳw/Mnݺ˗)7*t䜜@Ҍ9}L&O:۷o0hn񙙙aaaϞ= sppؼys68}7E1Ν #/rzꕷGtttd]@1...b'n2L@ qd2) RWWk8z(D" 2 }}}e]Hcijj">}$ZGo޼߿6@?~|iii}vUp\\w\CkSPP```\ ӹsZ˗/OJJx W\uMg۷oş4[KOO_RTTTTTTPPPPP5UWW >YCV)+Z=Ɨ{ӧOuqc A^`/G^57k^^^ލNUUUttҥKӧOppp~~~ hBUUU?jEjnJ֯޽[`BQWWǍcd]hlB fϞre]ٳ'B(((HFtZǿ~!ԷoqӦM!{{zL 4UUUY5k,}}Xyyy/^ttt\~E}e;;7 B|Jhmmy憼@K?!rQ+,r튊 [[ۆp8OOO2@H4TVRRСɓ/jkkˢB񔞕_qG#Y,BhӦM㯿266&EEEET=B1t^o˗fMBc~uxhݵ~5,ѣGw޽w^II˧LbffքE p\nyy9oNx bx <At#r~o!b|~ TijjUUU|x---HTPPR$IUUUSSH$S("N$555sTf֦ʕ+JNN633۶m۬YTr`666fJOOpႹ+c^^^_ RRR0aBhh899-^xƹ.\@}Eo}100HJJP5jTkdrbk֬9z˃)h 233~*oAB wAPRR舌?ItDf0d29//O#2`r2+|ܸ~@ =z޽{ݻ{yyɺ233{uxxի۷ofnn)))={|AgϞEu֭acΟ?k[@0gΜϟ?xC. pYYYD"?~ʔ)ÇǗ|yyyϟ? $6~yVkeew={VS=z4xgϞ={vpᠠ ѹӧUy}( SSӔUV5c+++L&Ln.BGM&9kݵw&''۷Oݻwܹ󝜜V^=~xKK6?4NTTTz&Yv544pWKKK!t m~,Ytr|옘gϦx3BիWݣ4e{xx)++{yyݺu+88Xbׯ_O<9}t]]]I@hI6$\z-+??)SI3?~o߾UVV< FzG.Mر3bPx]ׯ߹shSJ_IlllEΟ?ԩS$Iu x_ A^7Pn{VskvwZwԈǏİl##aÆ1bȑmw;R,**[QQQAA STT$j嫤===X]]]LR똕q໸/&]]]CCC}}}ԩܗ\V*++BBB""" nT,Y[͛7S(YW@k' /]f͚ʝ;wΛ7zB%%%W\>=%//ήlʔ)AAAׯ_:ujUU}JJJׯ_?t落=2aѢEO>EQԠy湸8998pcǎ۷o_xqӽs@RPP8}P(͛'G,5u}k hcƌ~urr̙3hѢ͛7wرjk><%<8qĥK>|1cyt۶m'NܳgOjjjVVH411qss'cƌ}ŋo!>NS(j% qGd#$/T>_$:" 6mڱc}vPPP۷oȺ@%;e@jժE[pcǏ1bÇe] h#wƍ޽{:tYPUUջwbbb={WZZ<`AiWPPӧ_qYY~TSSS?511166600sEm 5`h4| RSS\rٳe]{=~x\\ӕ+W?&&&&%%%&&y鹸lذѱSl6[9cFFƧO BL&[XX)JuՠH]vڵC8fGEE8qb!444,,,,,,,--wEǏoܸ;===oo WWךh&۶m[d͛g̘e˖kN>]EEE֥ cqqq;w|a߾}?~t:=""ɓ xnII b2w`0km(xGd6\.!$Y(VVVp dfԩgΜ=z+ ܷoߔ)SN:6#ZZZ[n]~ Ϝ9cii9sLSSSYW׼(ZS gDDDDDL45kVB Ɔ\tv]veffX,;;}.[o߾;˗/o߾}߽{W˜9spZX9ioo/>//ڱsv*Gm\q855U__1'OvZx̙͛d===Y@KW^=v˗/ p޽QFɺ( { B<322444tD5&DM"s8|UUUB<OUUR(B_KHHXpرc-Zw^gnОΘ1ӧO_|y,婪Ο?/^۷nݺ޽{{yy {}5ɴ3f(Zb%%t|>?))))) D"d*F"J%jjjd2YCCCMMMMMMKKD"AX [,+99ݻw޽{mjj*&~~~666O-HuM166666vwwx8C.]ھ}P(TWWǹ^zv|>?&&ᙙG0`|3glrÇ޽{ԩ+W9I6ĉG-..8pp-777MM}ƍܰaJ2dÇnff&L\ w5򪪪._|݄o߾ BʠAmmmmmmeS^^^^^rr@ъZ*DRUUe \ǣ;v(/^<۷UUU={3gP(.;D"M8߿7o޿Uп~Ȱ3waa߿`0'N8vXgggx߾}[ntґ#G}}}LSz1|>Ç/^{.L;wŋMLLd]{6颎ș:u5,UUU|(f! @3gѣW^gd;t퀀}-]n*JJJ8v|ɷoFFF>y$ fwmذap @߾}Oh4ܫW/UKK֧H$dhhذ,,b-?ZZZZYYYfb T*2sm@ HNNϿ}doob ggnݺ@+* 333^|wر*##ťgϞYuuurr{ݻkwѣG0 @<… 6mZzȑ#O>n8@޽xŋ׮]hÇ<==UUUe]hpٰZzBIi4Z.]0LEEE}۷O>=z&aȅ7o޼|˗^*..VVVsrrZ` Z2=^^^SLj||b=|0222""˗/nnnaúw$J 8p?~`08}U\\s!CXXX4K@:t}]r%$$dFFFnnn0~o޼ sNbbie]h߿ R>%q':NP0L2bXfKtD DQ&M4r}?~ƍgφ^mLVV֮]Ν;gnn~֭˺"h.d28?6m*--UPP033ѣm=,--jmXyyyZZZZZZjj*trrڰaC>}o%jtKfSG-klcZ2kkkw С… ϟojjZ-p܇^z5<<:;;O>}ذa}URh,-----/_^UUC'OTUU9rdy괴/^<<***//J_~%]]+VX"55544Ν;ǎ#Ç;v1c樠Uxw 5007n֭[ ~ڒ+W_p8L&S[[!%%:HhL&BF:"DuD&zZ߿رٳ$i˖-kpSz8p]tYvٳО奦|>_YYʪ[nݺuhZ?HOOo߾!:t`kkkcccgggkkkmm-q]ԫ%Bl,3BHQQQ  2̘1l- =ztʕ0&9`'BV (44ڵk ػ303 `^=oVvԴ,Ksۢ7+,4-5I3PCDP\fefo Ǽys0=>lRR)nhh8ϝ;wLЯ_!CtH$JKK;|#GryhhѣGտ'V`Feee{=|pVVZ߿?}[41111117o={ŋ} /pgk֬6m3獵Yf垞jΝ'O޷o5X,vqq =D.@&~o9r믿>tPsLT~֭}vrr… h:V[PPpڵ<P...quuтP(4w]`(//sNaa#&666ឞ.:\-ێ8%s!ޒ$7laaa0B++t޽W}6msN>}&O>>EEE~~~xȏe477޽{f3f̃۷_g?~ll2T*UdYYN#8::D3wWSSSZZZVVVZZZ\\\\\L5r\&@ -Zm̵ѭnfk9s7ެ/Xv-z̙܁v]1`Ry:NU iQFOwQZZT*;O& ^RkIII kh4'NjL(Px+Wt:]=<|p'''s… BV~KJJjll7n\PPPUUP(ܶm3[nO>}ܸqjښNl2o<\^XXأGK.555 4K2?.f?s=ӧ׭[K/'N:5>>N#ݻm۶ SΞ= T 7h4EEEwܹ}vaay咒\N'tϏ.Ќ;~njZf%%%%%%t !nnn>>>>>>Gf^^^'\.&T&X$Je2R鳍r>T*iẅ́ӧOgffz{{Ϙ1v˖-}ٳ>+@3lv:xe] oVX?\r{w|K !r<+++33333355UzyyPrqܫ .dfffdd<?TrP(l6 okkKݑ!F,B) 4hРA*СC۶m:tI&M4p@sWD8p`۶mgOᲶnٮ/KJJ¥K~j^Orqqqssswwwuutqqpuuuwwwww݇nhh80`NW9y t@cJR"rRdɒ6^Y,VYY{gii'l6k?!dڵ=`֬Y/ {ュ[nݺu +xÇpr3gh(y׮]|>?66W^qqqqqq:/^x1;;`kғȑ#SN}999ꐐ 4lE.{{{+ <&(JB%R?jB >2"@Wphxǎ۷o_fMllQFNB8~zСCn޼yܸqvvv. e2h***JKK˙gii jjjD"33dgggG#NNN̟\CCH$_uuu555{ f]\\ܺw>p@OOOOOOooo777 o#wfmmmn߾3ϬYťgiiiW^yʪ[lܹsy 9r| zDQ*4]zСUV ''8&O,BÄܹCquu{uZzuRRRϞ=d#"͛={644\R(HH2+ 5 AGdGoҥK.=sΝ;>mȑɉY***_>ܻwy. zi4bZӐ(mLq\Mvrr2+;88By<ޣ:DD X,nlllllQ̱Nc^pk߾}i@;rUeee>iҤcǮY&===55u*muuuppp3fl2`0lܸqΝ^x_ng :Mվ{כSG]fM~~H$ JIIYhӎ.;;{YYYeee.../ׯ_+o=R իw}ڵŋ?Bmm-Ӕ˗قn?ә3gΝ;WPP٫Wy۷'yɒ%O?5k\t]#}#F̘1~@qr߿*H.]DS۶m裏!aaa!!!wRhnݺuƍ7n\~ƍ:޾W^'NcsW εk׎?~|M[[[Ad$,yk0Fq19"97|CGg͚aϟAgXj;x+VIKK1b!D*FDD2O͞={ݺuE٘Sml7ꫯэ3Pyyy<sjͯ}c.gϞ>|ԩSw}… ǎ>|D"9tдihCC-jhh8z>zu; Hyԩ+Wt:??']vHx[XX߁QYYI|jرɓYj՛oIG>Ӆ 2 Z`g}ƼjW VX2C=qĖ-[M fuل .Z*77~&t֭= ׶gt_|1| nnnUUU\㝮ZjwiǏo߾CFDD%%%yyy5x˳A^c^NNN{5w!O(NWTTDɅEEEEEEeeeZbgggM޽oƒh4ƁcXWWG'/HHӟW_}tҲsΝ>1cbbbnݺL֭[WTTd*[[ 6>}c׮]>NcXӧO8zݻtowߥAAdxk׮ξϟ///'DGGGGGDGG%%?7ǫqwwߒ~ &(LMRemcQQQgΜ}?z߿ݷNU=_kgΜƧz*""ںԩS[l?w>l~髯u);%%%77wժUl6{ܸq/Lxyy}?KI\\ܬY]ZZ}vBHaaal;z(Ř?~sʊvmcT*DLrYh4DV755d2ڞ]l"pt:Tʬ X[[[|f5((ȸ񂅅E{ `F;v())YdCU__ruf2G"tD 2sUIGd鈬VPerhLF{ WVVTVV2_1'?6|뭷>&kg͚~zf?//ǹzbʕ+C 5j ВJR*L.Yjڜ%w۠_Y,P(4D\.aVat ZgϞ[ly(>~ҤIš5k\Cd2O?f>Ճ9rdʔ) ذak΋p8sb;dBH]]*))'ܭ#I_&Bxrʕ+WcbbbbbV[{XYYZjժU-s<2dٲe|׭[f]GV/_|k"H(޸qcΜ9.AdX$x<\nggG* "Ӆ.#emmwرٳ?@]԰a233%ə3g^{۷BMfD[nٳݻSSS7mڄ2Y|g~Cf]]}SSSXX{GAd6pryU*k4S2eJAAرcg̘qFFcV]]HOO8y2!Heee|}}ϟ?r۷o& $xΝo>ձX,???!nDV(ZeGd' !vvvt8RhGdZmH] >ovÆ :ycǚ(`G !߿k׮'NΝ;wƌ... IgϞǏgdd<"HVՖAdDB18Ύ qK۷aΜ9cǎMMMU(;w3fܹs_TTdN,&Ld4T*JIY,BZvDu rssNc:"Z~)ꎃ_l6;99999YP8p`Ν/E޽ kagdǎ;vŋ !III[n3f :ٳgtwƫ! L*B;"T*Ad0egg7eʔ)S߿?==}K.urrJLLLJJJJJrss3w~;vXzzz}}?ӧ3$u͛wڕ7z}AuDfvvvt鈬T* !LGdHFGdxL8::N>}zǏWjuhhh߾}׷oߐtJN߸qΝp׹\yĨ(:gΙ3gC 744zKK˞={wDfLژd|>_PLi#dfK@ELLLLL… JeVVVVVٳgw-A޽ۧO}vD:PWWw .??Q}N8q ƍ7jԨ>vQSSCᆆղ#I;dB\.wuu喖 #J"05 rNwڵ3gΜ;wnΝ1!'"""222***22200 0?V{֭W^|իyyyeee\raaaګdС~~~?CݾNW|G\DjrS^WT4lYR1;@:|gii5{lBHuu ٳrJ^ph(9<<<88܅f_R,,,f̘էOWWWsW dРAnnn<vTSSbjCCI;dBD" &L)OT 6:"# O(771cƌ3*k׮]rի{ńԳg`ܡx[n֭[nݒPٯ_3gFEE1- 몮NLLGrAW[bqˎR+ nSS!eGdZM&ӧO>HEEyۋu:!+88ח.xxxXZZt%%%%%%EEEn*//'XZZ%$$+AAAƤ$V{I''͛7 !te2ohh`j#tDFC aFj;wt;wN8Q^^La^^^4226.))))))//j6M=zB2RƏ_]]})OOG۷o[ZZVbdP& \jGdJEFBgDs 3tm_Ο?_RRBE]]]J3tuuu555ty4B$zPPPbb"MyxxXXX(kjj?~|^^މ'dpG+// ˆ@9gϞ#JRJ!DR#2C`ii=p@jkkjeeeuuueeeNNKooM9;;;ՕYuqqqqq]\hD"H$4v,D"f>˥rww^z58_bcN~555gϞ}d)dBH]]z#2 4r8?V]3*ЕdjDDDϊ⪪ں:x?}4Mt:f>˵@ ppp`B!rBƆɑ< JH$JRRbD"HRBccqҒf]\\"##i"$ЪDFsi__G_P(wΌ444888LH$w "rggg:hBt:Np8ZVף#2BPNTIIqĖ&EZ666&-,,lllhD(X,.pX,P($ЗXZZ [϶xjuJ$^<+   XLijjR:N*FTVit MR&m&|chCYYYbbӧKJJz}hh(3rC*B\.vDVTFC:p.t[4LS- BVb:T׫T*BhF !M<[XX;::r83aBӨ'Hݾ};11ȑ#-… XRd2V <* 6fQGdZM }P >vttttt|T("n,mNM1MKKKkkk;;:ZVݭ>@vűczzz=2!ܹs???Z__O1 "u& B`>5551ߘL-vvv4b_ljZohhF"ж2TqUVl^r5X,X5rYm[:99 rf؞={^|޽{߿ߤ#seB ]Ad b1!eGd+++.+[ "+J 2ay@jkjj***D"qԸƯ`ɞY[[3ƫ<f3/`fbL dbF#i~XT*hܙyeښ&u'''ڭ[7wwwV^xO?wY|*y&b"L"R)'r&oB "[[[w=L"?^_SSSYYYYYYUU,TTTTUUz:s0Ott4" ̮q`-/]D {{{OOOJ`2 \R:u6n8c 3V"Hjkk/ tpp0v4P( R[Gdz :"tjZ?djjjt:󽼼<=={ISŅF Wݺu֭[```ju}}=MiWTTTWWǼZ@g:::w}DMMرc ҆jb^j0\]]H$ -,,I$jYV777AJ 2M#2@gQ___XXXTTD4s\ZZj !\.b3ϸyyyzyy1Agkjjkjj*++s"qss3I'XGxY\vm„ ԩS.\|xzz2#NNN&briY.BAdJE0AdfaC+"׮] ///vMHH`nnn.fZ>%JMQ_tXRBBBBBg҃?۶m5kVtt={:?+WfdD"1iLd69n5T*ik4 2QWWg;#xgϞ#FYU___ebްX,OOOOOτf7nܠUUU>۫W8ի&MC 1w9OT幸tDn٭ՎRŅ`SS3}.K 0)&||Bk\\+K㚻L(ݺu4hРA7nwÇ ^Ą{nƚv5s̀ݻ,M:"̔H$vDP(.J%Tqqqfffffŋu:}^&NH̦[n8p ]J4m۶>իW&$$DGG[ZZdhE]]ݬY~W_}/N%;;[(J$Ȏ&3bqˎ2ӎȭU*ZBoWS(**())ׯСC-Zb]#tF`Ȑ!C 4jժ A <8!!!&& <c~… O>d;K.geetDnDH$nnn&LƆޭ#2h]`!''YYYGmmP(LLL\xBBB,,,]#t=...#G9r$]:~ڵk~m69tСC8v}D"yƌ}@ 0wEwtDh4RdfR@ L;dBHSSLAdjN:xlv޽gΜOPx'M4i$pڵ'O|xJJȑ#]a3gdiiiÇ7w9mQ(7nHII!0 !vDnDMȭvD ںCc L&;zF=rAٙ:xXy˗/;vSNHHHHIIIII3wE}w)))7nd˗u:a6 rBHssJ%%%⌃LGdZn޼yC?rذa|Ib:4t IDATbK'ELLLLL̻ᆱP(~ÇX_޽QF=x`++,e˖EY[[N<Kvv`pqqaE"i`0, !|>_PYz&,#2<***~]v]p6))iӦMƑ>nѣGꫯ222rH``7R 66 zGGGb "+ #2 "\2}#o*5uÇ 2?Lvڰ޸q?駟ƍ1<zjժC9;;K4msI;v,::zƌÇ饗X,gzeGz'''$yy󼼼/^=220--ɓ?]@'~7nDDDL0O>999.=: ԩS;v(..~hpYkkJBH$rrr2/HMe2mlDfX 2L:"Foܸ188ѣ}NN]@'zE7k֬zs<{y͛&MdaQϝ;SOqܪ*nݺqV+HZvDH$mtDV(vvvtҾ*Ғ.FA̝;wڴi7o|7ܶmO?8lذ'NqaÆxgggggM6 ,b577zڶm!D&~~~|KZ… CBBݧL"L&Th?66>P<tS:nɒ%O=nfOlkk7zsoguww }wu:]˽?W3fz ^zݻxBgUܹsΜ9111vvvAAASL9|;O?8p 88f)}?|quܹ7n$KQQQ={nddȑ#w믯[.?>// ޷YfmذY9p@|7{l\\lIIIIIɯOd{...&TWWd2>W=,Yb |ҥ &1_}z۷o'%Ϗ3f…ƍ6mڗ_~)R7n(}Xsձ~mBHUU;ՎȮ&#B DVT\.Yj5:"@W8jԨ9s,Xٳ!w۷v֬YX,6l!dƍǎϿ|rnno6md]v>3:nСW^eF!~߿z겲/ͭhBݻaP7\r655544477wʕ?'w^|yyyy7r\~ڵE e˖gb嗇Uh-[\ԩSO?4!?oܹ2s˗/򒒒_~e뇋[.--ȑ#/^4o=୷ O\m۶>Lnnnf:" !,\.'w "Yt"''''33߷2wEc>*_&H'$ D%P %.DUWeEV*,(Dx#,PH@ F2p{v&PA^:g2˗ !|Ʌ _ReggK瞛7o^׮]u6gyF~I6mڴ~]v'OBTUUMm'N7n\bbK/4~x!ڵkYy ]t1bĂ yyyK/ꫯ&$$4g6k,!ѣGWcG]BcL7~k6m4oo,YW^}ٸq N>_u]Oٽ{wCA䪪*OV?OdR#Xol6KAd&vFd@Sw4oq7)t7Ҋ+\˗/B\nذA>BkN:ۅ;vp{SWRmԇ*KOOB,[YĉB{/%%ET*KAz4G]O1߱ƨ*СäI6mڴk.!ƍ/Ԙ1c,Y;[ދyyy>`||q7lPTT4}֭[{{w^sɊ;Sa4 [#rdd%6l6{6"KAdw544Tۈ,ӈ hzΝ;7|+WJ)ͨQ|رcu:]~~=#xDŽ/K.~6"6 ]t)..3f…+)4;V JkL8رc.]zN:]w_vm^^޹s:w?cIIIgM#W_}UTTԮ]{fff}G#GN|hDk^j BB(%K;222**jРA[l^ڸqE"##{xb9z%,X0a„:=#{RBիWw]*O}fΜv̙3'OܱcG*Ɍ3~G}K.aaa&L8tЕ}̛7mJr޽#Fر/  5F3&%%%44[n/ʕ+̙s>͛?p\72LxػkΝO=;ꫯBvt:oߞ- <kjjG#`BHȡܵl6Nn듛*))[_y_{4: \-?߾x⧟~߼۸q-[v{^rrrޮ]^7ٳ"77w֬Yeee[ouȑӦMsjƍ[ĉ:u:rȚ5kcǎI󌌌{wB/%%e…JOO?qD۶m׬Y3|W7?22rʔ)줧?ΨQ|}}eǎ7nN8T*>`СW~ ???44GiYYY||낪Hjjj\S¥d25"K-B"#KAAAv]xu" ͑l^dɛoBCMf„ K.ݰaCNNp>|x۶m[lپ}bر!C>>}}{/ZmTTU555nC^/7 JRf9lXPDlB# ѶmیF㣏>ԭޚnݺK ";΂[n߾=??_(޽{Ϛ5kr˵Mo_Ӳ6mȧ555óYzFdDv-Hۑ d4"n|6mڵkll7r#V0͠A/^ˬVٳk׮;vTTTDDDW_۷oFFuĉeee'eeewq|ZUU%EnCo0kٳY7SNuۻnݺ搐 ٳ{={:t… YYYjvvv.]|||U~~~hhhff<)++O "d(Gmmf֮Ad&hD˻v]͚sUڵ+..>ࢺϯK.z?~|^ڴi󳲲SͦhR#gYzz}xxpI$ !fµY %[V)|lAdtҨ x78jԨǏWWWzSN޽{ffZŵcǎg}V>-//w:A䊊 kjj:u6!,,LL&[pXVfI]C9)//.fMV !:tcvmRE.[ !\ȕўx6"AdXoj ! ju "ӈ r-.fȑ#˖-FM6l233SHA丸8yRYYymMMMDDеY:DX,#lESkDWA8pi^ Bwssk ^;3**JRuuWk͛74ۻشi}}}IYYY-|FdV[o#rxx lBH׮M 24G OxYZZڌ3S]]92gΜ"oo&w;w2if;aYYY||뤢³NDV*V6,,L6Ԉl\ "ntJ򩧞? \5R877UV/u~{%$$<HssNAzu: "zRi4r#rCAdF䀀lמ7'.]t'N^5GƎ3ڵˋ?~|ٲes^M6$&&mD "!"""R#`B6"InAdih {kFdhN믟9s{9UVV>#)))SNs}p|/BFFFhhhǎ}ݻwK* o~jYϦ= ĦMꐅGtVUUyZ³Y "{6"HfY!ʍv]J$7!4"@5uիW?;vr* ^\\,PǏQe&L͕OO:uԩ/t^uɓڵ뮻|MJuykdWBy睗 4ogϞ{_رcsu:nq[&5"{z܈,fshhtڈl٤ fkr@#24_W.**5jvn*ŭZzϝ;7|)+/=ܡCFٳgW^ݫW/UDÇ-KAAAnnn׮]u:孑8p`>>>3f̸EXb̙-֭yla]]]EEkRو\SS:t:z^RIA䰰0in2܂RI7"DfC|M^^ѣ"_Ek֬BL8qܸq/][q˗W>>>] IDATƿlRSSo߾7|EgϞvkdCX,SNMKKg믟~'={BlٲW^RXVYYYWW rxxqM&S]]J2 5,[,@___DlM.L74wo~e˖{{G73g! "Orrr^y7(//߲e˖-[ "''gҥ| B!D=z;m4y5]v 2Dz{/B .lڴ_w !\ VmѢPj/// cʮf988X:v "\'~hDaÆmذAfk׮?ɓ'gϞ=hРk~}\믿^ƚoRȋ-=^o?>~>@G=ns reeJ+ "z!Ԉ,!"[, nor*⮻/,,|l3f̢E e <0k֬ٳgq]6==M6n󲲲0R)O*++=ER9<<`0l6CBBc*m6Ad@SգG}%&&y睹N;jx!{キ`.\lɓ'-ˉ'>3!ӧy={~&)??ڴiKZ3w'|vر~!1٫LOO߽{͛_|Eoop]v/ŹN "DDD u:"<۩S>}]Yk,XGY hڵ;vLKK|IV/҈ܢE N lD fDl4"n*]v=xy~O>d޼yC /upеBt}WwԩS/u?͝;711;B֬Y3|z_:w\߾}]'+ZmDDPׇ ! [944T:X,r(jJAd7)S?~o޽{7ܠj1cRSSKKKnW_Ba6D.))IHHpTTT4Y׫T*!` nAd 2պu+V8p 888++{ݹs7@JJJ׿vaӦM˗/߷o_)\̚5k{n///w ;Ϊ* jFdڈl6/DWlAd֭w}iӦʾ}0`Nx̘1۷_jӏ?#( o cݺuwO=1ڲ2Dtv3l4kkk#""rٳ9$$D:6R… uuuRYN$7!uw8p`ƍuuuOKK裏,\o[n:t-ܲaÆٳgM86O{G}DDBĸjBFdNR5"Kd*6-00J# 4{o~~ݻ322^|6mڼ?\sUUU-֭5͊+ ǎ+7_&&&0WKJJ|}}DlDmDlAdf !1Ad@sqw|ggϞ7n܊+t钑WTTx{kUfj_NOO߿?#Ђܴ8+V1ǧ mIII\\3gΌݻ%K w\ñe˖~E#F \zF׿ս{wo~8{c=ЂDIeeexxg\ "GDDzRZr#[l6{iD4Sݻw-..O򗿴iw\3g}݇~ C6(\}iiiݺukhAIIg9&&sV wzJe4r#l׈l٤&8#G9rdIIɊ+>ŋhܴiӺu.\8c Rٻw=zJƵs۷o߾ܹss6lE©9TUUYVkkk݂mDtB9ֈ"X,B)lلRfI&ku>uuuRf͚2eJXXJ$+wԩ|׬߿G?xvvv^(nz}Qppȑ#/Dو&**m*\w#r``&P܈p8.\@#2۽{ݻO0RZtSN իWVVV=233 ,ˡC۷gϞR'xB z{>R!%%% "!!A8ΆZCnC),5"Ks,f[#jdǏw8R(yǎK,y7III޽{xxEmm?}۷ѣJ2##'"|}Ν{.$&&& @t .x.pt:!J2rd2h)s,6MD KB2)`ٳgWWWtQ%T*oυ N]TT$]oFP(hrRRR\\\bbblllLLW&a2JJJϝ;WZZ*Ϟ=kۅR"<##t|뭷J1M սKt&&&+**=K ns^/7"]-?vmDnrAp3m߾}Z֭wϞ=ΝJR(9...!!A/}mٲub)---+++--UגhJ={VZ) >nVa_⋫lnAz'Z6 CTT<7Lb ~V>iD pk)++<(emR!_dddddd˖-寑QQQn&,8jFvJјfVZu1;;occc}||Phn.\hǏߘ%%%B rF :NjD6d2nZ:6rD>[AdODDDDDDJJJVUUIUUUr6Wќ?= q &EOuuuz^>u:ZuVFl6l6d2vӚk\۷;張>#h;w /III5MhhgnX׷kNa0d - AdpkF:ff t:Z-ZVb0jkkU*U@@R  OS)Rvg)j,g(bV_nHZ;<WWWכot*Ja4J<7ͮA䐐-,7!VR9|*Z.*+Y=:22! !jull⚚z?7"KVoob  -4iR/)))q "_p*h46"]M&܂DBl Wx)S$$$4* ree鬷 N w8&FdD >}zxx/|IWjȵfYRf)7";"-g;2AdݻlٲwyG6g#ŃȑnC^/7 B9lXNkٵY:ZlO<ĀFyIj4D.// n[h6tBJe4aaad2 ! "[, iA W9s.R!DBBPV[,n|#lBȧZVDݻwozמ;wNѦMayyyllbn0Zl6w "_Y%/0 ?x~&Lp9s&::ZKjDBxu:BRF҈DǂFdNѣF}|.'{ٶmۺ "k4Q_Y ???9m| t,'hy睼իWjP\\DVձns^.0 nAd,v{]]tL#2p]-[lڴi ,իeߤ %5"t:J%0Ad# ˍE!!D/ӟ{+OqqqRRjjz5MhhgnصYTsP(S"FdoG5q_Jc0˅ 5"{! [#dǒzVtдD@cǎz詧zwVgϞBj JBFFPf9)L#2p]8p`С999-R(WxbDccc=k4HϹNjdkYjAlD& \[˰anO>*Da嬰DVT*)1w݂f-"hDÇgggnz͚5W+{Y:d!Zw}uuuAd`0(Jyڈl6o-ȮVU:hZ" uuְuۢ" ryyylll/҈,=R#Dl2hР_^ ^-jDnL٭Yްkj "Wߺu C}WIIInCZ}AdNRĥ4" Uߎ1⡇Zt۽ Cuug#Z\ozgfl6)l0\fsAd*!KM1 | ݮVb1 &n˧ҚnZªP("""\TRR||3ōn&Lxg.\x-~c"!"##z^R.\`X\[#rpp4lr( ! Uj5Muu|hf3nKz 9詮NB6R ciTTTdddddd˖-]z?͛7nܸk.Fd\h:N.Fk#rPPNnb)---+++--Uեck]]5Ю]NH۳jVkۍFllԻvZTT~L&}T*UdddTT-[oժU|||BBB||gFT<W\?ڽQqqqttj!%? ;l2¤c,܈l۝N'AdsΕ|,'z}||bcccbb"##09ZJwGP(Zh:q;mK EVlVVMJJ ƛz_-//?>s̞={r```۶mGrrrΝSRRڶmK:Yt/سgoƳuZ(..0`PVGFF{h4sVT*}}}݂VN"[,Fd 2xMUU;.((8~ѣG+**:tHNN߿O?-]bz]ԵСC|)))R4KOkK>Sׯ9sɓ[8))mX^^Z6""Ba0|}}崱hB4Ԉ, [VAbOcǕBΝ;{ゥ;wXӺu֭[gggΫkAAc6oެV!!!R4955ňq5>|2 [lׯu{_P]]ݶm[yyyy\\\TWWDt*JJ#CCCS 2\'.\8z}۷GJ㴴/--sIIIĎȾ}Wh4)|ر-[ !2ӽ{p)77wʔ)z/bbb[ !D9rDX|+"HmjN0aѢE/ܹs.Dn߾}h4dϹV5l2KٵbHk jkk>>))))))#FBTVVn߾}۶mׯw %oRGUVVrJo! "kZPYDj r@@@@@tD 2\f۶m[^^KKK[hѷo3f 0 --MPx{ EGG?CR浬l۶m۶m[fܹsCBB!Cx3g׮]ۡCoH={nr!DA . *J[j%M&SXX|j6k#B" CN}ѡCfeez{whF}G}TQXXnݺ 6|СC7Z YQXXЦMU% ZBR6";N"5";N "TAAK/O}+V:th@@w\Yf9sf׮]sϼy󒒒y{w71ct_ݱcMգm۶n҄z/h4!!!R+),5" Fd d)vneZ=$D\o_~iii7o2eʹs֭[J5@SP(ϟm6[NN-VWW{{w7owa ,駟zM5۷6Ԉ\]]Y,tB\[[P#lBHAd"h43g&''?#111ǎ4iR\\\?ƍO>=bĈ9snz?vCln6mΝk/.  ",5"T*^/hLj !"df_NJJ?Q}z{_u4k֬>`]v6lݻ/oڸqcffkSPP+HYӧo<|BBB4Ԉj h\Fd@3h&M_L>n(BP( *((駟>tP^^^eeeϞ=#Gx{_w}׳g￿u?T>t:]eeg#rUUno*::޻T*!ԈT*L&Shhtو,j& 2w 3fknŊ+**8qznB:t]tG}ԩS5t:WZum 8uUUUTT\FDD߂nFd)lZiD܄VZթSsN8p̘1M40\; VZu^{d2y{SׄOt2bĈÇiii%+,,KNNv !.҈\oYIAd >g3If+m999C{~̙%KXz= &Lpĉ^{NLL/+?[Mo߾vA9s&**C-y{{['UWW>R|}}uuunnn!{AdFdl~7U*վ}>à .?2dHhhh@@aZ80(((((h׿fDL&,X !4… vS|İɓ'WWWb7,,3n:rȘoo޽{/]d2y.OX(_3f̈3f\UUu}%%%-^_w3fr7l>͟~ܹs<==&O|6>W_\)T* R*/rllԩSuvСM6,ݺuk}^l`#r]]ul6zG8zSȖFd)Ȭ8w܌3vs= +&Lo[4fyĈ۶mk14rȍ7J)UY{=iTP?+)6m9r-2gΜ~2!&&&++Kj6&Yl ͞=wm1zU{+ϛ77߼ܹsBFӳgBܹsz-ۼg̲׿_y#Fؼ ZPO?4}t!ć~xw:w1&++kٲe֭ _?IRأGqɬYN>O?>EJo۶mذa-ή]??3gHu:_עEj⫯8qbSS\.:th׮]-Q}_~eC ?o=<ѣG9`//͛7_Ν;׭[?iҤC?~rD:ww߭\ܹs+V ={ʕ+۸fKlE7߼~~~oc*++?쳤#GmN^x8p JJJZ'z)ټditժU[믯w-V^/={2dbٲeO?T1k֬Gjڢn؇nu]>ӃmظaÆQF BB$$$>^ZZaf#JBh4oooqN'> Ҷ`puuBv9,믿>bĈA8p %%֬Y#>}o=d__AI|b֬YZ^RRRVXCY,yI&>BGvq{GFF.XGB|wy/Rk6wܞ={{ッbm΂ ׿Zm+vĉҨTu-w-x'x":::33駟Bdgg;>eB5k|r|„ {؇X_0aK.YK.ƍh4}]vvٳ===+:>>PYYYxxͳj%c9j htww "ڷ{G}駿kKlz#6Lb}pڴiUzu҆%`ѣG[\2A1n8!DAA%߅ɓB~EP( \>tP!ĩS~_emֿ/̙3Frbmm!Z["߅ΠAOyg}v)uw\C_=ߺuݻ^WWؠk>#%%%?1cLnn^# rUU߈'kDĸ u#ucǎz7tp.c=Ͽ=L&sr.KA!By!E3-G Nijjjjjjnnnnn6Eu&&&^e577;ɓuuumKg:"܋=zڻwo^^+2l0WW7߼_ wqݻKz믿>]v>䓖RnnGΝ[w$5Z l>64"ڥW_}uʕW1cb ?>f!DRRe>ƍ-ׯBJ}B-Z1DT!իͶ\R/xYuIII2>tko2 my*޽?m۶ !lr ׿wQVV6a„z.^JNNNOO߰aìYN>}{&b/JnnnnZ=?l6;"۬CB:oooːIAi' 7 6n6Gm۶C#GܸqL&BH_r<"Ξ=wޱ̉ʒM9f__|E<#oF !,Y / %zǗǎ`gϞ sy-.Жg歵8hYc]Zذa?{mڴ7 ! '|r g// AH!מSO=xŋ]nݺuݻwnݺ1ӧ\.Hrsse2F҈{'VWW>R\\\<==u:eHzzzZvm6"744477]9Ad_}#51CC fK,Ynݼy. ;vرcۋ=<< lٲ[nWv7SNYDž-ʺvjjL&3>>>!Neٵ" ___!dBD6m=zܤ|||iӦ "WTTرcΝ;vS(&M6li˳Y,(--̴9T[[k3RFaDj-Ҷ` vv 2܌vݯ_?WWWg/za:o6C YhlvPԔ}w:tȘ0a `8q"##PiiiDD!RP3 IDATzHVKA人:q h4Jc(hD#ΝKJJr*Znt:]mmm@@/8|F['N8`~yzz:kl>y[ip'VWW !5"߃pNgͲ" Y "ӈ h7jjjl&\3RneeRZ7"kZ///i 2] ZW8KyyL&޻w^zY7*9qDxxo롲2WWWck>>>:th=RuuuAd^/nDvwwNjjG}U7{c^Mɓ6JKKd2juBZ-%Zu:e[XoFdn8Ç/..>qℳrsdb|.v 2̺Wl޲eÝɓ'{as,<<މ555*O "kZKE#`yQFFFtto:5h g/?M4 朜uЈl "k4ooo!V%m[fd" h … ?rg.v !Nz 3{饗 D Z Fd{Az^+h4֍EY*H6LfY FBq7<&5cƌ 8{!ڵr#K:{!7'Od2AKhDVB:fFd 4":dAnZ_|78{-7/,,,))iMMM֣͟~ܹs<==&O|iT&dk?l6??Mr?pffrs9~xΝ-`k]twnuuu@@@ZV^,7l4Rd2 pÇϘ1ѣsCh4}),,B?>''GXOXpU,O>}g}f6/RRRbcctR__K#Yvbԩd0a+t~TCMMM6U*B IAdFdL& ӈ [r˘1cJKKʪU W\yܹ+V|>S!ĬY=jnM5mL$;v`0wso=###,X裏ZO0`"??͚5\.0až={wqەJC.]کS%K\5k!~Bq jjj6m7|nݺ$g/SWWo/\\\jsFa3Ve2m/o݈!m[7"LDvqNk׮aÆ Gb2 !ԩS~h#^?mڴ͛7_rrnFYYYfA922R.[UU%U*ݷDviDoaaa;w ۷MB^ݛ+ 6{7/<_^Rr-T\\<`qͤȑ#:uҥ⨨({VWW Ȗq]]] L&D6B)LڵkԨQcǎ}-66VqFˑ׷ֽ{|۶mBlٲqBnݺ]ڜ5kꐯ;vddd_~eРA^رczd6Gr!R٩S'!^ohh"t:wwwK˲`prwwO-[<ٳ^Q;vw !/_믗ZjŊ222V^g0rss׮]+o駟9sFڵg?bĈ#\\\&O|7a4-Z4t/^Mѣ)))F% !;uP(ZJeZ-hшemш,nFdg/pd>aæNګW_~y̙6wpl~aaaϟ/3gomp3f8kڴi҆j6]hq0##瞻9O>qȑ!!!7gΜYRRvZNpĉyٛP\\900搥U5oAd-%''8p`޼yӧO0+.{oHHH\\ܢEVZe=_3gNRRgJJܹs׭[ꫯ9sfBB{DDDffᄏo>77#YfbԩwB|n ;~8)SL&SjjZV \SSs AdNݲ~4"lСË/8eʔGySLyZPPg}f}ĺ8-----޹%{~.رcm oK/~"ǎsuuMLL9ZTT$ҥӫl)da'iٵ"z rppEFd]?~{E:{Qu>xlRו-..d>hQ]]m҈,˭+ZFd`ݎ~ ".`ܸqYYYoubbb/^\YYEׅ>())i'N_x%cko8((AAYT !j\AdNgDnD& )3f,^>7oމ'.ijjj/_ޭ[Yf˗/pВhoOLLܾ} /PRR;ΰ~]ڛYzAdR)Fd@#2%ǎ?dgg5ߏ{=)6o|}͛7ƍ ,h<2ބΝ;B8" ;Ad{7+V޽G?xPPНwzgDfyϞ=f ;vV]fRꫯF)l7~嗾}5ϟwЈ\QQ! i=T[[+7"k4.& {a111 ,عsgccUnnuֹsfgg'NG^TWWބeoBeeDV*2O "{yyIfh4 "]]]7\ o̙3g|+w޸qiyŊ ]ۿ"##ބb!Fd///KؚRQ(N[fӲ+]6"D\ݺu[tҥK+++n_N>!55ucƌIKKd^&njeeeRhLKK`0ݰa÷~|rB2h )FxС]vܹs޽Zoo֘1cœ@\*رc/v0K.&8hD"[qFd.ѣG-:p޽{o߾rءCvmC qT 8PWWk.GرcQQQ Z|СCccc@\u{mnn`#rLL ~ZJh4L "{xx\1cƌ3FQRRsΝ;wO\.OJJJOOHOOOIIquuuzq2gΜ9tÇ:ozx`={^&]v%$$9S\\8\YYٯ_?CJ2::ZVAdV+v[z=\I<< (--y3grytt-2o޼ g/ם={4559nD...B8"455]ZS&I:N@B !AdͭO>}v N8sĉ;v;R/22R&vͩˇ fٳgΜɑbǍR899yرR<11ђٰaCzzzXX9EEE "22ބJ!DHHHfZ-jL&j4]NgiG0AdIPt޽{w}tٳh]V^]WW'R(" Z-J֮]tILL?~;NHH ve67nC9V\\֡C{***6jussKPXFZ ^4"p}R(ݺu֭-kjj̫~={ݻ;vCCC###CBB"##`9ETUUu s㚚iNhhOOO౱rܹ رc%%%Fr<8**:R*BKzTxyyYv[7"[ Dӭ655;wR{ܹI)C!L&  Kpuuu=9l6JKKKKKϟ?/}ꍅ6`)SXZ &p5lܸ1<?օ6!쵞K***wnsSNҶZ֭hFd^$m d2 2V ԩS2egxxx85w܈lDnшh,֍]npXvmΝ r2C "[7"[zuKnD>{ Gd bccLh4A#}QAd!mӈ 8߆  Oޖ "WVV !4"kZAdFd3{n„ m_PP`D 9Z[[+M&d  ƛ!k۶mYYY2Yן?k׮TTT64" !Z,Ad^!mFFd{aRRR2ٳf966J___777 ZDh4BK#djll_~ǎ{iL8\QQbsQ6u:^#r"ӈ vrΜ9ӦM߿O)(( 4ۻlpp!Je6؈"l݈l2"N`2^WWe˖8s*++5"+J!%,|||,m"ӈ 8ACC=o_> '>}{8hDVAd˨Fn ^ F77/zCOCCĉ;wmK^YjDBj___QV)ɤSIssC=?l޼9%%5ŗӈT*]\\ FD  ֯_?x=q6$$R:DdҮ^' \ka„ 333/ Oݺus0l6kDDر\.J& \ f?S޽/"yyyZb*++U IDATJSNv VmD fdjAd\XMM͈#mv)d!ӧwxD 98ܺ: d2:L#2wEEEÇ7wNHHK>}:..񜊊 777??? "{yyYvMMMB(hAdp]_۩SÇ_f Y9((hmm}QA!5"D~С[l ̫iڲ 6"WVV`# ӈ\F@iM466^\\\c.unNA&Rfs/2P(|||.w}e6_z%Kw}~_3??l6_"88%z "ZԤR5dzd2I)aJԤVZ%UuuuMMMBR)hjjBt:ܔWr\& D|___B'e<<<<<<||| ENry <Zm2ZPWW'k(JMNzVi7k@z|EzUHo˾+SyzzoURWW7y~wޙ9s敺3g ELLim " iAdYވlD"jg! n@&wR8LRigVt:V8 !O&Y:tBtQf1)msҩj7٬RRZz \MMȵ5H5ooo___uSpuu;`0TWWWTTT*JVJed2ټNf!Eu]\\,RV"q{Fx/6+-F-/Gh,hzYV?^VFN'}8???______N:Y;wCnn7|3z+x'O^W^6 N BH_sN +~iDvkXZ[kRaaaҶDJMI]R[T)EʜrS*~"kJR2jK[TJn)ɧh]Z^^^ o%(((((;h4?UUUտj[mJo-RgY;=Fc딶Z...6`l\500P %KAAA!!!vxx L±m۶=AAAOJJi5l ! Gq!/Fvw&AdpPTRLʄUVVVUUUUU?^:XUUloוtfˋ:vرcG__:V[jjjynnБr)&%ՂCBB!t:]iiiEEEiiZ^FiW+˝x;-Kx;>blVWWWVVJww!}m:аhѢ7x//|5B999#Fp<^FoMh/,5"Dh&ܹszLP))%PR'<<3FJЫ*+++**~7o%aaaQQQaaa7"+EEEF ˃jTTT~###CBB‚,q@\KޑKe)S^RRRYYYZZza)_n2;w򻘘i#""CpUO:uoٳC455.\񴊊f{HA@!RBX#AdAgIJi\. biiiFrҿh'L&Fܜ)v>e)ٹsgg/:uԻw޽{[,..*.~L痚ֻwݻVZhllڷoWXX(ٳwݫW[n%>>Ņ ~r$+++++رc6mZbEcc4}`..?aݺu&Mfӷo N`900PV*-5BzFdpC1ͧN:x;>zVuuuѣGJJwߝҳgO___gp;rDVgee9rѣ?oQ__흒ڷo߄'p"V?߿޽j}2eʭڧOKnDFFFFF9R5G_8zg}VPn[̇.ӹsϟN.((OOOw 7C۷o߿SSSrr:e)O_8ڝ;ZW&KYF%$$yC2dMqժU=\xxk򺺺 ,++sɢ8i[TZj4Ad^o)K6LuMܹs޽{9t`\tifffrr21u!%%%%%Emnn>q_jբE3` 4Ҁw7nܾ};T*Uttw9o޼o Ə?~x!D}}}o?2[o;>}Xpaӆ /^\PPx'|گB/Z 28wwQI=! @@DD@е kED]u-kQH`aHGjReI2Nڄ6Ix>:>gLrp]yOίqƝ;w%''?tu@deeջw޽{Ϟ=[qʕ}߿Æ È#&N8a~ AY&%%nnn#G|wFݽ{wK vvvÆ 6lo]^^sm۶}zhh>}Xv;p̙37mjJN<0^_\\lfxoUUJt ڸqMRSS]]]njt &Y:ӵkYf͚5KQXXiӦ_uܹ?xBB„ &NxmA_eeO?{ngg;ccǎtixzzN:uԩB'N|=z1c_HKhy'N7oۧMv٨(s޽{: ^WT;"k4aDh4NNNr'"*t.]z""" 2tm۶?y衇H!]t֭C 駟ح[.hډ'xg}gEEE+V4i)d@{eff:thĉ}]tt_YFffh8zjN2'\PP h.\^^n0;"{zz0 "w׮];bĈȯjʔ)-5jN@PvΊi+{{ѣG''Njmv'IYYٝw9mڴA}g111. _?wyԩSxp~zLNN^t'|rȑoƕ\M6 !}QwwwKb~޽EEE 7otEӡC&MԫW_~jӧ{iNmU*NNNRɎUUUݔ["t::"̙3?|pKWd~acƌٱc/2d!Ckҷ=3III?֭[XX{gZ!/bttt@@YJKKMThxA&ܼy]]]oԘyrLVz+`kkk2lѢE rssKJJ矍Ouѿo={trr 4iͬI- sJ*..{_|zIs'7 +W={v>}###g͚uСkޛ[n+Vؾ}9KW);;{񾾾g޿c/^8tPNwر'|&%%%3g y饗zɀV+-knxNVXgc[}LpcZVZ@~;;-]εׯ4iҠA\rÇO4=P?uT޽YPPrGdFmJ@Gpen-//ҵ͔)SL, c65~x +my֩w}!rҖ'|x@xxxUU"7xJLL1V\嘬Ι3Ǥ0i_6mӦM&S(w6ƕz+Z-k׮ƻw7Փ?-Q/ ;;ҵٵkcocc#ҥˋ/x&EDD~*&iʰ0ٳgjXi4kgxm[i5)F"?[WMhG_}U;vXo+aÆ5W₂{=sFvm>ls{_zDiyŊ&ݥOAH'''˫ޟ}ܣGzۧ<Yfmٲn-[n:'''xbΝ*3f/ܲe>zĉgyeӦM_I~ӧQF>}Z"عs/--Zdf M?_|dɒ'OO111'NxͿ'Wq9&V^+77SY~￟gϞÇ !>#y!C6lؐ[]]}̙744W.3VPҢE\(S-Znͽ|ʕB{,55:;;{ڵ~U\mٲ{t-EEEq||O?} 1/N-Tf>˗/˓vZ?VZf2ǚ3d]Bso|gC Vx˥գG6O> ,H}0)dccF)o7n߾-1Y}w<… M :thڴiݻwwpp#^^^TnR9ՒM)9RQF=|MNN b˖-.1i޽믿^QQdK܊>}49˳959ǚ3>>Jɓ{7&&&!{r9^occx^ )mIKK,++D&L669& Z%/խލΝ;%%%>>@ IDAT>Җz[[[Ba0[l?~lq%܊VKTTŋSo9'?yO>yyɓa`*-1 {NMMMMM)]Ɠ4?g s+-krmukumrq+ͱ-?z۷a,X`BR֭[O?ܹGcbb,]Uzw?+W:wǎ#Fhrabcc.]*;w<(-,, سgyy/ƍ{9iyBiڳgNzQcuuu...m۶}k]ZZbݻw|7W}r+O8qg}VڲqF1Mѣ / /8qO>믿~[ʩS.^N8ҵ#ߛ钒ᄅS:;;;wn7ƶ8~x]v+xNVW4G;c{L^]b,ɓs̱t!Z^v5kon0&Of͚QF9::Zѣ<9#AdJi <]`ҤI䓏?XqwJƏ/xۗ`>`!W_}U\\,o|'g^pΝ;KJJ***Na['xUUUҀǏ/lrMF,45܍Ʒx Z6h$J%edRBGGGy|soD'őm'JKK 흖fZڵKɷrww~w^xpaaaPPѣ5/LaaaƿR`W7ҲfŖgV'V+oҝx7{u-Xʂ 6l`RSS}[[[ٍ1⣏>̴`I7ԯP(+wމhn3ߤė^zxu򖰰{OZ>y… Bݻwr:"1DEEoΝׯar̥P(֯_W_%''{{{3fҮ~?vy{{'''7nl.}eO?ѣ̙3>,|bbڵkڿ _29_׼y"##M:qĬYzݧO{.55 &L~iӦyyy #={vٲeӧO9/ZZZo߾ݻwٳGۿ &7.115}nIIIf lnqGdPYYٸ#j0Z<k4! mv=L<믿裏>ɓ'ϙ3gذar~T۶m۶md -a׮]K.]~}PPG}_GvtP/~衇z3-]uСgs[&*77>o<<A3 O~~~aaa߾}_PP·(J[[[)j\QQa0<==TUUZmCCqYPHcN':~tHO=TffUʆ[oeddX4\ \]]oڵ =z5jTEEŚ5k233̙C էO}mݺU 2˗/t]hX:p̙3#""~> ##^)䢢~߾뮻BCC}}},[fΜ9{)//߽{o9z[<,ػwjKz+U*jZ"j4GGGp#rGfԩSNMMM]bW_}kcɓ&Mۭ-]fѣ:tСC-w!!!3f̸-]?F=zcǎ-^GygN:cƌCvćK͟c͹ɝxbJJJJJٳgWX1e[[k9gvvvZZٳg/\ps)J!DpppRRc=u.ٿbbu:]Adoooy¤#IZj #2Ad:>`Ν?5k>#oo &qcǎ,j˖-6lشiR 0a>b=۷w}`իW\rԨQ]tg̘q_@Gwe)|ĉ3gZW^m:`Ͽ|rVV˗ϝ;w)=eʔxs5;}Fa!A:"GUZh4NNN2@bee5jԨQF !N:믿nܸqƌ111C 2d,])pK(**ڻw={s90`sN0!..mO<?ʕ+,Y=|cǎ;6$$57[uuΝ;lٲm۶tӧ/Yd-dKJJ /ʕ+B[[ېqDyyuttt>}Νۿ}YR&qvv7nܸq;v8x֭[,Xv*% `z544:tЙ3g띝¤T9Bo߾d_rEnx$R)JjFWVVQl̊W[ݠA $j4'O?~;vO"##z--_- tzUUUiii.\8pҥZ[[>}u]}ݻ*ne{+?8p7|SRFEEݻwqqq.BӧO:uɓN:}teem>} /4(((ejkk>,MJf1?\^^dGd777ygZ-͠ : ''8PZ9iiiiiiWʪB={FDD8]}}+W233/^x .!lll"""'Nسgϸ8{{{K k...#G9r`0?ѣO>y >>>R4966622Ҹ(pfff;wNgff644899Ϙ1#11o߾ $z۳gZ&"35UUU>>>j: F':V ~?h}RRRRR6==] |UUUB]GDDDDD r8])--ʒɑnnn={1b֭:0++XyKaa}vǎK,BGEEEFFJ### Eaa .^xEC&/_&{G wzڲeKTTTXX\reeeB~~~*++AdZmDD;;㍅r3++ŋ[l5 B77]I j:'''???///77ʕ+/_ʒBBVVVRt~ذaR><<ҵ1cպL9-zu֕!"""zjDH HZ˗t4iwrrٳgdd̙3尻qZ͛ǍgxVT*[" JٳA?u:OՓd4X[[+=rvvׯ__XXX[[+qwwsAAA~~~ĔaZ]\\\TTTRR"G rss***avvvAAAcƌvh'lmm{i+//[fdd:t(%%PfN)(S___\\\PPo;...Ƹ 82!!! ²&9sG}d! -JߎZ "j]:"4N lohh(**;>|877WmHP_.]|||^^^ hڲ’)s\ZZ*oh4xwwwm&'p tPvmfQәOO<~] ҥKPP_ppp.]-q50V-))+**-.. 7Kʠ4{#^^^۶msrr:t!Z"+ gLR5l 2@( r>}5n[ZZZTTT\\\RRqiPY NCS*JdKYYV ѣM6E888DEEEEElrbEEEz^igg'}EЋ,WvKP%%%JǽrIIZ{yy)666 8_lkAe˖C:88HNNc 3Rtwwmyy&cLȾ>D킳sxxxxxx c˥RjVVֱcǤ-UUUGY[[ãewww777kkkOOO(;;;rNjummmee^///SyyZU*UUUZQQ!e|6WWWyttI׷K.&1[[& R)*=P._,E`+**qww rss0foolggwS.kjj4MeeN& ixARTz RS}||"##eu";v+m:ʕ+ J[Zt:qGd{{{{{{yKuuqe N?@ivIuuuMvPUTEEEr@VV5bkknmmaccjoo/IЉ'CPXYY !lmm/vpphI|ꪫܥju:NQQQa0T*B !0BTUU׫T*^_QQ!Nk#""ғ^^^mX:VVVRW݄JrJe-,,4gj_]zjxxx;;;K C4O/Dzp4yLnQKZDHmmԍXR544H~EENSUUU555ғ5Ii{zz^Ծn;v(;MG3"KM5lYQ]]*jCnZ|qAd()UQQU*ҕ-shU3%(//FJPHB h.r-MFK66vvvfh4RTFN=kjj233ş `9+}>D4M&M<,Ҵ"}޾qZj-4gʕ ֭[jSYKqGAd9y\WWWWW6X; IDAT'wD& yHZs:._9vBk&XL5&eńR/6 hٌ{N)(7!5*6TMؗ?sV]nosrrZPTTԥKiɎUUU&A䪪* ZFftKJ4޸͛7k4ӧV;"JMvDpww7R]]\YuV.>RRR ߦ*+++++["J*$&җcȇhZ@R6nx퐯\"h#\^^nY4 "t:"Dڋ%K888w}m=0''GѵkTWWk49RZ "WWW !Z";88 2:;\sXaDvqq5j٤#u)޲"X`>?T(봭KJJҪR4i,j5,-t?|dɒ'|:VYPArooo1mLϟG}>=yaaa}}}ղ "-jZDny-X`ҥ=,rk׮-)..WJAdr.Y988\cAdn,PQQQSShjkk+**t:VmZ__/VkkkUiLsbmmmT(򪓓uUt?{luqq9r8CCCCCCn]Vo޼H,d+++ ]n;'' n΋ԴDVTrGd)m< ZB "` G9rHjjٳgKJJ...ѱƍ47={ܹsҿ[n-,,B899EGG/)))>>BXv?r^7++KrYTR)uDBY988\k( ''ѣRѣ*:::o߾ǏիWtt4NkɹǏYFVK~X[[[lI]]ܹs/^taÆ 6,44%~.]8p >>"5deeINNvss\/gdd/[{%y̘1KKK/_3o޼nݺ=khhtJCCÿM6/-BBzsJJaʸ#VZVp-ŷtD999k֬:dkk;|ŋO81$$ҥ{ڴiӦMS[nݸqU>ӈ;sW(.c;r?~̙^z_l?/_, kyXIIIU*I8he)~õ 8pm?1c9;;O2eʔ)6lXf… g̘1cƌKTUUφ rHKW?.]V&MjZDg "[Y@g;3$$䥗^ )SBFGaee5`wyҥKG:uʕ+.]R,]`y愄n…[nmo)d!DFFFnZ-J:"A 2 4iRee~[TTrɓ'[:?|ݻ^|Š|.];}qƍp֖. ݻwoyRrYi48pWi޽3foz行;wv@'P( w,^ŋ JJJu:k_233O_YYykZfD.))BJeeeB&;"DVrcx:"n]?cBB!C ˗/. \]]~=z4.. yוJK{,::رc)))8pjICCCFFFnZV\\,;"KAdoooa*xKuu<YʯDZjjj˞={>qqqw6mKP( h;''穧ZtiXX?o,?[r+rԩiӦ|FcNGdrs+**;"  UV駟=ztZZ-]WEFhWwW_ځZ***_t]7Omm_|ѣGgy殻tkf=KOOBJRP CUUƖֶ~GЊ͛7'&&w}_|EDD{.33ҥKuYKѼ?̙3sss?3K鮮-+((0SVVb!4 ZqYV5ə>}qBCCO>>^aaa%VofN7G}gZjլYÇ(<<|Æ KJJ/7n칪()))""m7";z dBoDX,kZg B\r婧:to/׫WϹ<ׯw(l66dȐ۷;5t͛7K)Uɓ/_.zر>?|ɯ:tPtԩS !<}t~;n8!|zꄄj~$g`#2/:9rd̙NO!Xb^^^SLٵkWAA={(X|}||f̘q'N*j˖-_}MٳvGytgΜ+B]v9|/2?<(((%%eY!~e˖-^ԩS}TTԉ'̙S={իW+ƍW^5LgϞ}7l6o-`Ԑj-Tʕ+̙w޾} !Ν{ou͚5Bɓ'm6[RRR-nڙ$oWFaK:^vlD{YQQє)SJaa޽{ !NZXiZ/([ztSNVNR~,Be'..Njo|ɬYzs?gmҤɨQn#G:td2;vÇ'No8pcƌquuuh)99YqӍeeer%//F䂂???lv-[>֙B 0 !!a޽ƍs8EP/1A9OP)++G!JJJJnnne..)------+++++o˯\6mmy睲#F;w`0IaB忱@B~ر?䓁zxx//ծugǎ~gSѸ|.]t|IFF7|ӣGgvG\pAR5nmF$Wt:] ^ www9m,j_v^뗙{]:{+z IDATGRvŊ{g_oQQQ7o_oذAѼysح[7!޽{>Ԟd2;w,Xr"UMHHB̛7/**GP|w#nPo2*V%-[|Wo~A!֭[p;oƍAqd˖-Ǐ yWϓ'ON6aoY-ؓDvFYb! JJJ}={DFF:{ `߾}W\7oܹs. B|rqԩBiӦ}gvNZh&޶BO?ҥK.]zϟoQFUȲ-T ՘+W&&&ZքիW !p;^z;jԨ˗/;qv>$$dfɒ%_}.^تUi4J%G]f6 6"Fb˒k; 1ccbb=Kl6ȑ#7mTnن }vyfiݩ>,wӊt2eҥKӧOK'NtgΜ^XhB~{*+?Ʒݴiӈ#n0hСCjj0u/BnBe ͡XZ^ۗ_~jY;d2ݻȑ#wO:?Y&55CǏCCCNm֬Y7nVZ(333CCC׫W/cǺrw駟Μ9#I!~nZ﫶c#2ܻݻ`e˖BPlذ/  8pΝ;nݺtR魀˗۱p—^ze˖AAA?ÇŨ;w駟}||u6o޼ٳg;\>{^{-22RJ8qbܸq:tPT]ty饗Npкutj4`ӠAmnDnٲ|4L "j_q;vt 5vP( 믿Ξ=9r$>>~޽7LAAA}OجY;9oq؛zjD thX,EEEFZVke1 ^tĉ=z8{ ֳg%K]7ϙs8pѣVAqqqs۷o6mufx3nܸضm* ']eff֭[ߴS!7"%B___`"L&9lZNwZBdff^|ɓ'N8qĹs犋wOt޽{ wQeBVZݴ3++-00PvU*b!Je! \gOӲݻwlу~7Ý=]t̙ ݴ3++aÆ...r%''>,)!ܠADf#2 IMMu=-55ڵkf͜==̙3ڵLF ybYAAXa4]KL&Z]ns_XXA- BPTܡ ;vׯߘ1cH!5gϞm߾}e:5db6oDj?)YN*KJeUf! Z,;v8{e6w1p@grlgϞF䬬,ȹB 7"W* "{{{K 2Xf ;{._Te˖M0كCRSSM&S7"srrDEAd^k_1 BZ-WL&DZ_޿={=p1 ,0aB@@g9sFPm۶2YYYAAAQڈЦو <C~5gR7"'fYPQǨIܜ=i.]ڮ]_}Ξ1ݺuKMMBh4 .H{Re/~ҥK.}6>SN͛70`{{zP-vڵhѢ˗\q9s}j6-88X_,(((hذ}`0T* \jU*U|pjҤɲe-ZW_9{f!!!OOO'5k!&O|Itʕ~W^һ6SNYs-Xcǎz%&&;vԨQ'Nt,gV2hA zFdhXn} 2ƍoN6_~q,u/^|3f7 !~޷o_?;w?r{W~+Ww"--mȐ!-ZXj1wGQQQBBBv*Ӝ%_Y۠A&I!oDX,ui#8ٿGyd͚5cƌq8u˗>\9r믿.g͚jwܹsN!B9r_Pɏ.Btڵk׮o\ܾ~yzznذ.EQkoD+999z} ZfYV+uB믧L/X+:vO>dŋ|'xBvzp>qE'OW^ddde~M999; +FdJ% "B`zkƌ'N,,,tD^ͅ7o+6l(ֲeW_}uBlݺʟrJ!DDD]-YjU\\\dd^V]Gر{e5MPP}%77|l6;F}a#bK "k֬Y֭[n]\\ܥK=N6j(!ļy.\`?ܾ!&&fʕV5!!aBJ޿Gk֬IJJ2ot!CT{bI~;{{ױcǺtR 7"zk_4jZ>"ץnP=]v&Lܹ~]P8{Zi+VHMM>}ӅSN/䆣G>3W= kl׻C:PywUsȑ[n̘1VZZzɧz&66V>WDB߈ܤIh2 >Zu)Fd_jj pygOT+>|kԨQdd+`cǎM:5**ۻSNӦM[v~Z=zkݺR ݻe8P^j2 /r^ZhqiRN`6+Yӕ:!6" h6J#ETVj&6";C :ujN^xYfGP 4+tҥK]{]Ȓ˗ߝTf^{%K< ?^^mVfiڠ +(Y "߈DVTbOFd@bbbŋ/...vP@͵{ݻ?ӣFJHH4i)رc۷LN+**߈#(Y+ Ad-VWf" .{.99{72g,'N:th~Μ9dgsر, w%p؈YPPf_4 jZ>߈T*ou 2&jҥ}?M6+W,**r\۷oС[neee'NdF "9zariij߈l67"D܋ .N2EfjΞ pUVw}{6{PK.L333===wZ4hР|gAA}d2lV׭=@ Fp ZlbŊG}tѢEM6}O:칀$77w͚58qbHH޽{!C8{.\=<<ڶm[W* XS; BVɤRb! 5nxܹW^]h;uԣGŋ:{4(**ڸq#<ҸqO0!99ݻGM߿?::ӳlDF! 6"[,RYk8*4iҩSߩSwy'$$dĈ?jut@8xiBBBFm6?iӦ ^zUիaaaoDijӳgϥKlٲ駟>|u늋=PǏ>}zXXX^?쳜_Gn]8^?sLϞ=+իW`0M&J^եnPG0`sWZ#1bĈ}Wgnɓ7o޸q#Gƍ7a„;:{.TÇKKKoi#rFFC6"+mDX,B6"p] 6>}cΜ93>>>>/)) B={lժE-ٳeee-[1cF>}œ= }FEEU WWW"mD.XtBFZ׮]+++STj !J=F Fd*jBLKkDGGw5&&&&&&<<&?z#G9rѫW !"##^y>}:{Fm۶mӧ-%CFF?///y,??_q&IFdJ5hРA !V'tM͛gcbb\rPPGsǏ㤤$!DFbbb}YIÆ =&ps̹^0;77:d Fd`D6͢\qJgϞ={zرcRtժU"44m۶ڵkӦשS(**JLL<ϝ;wR__߮]3FJ7iٓ8xl^m_^Yӹ8h46jH>JAdJ%V-mh"j__~O:fgg9rԩSgϞݵkג%K aaaQQQm۶~iϩ%$$\pA;w.))ťYfQQQÇ9sfLLL˖- EMcǎ͛GDDU#F4hР|g~~Zvuu/FZ-M&Fd//[# 6l8lذaÆI˗/={VJٳgҥ׮]BFFF7o\iNd2._"LJJJNN.))qqq 1b,oӦMqxKeffDtE#AdRyK#puk˖-[l9j(RZZ"ESRRvޝ^RR"v&7o޼iӦ*ʩQgedd999RCÆ _ѣ۴i#ŎXjwMNNgΜyKWegg;sss[hQ9??~EX>,_jֱ$=Ad@Cⴴ9 {.77WjPTaaa5 ~J?Xzhڌ FsUV~jZMTJnݺ=cr8Ѯ]\]]KWedd!BCC999+D6Lґ 2{-75 )))YYYsLV[VV&7l0 _{뎳X,:.-Q9˙ IDAT СCpp;Yp/ؼy}ݧVo*)b_iРA|f3Lj6bcK " juǎ;vXl)k+MOOb:XwsssH'{xxիWKR~d2Y,Bh6rXzaZoU~@1۴i"CBBXl^~'|r/-.**u:]DD疕9lDv"{=!///77WN/,caab1ұ2U^=yoii`6####>ۿpqq̝غukaaرco&MWׯo_1B lVTjDVjuxxx/vjs򱤤D;\rjP(䨱R֮]{W7nl_B4lذ|Ns"K Dv؈\ǖD <=====ndڲe˼ypmZZZtt}E!7WP>\xtMjt6m***3fL-Yxzz:,B ?$M&JVmD& :b͚5qqqzaIIIVVV&MZQFzfs؈l0_7"{yy`5K~uUڌR "t:!D+f9 @>ֽ 2P\2((hĈU6--MYo! !L&JVUTVa 2j=W_M4ݽ Y "; V+f[>Z,@Ͳp3fT򴴴WWW6"t:wwwmǢ^^^Uf" -//>4i_ޤI 6") `p"L&j% o̙3|ƍW* "t;oPTVy 2jm۶-_|ܹU^,HKKs؈mق7;FZV@8~{';lDjB 7"t#rIIɵkT*tX, 2Pddd 4Yf˗/|FQl6 !RYTΜ5 Ad>ZO>߹sxj҄7"{yy/9tz VUp.l۶>[5RFFU؟_ 6"ױ nAaaC=o߾  ^RZ6((M&ۈ,JO[sD@a&Mt]v5o޼ZRVZzu:}b`#jun#*d? 붩͚5s(j4 7"Z,AVDv؈Lۮ]6r 6l۶O>x甔p6" !* "oD6L BN[,WWWj"3LC ?lR)dq7"zkl6+JuVk[,psaÆ;wn׮]]vޛ[,VD.,,,(( *߯D?ETVG5N4hPjj;tRrf /fggl[݈VdRTi]fZ}t:/_};BB !FqȞE^Y^=jDaaa^^N~Bb4*.,))c66Bzxx(JOOOU^taÆYvݡOIII_C_ !aBWF|4lDURRj322rssyyyyyyrd2_%eիR<<l"dX VkNNw-~(CBB^εgϞ~}ׯ/F됅FRٯ4ׯ_ߡh0&!\j52V%.++:xmҤIΝ-rQgk 9]]EjjcǤfY744T %/מN|:W~gǎb 8ҬY3V [,%%%)))/_kR',,,(((44uRVٰaC]u˖-oVXX'3224&;;fI͛7e8l޿wyg֬Y Nbjjj\\CQ6jԨ~NW~#r d+'///999%%E K㴴!RBݻwG5jVRծ^z -..jW^j陙/_>xw}+9Û4ir⧨fԩS >wCSRR|IFF@b4i"fsj;:%77ܹs.\8{3;;[&]}aaaaaa2 먏;z5!w6mڶm?͚5sqqO*11qљ6m2dPјPjڵNP31{fsӦMc 2q!T*d4hP۶m###6my_jcǎ;v/l̤ .H۷k4!}4m۶D똕+W>]tٶm[hh]T!Df^_~}`hժ}d2T*hXޒu$??ȑ#G=rȑ#G222>>>[n׮ݐ!CjӦM ŭQ(qqqrQӝ;w;vdee !|||vS~-j_~믿tkJJB(Dj[^aY׫jJfqRRQF111=\ttcNݻw޽N;b͟}YYYY``JڵkppgF奦3&))i=ʕ+aժ5jT%E^k_1fW5M]SD8GpBiio׮]nj#%M7n4`0=zTʩZBu5666..sήN۲e3lf3RRRٳgϞ+Wx{{coFLLL˖- gDMVׯ_?阝-O>=o޼W^yEVݻO>qqq]t!\ _~믿~ϟV5ɥKZlPnDB8L&}b- Adf;~Ν;wl??x`̙zjӦgDӰaáC:T:fee۷oΝK,yW;t0`zzz:w{ƍ'Oo=&))s(j4q }h4FBRIDz2U;Ad]URRwލ7nܸ1%%ݽ[n'O۷o=z;vر6ٳw޽{_~9gZ=x#F :a-8mڴիW?~ѢE](--MMMmѢC]*t:Qn#^B?dBVfc#2Ua4m۶aÆ-[7klCݻw R(۷o߾ /PVVv۷oܸq„ ...qqq#F1bDf͜=iu'feeXbĉG! #""uB|BD6 BZ-W6"f!D[wŋ̙[~'xBǗ/_NIIYp^25K.]mʜ=]Ma֭[צM^xaҤIiiiӧOwuuu\eْoD6&)((«*܈l0F>lX Rk[>CQQQٳ^hѢv9{. 1cFBB¦MJKK ־}իW8{4'ۻw}c711w߭,KZVqȹ 4p(zZm_1L*J>fRP(a 2><`O/.\x~.Q(u/7Y-\\\u3gDGGO8122rʕv'xo߾vZ|yXXd!D իW^P7ވl6k~2 "nܹsGJKKwyС#FRRwBMVVZ8hР)Sta6s% Ǐ:zU>nARRR qgff`#re7"Dsy:$''oٲewP@ tϷo~ݻw?~;ٻߤ{{ZVJKA6\ePr]x) pWAE PQDz(ʪB=4;q~7$-ey9s9IxQFu)99y<˗۶mkm vpp>LXԯ9YD4!aڵ:tػw_|_1M T۶mǏN:Mz'NݻKꫣG5AK.mֺc=&YRTT$ Nswwَ._ܯ_^z駟p¤I;tӦMC >|on4׬Y3p_h4Nd2j̙[ٳMf֬Ym۶ 裏̟K:_ԩShh͋&{9rd֭ ELL{WQQQATMM͛oT˞8qgС{xxQO+>䓡sέ~|xw/_޷o_//={ la˖-ӦM?~#GZ111/wѡC;vإ_5jT.]NsxwFy׬YO/["z)++[nʕ+/]wS@MMի[|bڵ _>uɓ'gΜ{כ_~˗ǍWSS3lذgϚ*B[]PP_GFF ҭY߲M y*i߾ !s׿֭[w/ٳsmݺA.]~={ڻ… :tKA͛<ߢ(L#jt7uC̱c4oR ɓ_|322,Y[. ^n]AA'6mҥK_XXVO>'Has:TxWx衇l޲M yaÆ/~WB˗/o{.>>woFzz~ح[nݺc=vӧOři/DvuuyVQQDR5)LDW(w}j?:~MnR۽{oq裏>Zn]ZZG!}tM'xJ9s{/^*ԩSoYL-[v2lǎ֭߿@@'ڳgիC_v)z3VX1k֬ۇ<ǎRB=zl۶-&&FPkɒ%Ϸ8}ZDD,w?::z֬YNnlqqqQQQӦMo/^|׿3.] ;t .ݻwXX;cǎܵk4 .\B 4k֬-jZPZVa)t7vDdh:t믿;am0p]v3fc0N8w޽{;vL.o#F֭[&… ;vy(;;;44桢"!D@@E"h<<55[nևj "K-jb"V53ШFEEٻ I-,,-h޼}||,jK "ݻۻ I BiFSN:uرcG|L&СC>}x>}tI&ٵٳg۶mk}իB-[d=zѺuk>gϞڵCYYYCBXmND4t:\z-7\)j۶'Ф9sAAA IJJ1bC-ZĢ"AR///F14t:wwF.g>4EÇOLL,..w#ML&aD-7FVo߾}ԩm۶޽ٳoU/2rHRwRQQQnnn"[+*****,&"k47uECnڴލΒ~5k֤3g,]K.1|oڻ%))IQ[j"j iWD4 駟^lYUU{M1]3}tJW_oڴcǎ*jw%K4ow#MٳgCBB-[v͉6o 24Qg.((Xb=%&& !'N6i$S!퐜aÆW_}޽4-gϞҥCEEEuLD.**:Fus}wKTXXOFFFΝ;`زe˴iӢ=<<"""ƏL&dw޽{vo߾w&`0L2K.?Ksٮ]<)-Dh4Ad^XȎn`7sݶm۸q:nvFӫWt!D^^^rrF1_0k֬xnjjjjj_m4뉢RRRڴi3lذ{"y5>lBBŒ3x!:4|\s]W'y=zرc8(p~Ѭ,!D˖-m-**B[jL&3WUU{zz*LD4B۶mrɓ iTӛ5k|e˖m۶|-[/©SZmFFƶm'5L$>}ܹsݻwͣ>zq__߉'M4) cǎG-6o<իWGEEٻ&'%%4&&L??ھ!'"+JOOOLVBGu:]cAdhڷoرg%| m߾]1{3fhb̙/ !.__~7rCݷoR<~{뛑o_ј"͂5̼xf5mݺg={>k^D]<բEέ-R|}}+RLDf74uC ǍW]]a'''{w !|ASe̘1iw޼y۷oB&3>SH' !zٳgOzT5kuiӦMB:thǏWTSN{M[l|8%%eѢEÇwvv?ĉW^5_}">>~ҤIaaaaaa&MZbb7  ~{ĉ/~*޴'Nv4332̢T*}||+AF<2@! vK.ƞ9sڴi#صkce۷WO!Ğ={n7l h׮uIII^f0/^f`hz衏>(>>~ѢEIVɓu"XוJDdV+4Ut:4D={?~EsO||hwGwzHdɒ+Vdgg/[|All RRR.^(M&|r=ߧO-[\tI/Xt>h2_y|qttttttm1 Yvڛ_#hժՊ+ +O?ܢE {wسgڎfdddZוJyE( nUUUeeǍ41`9s{5f̘A9rM T^^^\\\Ν7ߐBn8 ɓ'cbbj[Zۂ"uY՚z 2)jٲ͛;bĈ~M HVV֋/ؾ}/C wS/ZG- Ų F4D4uQQQ{MHH(,,8pF{Szzz\\\vw}799'd 9֭[DteeeAdJ%{"NBX Ad@}{i{쩩2dH.]֯__VVfタ;mFj۶]-Ztʕٳg;;;ۻ/oݻ7(###<}zcwhcAOD& ݻW_}1cƌ͛7w-::zn ***~1c4kW^ѣlj'>O2+((HIIׯ_m t:]QQQA'"[5yY !kT 2[W^=qĀϟڿui4{wo߾q>...۶m+))ٸqcLLC}ro߾-z E]T5i\CLLL|||zz_|6uVZ8|`wwIOO?~dd{7|ҥK[n5j9|pΝj[!DduJcQju:cc8ڻ@1iҤI&eeem޼yӦMk׮ ~|Ç7ֱh ñcvܹk׮3g=_|=cpS~~ձ ##ǧrqqq@@u]TZ'"_gw &"n-Z̙3III3f8wܣ>0rիWgffڻAt۷?쳡}ټyZN'&&^3^ǂbz}&"F &"nΝ;wyܹ;wܱcǬYz>111p%$$ܹ_-//ѣG\\ѣ{ap+;vu cAaaa='"WWWt:ʍ{"2v)SL2E&$$OVz E 4hР={Js3g)K(7/55,++ɩgϞ'N4hP~<== n_~{MFF?\ǂAYוJehhyTaDbbbbbbf͚USSsi)-hѢszzzׯo߾={ w;:uG=x`vvSllSO=%q*jÇNZǚʜG&'HTZLD"3!:::::_6 R(СC֭7o",,,?bbbs~h⪫Ϟ={ĉǏ?~<))ZPDGG4hP߾}quؿh>|xk CA꒒ C*Ǽb3~ݭ%"\ޣG=z̜9S+LO8hѢb\a %w]ΩJII믿OũS\]]w޿_~gϞ;vvk1իWZmAqq`Dm"HLDnBCCG=zhi7--M?~h4B-[vԩK.:uܹsdd$F2%%Ν;SSS;wܳgɓ'v͢aIHH|}}=[V(>>> B1wJZpY,|]]]ͳ&߬,݈ 2t3%X[jlӦ)P+L<<<#]\\ ŝ[D\^^^VVf0jOʹBFJhPJf^rT*,eg3ܸ7& Oeeerr/]tE)s, {svvnݺuDDĐ!CL"2[liN``````LLyh4fffb)))[nrJUUO %GDDk.22cǎvqڴiӧOwo:WWWۜR|}}-:ܺuhnA&h4'%%={̙3III)))UUUaaaڵիׄ )aaa $dVZjjСbuuuzztի555NNN:tҥKnݺtҵkp@u։'N}+p))):t{D >R|||,67] 2VNNαcǎ=zɤl!B"Ǐ~~~+FGGGGG*%%%|gϞ>}z˖-q^zիYfۺuf̘1gΜ uRRRzꩺ999m͛7705Z'cDzm۶ٳg\\45<<,tiӧ?PѲe^zݻW^111;-p¹s.X`Μ97s+WDDDԽ,???((/ϵMDԩy 2h>'&& !ڴiӯ_W^y%&&&**qGL&nݺu֣G*ZԩSW~ׄ111ׯ_LL]!zl?7yCBBlR*aaaEZe^! N8p~믿t:K={^zի}m߿*/?vؼy***<<>pc*++[j5pW^QQQNNNuk߾}'N(}ܒk4kãe͚5yHTX-ZVA؁`8y}>|C 2e44_B:tСC|?OWW{Æ h| b!O="77_޽{/^o1bĨQ)pIa\QVUUQTWWMኊ ^/n4{Э*m;::* ӲpLv'pvvjc4U*U}V^7!Diii^^m jeeeҶE2Φ`L&Q:G܍>󸸸#Gn޼ӋKRRRTT5[ץ,>ZZ`0DvcǎB>}̜9f)ą>SO=`HLLLHHسgs='0`رcnjn6"VZ^jjZzJ%mj4^bYYJ2RTQ JVSh4הJV!ta&V(dnD=MrR\MhR)rinPBpwwqssswwpwwW(^^^JwwwSwFMM͛ohѢ^ziҥvrhǏ?z}[~Zݹs纗$%%%M![nݺu3gBBBy䑥K8P\h͛^ZVVf4QH9'S1 e $WVV}XTT*63#톆ݢm@*++͛x'\6=烂^/9AdWWWGm@a4׍7n۶`03f޼yÇ'0}7B+WN07:,kk/w0tСC\_~ٸq/8sG}g4hMh4R8// ??ߔ?6_- [‹6)_o0^tɴ]]]m:3888(((000((($$$000000$$D]p/\f͚{>׹s9Yc}żXZZ*Zk=YD*++ۼys||s碢.\8~xihhv-2eJ_Fqȑ#G,** 6 tP!h\v-[:u4is=geH;OSOi4yرǜ9sL%RUUoO*jȐ!+V0qh͋.1߻w+"""F=gu/d'V^gffu7ӧ`0|7?/^ؼy={N>{ɺyMӧO?p1c>"=m˗/ߺukRRRDDowRJ\mU=qRwϝ;WUUe2>۶mܹ3g`֭۹sƍ󽻓f̘1cƌsǿKsΝ2eʋ/hv+**͵Hfgg~~~-ZԩiЩ)j|4JҸ6mԶ`0b eÚ@e#E(:Uk[qZhUq*nqUUAF$?η旄D'9s?ܛ>Я@@;/Ɍe=3gΜK.-_|;"ӧSNmgaaMr\.Wj=>h[޲eKTTw}7k֬6rppSE"H$>|x\\Ԧsј&;{{ҭL&3!!aݺu'Nr /;8;;иAd !s̉ԥKkjj*Ɣmg07n ܹS/9b<۷={wT!bŊ 6Hvp+ej}U_ ܹsw-N_zyy8p`ڴiL>ӧJ.fZ%%%?fg͚xb;;;Uo߾Ƞ/233sssūkkk8gmmmkkkccccc,S @PXX+P}"//vSSStrrrttZpY}ҥKwR#+kgg?MfϞ=+io޼rdڵk=3qˢE`-R Yf9sf۶mo޼YlYH!۷ԩS:::s̹~:;qƐ!C!{ן?Ǐ.\Krƍcǎ~z„ `РAO<B_.u؈;vXYYeddDDD(Y8q+@9ydddݻ9啔yǔp̙>l2HzjÄٳg?~gff lpLv[ 7oޜqBmF7)L׬dǏ_>''G~4Ͻf͚~&RȄSS˗~z'OtqqoT]W[UWWu_};t IDAT0sdff:99M>=222...55"==͛111ϟ8qb~>3`1Lݻ5*$$dڵtғ'OJJJ*++_zu֭Eu֭K,>|u޽'Nlٲȋ/>~w}]BBB !O>%x{{7س+"x<|>VDUVVn۶mFFFk֬>}w7n Jׯ۷-ZuVq#"">>C\eVn X׮]egyGo>|B*??(LMYdi /_..O?=={D"ܹs'OCڠ?cڵ\.w ,hE.?Hiii/^x}˚BҡNNN666jjX)>%%%r5%0 OOOwwwooosssCUVVnڴi˖-~~~{*$""bӦM رcppڵke7 0K_8gȸ|eԩ岿!A^:s̒K.X@GGG5yqqqbbb.]6$''?~ܥK333E kBl@ #]hKZZ1Y& Lxx[]]]>lH $ e{>!!!w>z訨(N"S1ccc6-;{yGY,x=຺: AwTp_)s{_)_s}AgϞyzzʶKk2T#۶mۺuETTU]b={ŋ/^x/D"3Myxx:;;[[[#p ׯ_ѯʹrB8L:::>|x嵵۶m6ml+5k֛7o]`Oϙ3Gv1c2ʓ&M=qℸe̘1l~-uUІ%KDFFNq$$sgϞ-**"hhhֺΚ5ӳ]v_;w`2JJ]\d w{OA&zcǎ=zС6B}շСC'!˫=wӧ !۷o{AyA%ihhB9Qh#f>_!jˤdrOVyW^ݽ{wǎN~ƍ4iRJJdr/\|rCCC3fܼy}֭=x>5kСCebbҧO3f:tʕ+^:tСC~zzzΜ9晴W^qܮ]6SAD?T(k.55ZU2Bȑ# B!MJ  twB-9s$tvvxCbbWX!5ȷ~+7jժ̆T3g(D.\T:99INsDfir lTp_)sdWܢL3rE֖rYCqpp(++Z=555gf2wVu--ˋ-c277ӧ>Vu[D7K|Zv  IOO?z zEמwtt/:TXX؂:v옆FUUU=/55M4'}}vGGǭ[*n0DM1 STT_>|UҒD"QTTԁ?`0,YBD{y9!sڴifH B-mmmҥKϞ= k.333.'Oܰa˗/===? H ֭[G[)99y˖-o޼i׮c``_|ѩSB(Bܹsv횷g}6|>qFff@@iE68fX,w}?5jƍ3N+[TJg|I&=zٳÇ'Mtae.\[u֥KFEE}W)rrrN:u…7oVVV <_~{PuuQ-Mdv&PN?l |ѝ;w]v횚Ν;:488k׮\~ϟOIIi?nݺBMIII~~~...&&&6m-fff֭oS{AdF8sLppU~UҶ![zƍO>=bUע,#GÇ>|!CU]*AY'ᣚ=՞lǚA%***Μ98vؐ:4maÆYXX8p+VpǏe7]zuFHqС'Nڴj5Uf|ד'OF >xk׮g͚UVVj---mڎ)))!!!W8sLQQѣGO4ÇGDDy&!!aʔ)Ovss6lXll@ h쀉]tQg~~~})))a2@R[[[]]"5|&k.UMPH6Q$| ?LY`At޼y[nRrOw[lݿ~ڮ]wZ~3-]zDx!XLcvhڰ0ggnݺGy<ނ :ti&] +WvYCCC[DDD>} uvIɡ=z4c www''#G޻wO)5i$kkk//˗F%K  ussLD 55Gr m"G&DLL jM:`hhh0nݺ/,uuuk֬a2'O S~3H~Q8;;x<P(:tKPP&Оgd2?~M]tnOKI_B8@[OBO$efLMp ~^8&'QfC[Qcٳc2+ٳg FYY2}||V\)wڵkݥ_xAINNЇjݻ. } .izjEGbܸq&&&k#@E"ѣG͛gaaѣG;wX,nÇ߼ysDDDJJg}c++n{ן?Ǐ.\PfӾvnܸqرׯ_O0A  4ɓ'BpH7::z7o0`!d۶mgΜ˖-DWV;w}F<&̞=|>?33' gO }N:3gΜׯ7n 2rH##ݻw'''9r+))i͒7)[KIYZZܹf͚~iff`1)Mp ~^"""7ODDĩS]v6%yzz8qݻ:uzy$%%999)3~^^MRXիWhhhm|M>}ZXSN{B]#YMM-00? Λ7OWWÇSRxxfOv*ف۷/!dѢE#Bd7ҷ=' 6y▫WB-?~111Qr???([}M%4h󣢢e=J6x#'B%RA.5֣RJ1gϞݻwB&O,;Z}1ytEǠF %Qr{!^ >}Ypp2cVUU1'O:mڴ#GJ5;DBHFF2Gl"<' ^>vڰa^UUuUP(w+(kkk99s.GNKKsss-iiiƥ4'fffVRR+]t133NKoleeetQ@.ۢ&$w}왧,KPn]]J\\\PPF066fٲ([}r]ѣGGEEQΞ26xʼn4*ںd@B.]He'Y쟛MQg45yy^|.;KR631wqqٵk׌3tsvv1cƪU033޽{z:b K6:ujر555JB矁Jޥ$uUD2ٟ`Ξ=;??]ZVګ8z(!ƍ'NݣyAm/e.&]ѐ8 QFmܸ^OOP:usNzzzllիWccc;wf͚vQB@]]!Dn\ZߥdbbbaaQUUpqV~ƚp( 姷۷oaٙ:uRf@;SȄ)2Y .++KՅL{{VyO>2L&Қ4i҉' ---?tΝ;'~}iBHeܡC$OB}Aϵ iiip///}}}td'P1WWŋݻwrE.wY!Eݾ}d75~{ kntu-_<%%EujQϋ3o -n߾=z(CHԽ{weg0 Ȳ(r\>.ҡCǏ ֖⢪3 uu9r4&&fر?;6m&<<|׮];wܱc!d̘1ݦLBٻw%KRRRRRR/^Lt!Ύo 4ͯoݺ5=====~P~>țt=::˗iii$~n;{ͬ>}:!?9N||СC !!!!۷_~bq8f@y\zu;v:ujXXCxx7Vpp5{ݻwo߾]bZ;w=O?Uܭ}VVVʌgbb)wA⁙VxP oijjX,U x{0`r9EUץl*f}jEvڵ`WWW++I&=x@2 ŋk߾}MMMMMMwsΉcׯ_d[[YsĈO?~I~o߾~zwĉ----ZsNBBBtuu;wzر[ҭrgR ӿvfffC z*O?%%%MHHHիWxxw}:G\dH$j<<|844OWWmopgϞظ3wӧAnN<)9T3O&88b;vd_r׋/~'|bcc3mڴCk@.Նڕ+WgϞ}?^u} *++/_pŽ;zxxuVZZ… e ܻwk׮Jζ Ad) ()))߿LLih($$_~7:;;0wݻwӭ4@\j.]ܿ_tB//L6rӧ[XX<}L|hű g{-L&3!!aݺu} ]wJΌT17nܠkܹW0{_~%44tҤIn3]bņ $p%mBɓ'߾}͛;vTu9M˳g^|͛վݻkI|_!t6tnݚ===Gb Ӯ ޻wݻwܹ{nUUUN 2|~5ooQFmܸQΕN=z?cܹ?9yիW[ƍqȑV`EdtrssU]Nc++Ǐ_~}NN ?55uٲe"H|ٳg?~gff =>>ҿV;͛7gddܼysm۶6diøB&-\0..ŋCMHH''';;qmذŋb_5yn6]G ];w.//=RȍUQQq޽_~e̙:u2666lؑ#GݛGrYY{dSU&,X@[lٲtҮ]>zHaÆ ˗/5hРk׮EGGk׮ 4HjGPl˧~z={DsN<9&& _WWŶ="4Kppp~¾={ܹG.-9w܂ ӧOBStrʕ/9]tizz|UVmٲ9f&--BFEExŋ'%%]xq͚5  "PS4d[6{ԩSU]* 4hРAH$|%]3--… ل]]]wwwWWWgggGGG''';;;fff}6334y\TTD122rss1ckvTRgqqqbbҥK%77ή%%%^^^R?_<&&&444,,W^Ǐ_rjǎ{ĉ;vBƌHHȕ+WBCC:w㣩ۛ7oFGG'%%)sЫW޽cǎSN_Ã[ZD}3'|nݺgZjرr3&Iֶ666666֪Z"@L$1 khh8888;;-^e3gΣG>|.zӧn]xqXXdc\\ܰa8m Ǐ7n\oDha"֭KHH 3f:~X>.]ٳ<ov233 7ېe˖7o޴k100/ԩ%[&MtQ۷oӭ"gϞ>4iÇZJ4*[s:tڵkޟ}%JLLܷoߍ7233]]]Ə/7{ 3f=ztlllk rOjkkccc߽{UV9NX thFFFnnnNNN~~~aalF Z ͕Jdggno$̱T4SLJ [pܭZZZi$;6eʔZ6300xaÚ\| 2?ܹٳ666gϞ9sz(4}l߾}ZR~~wނQF͛7/00PE}jjj h4977W x}aiiiiiinnNZZZ=xb XPPPTTTQQA;k׎> 䃝jϥeeddoڵkTr@Ю]CM8Qvkyy烂$ݻbŊqK~~ͭ[ۜSxiKwe x]n?}#FPuu !.ZFMM磣/]W_͝;} ܭ2X|UYY)gmm-S677711166611155522j3W^^^ZZZVVVZZZZZZ\\Lh˜]fff;CKKK6Pk׮6* \PP nQcSSSv6-e !zzz BСΝ;7lpĉ[XXL2eʔ)~~~}V%@xÇ:T\\ܿc診.PG}|>]T2b^|yU\\, h(YL&uu$Z.))Nj)o%w?~ޯ_?U)U{{T~\Bܭ9N>}^?bbbo2a„ &tI@tǏyo,sqqqqqQЇf5fff*8G__X򭁁;>cZEEx\.r\6͕l6Ñ|Z`H=* Qccc򪫫/\zF핓`0DƊJ\\\֭[O?=xܰa1cF٫W/&[7ݻwϝ;wԩtGGO0{. TȨC ,//ZUr';;[2CpNK522122ѡ[utth#}ahhT%*+++**8ϯB8/!㺺:Ѵ ~###ss:ƦRVh)/^ry577oR&)͖jrF-B@ FϞ={m۶{}m۶/_9sܹs'Of0C6lXN,Hw]Hԯ_+V9UG.܄***l崱DkjjX,VMM }MZWhYJ622b0L&>C̈́]&0LAϋK"뫫oNKKK[[I}?BPr=l.+ʪ*㉻x_z5 !qh>B/<.9,;Vظ }4UL:::-9U<{?srrdKJJLMMe9d H"nnn-ZhQIIɓ'^m۶˗[YY :W^B(!>}z֭{]|O>_MLLT] @#hiiiii}+JVB\.R]]MW-**!JeI]]]2H %IqҷA/fRSS344$е !tk_ ֶ o@y}%˗/7v\Hl6b<AdP SSYf͚5K(&&&^zի ,߿``$M{ٍX,>}̟?;wEx ]RܪJZnh 1K-L "SJN8K$V&abJ &J К^z)X۸>999|I}[x/55nݺumٲe555>u͛7-[rzգG={v#:|8ΣGܿаo߾ .ׯ_Ź@h}ɲsέZ )/E\GՁ 2@۠l2@|{رbyxxу}}}Ց x֦p<ӧOфݳZVV& MLL[#yAd?~-(((**+h.‚Ɣ[KKK.,,QczJ=544ͭmmmwN䦫D믿f̘jժYf5g,BH\.!+"$######___M".mO4"qg555SSSy$MMMY~?쒒RW]('mmm%*<#P˗/O:o]vm3533 T;VDhU fI;v(Cmm-*((`XW^[x/Ȳ>}k```ddZx<r|>eee|>r/f2qSSSWWWp9]B]A?ƍ ޾}{GR2!bo^fJ`EdM=9NIIܥvy<ޛ7ol8&pdG322200,3 ]]]MMM ===Bmi׮:ikkkii1LlYi:8 UUUe\@  tZx<^]]]uuuEEH$b4gx<ƚc<7EQ6^L⦦<HKK6lX߾}8 "SfK{+"UtcRTFF!BlH$nB4,(N9+fi kNGG`B444455uuu !4^xa&@yyyyFrpp8zF,\f;q\uuumm)= 2| Z$;̒R>R┳2hVXQKKK6"63FWWGڀ2/PYYYoH$*..V2e| /HnllLŽzjW\ nHP_S[[+{Ěr?߈PSu;?~ &Î";QKͽ2t[be'55%wpIR,PuPdaAցql"z1请77Hgg&<9''}!jjDd@?tPoo#G4"%%EUWmllf 2˗/6ѣvvvM~7ڴiYWA]AdJ%RAAA뙈lbh4MYYZ.)))..h4ZV{Ν2TSQU2ڠV;;;SSS sss;;;##晢;uԤI~'++BѮ];:S999666fff֥ rkL&RrssJe^^R)EKKK%uVTTӁ&&&666fffQ.r?ԓ?uqmݺ_P()ɩ9YRjNDh+[]eddeffUK6mTYZZZ[[u٦M9R&q=t:i\J*+++,,,***--_Js]*ݹs9vvvrQznnnnnnnnnЫ|>wywߕdY ǧz666&3֥"---555###?FFF...nnnr[ƕ2Cdm۶RJJJNzJTH)R\%>mnn~ ~߾}_} /wƍ!CS\s]RW[,((m1ET* Bq ͛7ʤvyzz0Czuqq11iu;sssrssqљoߖ^ϟ?,#J?xxx<БhArrrƏxСC>UTTz{{Rjf< _Ke20`СouVnVVVzz>}ƍ~-%%E (yyyI_&))iҤI**22gϞቷo.//kW^5뚈L&77W_r%111>>>;;[aeeչsgoo!CK[nE9ӽ{XYYVu4ull?D@@M6222;{_| BSOMNNSZֶ ;l"Z?#66V;BX[[92 Ϗ͖Q۷o?hРyyyzʕ#Gdff !,--hr@@@݃k)++?矿˖-31yx7nX[[;;;S]WY.W]))))//g"2Kyyy|| .\jqnFխ[7??:;nryhhhhh~ETJ䄄+W;v,##CѡC l=h%)))O?k3a„tBUOF)--+ܱcǪ+B{HL^p]zaǏn60888W .]$nlݺ7B׀ME3t̙ɓ'[ZZFFF7P()FDhLDZu邂ݻwoӦۄ8pҥB_k׮h4&&& 4hPHHHɆWX1z[VK>4 "(()XsKRU]a"2Vx>|h܆ znݺ=}Cɭ''lļ ۶m{g ؉B,!K-Z]-Dd8NwǏGEE9sF899 4hŊC 3th„999'O矫%%ES\s]VtjAdFӦMss&n" xZƍ~m//3g !BBxyySRs]R !jNDi7#C7xdݼysÆ Æ svv]xqrrrbbUBBBH!À:u믟8q";;k߾`hNgϟ۷ ^fBB\~ɩpvvvÃֶilΘ hb;wܾ}sF>m۶5tk@-ڶmjO>]v\C'O ӧ{lrrrΝwߍ5j׮]ކzo5YYY]v^Wܳ~Сڵ{ݛm۶0RhLLL f͚=,,lΝ˗/OOO7t-Iee tw4"99s3Y&U],((hU "ו+W̙ammcǎnرh޽{XƍΝ{'֮]ۡC &8p5w Fzמzرc5$\s]RW]d"2 jwȑ# ,HMMݷoSO=eaaa& GǏرʕ+ ]s=s̞={={vnv%%%7oެ?\^^T*kVCLDஔJ˽Lo5033uԡCΝ;׿C7UBQYYY9''G:YTX3:edd̛7C֭{RRRvj辀C+V}ƍcbbz9nܸhCeH:n=zO-Z3|pC7uw2 rvv։ȵ @-Jom۶ŋ,[}Ad2]4S/Rll޽{srr?jԨK.F`sssC nnnVVVdeerbAAFtRnڵ bܹֆY# 4OU&;ܹsǏWս{ONN6t_Ax?SgggCu,277UDdFCoΝ]vΝ{ڵ3g)y:tٳgw[w1tSJii :v8u'xƍ~m.] =kH9;;ɩ֭A䂂[[ۦ% ]bbß}ّ#G^vmѢEVVVn hd2٤I׮]'ܹM5[sܹ!!!qqq6l4t_Ԑ K[LD5t7T_/k&C7 2U.?Ǐ׷g:99999n޼Yod2|Μ9AAA|BѼ;vZbEgIh7|-<<<77ZA.VAO_/=&00pɒ% ***z^ziӦL:K.^^^cǎ=w\ՓsrrLpŠOoO `kk{~۶m5kV>}|}}ã6-W^B=J2H'M+??Ŋ/R޽ZdkݪD...jj"9SNZx󍍍 ݑ4iҞ={-t:N7jԨÇW=z:}M6I/^\lُ?G~n9s'|/t钵ZbЭ~kƌ}Y޽{GGGN={ƍZsBFӣGY>oz>1{jGU[n3gΆ jݪ08q"""B_1°4ڕ+WV\}vٳgO>M5˗/#>>> #Goٲzyyٮ]&N_ruuZ&"رczJOO.\R_~={,--g̘q J !6mta^{-66?3g7o\۷_~}Ç|~Eqĉj駟֯_n:WWWB~B޽>߸qc\\\vv?cʕ Lvر|۷o7}ݾ}0>>~:nѢE RRRoy׮]M-4Ė-[V\P(N:5d!|Pm6!ccc o޼k׮{^zҥ'xw5x0^={駟ٳS/_~ŋ?)d!DRRQǎ/vqqMDh4BV55Z 6L8Q B̜9ͫ8g!Dhht)[2&&֕>}O ֬Y_⼁U 5Sm֚aÆ !lRuرcUt{wFGG?3:u277g>}oIZK˵kV{ u"F>^/RSSk6iXׯ7110aBaa{;Jqݻ !{;v{zz޵ݽ^bbҥKU/^(vZut--_x/^tE2Snn޻wj[yyyqqq=z/1''G!|{{{!DEEI## 閫WJ+W^ڵk۶mJZb5nnnҜiiK ! iH&$$5Ç=RY۶mU*Uͷ/ho!XNNj۴i#zN\\̙3W} \~9~>w91W?~?߷oK//w}=(/B8~x=5fff[n uف@n~ Νt?|*/eRZJ!ݺ !Z~%77!7JTTTTTTTVVVVV3wixUS 9wީ7nܕ+W *++322F-4ӧRdW{y٤UV=㦦?ƍ0lذӧO_~}ذaRHKK[~}pp#RRR>cBbŊG8,HJJj5/bAAƦl"@kzoٲeԩR/kᆱuV!}>w+.+8uTVզM!D!8-[jVzիWk֬d_|EV-АO!jT%;w~7>|9!ġCqwI&hn:|pOOŋ^xSN2tw\|||@@@5B[JܼF155h# ھ},Xr^xнNj믿~WjuTTOH 7]6lbK.]~}&NX[ֻo!XCoٲ%))ի~8Aҥ/^8c CPTT7ߌ;q֬Y۴iS>} C%k"rqBV5Y!Bh233ƏeCR'N7~N5jÇm=z2L!V uE1cƧ~t钵?׿{U=W_裏5,Xhk 5$导 IDATWyIƍaFӣG}̙3?}}~ j}kkTjΝ۸Co߾ &l۶-,,=TFFFڵk_:::>'N 6,33iz;v/--566hѢ}U]\z'|P(抉zL&ۻw>p@\;vL:tЧ~*mnڴI~믿޹sgWW)S?^J! !zk׮@}Yfn_|}}}wI.]Z._n?'ܻw3<vj ؜?>,,w޼y6lZpBC>Sx̙3z5k֬۷^?ƍ9sٳu~@JJJ2bĈ~Grr:k֬֙BB$$$8::֟BB;99L! !J\.hlmm˖J>}:44_~y' KRaT*k>sss=gϞCĉ&M~kqf͚uSN_`#G5LR^^?V]3gNLL̙3g c " <| ,Xp… ̊ .:t_~111 ]jՄ ܚDeff):T[l Ҏ9ꫯ^M,""dyNjj_ܿ.((觟~R*G1c)ZZV^^Cł&`"2F3fZ)[[ۑ#G8p`zO:u)Baii9hРw}wȑGInnnVVV'"׺T*k5M훠Ŗ 2FO߿it:]z. eȐ!twx]YYyʕgϞ>}:***--ܼC۷/ (hHȵ hRSStΝ;JR.UΝ;w\tttAAy߾}_~HHH𨿬<77 V-((u"m5D(//]"''G "[BBBeeeNlٲ~իM6KLLkYVVNsss阈,"@dmmh ЪI~kbcc]VYY8iҤ+V/ 4Ć̃B:9//OQsF! x9;;K1;!ꫯ=<< #.!!aĈw-Z'"+J!D:޾l"@kԻwﯾ]ٳguveC7Ҋ괴NDUkήZm Fy֭xC7Һd2LִUSbڵGvuuuuu0aӧ՜8qbĉ'NټysQQѤIV\rʉ'޹sg͆?:ujɒ%nՉիWC*322\]]kR*5'"T*AJXZZ~g;vؽ{{y4L2…Uw+++m6k֬>}XYYGGGK2L&g_|bƌڵȑ#BѣG*;;+b^ZOD Z655.[C70#GN:_֭yh4}!2335MՂ_}Æ ?:ԫW$Ç/Y@֤$!ĨQ˭[=޲j҄ݻwzt)miN4jժUIHH(++kx֭ͭ}F{SXX(H߻s3۷o7V/b||FpZZZv%%%:~U333۳gO6mjv{B̝;gv̙kU-8p[n&Mt̙?bذaǎϿp’%Kڶm{E ˅[n]r\%))wixv޽}vCߵ2##CQD亂dBZ;''(ss矆nq !Ę1c+ǏZ0رc֭{==='NG;vlذaAAAܹsE}?tҥKg!đ#G!DvvvՓv{F݊ٳw'xRzjHeff&"T*&"Z#77H\nUٳٳgVZ㏛O7nl=֭[www!O?]lBR "_|jt)mݺukG=x )dCtqqq={lHqFFF6mkniZZ]DdVJ.GEE=ƍ{*++ Q#ؿ~e޽5:wo>|ܹsBC5[lBtI" رCѱcGB|Uk#F4|[.] Qv%Lֽ{KkR*BU*U+,tм\rONN^riӌ Qˣhz葒_9s'|"{[;w|߭'WO633ӯ,ZhٲeϞ=/_yV/011ټysDD%tܹiӦݾ}?7t;-ڱcիWRVZZgϞ[Ν0`@jjjvw駟^xqtR0P]@@@ttٳ7::<666ϟ sqq7oކ \xq̙VVVz5kW^cbbMֵkW >_BB,]Fj߾mPP_,""Ǐwwwwww?~ÇI!7ZVV֋/c]|r3!XQV]j5W_}ߖ.]i莀櫸>z֬YSO#m۶]jw_^paͭo{QQQu?_lv[&"w={te޼y4PUUVV~W]tyϟ@ YIHHh4kHN"rqwi "b.]ڸq۽.\m覀f1c3qܸq[nGmdDňvvvn`}ZZG]Jm۶jil34##nڴ֭[|IqqqDD9s"##ZիWݻwHH/2k֬g0twgϟ۷o럈PmQVqȂ 2iO6̙3IIIߏ=:dgg~ARAZɓsڵUO8P(-[kHZ̙3!!! ZVVj*޾]X&nhԩӒ%K,Y/ܹ3""wcƌ;vl>}d2D%%%} g j#~Kzzz]AdJ阈G`9;;??|nn,Yk׮O>CBBZgjcbb>|E?ݡ;vC i]VVSWYT !"D<$Ǐ߷oߞ={֬YcllܫWAId{{{CwGVpBTTTddٳg CCC?cǺA<(Ǐ:th322t:{! x,,,ƌ3f!DNNNttٳg;~JÇ?cC i߾EKUPP%jiZOOAYf>>>n\aaatti~KZZyyyB\^m]VwСm`4vرc !n߾yĉ??(((888((W^͗Nvڅ bbb.\988&&fΝw155ѣGppK7666t0۷o_pA>VT]v  8p` ZǏwqqi-iiiNNN&&'lJCuZmgg.[2cʔ)SLBhڄ)iz/ʪO>ݺukAV<))ʕ+ 233;v ~cmmmN,;v! !=<<'\Ad@sgbbңG=zKBҸ8indd䧟~ZZZ*h߾_@@:)y|ʕk׮yyyu}AAAfC "˦RŖ 2133۷o߾}ˊ7n'&&ǟݜN:U[TB< ;uԩS &̫~U(OuVyy\ڮ];vI?;;;ipE̼}vVV*<˓j\]] xPP0222lxܹsСC֭*++<==*ׯ_E)|GAd@ ryPPPŊTh۷o?>===??_d...nnnҫt\.7555{2 NT*222233Wi]M?°oM4*++k߾}] @kdll5dȐj[R6##gϞMKK+((Zlmm-dU)ۛYZZZ[[K]ŝ;wT*UYYYaatY-j\NMJ;]twwo۶Z;w /55UqAdJ%"= Zw\}B777Wt~mڴXZZ/"\QQQ5|>j\TTTZZ@}Zs)r-]ZYYu`w9pի[ndvպ[PPPZZTm]Vd2[[DݵRєҢ"eIIIqqqKV7''L) ccQHLVmrLMMmll,,,K)\>y8tPiiSO=u7TMW+9YV[[[7Ֆ 2B.ؽ{w~\\\׵[O^h+VFܛYnNNɩMo="_=֭[ODR `:> nsrrkVD61t@8yw5޲,OOϺ 򜜜jj{{F<Dd< ,Y2jԨFܛVYYو*@KuܹSN?~q ! "DVխ6Ddl:7xdžڸnݺejjZWAnnSDf"2Zo6::>!5 IDAT5Ȩ!999rJos[4&"u֜9sOާOFھ}z Ꚉ\PPj'"D@KUZZjժ9'55ӳ]VR oۥKN>meeu?Gݺu{u*ʚAdJ% :.""Gٳ}ھ}vsrrNNNj>BD@[o?ٳgO󨂂|OOϺ rss5'"KAd&"-ڵkW\u1ci BSWAnnCuJ%"-֭[͛>sMr`JJL& ''ĤzVV[[[7I-ӧO3gٳLBlaaQWAnnSuZmkk+ɚ 2ZÇ;6,,l͚5MxlJJw=5U*}vҲD@ ?;vڴi_}U!NII򪧠 ZkNZhn67m YP(7 2|mٲ套^z-[ οD䬬,fjӦM{7/^ W*jT*{{U`bxUVVҢ"FSVVVKJJk^jZw)++_J5u=V)ɪ#LMMmll,,,fff.I-\pʕJ! !  NqvvVPcAdDTIyyyU/UX[[ۛYZZZ[[iF[-looVQ7nT-PTeeeEEE*Ju\.~厎666wh4'O>~-[^|##:UR "=ޚ9;wWU " 9i'S*J8:6i'5225r I7f3 9DTp1|ku_^Z[}P¬LSReff啕U-R`ۭ5z(ףI*++KKKˆtX-N}UNgM.vXDRƍgϞC6\\\Vk" hՊ^zUR_nݒj̜mppرc :ɸ011 KJ'K)픔'Ndff3wСjFյ-D\\ѣ?ޭ[ƾ]ZZZN(۷TPP`ooH5}BeekRSS̱!|/xzz:;; >Yttt&E.wv7nPT999K<^YY)077BR.iiiMɓ'Ǎ{nOOpǫWzyyQVj5MiiF[(??ŋgJJJFFVBXZZJׁFFFRm۶׷V0SSScccn*MBo? k׮:uR( իWϝ;w۶my07MKKٳgjήMۢ"^Of,//j8!!A ZZZuuȐ!^ƞUϗɉBkNFdz4gy&**j ,x`s^֩S:jjun !"l .$$$ !,--v:rH)0f]vݻw޽{Փ .]>7J%޽{HHHppp``La\xq„  {-++򪻆 r"Ry)||̙"ccc޽{5[nĎ[{{A 4pK>wܶmLLLPrHHH@@mnmݺ^֭ۡC҄LD7MQIIǥqll4 s!!! . իm⁲8pCV{ҥZvmeeevz)رcGy{F4… ?gy/l۶!--ظZ֭[󅅅mڴiD4ׯ_?vXtttLL̹sZ[pp+" G4!ݻw޽iӄqqqRx~ϵZWhhhXXXXX-į矗+WxzzQV۷o_|aamkN Ç8q&&&FZdI-,,n͆Y>}#tDܹs3pCի5Gj{%K 4ҥK26SwMAd;;y xt:ݩSvuС .hZ??ÇKckn-Q׮]v/޺uԩSG9rooxzz3fVVVr7ۊ$''GFFŭZj\TGAeeeAAA.D& x@nܸq]vEEEj#GΛ7/<$FYrE?tP;d!D.]QBMDNnU"J駟nzIkkϟ?ذaڵ5ڵk]vm۶O>z'|ɠ {l:4cƌ̙&&&rw\rM6^^^uԨj;;Ko=nhݻw?c?=<''gǏ733;{ҥiii111AAA۳>{)[k4͚5k~oСC=M.99K.uo߾%رcO>ڵk{ÇO:BրP(aÆ+W&%%?88xƍnݒ&'***00pΜ9SLIJJupSwZvrry^єD.h4>((h*OKK{wn hDVVVӦM;y3gw/{xx,Z(??_֚G8p`JJҥKmmmn\rljȷ "zKyy5kzg}{.\8qb6mn OP4Y2ݻ7|T*gϞ_tiYYYr%)S+5kָԝT{\XX( t-[cΜ9>˗@jH6&V.\(߲eߢEJOO}}}?ߞ>}zr7U_)))B;NDm߾}Ad۷gϞSL8p`rrիn h,--_}wy/ܹ\YY)w_˓&MRSS'Otbqʕ6mxyy]R:tP};taj,R9 >>~˖-.]8qQ&''wԴ2ggKmڴjNl2 .ݻw׮]]vM6;;;;88 6СCy^zo߾}zBBP(*++Νi&!DIIɫڹsN:}U%]h^uɓ'U+aՓU*7j(///++޽{|VZ_WPPI==s?CYXXtiرNsnnSO=Sּ} u}Yo^ַ6oX~+WǦdÆ =z(**}7eO![_~1cÇ &Xf͛w…ϝ;rϞ=_uMnݚ2i$V;t?pFqjݱcg}T*?SggW~gW!ؾ}իmmmWZVᇀG'?ŋ322wڕQZZz7xC/\PZ]bEZZc}իW=.XlYouB^z… ? kȑqqq#G=z/|My77nXb---=ziӦŋ~~~u999\*,,k֚=z^1cGyyǠA3gά4p@۪'Ν+=q.]2j^j}d#4ȯPT;O=.DDD!:o޼k*ʚMhǎ666zvܽ܅ŋFFF^pAJB8p+Wo߾֥^xaذaZs:xD?k׮A~\GGGF111Q(k'..n̙'O4Ԍ?~ڵw|_5~ݻw[v_GEE988L6m֬Ynnnr𢣣322~ ܹ3>>O<ѦM-[4Z̀ WPP0t˗/=zIUF !n3toHh gstVj:N2eeeoj >۾:nܸq ׯ_t=+RB)2~lj'NHJJ㏇ fjjcǎUVs  2dȉ'nv squu?~޽{,Y"SBD;>JrvvuήZkN"@kW\\#dee9r$88XvB֭w}Wƍyߨ(;w !>}!=ZU! 9 _^_{n^|Y|+++Bvڪ;HV WZߪ^ۿS{]<|Ǐܼys˖-G L?:TT="׭%ܑFy'juttgBlذa111111ÇB>>>>^"X|bŊO?TcIKFBǏOOO_|e˪].V 'gΜ)5k'|r⸸+WJa{sm "Xt+W\{}gUw WZjHH듒n޼yoVr7*33~)<<<"""55Uft:ÇMꫯVۤ߱cGVVViiiե?ŋfffaaaw| "׽N:Եk׈yU*77wG7n|`bbR~~k}Ν[n]tttzzπy3gnܸamm2}~s hXYG SII]nnnΝ;_}۷o@@-qqqfffwTT.Durtt˓ USB3fi/((_WϞ=!..䎕**$$֥Ȃ 2Niiirwjiii&&&'Oٳgnڶm+wS-_\\\```}*U*Uj]*((ݰVĔHP( EV=!Z'|2j(ggggg{رc 'nC ꫯ^|ŐRƅ Dh4yyyε2YDdh vƍ3F^ 3J-&&pfΝ;w2v╕8p`…r7Һ'n79??ήk~ QN{.&&y˖-J2777***44{oV^]QQ)w#K\\"((莕*JQDd9b"2R6lXtttXXܽ@6Gٷo_vbccݥG=zleee+V$]pB_4Mqqq}6i ԣ>:jԨ_~֭[r2>S...?ZmUNyYfի]v'O>}P( hrڵB3fD#y뭷JKK~miu3YecccaaQsHD5ꫯ^uiJJJeJΙ3jOϟqƕ+W~~퍂,,,u6w⻭IJJB9rݺu z7ÇW\tRggg{iuSZRAA YǎW^rʯZ^Z+V~gJO?矫l޼YK/]p4==0`z}}nẅ́+V֚E&%%Eq^xرc%%%qqq|A^JKKq;III'N6mܽ:nݺ|r=ٷ " ! 2j'O׿5k֬;vK/!ϟ?gwwsΛ7j)))7nܲe?~bȐ!,,,}w.\xW5B7~G_Gu!))߾7]6rΝ;oڴIPNh܀@fI&m޼'"55U1fÙǿE5B{o#G𰶶j_>t9>>rSק8++KPn"r~~}v\1Pؙ3g͞={ѢEVVVrw4uz_`Aee%K^xf7LIIپ}An w*\^^B9# GӧO;qDPPo:nܸ͛rw4SN͚5u„ eee6lP*~ܭ^RRR BBBYoDcלDܯW{챵}ǎm۶JŹs̙>`O>$77wO>.DGG[XX׳>++KQGԴk066:tСC-[?lڴiҤIƍ7n\XX=wp…]v:;;O<922200Pp<ZpVVsC6`"2999͙3ٳ?ԩ#F8::N0oQr75kgϞ=˞={ݻWT._r9^KMLLj]%\[nݺu{w5{iӦ;v̘172b&T*ouϜ9s̘1]v54?SV2(JwwۭD6 htEEE{ݹsի?裎;z{{&ZcǎEGGĜ?^P 0`Ǐܹݡ>|1(((Jۭ9884Dk-AdckkSO=S111ټyF0qqqZK.s1bm[Ç<&gdd\MDG}GǏ4J߿C=tW!B@DGGt:y慅 FsŋUJr̘1[g"Ad,--G1b!Dii'P֭[+++z%wh Ϝ9{̙ !ohh?񏰰0777{ăv!CVq|͛ϟҥzcHH!,wːGYYٹs d!DBBB^xmBNrss%999 "kB&"D4Qm۶߿gJM6{B77]v_k4K.]t)!!ҥK/_j666O<<رܝ  KYt:&"(z=pjulll\\ŋ˅]v>mmmnwˉR8!!!99ȨSN/۽{B!wh_޾}kN:Wϓ&My󦩩iտ?>>{ i3Dd@4zѣGKZ655U %'$$DGGWnBzyyy{{>;t kԫW>SRR4W@@qd-yؽ{^;v]]T*jM! ! _@DDDHgZիWhrJJիW9T*5]vբޞ>GUQQY5p,}JNNN1a)vܶm[yFk׮A@ ۭ !ﷹ 2e266ҥK.]{1k׮]zՐ=y/ :tpwwwvv>ܤOAdffT 3''Gɑڶm+ž'TQQww}n/T*un7/" hELLL:wܹsj_~UR_WVVVNNNlll\\\ 666ݍ7 kNffffggjËsqqquuuuuѣ޼\\\}G~رcBRr|!WEammXsIѨj)k+S*R PߦMjdSSS[[[333 KK˺MLL***JKKoܸQ^^n8,++vhK_n޼Yu+;;;GGGcƮm۶mׯ^T*ۭD 2uiӦ4ٷׯ/7n())sk 333áy_V{uá^/**]lllLMM U>WbddTYܼys/ 5Mvv "WEemmmmmUKnݺuMC.ph 5pjnCccckkkáBZ`mpXfa޽eee&L U*V]A^^uעD@vvvr747n2dHSkT*u﫹 2ZWFEEOpR466#D䪌nh_رcڌ gg6mn;7//>ki"%(//߰aԩSATnU2*h ֭[WXX8w{\TzxxnP1*h*++?gyv̬c"r^^ rUm޼933W_]VD|!=D@V\\`ᄒí[;ut\BѾ}{l%" t .]"ؘ-Z6r7ܻ}}~=o&#V\ \%%%M:駟cǎ_|}n^8d!Dnn}ޥ! GVm~Df"r5̔;~spp <==(P! 椢""""99y޽ LD {SN:t( A,//ԩ z=A"x׷nݺu־}6ԞJRD...trrj;  t^z۹sѣp紴4! rssLD 2:Fcǎ_~eĈ yzze9cZ-"@MZee)Sٳw?==qB\BAh˟x≣Gݻwq;MMMAd[nݼyv(//cbb),HKK޽{jq5DH7n())TqCFcاzP(UؘZYYmphaaaffV^p """;`ƻQzz1c(urrj)ԗF˫57,--z 6334555T=4111\(e m۶]oZC^_TTTƍEEE7o͕Ά՚gSSS)loo_ҡ=XѣG9ss΍wN:QD"?:.'''++++++;;%333;;[Vt:R¸;vٳ4EpRV7tu/iiigϞ.qssBɮ/wsssNtt?ޭ[;v7꽔JVcӳQh"Z#FT*^jT*999ZVrwwwvvvssRҧШ}||.+//ϗRڙ*JWTjZK...^^^U?C IDAT~„ |Mա$==]ѱc:jrssf 2OIIz82׮]h4BmJؾ}N4ݽCڵLy\jeeeNNNFFFNNRJMM=u'8;;WK'{yyuQP<@S/^;̚5O?566~7MNNrvvFVo4ӼD(yyy /^>j]Juq?LLLk.]~8gϦݺuKѮ];]WN***2eʁVX+|x׮]}}}===MMMammX^JNNNLL~T*!Ehr׮]&0O['''D...`"rMIaaall3gbccccc333VVV~~~ݺu9rUT(r7P(BCC ' .]$}8p ;;[aeeջw///Zv+#|w#F@ k" hΝ;g''' !:t/[+wh, 4hLAAK?QQQ|Nstt4]\\dWQQ}ڵK,Y`z>%%oG#---&&&&&&66611Qb!!!rgΜ1 NOOB8044gϞrZ$&&>3III6lxdAR>|8<|؉ϻ۫W͛7W0ٮ];vZ*uAP233>w5zyy :EbG^󁁁wF=f777iӦ.[lɒ%b'LLLV^=s fFr 64? vѲe/>(((77ҤIQ 4h݋?~cǺu֦M?<))IG*Ι3GzzzQQQ+Wk-ǏiӦlssډ^P ._gϞÇÇ3fzzzbJDD/'|+3gꫯf͚)vW֭۝;w*"96vDTT*{qƬ_~eȑ5QtmӦM 255}z&MĎT3MM͡C>|833sAAA^^^NbC>?~ʕ+Ǐ;TE[lULNN _"2\vmĈNNNO^vmFFƶmڷo/v.5o|޼yIIIAAA2lС:t駟Ď&gϞ-]M6.]_.]ԣGC^JJJ6m*Qk% ~u=;; f266;jD"H$b444 rk׮'On۶}ȥvrqq]x>j(u夤nݺ♜CCCCCډ^("^/..?޽L&;sLxxt EA&Եt۷o8p;vN:ikkWr٨)S888jjذaW^-G># ''e˖d?_- X[nٳgF\]]=Z~wUY9sf.] ۶m;nܸJյkװ888&<{a„ ?ع.!!ʪ1wBسgς G''' |c^:P( CBB^zkȐ!ʖqڴivRy5k9rDuo6ds)3fؾ}j6&&H5R?nUoM>}Ν/չsp ʳf}e_> RcǎiiiwgΜwߕo-TpT/?u֕;q_ϝ;o`JK,ٹsIlҨQ#ü{ٳtĉͳ;T5?ŋ###+stt3fʕ+k%aGdy̙3.\xʕB;f``0}s?~ ڵ+$$x޼y7o޼qܹsN8gϞ?/%%e2_~ Ν{ǏߺulbnnuJfVU`pѝ;w6i֭[>矝nܸ_Wk׮ȨϞ=]tBXb]4KKKGo2_^pS7V|W:$´in޼ٳt^z饫O8qɎ;Z)\2;;{,Bbbbv^;cnn^ y;"+!!aR߿gϞb/ww/Θ1CAoy}t… ymݺ=,,Lcݟ~i \~Gtru S6m4| 6,Yk׮QQQ›L~Ξ=o߾I&={_~+\vݺu˖-{e]iӦ7nddd+g5k'B׮]UZܱ͛7ϛ7G077ʪ*jׯ_hmm;YkrssGgO{Ν;#""͛7~x]]]sU3;;)S|̔9ւAnuijjݹsj޼y^^ޭ[:v:xΝ;7oѣGǏ7iDL#2L򔤤m*$%%k׮iӦ[-,,555Uo544|krxGG/2d\cM6-((GV T˷P;ѣ͛+ikkK$UWs֭3f\rE53|{~֎/^̛7{A^ڵ맟~*,,:ujyÇ? ߿߲e˗/םͨS4_hhh֩r4 l+KECCC2Ցʜ};T3111FFF ܸqK.g["g޶mKAXbڵk_YޫL*^6((ۻRcǎiii3fl߾]5Be+?K_yW_Y]`Aպε`{n׮]-_:))ÇJHH裏ƍסCZQ;[n߾}ɵJPDkƍ˗/vrr;KE ޽{8 Htxb. b׮]Aptt0a'|*Vŋ%KIROOoyS=nݺ۷o;::3f/-"J׬Ys<{[nڰaCllݻwuuu~...UDxY\|g:;;9r޼y/-ѣٳg6n_rl| c)"FGGϟOOOիQξeeeݻw Ш+޸qرcGkѢŇ~8nܸ^z[T \\\~̙uIv("@dɒ/B,uKe_Zܸq[nvӶmѣG y㗤k999IMQDw^jY_D١?޽{Kd'ODEEihh{^^^NNNՒIJJᵓ9995H]QD(44ٳgƍ;@988300jEdBoff6x 0q>iٲeӦM_;KKZ("@Cbff&vH0`]蔄ϟ?ѣGFFF={?t颡QCQ뙘;vɓ'IMQDΝ;:u;Рu)335#J###ï]&M{ѢE]vҢ nݺ5bĈ׎egg  QNN)\l;;;/^EEE)rUV={\bE^:v쨩)^jW\\ԡCN>x@vDEdh )MoP*^tƍ7o޼qF\\\iin׮] jժ=zЅFeeeK,MM-ZB*5E-Z䈝hвAܹB066vqqݻY:w쬣#v)&&<055Ңm5n:**J@ӬY;wvԩM6DD BLLk',--k!b_}h p<4,˦WLiiuָqVZy{{9sBO:tQi!ך[nuر2YYY5GQD;pA w}wٲeO>MOO 8p ĎVϥ\xK b!2664iҖ-[^x!v3Vy󦹹__X"33V>Ai&++#GasqqbZ`Ç[@L K.]xŋ/^\ZZo$$$۷oѢEbgiXbbbA+F([[e˖}w;Kѣ>iٲe2rСC3gҥa۶mǍ|W"H$Z"G-Bddd-hrԩS۷oS, έ[5kɢǏSDYlѣ/\``` vzE*v---M섄TZ~`>>>wܹs?P(B:u}]~VZոq7IHH8rȜ9sTWA|޽&&&Uڵk/_F3R {ܺu~uڵkU ?;tĉNm6 vŋlll^;߮[.;;/~;88ѣ ;Nr۷ooذa:::ǏǏ޽{o:cllaÆ[nIK.M:ɓ W9 ʓ+V?~y|}}i!%""̬2-dA,,,j: Aׯ_XXXrr[LLqԞ #_hQHHիWAP6f߾} i-g222v)'|R0PJ#G\~φ $؉nݺUr"rePDkddu}|| ؉ȑ#Aشiӷ~e˖nnn}vQQQRRO?$BJJJ%ѣǡC –/_\o4#B߾}CBB333W77B///\ҹsӧObi]V",--k4O= d27|_~hٲ؉ԒT*رcZZȌ3o.ʝq,XqFջt^yŋuuu+?1OOϟ쯮*))Yz_=lذݻw177?}t+3ߡC#F|W5L#2\tiDDDNNuJJJ~#""Ǝkffֶmۅ ~3 ;u4sL??oGEEM:]vVVV}ٹs+W7+3#… nff֢Enڴ?-qĉ{9v- ""BCCvDH;"^tkܼ֬y؉Ν; . 3fƍNZbÇ+3\\\l``p#Ft0ƎWYtiBBBn݆.v(Ξ1csffs~ZuJDDĻ[lBȯEPwyׯ_ٳA.^(v(Ș5k}HH###===G.GDDUr>++KȯEz:u:uTHHȣG{yB!v.@Liii3fhӦÇW\GI$sew)((ԴTEd@e'Od۷߳gOQQعvܹaÆn:88xÆ ,;^-""BWWťfffZZZ5 x3 ҥ˧~ڲe>;PrsswѩS???ɳg600;*RxffuF("w駟̙s;ve֭>;PJJJ=:|pKK˅ v9**} gzzzV~>##"rePDTݻէOkZXX{RTt[gΜ=ztӦMǎ?tUt̄5"2t'--ן>}z˖-^|Y.x3iiik׮urr߿[>_uذabÛ ӧOOȰHDPPdddlٲ=< YYYAAAΝ+..ܹСCv%v:48w U ݹsg:ݽ{uo*vZSRRj4[=@P={oՕR2jNJJΟ?}mmmee˖bCػwܹsutt*yQtuuk4[=@ LvMe+ŋFFFRd;w)fڮG^=777$$[.''RdnR?~lddԫW={ꡨ͛׮] vssS ΈZP(,,,Νtʟxg^~u\.W/\)vڨQ#c(++ߣ"#####cccʌt`gϞxggk׮u֭g70 uZVV`lihhmVUJvvvnܸ1Q{^xqh͛EEEzzz..._kv444N ~yyyZZZ?ݽC}]7("ݻwUhT*;ؾ}{GGGggg'''Fii۷ܹ ---gggWWWeCbEҿf͚Yvvv?>JUPD+\|LLL,..YYMn۶m΋{yrrrBBB\\199ŋZRU߬yQwhb߾}ǏYr\__޽&LlEd@!޽* jmG[[[===S7D2,33ݻAаUՎM?>''Yf?+;;ܹs5ޠ d2YjjjJJJjj,>~XDbaa&ؘ[[[hBAaaaFFFNN333URAtuup;;;U;A___PoqZZڅ 謨(77۷oPDK MM6mڴi/m{ڵ)tttdsss+++壅Xߊ233233gϞ)Ǵ̔=zߖD܏$88/x322APEd@CԤI.]t|ږ(ǴLLLLLL5kz411i޼KuuukUBғ?|?̘1C"TtA("WEd6mjҤɴiӪvzzzy("@޽^VHOOР^[Y)UVΝ;+TczOK[IJJڹsw}רQ*/BM#2XYYɓ&O6PD~S 5bŊ7n\vMSSʋ({QD~#~ǯzΝ:txu=zTTTԪUj hÇO2eٲeSN}˥A`G7B?'Of͚_-==]SSj8("@DGGo6lΝ;%/naaK5Nn߾=hРN:OղfzzM,pPD1bD͏=_]RDP+..>uꔉI5L ("@ dffݻի-[)"WEdu:::gϞ533şy򤸸H*-,,,--UTR5RH$M4)?иqcccc}}}==ƍkh)꺘//f͚oM6XXXEdAAAAnnn^^^~~~^^I~~j\RRot ===}}zW&=}266600UՔU/7onbbbbbҬYO(9s?tuuX_RRRZn]sW(**,9V>d2ռvN&Mꥶje'j EAAA#Ϟ={yIIr^FEE)?]aaau7nlbbҼysl֬Efͪ(ogO|}}555kZ"2L&̼~VVփ?fff-Z011Uqc.~A$IӦMye鹼4eKD9%[AQ{φN.ϛ7o۶m[l3gN-\1%%ã.TPD4,wU=*̬[lٳgOsss+++壙VYYYYYYU<.:;;;##CvvvG+++DRjx„ 0aB-\ŋcG*( 3P#bccRRRTcRUU>ؘy|,(jJE::::99jՊvrC5f̘7n 2v.looV;W7("\e8...!!!66Ç ۗ/+ۮbGnryfff޽┍d'''ggge5YCCCԨ).]=zё#G:vXk 4hP~~~ӦMkEdZ***q͛7UG `dd:;;;::S~qqqqqqق (:tpss355;,\._z5kFg##ڼۗ/__("Ë/bcc######bccʔ۫mll?R%vڨQ#=yd„ !!!۶m:ujX` "##k"2*))$L֬Y={v1!OĨ~ XXX~={411;&^#99?===E0b==_~E5:,:::,,,,,ŋO>rvvvuuUzۡCmmmcIMMCttT*ڵG>}ΈGPڵk;w󳲲+I׮]+VE ׯRã}:u200;#ԉ\.OLLzjhhhllf.]TFə2eɓ'ϟf]]]( ##m۶M44444411QUJ~/v̙38Э[7qdeeYZZ?C$"2eff6mӳo߾۷H$bD=>TtǏMvȑO?tu~ҥ>}ܿZ,"2=~ĉNJڵ6lذaz쩩)v:4PɁ/^T(=z1b'N̙3ɓ'v9rqkӧO/,,!"2?~<00… ۻM6bGǏCBBO:cGGGe#wߥZ-Ν7f333/ȑ#bQK,++~~~W^mܸشiS)++xbppp```rrѣǎۥK+LqիWڵO>b'zOD-QDTb>,,{̘1;nܸ痖foo?qI&YZZKDFFN:5!!aժU;+ѣ[n>>>bQKbsZYYѯsaÆBܹ޽{Ձn޼fĈL&;]]7޽{kkk/YnAHLLtpp;bGd@?~7,,qҤI&L077;PJJJ~?ӧO?abb"v:w͚5+W>}ءRNN3g}%7cǎ;wfgg9r֬YbjCzz;>hܹ:t;T ɾիW?ydf244;k{VVVbgQKb lٲeĉiii+-d4666ׯrqq;}ӧ:411qɒ%u,BRRRFh!WEd@/ZСC+WLK% IDATK[f zL"H$SQzzz'Oyf@@Gz1xs >>~رaaavR,$%%mVj"25RW_}eggw͛7.XH\u-ޚPD"6lիWϞ=ɓΝ;7Ν;b%qqqcǎСCll?޻woCvډBQDTڵ۸q g̘+v(n۷˗>EFF߿}w 3fLWRbb)Ed%$$o̘1 JNN^bء:J"|7o޾}ÇUbbbFݽ{l??~(" ) ___WWׂ+W|F:pɀΞ=sNwwwSSSSSSww={( ՉD"xbܹA*Ο?u֭ZZ~}k)O)++[dŸqrss_x)a埨&O:5d[[[cc]ZAL{\U*:%EF1:L%eCjjlf}>>~sEa#6cNeD9FarVh*Ju]?훐Ļ?~_~]m=?]\\*9m||رc۷oohhغu~˝Uqww޽%KR iӦ}WokT */uR[3tFGGGQZZ9SRTT*Jө-**|-L[RܹsWT=_rBWLBBH|ŋK__+VrWgϞG|rϞ=:{;v-Z̝;+%%ѣ#F-dZ־}{n@cǎ;KڵkviÆ eOoB|nxx={aΝG)в69 UWQ;mZZ" DPAz'_Y%ʬXeT7|sƌ'OBU?|[c!DZZ7|Ӯ]w}ܹs+Vyeݾ}@UF޽{gϞhѢO?T,믓'OϏݻbB3g&'''''Ϙ1CJ^F@@en޼tR!ĀKBy;v?cۭWs 'N\dɡC󓒒/_.S?\xŋ}```> _Y%ʬXeT>LKK[~UUk߾Ϝ93~xc\xouppۻwĉoܸ;fCCCN.\BPD~I'Z-++/44T,OVyZYoxxBBHer=#?~ʕ1666BWWײO,XPvɓ'/_\`!ܹsϟ_ay2'K=}NNN׮] 0a?BeVW+wUƔ3mڴu_{0`ӦM||۷ٳu#G4jh#Fڵk XZjyyyr("۷oZZZrrr ,j:$$dݺuϟW(3gΔvU7n<~ȑ[Y"rIIɬYWPPl2sss-aaa ,HOO2dԩSMRPP0o޼ݻwߺu쥤ŋ\rUVFrvv~R }:-[?I^fJuY|+ҥK޽ӱcgQiׯnرc]v;KFQDDQ߾}h '"" EdZr蘘w}ٳgF^E$99YP899֣ ѣAj"fcm5}tZ]}U*5㜜 OOyyyyٽuFrrr֭MLLRQDm w @m۶ݻfff>_O777ѣ7|P3_r (77\Vkڴ";;[SDKLLLLLrJzy睡C_^rr,w"2h#ccもSZMo066v퉉 W^B4j/~N:ɝNyŋ;t w"2h# ,SZB1cX[[1VhuYjjjiiA:v/ȝjǏo׮΢E ۶m+w@Gƍ)))r. BPTl蘔y̙wuӨϏk_9::.Pj3^7Rٶm^z˝EH%jUi.7sT8,66-c_>}ŹɝEZ ;H]tuuNnݺ۷o˝/E''ĂԐ6mx-… h!~III:t;EsZȯ_rr⭷ޒ;HQOgرcƌi׮ͼUPPйsk׮ !Ο?_PPPvԩS4/^x͛_tdt[[[ooQFU#rʩS͛'^(*xFFF/;6mڴAEum{ycǎhB8uǼyΝۼy3g~G;vXhQVVBjYXXdgg7nm۶͍_dɱc*'LnժURRRٞqeƔԥKJ+*KT9r'Nppp;6ׯ͛RG ']vyzzfddصkbڴiSLꫯ֭˿ۖ-[tttXgϞw^\\w}׸qׯϝ;EǔT*_mQ JO? ۺu+-d:u]u;"۷{;;;7KKKk׮t&--MZ[4a„'NHW _HHY՞8_UV/\;˗/N2jI| ݻqԵklll9ҽ{w VVVfffovDDq/^ܫW/}}ݻwX1BqƍQ(cƌYrbƍUNrnܸѭ[?seW^N: B33> ,PTr'lmm3{yr؛o9cƌȓ'O !~*?144TѶmۗSXX(WhF>|NѣqZ\\AѠA7իWNT}Be˖ݼy3((hҥe?|0--mB˗/Wrw}wӦM.]***׿%Bcv}+W}ݻw8*_~i߾ fΜJ ر#-E~~~+Vغu͜9sܹ#w(F())_Ə?hР˗/ϙ3P\(ӝ;w;E]C|cǎ=9s~֭[O4)%%E\lrssڶm;nܸ.]_fffrBΝ;&wFVM>|v%K\vQ(r^u3foܡ,ʲ;KŽӠAI&]tiB}oŋB!!!ϟl2Z5Çh!W;vD .o޼ҥ˨QܸqcsCTFFF_~Ϟ=*o߾}Y>}tt ܹsΝWX!w"2T'N_~<9rlhh(w4*jׯ_m۶QF۷ArGË377ߺuG}$w"2:m۶mݺu'NhڴСC Э[z {nܸ1))79rQڵk'w.TѾ} p3335ĥK֯_cǎƍӧ}155;P^iiѣGwEss1G:::rK:ujTTTRRA WΝ;ؾ}YRRұcǾ}UPȝZ֭[R8**ѣG}4hPm:vأG@AINNNxx޽{㏢";;>ӳ{5;Eiii|||LLLddѣGU*Prrr5k'w:"2u{aTT޽{\ңGljj*w@5qqq111Ǐ/,,455gee%w@*v4hPnn.U Svvvll322=sss1බ-Zё7<gee9rD uEdR*y322n߾}֭[nݻwOP(5kfii٢E +++̬I&fffV޽+}y۷o޼%}J !4hмysͺ7h@Aoiit/R,uEd*CM)Yjٖ޿`ccclnni'KRMX:뽞TTT㼼ǏJ徔-XYYm7o\:l޼yƍe|5h 6|gn277;K]FcM7Wy[RRR zɚC===CCCaR,[Vyyyeh<(..>mBM/RZ:422̊߃Oqx ?~_\\G>|aii^isb͡4imذPPwQF&&& 4_P;?yS+&֫WORՓ;uL Xv!CRPGT5k֌5~rg("rg}&wPrgOAAc;`GdCMNNNIIQ(rg Zٳ۶m۸q#-׆P]ȝE#2j۷ƞ8qB څPedd|!!!rg.P[3777>>H8ڥPT#GLJJ:v-׏"2jZ=z}8pY8ڈ"2j_7o޹sg=΢("Yt /w#w[nڴi}_|!wPrg*þÇ_fB;V !** ի'wm#wv;f̘ 6B (" ڳg;#w<EdV())qƕ+Wα|+ jժufͬ]]tǧ4""A8x:(777%%˗333JظlU٠AS}J233SS|޹sGдiS{{{GGG֭[+ yc?nkkwf͚AP䔭;wN*9::kNS8;/+**Ҕ/]$HVutt䎌jVZZ:mڴ˗Ϝ9s޼yՓ;"2Q՗.]KLL>>//OWW޾SNo=:ݻ^ٳgRIN81x뇅999/"2&*((8vT;mӦT9uuu566;&T*SSS5##;J$ݺu{7䎉gYvIvesssPD?zhtttLLٳgJe-mn&Mȝ5WqqqRR~RiccӣGOOOOOVZɝ+''/ܽ{̙3{("䔙yСǏ|çO~qV<==ۻwoj{7nΝ3foׯ/w"TEdk?s޽wܱy7o.w:hǏ>|8222!!ׯÇGR~oooeQDZaaa۷oU(6;]nnC۷gϞwРA]vU(rS ۠}^iӦr'B5 x%222oկ_}lRh@yEEEGxx]ܹsg/oϞ='O.((Xh.wTꔕcǎ[8qaÆ~~~~~~z222;|*{ݾ}Klll 2dJ.\?~#G&MwߙʝՉ"2(Y&""~2dH>} Tљ3glٲm۶7n8;;ÇJ[I .\hћorʮ]ʝՏ"2ܼys͚5cƌ裏 TZ};vA7wߕ;W5uٳgϞ=PWQDTѣGWXk. cǎ=FPRPP}'Ovǐ!Cׯ/w… 3fiFDxteJKK7nᑕqk׮}wQ|g'NС×_~ٲeo&77Wh5۷GXPP}vZu;"*_]x7>ӧ;;; BB3|+WӦMk޼ܡQTT?/XaÆ ,6lǃ:"2T*նm۾ׯ=z̙r@B TXXzꀀS6jHPOQQO??Ϟ=߿ArC5kֹs>ZD@ ̾ 50~Z`b̙zzzrz>|rEM4irFTӦM۾}{~.\(wZVf+B TV^^ޢElll/_{ɝ蕸%KVXQZZ:gΜ & Б;&RT?}bbw^y[֭򲴴433իWTTt^VZãiӦM6XfM٢BP(%%%_}ۺuSNmӦM֭}tKiiY Sn@eOy___N:}wŕ\i*Rť춻Ϟ6>>~ر۷744lݺu~NbetҰaC77*\UJiӦ'knذa\jdjjpsεm۶gϞ}1^e˖9::.Zhϟ5k-dmƎ_>zعsN6@<ܵkWjZVDFF.TqKWuuuϜ93o޼;vhnKL0 INN666Oٗ'KKǏ_jUK;vRH3O4iŊ/4m=h!DAAӵk4W'NO?}͗bïze߿ǧWnꫯ*8qG֮]+nݺ|>l̙-Z;ǎ_urrˋeo!]v׮]Ǐ?tP^^^ttt^&&& _}׬YSv[^|yJҜB:tswtRKK˫WV2.Cj*SS+V$%%ݹsg -l۶mc޽)))gVsΕ]vyW޹sg hѢW9rK?>{U7m$7n\bbbaawٵk}tIJJˇʛN8ѯ_-[ڵ?۷h!CŽQRR2eʔ+WN>}٠58z  z޽cǦO?hNzxxĈn~#F!Μ9WW3gH3HL:U:xYfu)>>^x{{GEE3Fs2**[3I.X`Μ9/4S233=z$iҤInnSNgϞ}5P-BeVlɒ%e_XUifoo:8::+ٳO?mӦݻ[l)w*.. =x𠋋ԩS RC"2@! bccCBB &wմiӜgvر%ssܤ$'''Ď;gggVWݻgjj*P*{򌎎RfnIKKk׮t&--ήqw/QD\lkddTXXՐMMM* a7{5W̊eggKgJKK &y$%%M0ĉ1~~~!!!fff]… ~~~:u;s^_wϞ=JGݽ{;--ȑ#5\RWs,ںO4:::BR͙(ݢT*JJRTNmQQQm!Wf*ΝJ1*bB?wUggǏ/^W^w^bEW;;;SN9;;rǩXQQիڷo6cƌ7oB3PDm{ݺunnnr)O*ݮ]i6lPo&pppx熇kٳGakk+vYqȑr-KOOOQnci;PuEӦ !LLL EHHHW{5^W̊UF*y7g̘yI!^_SSӽ{zxxرcr?RSSgϞݦMǛܹ3--m֬YrGCMGZii!Cܹݮ];TO?B믓'OϏݻbB3g&'''''Ϙ1CJ^F@@en޼tR!ĀKBy;v?cۭWs 'N\dɡC󓒒/_.J/^xXv?Wx_2+V{hhhzzÇ֯_/|r v^rE8K.ԩ F9p@ |pFpBi[\̀*b)"WgϞ]vmtt|ͮ]zxx 4‚rM }:&<<NNN2aQQѡC8y&MHcccjyPN=dm6Z>ٺuJeRRRTTTddcJJJ܆ާOΝ;Vc`I@EDDlY 5\{VQ(|ADD_ƒG9rرcVVVz_}ssWxEdF )֥K~^9O>}ɣG|;ӪU+#EdF999rڝ;wΝKLL,**wttر!C~mggg===cOEipUC0,)b&Xq B"P,ڎ С SGajUM[ ELV)T"-$]F>g9{wP5o޼0PVVm:t>|ڶm[c"UQVVָqJJJjժv*$&&&ʋY>xEEEvv;Sf͚ :v8~]F6o޼` +_ѣǢE( *0997ݻwY_D޻w=ܓ{Q,jK,֭[z6oޜtq'NHKKݻ3<vpvDzѫWԩSag68p`=PEEř>}z޼yG7Y^ԁ_3g `ȑBr0aBQQ#<vJ Puܹo:thvvvY5Ǐ[ WF=}-ǎ{ٜ82Ӯ]ݻO<9>>>:jժŋo߾}|AJJJ^~z /={1cưa_"2@6o޼A͘1cĈag<'Nl޼>x]w͟?ʔ){ >m6i?OGٺuCZꩧZly[&lrݺu.rF>C=g^:..sT, :uԭ[_~oz ;a8p?EdV^^c=]v}NtU:p1c,Y߷oF5k^t;ڴism}ay>?;v4j(%%{6lXϚ|7M~Gu]?k֬PD;6y䧟~G3gLLL ;\|QF[n̘1&M_~؉JWYY?eee=)5,6\222{iӦ͞=uӦM+++ ;\,Yo}kС1b26Edo۶mw/^xa+ڵk{խ[ӧOoԨQءSD \wuկl|giiif*-- ;oٲezj߾ÇWXktMaD󒘘nڴK.#GlժդIv.Aii9s:vصkǏ]reNULeeeڵ+;;{֬Y ;vlvg̘1}wܸq]v ;C T\\O6?ر{=$$$ .ҷz{7 6rȖ-[ ¤ WbŊsKEEEwqǠASNsEr{^:zhϞ= ү_ڵk § QQQdɒ9sꫧO޽!CרQ#hYfٯo}}mРAع E^xaΜ9k֬IJJ۷o߾}jժv4|ANN… 5k6p!Ck.\p%RDRY~.\vڸ={{^&MŽdҥ .ٹsgbbb>}߽{ի\\r|7srr-ZTTTާO޽{w9666tTQvzrrr-[VRRҹsϲm۶aG"2ϑ#G-Z믿[G233SRRNǵȑ#ҥKsss׮]smׯ_~Z ;\eAiiinnŋsssWZU^^~GK*\D׭[WQQѺu̬;#!!!pRD dEEE˖-+//OJJ;w=666\e{.]?<}t6m233#㤤µ@+HQQˣ䲲o߾CagJtUVZ? 555Z>nѢEZ ɓk׮K *++222f͚p8qb͚5џ֭[ hڴiѡC&Me\=z薷;vEm۶"r񕖖lܸqƍ6lظqc~~~EEE|||ޡCn!P("pUڿ^^޺u֯_iӦM6A޶m1--Aa)--ߴiSsaÆ[x㍑oFFFF6mbbb U"2ׂ>(RJްaC|ԩ Zh=6m4A}G۷onݺm۶h<--nݺaG"2צ۷G۶m۾}wU^^AzΪ&l2...צݻwY88hҤIHNNNKKԎԩnlBv)%و=x`d ...))iӦIII͚5[h9ڎ۷{{~Ǒ}"}Eԩ xrr 8\u 8vXd={S;}td,>>EM4iܸqƍ5j=6n8!!!>>>g?C{={߿?A5l011#[rrrbbb\,p.tm#޽'|RVV^Y5k6hРVZu֍;i5.у9r$zzĉҳNȋ'OTÆ G͛7S%z ʡ _ձc:tf;̚riiѣGKJJ?9=e֭[ViڵQ𭨨8vXȑ#5k֯_?zpg=k"2ԩS'Oљg[Zj]w]4&&AgDuԩ]v>P("_/M/א#IENDB`snakemake-5.10.0/format.sh000077500000000000000000000000541361131222100153520ustar00rootroot00000000000000#!/bin/sh black snakemake black tests/*.py snakemake-5.10.0/images/000077500000000000000000000000001361131222100147715ustar00rootroot00000000000000snakemake-5.10.0/images/biglogo.pdf000066400000000000000000000073361361131222100171170ustar00rootroot00000000000000%PDF-1.5 % 3 0 obj << /Length 4 0 R /Filter /FlateDecode >> stream xYn4|o? !w&g.0hkKv7BJ-~/OL\-%~ăUj\ihC'+G4z5Jq]8>5e #땖;,{vTHW86a .ƵfFTfȆ(,::ڽsW?pyEw@. xt qKSTӲr}NƢVc%<'ⅴW 62 6ؑ<8БF ?`bvG"8k PʌDo]h֢'+ }AQ.NZK ىğ*z,Uv_Gd o&NL*O/ bduٚmSl 7zf bCEP?ǺIɸg9x@4:)UK > =qeX?`kbJDD]:@RQdobF%o3JdN0~ޅHQg$;mb@`&"TԋÖ_etzY^Mbā)`$jv :7"B9ycO:LG4ߨ05N*/oa3$>*͍PYqQt8oI°Zub:s'r_,M:H?D~c-K ja-5VC7Mf"_mROխ菍 p& J "nVBoD`MTIr]F9Sc{u],I1}4]pB--v$^Y5\/D!VId6~0bz]cT|L:ML}%KX8Pˆ ?N5|1]ti`xuǏGF_Ufl-ITV #:[=,!H.R!tڡ'I D8oA`Ut$l"JYCY$)B@}^#/S]^o܁N缇 nӤ?2.Vؑ)~ rwRn<dQ(F [ 3;]*Xgo"J_M9u#R΃Yȧ_`[.AyK]rrL<I"ARlwx Q|~(䰤s]>]=II4:\vGSo HJF]潉bHvt?3Z AһvX<1f )!uj<+>ctwv1gv=N-4vaMvbk D|KUREBuOT/ԎGhqk~h7h9+mXAr5:nnP5xm]PȪ5y({K_vNDFV7j@DpnIދ$C4ɔNQgz_q?v;k\& +1pԲy?θg_}(~obSO2crIo2[\L+a~_]z\ x,߃LEhՁ\ RgT G"\j{g*qDn1ݼT%PK`.aA1){ã}?v?ac0rC +?ז&oo Ӧ!#ʅ _hߵp(?)JMgY{_X 䪨22?a endstream endobj 4 0 obj 2906 endobj 2 0 obj << /ExtGState << /a0 << /CA 1 /ca 1 >> >> >> endobj 5 0 obj << /Type /Page /Parent 1 0 R /MediaBox [ 0 0 210.587494 27.861328 ] /Contents 3 0 R /Group << /Type /Group /S /Transparency /I true /CS /DeviceRGB >> /Resources 2 0 R >> endobj 1 0 obj << /Type /Pages /Kids [ 5 0 R ] /Count 1 >> endobj 6 0 obj << /Creator (cairo 1.14.6 (http://cairographics.org)) /Producer (cairo 1.14.6 (http://cairographics.org)) >> endobj 7 0 obj << /Type /Catalog /Pages 1 0 R >> endobj xref 0 8 0000000000 65535 f 0000003320 00000 n 0000003021 00000 n 0000000015 00000 n 0000002998 00000 n 0000003093 00000 n 0000003385 00000 n 0000003512 00000 n trailer << /Size 8 /Root 7 0 R /Info 6 0 R >> startxref 3564 %%EOF snakemake-5.10.0/images/biglogo.png000066400000000000000000000416661361131222100171360ustar00rootroot00000000000000PNG  IHDRd;;sBIT|d pHYs''G܉"tEXtSoftwarewww.inkscape.org< IDATxyxŵ=۲-`2ffd'Hn $7HB,@H%YI`%&;C0e$M11TOl`\ކN?FFFo0uR h4Fь>ȷOr  ` 3b"z^ex|u:^a@*2v8;$-DtC<41{l0{D4?afxxxc&JVh4F3jH"ge XWWٹL%8N{X:gΜXbG)4hklzWK(Kh4F3P"oi|jV"IwwEj̝;w6 cøf780CK^$ JӯPFh4fa8ˋ֓Dt8 +_ǼyV0 a^w \yw\yfggSN$fuu{xzԩ$^h4FS >Hԡw}7a룺ztX{ywyfCCî#+]zT*U_}Fh4Q. r"<ɜ7?)e) 6W1?7|󻝝7 c ,RR_EKh4FO0Y58Nb7oSNg0!]vN;"t/wYSN9ؐ4i4Fh4ea/}!jQCT)|<M&7{\f2wyN9]_ڣh4FDB,_V3#@-t:4o@*'<5 c23` 3j"z"Zt:QxU f I4Fh4ȈM0IJ٨RSSF"_%o2<4Ǐ+5Fh437K[4:˭Ch4FSRT(h4Fh40h988D$j4Fh4MAz6 1ttt/>!| 3O%4 @?_2=eD@מj!Jk֬0yO07A.%wo5o_իW?~̙͆3nٲeMD fv0y ^2go2>O<SLa}L0."{̼hWW׋-؃a|̓DD] f̘1ajbOfޓv#3o ;& `-ADo2yQDSnc?g `g8Ϩ?gx@^g旘e"zvg0 }$7/Byu݃Sk$=q^(t|GG1000@cNP̷d2PI̙3 `*1!b-G4͞Cv!.BK)y)BIֶ;3$07o[akdr2ED<_,ғ&M?wܑ mmm[<LB']  "/}]]ݟ;;;FXu T׿GFFn[ň e_4Iu~0zjzB,p@B,04ǫ17mڴJ}&sΜ9u˗/6c<"zJJg-OԇJ7Dl;daMD" `ۈo(%A1&Nl۶~CD6SǏluGxBafaƧ929f?B*V*˲NaocDCq:"vR^g!ݟ1_B A-䷹f.vlcĘ 4Bk 9?cfLu>EL&waSW˅ID6{``y!F Hظqe/@u݅adD."KqKקYRgBaK fݩTj\Ћ$!/>Q%- ;ض}bUfM7d !iiiEJ566D$*Eo޶m%ILpy6eY'@u }bia'bV\*)HDt]r 3gFT*eڶ} 3 %RDt(mۭlq%|FYccd2Kń?ZJZXjm=(bADeMRA y11TF84Ϳ۶Q̚흲N(8γaZb%f؇BBuBf=5hԱm$f~@?nUЖ,Ku"fWUYyq?ht0`(åT 1!.A@f>B#-`lrݿc ܚJ-ZBLaKDw QXڵk X%,oii)ؼ WTTAO05{oVߖ[D;-m[ymw/w!Q퍴E">ޓ_U툨϶Yjll#-DYHbL&pJ؉/˲"T6SZ5ZB| +WZQ3öh>ǚYNDtmۥB|-K[*/ !PnUʎ|mAjrU?sm;b}BoB^n!!~sֱf>6fscl۞.`J@D !*8ihhiEG&K\RԎbB3 `("{I)=c`:dr*nT ƒ,R gG e"3wRJe= Й*֨m?-ڙom& ]MֽAx&?'rR&PֱrʞpC* A%|^I23ǍwE׮vl>7Sdfm~N5 -=:U!^7淶nA1"z9wŗO1f̘1VJy;Ԟl_%3gA7jnnr۠v"z"f~07rX,6LVJaM|iA5x6=ϊF)3i/0[㼅M<Θ1cl}}(G|˲d2NJl6{53-@ 9&Rށ{Mp7eCD 0.fV['?$6o"`i]8΅;ئj&W $Oc.%߄p0_  뺟*R2Ig; ø0l|3{vu B$t[̧e27dMŲ܀\m)f54/)$,"f>?<\4̜9sۆ&OܹsG,:uߕ?-6mhf R5H[3_ܜdZG{{\| Πu YVATB"24MKu*fᒈn2 ]]]I1:PI":q>Orϟ -yg5"isf>8<-a"ODOzʕ7#)O0y몔BD"`FXҮL(ض̙Yx*gΜm}}-f(J0~( ] ;%!Otr;Əh 6/8uEUnm;+x<[.A Q 뽡2󾊡Þp!;m{ofx|Va/340w@q[rA9Ejۓ~ m:W ": % jzg9q?#t:뺟! ~8J= b<;TI|[[|]ХRc\׽,ʝx<jTvc YMDIu]63A9qƝdX,vXI<8\.NICCC:4LD8r%8WUwMӼ8I∼G|$$f;IrQwww?̇},f޷#Zl=㐰5o1b!kBkm>YO\=?J{&q^t`gsr\=g8s%><~ Ub0p\GcؑυkSxwՎ/rU럃N9:Ͷ$EA0(ϔZ+U8~_8442o<Ƌۻ<(}w,%*8ι((q:sKN"Dv䪦Jfv# /㢯oeؚ6F&yflzA"(!v5ff !BNu゘nI,\׽*044$) /{)Y(%UbgFm{:f>yس^pB___%u[CN?NWW{|!iwp< B۶}m?,KFRHr_nT1 C!GL0M3WUoQ1t]׿/GoCu;^htvv.#"%20ΎJO088Rސe.G)q?(yA',y;-t<YI"hBܚgUe(xw|hCǹ D4#OYLf,y&39̼\Bw(RuU5C MR]j|O2 h*l`Cjka|>vh _qL$GH+!x<ޅ3ǕBBi&(I\KB 7)fΜm,@ҡOTlb؏Kyjs2 ^ZFK\#YUïU9-B<$F #r*UJlvT+gWArRz! =c普b*F`fTa|},.͏WꛦOJ A栨T+}}}oQg1%%B=d6#XiJ7sQQqgBNhoolqf>m%AX,n!D[S~[ӛ̼TBPāU?Ķl6 s—2ss9݈L,vuFͪU\'G&& \/٠4ixXj;;&BRv:RK)B lp|)62.JO*e}y'[8d$\_17Bf_,T)y-"dI N$5+MPNIm{'"Xb0(u a*&*-AI)Uv4Nhnn2*<5`2w܂LFaIjBYm6] e1#X|u{ꑑYӳ |2PIdq]2fXv%kbeuy4B IDATE*V˜:omm= itww6 !vUy'uGnXIM˹ZFD 5:Dͻ| WÐK|'1e3#ؐi6F3`)MMMӉP|/1 Z[[,&tԚb!"B[oWe`Bn8\8\>J%A|WTB?*%rK)z ŰUz$*U7onF3 3gNB"J-R?22rpP d2-a߃B!UR;RJ=>4HnRdB"¶OC=fC|0*M|0*-a@DwdJq1Yۻ"V sff)x70ڶfwLlҥKߓP/TV$,a\|M.m{ofnpc7*Nb;ض^^--IclaY]̬b9 ~_$"JCCC0WpZ&Q᎜D"1^ !oKѫvPJ0s)mf^ETQ ѭV̙35Ms;"jb P(Rʂf=KeY;{׃`֣0 i"Q.X@f-Tq^p+㍍GU# '0|r(P* !H4bCh:|y/[z蔲,cq!V2%f̘165@*wԍQWWw03gf候U,(whnn*dTM3]ȪXu0*~Bl̇p`{W]9P./dr*32scJ8Ǵ!ygław)3AD>X>Y)ܧ=Ӥ10O?F*nՓ[1W_dv5 @TrbVP8XjZZZ&b 37>RNUY%WS*j &(늛a2*nY𙢨8W 0 t`抛󻣟`8*2/-L&OR `j,0iҤ/UM a "y;D~#pWW׋^z]DcuuuLRo062̧ضߎ\=BD*;ӕԲlg3%!FFF*Ku0yr9;_Z5$ICV%:eUK\.ʜK)+Pɍ4ͲB ?R1eYG mȔf9_R΅9&7#*ps=m).R:"RW$e7{Փ'.]:0cy&|PDfm;Sik-BAD>U7UID@5v^CCgtEH:RG"LQsMDT%Vr:0KL/ QE O1Lw k +ʝ}1eO7$}p|3pV7F=i2p5T#KUeOJYqI^EjfuSS*"X%]"*{"ߣXvmU.---9'˭H*rp0jf>5-(llޡJVS)Y8ί]=pOH>ҶSBV("`Y֑MUW8s%LՐ3W\0sՔ4hB|4'PI|%$5WC%(PlUO%< `W81(sKmMp,tüb6G+Dt8-EF}Ah5HRlz5UV# J)QF^nF W7 fWsmDž$KSBlW$|eYJ49fN+F"vGGGR XvjE~}^Ǘض43NdCJy&Ψ0e̗#ZDž,#VYBI)-"E8-lmm=VxFr73j@VϜeh-30s}FLeBD_|F"&WD~-˪'HRf:֦ !ġG8I8%aoy Ba\ AWavBz퇦4H`.ODDћ\͑ϟ:{@j߭2g鍍9s|סl@"L/eAD> ui%Ns#a>"r Ø_+" 3 vp]5}3ga*f^24Bޘ`J 3-̋H 57o;0,lLr$ 3R2224gH$jlM%@D m\S+Dkk1PyYQ0jR/XAjH)3ma*T3L=---gم[jd<{"<[5H|,\UqnE۶-;X[[^eb@J$]Q+Mq𿯕`X,aXu3[wXU+/! x< B, d4TBcY֯ܗ_H縉_^Dt!3_fX@DOxDdr'ع}SuWSFLӼ6]'!ĩ|y&!L.o৘F"y{\]d<&3եQ(xl6;ؑ#!č|V[mt:텥}b.;@3V>NT+mB4UVF?a"!5]۫Zow?~|w2\f9"vG ^&2؜9s>?w %NjkkKx/s$+x\]JeX,v$3f3LTzj0 Bf~kѻcND_f+cDXJb<3NoIssslm>M7K) s~ ߘf`\qM۶)|4v!,T/ jii5̟Ж.[mUG6 iTBAU<>lٲͩ~e-eY "z@1Ng B9;;;c3嗩Tjl6;vb~<gRl{>!Tn(BǹT7ӄKCCelsr 9!R"z߅V<tQr]EhuӶOUe&K)O&'uww?t:6 Aq:&H1sjf!xJkLȏ###JRIӞ\ D.j!2xem7l:B::q]:˲VџWZRԬt:`d2'dBJ"M`faddBa+72yB6m6KŸD 3a_@Ck N%Ӡ jsۭ x%\'?DYL-]L5p\.w e2;J),-U\.w"|Ѩ^3z]SeCXQw?oCZ)nКQOANfwٶ.~HJy -Q ~Ξg~1*i}FC"L&svuXҒw:@Guu"}XKoCDwGet>h*x<~ r{zz[HSL1s_*H/'/`g2wDQ ΁NBsoMD!>.(1( tڋ)[0ur rt6?*wq mfv 2P;?w]7Sn1q<8/|?Wn18R6C;oBr܌|¨2O8OJ뺿,蠿?;}QEbش2jir_- v|C[<?Bv5ؑ0@444ĺPiMWW{LuO;1(%~KDe22̓) oT_X*n<?8K!t|oz[uJغ'uc(,=GaLq]`uyܺ48Ώ_0u͛V=p]w |?"ZںgȲ> _"y}<y^sbL&t]7aMW/TfӈDfn,Ɠ3 V09d GStuuKB_vDg:bAi?D"ay{ uDtnIDATK) }p.wk`-^z{{{+fae6 yH RN W˲Nd2GG5md;"#]"woq!d'/ʿ~a,Z":y*hw<̏ʕ+D@ Lvi|酎'Q wW2^ڶ3@t󉨷.nX:~Qc}"qmBJiQ3d %OѣDpw\T I˘1c_fM(y <9BJYMiwd8!΀okx|0 9+dl, |3*,bbʣ{zzDDJe (U~bDaͦi f> d C0]y` Փe74v[o$[1'/x^J\&DBS]022raM|3LD {o5̼=y@OOKe45F~!d?"FD{'wɿkue>[+^RDD/+Mgd65Dq~Rgnj># T*!|3 =+Dc wCρȿf7%/\h&(RIENDB`snakemake-5.10.0/images/biglogo.svg000066400000000000000000000254411361131222100171420ustar00rootroot00000000000000 image/svg+xml snakemake-5.10.0/images/logo.png000066400000000000000000000062121361131222100164400ustar00rootroot00000000000000PNG  IHDR>asBIT|d pHYs`.tEXtSoftwarewww.inkscape.org< IDATx}E?{kARZ EʛV@J MH*&!AQF 5Tbihk! `*_@l3۝v>%>33/;3Oe D:K@Éh8Q ' D4(pN@Éh8Q ' D4(pN@Éh8:]c>!*U6`ytrQ1fW4@`~$"D=s(\2"Y>^V` ^I3p0^.U$Ƙy%.{8WD~V]ƘwMX,"Wc fT4!"*Om0$ym<*12si"r{Q(2<|ߺc^cXSs.pg{K=۴b |m`1f6&x`9Xm%oAa- ֎1K-?yXC>ry :#D)OP;sD^I54u"2u3\X("Ls0[Dj뙇nJQ>MFcի6?;c "q4ѪB0cNd|ADұlȟK-?H%{8[DƲS ]Oj%uvnaWX~8K"^l-J@BD>2"r?hL>0L#"rb40so4V:peǒw2ڱy C' b%KD )Ѱh6ɫ \< 3}FD6* {m1SOrހO0גs]Z)>R.VA)MVUE/^Py}sY6ntcϕu ^\+oV~婌h7}ξ @/BB๚Np}N6!:?L|`]!P0tim.EV`$@=ξ qvVd -])QZ9޾xOI+m{ [vK3` CPm{;>n< i!h'!M^)k.&Z<@B`}VƳm|x=nyI4]9gP\)N<8}(tyvLZyʴi>Zx (Ԗw&Mt6JxvVdmU܌uiZY}%31'9QǹvBN }x{k,[R4[(@D#?p4s 'duI̪pYk^E!C]JQ@q4B2zdɒ3.\} c= 0)ylÐT@ƍK5tнQlƘ&",c{T(MJ< ܪd3)ar7/ݏ׍؂I-k/`9 8Gފ~7$yuiV DܞXlTf+1.&1&U~,"ulvvի+\2[5@D6c>K}4N%1{1yCھ<b3yt:"BW,]//lg"s ˲_*3tD >mK{77]Uy I_$KFeckq@@AP,8tӗȿP@XtiȲ7I'O|],Hx1@___ߞ+Vxԧ#'1 iZS<$Ivm? It꤮;,:(sx@2'.Z۾@Zϟ$Ȳ$Ivڲe>ˉBZVoOOύnu_Y`b_eEt!VJ,|֬Y ʲ4`kG*+3.\8}ԩ-[eN@ÉM@Éh8Q ' D4(pN@Éh8Q ' D4(pN@Éh8Q ' 7:XIENDB`snakemake-5.10.0/images/logo.svg000066400000000000000000000102321361131222100164500ustar00rootroot00000000000000 image/svg+xml snakemake-5.10.0/misc/000077500000000000000000000000001361131222100144575ustar00rootroot00000000000000snakemake-5.10.0/misc/vim/000077500000000000000000000000001361131222100152525ustar00rootroot00000000000000snakemake-5.10.0/misc/vim/README.md000066400000000000000000000010711361131222100165300ustar00rootroot00000000000000A vim syntax highlighting definition for Snakemake. You can copy the `snakemake.vim` file to `$HOME/.vim/syntax` directory and add au BufNewFile,BufRead Snakefile set syntax=snakemake au BufNewFile,BufRead *.rules set syntax=snakemake au BufNewFile,BufRead *.snakefile set syntax=snakemake au BufNewFile,BufRead *.snake set syntax=snakemake to your `$HOME/.vimrc` file. Highlighting can be forced in a vim session with `:set syntax=snakemake`. To install via Vundle use: Plugin 'https://github.com/snakemake/snakemake.git', {'rtp': 'misc/vim/'} snakemake-5.10.0/misc/vim/syntax/000077500000000000000000000000001361131222100166005ustar00rootroot00000000000000snakemake-5.10.0/misc/vim/syntax/snakemake.vim000066400000000000000000000045671361131222100212700ustar00rootroot00000000000000" Vim syntax file " Language: Snakemake (extended from python.vim) " Maintainer: Jay Hesselberth (jay.hesselberth@gmail.com) " Last Change: 2019 Nov 22 " " Usage " " copy to $HOME/.vim/syntax directory and add: " " au BufNewFile,BufRead Snakefile set syntax=snakemake " au BufNewFile,BufRead *.snake set syntax=snakemake " " to your $HOME/.vimrc file " " force coloring in a vim session with: " " :set syntax=snakemake " " load settings from system python.vim (7.4) source $VIMRUNTIME/syntax/python.vim source $VIMRUNTIME/indent/python.vim " " Snakemake rules, as of version 5.8 " " " rule = "rule" (identifier | "") ":" ruleparams " include = "include:" stringliteral " workdir = "workdir:" stringliteral " ni = NEWLINE INDENT " ruleparams = [ni input] [ni output] [ni params] [ni message] [ni threads] [ni (run | shell)] NEWLINE snakemake " input = "input" ":" parameter_list " output = "output" ":" parameter_list " params = "params" ":" parameter_list " message = "message" ":" stringliteral " threads = "threads" ":" integer " resources = "resources" ":" parameter_list " version = "version" ":" statement " run = "run" ":" ni statement " shell = "shell" ":" stringliteral " singularity = "singularity" ":" stringliteral " conda = "conda" ":" stringliteral " shadow = "shadow" ":" stringliteral " group = "group" ":" stringliteral syn keyword pythonStatement include workdir onsuccess onerror onstart syn keyword pythonStatement ruleorder localrules configfile group syn keyword pythonStatement wrapper conda shadow syn keyword pythonStatement input output params wildcards priority message threads resources singularity wildcard_constraints syn keyword pythonStatement version run shell benchmark snakefile log script syn keyword pythonStatement rule subworkflow checkpoint nextgroup=pythonFunction skipwhite syn keyword pythonBuiltinObj config checkpoints rules syn keyword pythonBuiltinFunc directory ancient pipe unpack expand temp touch protected " similar to special def and class treatment from python.vim, except " parenthetical part of def and class syn match pythonFunction \ "\%(\%(rule\s\|subworkflow\s\|checkpoint\s\)\s*\)\@<=\h\w*" contained syn sync match pythonSync grouphere NONE "^\s*\%(rule\|subworkflow\|checkpoint\)\s\+\h\w*\s*" let b:current_syntax = "snakemake" " vim:set sw=2 sts=2 ts=8 noet: snakemake-5.10.0/motoState.p000066400000000000000000000054241361131222100156710ustar00rootroot00000000000000}qXtest-remote-bucketqcmoto.s3.models FakeBucket q)q}q(XnameqhX region_nameqX us-east-1qXkeysqcmoto.s3.utils _VersionedKeyStore q )q (Xtest.txtq cmoto.s3.models FakeKey q )q }q(hh X last_modifiedqcdatetime datetime qC   qqRqXaclqcmoto.s3.models FakeAcl q)q}qXgrantsq]qcmoto.s3.models FakeGrant q)q}q(Xgranteesq]qcmoto.s3.models FakeGrantee q)q }q!(Xidq"X@75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06aq#Xuriq$Xq%X display_nameq&h%ubaX permissionsq']q(X FULL_CONTROLq)aubasbXwebsite_redirect_locationq*NX_storage_classq+XSTANDARDq,X _metadataq-}q.X Content-MD5q/XW8jMiY+lhpgarIV37vixHA==q0sX_expiryq1NX_etagq2X 5bc8cc898fa586981aac8577eef8b11cq3X _version_idq4NX _is_versionedq5X_taggingq6cmoto.s3.models FakeTagging q7)q8}q9Xtag_setq:cmoto.s3.models FakeTagSet q;)q<}q=Xtagsq>]q?sbsbX_max_buffer_sizeq@JXvalueqACH0 1 2 0 1 2 0 1 2 0 1 2 qBubX prefixab.txtqCh )qD}qE(hX prefixab.txtqFhhC  &qGqHRqIhhh*Nh+XSTANDARDqJh-}qKX Content-MD5qLXnNjTuf4V0VmDLLoZuDND4Q==qMsh1Nh2X 9cd8d3b9fe15d159832cba19b83343e1qNh4Nh5h6h7)qO}qPh:h;)qQ}qRh>]qSsbsbX_max_buffer_sizeqTJhAC$0 1 2 0 1 2 qUubX prefixaa.txtqVh )qW}qX(hX prefixaa.txtqYhhC  & qZq[Rq\hhh*Nh+XSTANDARDq]h-}q^X Content-MD5q_XnNjTuf4V0VmDLLoZuDND4Q==q`sh1Nh2X 9cd8d3b9fe15d159832cba19b83343e1qah4Nh5h6h7)qb}qch:h;)qd}qeh>]qfsbsbX_max_buffer_sizeqgJhAC$0 1 2 0 1 2 qhubX ab_cut.txtqih )qj}qk(hX ab_cut.txtqlhhC  '.IqmqnRqohhh*Nh+XSTANDARDqph-}qqX Content-MD5qrXnNjTuf4V0VmDLLoZuDND4Q==qssh1Nh2X 9cd8d3b9fe15d159832cba19b83343e1qth4Nh5h6h7)qu}qvh:h;)qw}qxh>]qysbsbX_max_buffer_sizeqzJhAC$0 1 2 0 1 2 q{ubX aa_cut.txtq|h )q}}q~(hX aa_cut.txtqhhC  (qqRqhhh*Nh+XSTANDARDqh-}qX Content-MD5qXnNjTuf4V0VmDLLoZuDND4Q==qsh1Nh2X 9cd8d3b9fe15d159832cba19b83343e1qh4Nh5h6h7)q}qh:h;)q}qh>]qsbsbX_max_buffer_sizeqJhAC$0 1 2 0 1 2 qubXout.txtqh )q}q(hXout.txtqhhC  ) ?qqRqhhh*Nh+XSTANDARDqh-}qX Content-MD5qXW8jMiY+lhpgarIV37vixHA==qsh1Nh2X 5bc8cc898fa586981aac8577eef8b11cqh4Nh5h6h7)q}qh:h;)q}qh>]qsbsbX_max_buffer_sizeqJhACH0 1 2 0 1 2 0 1 2 0 1 2 qubuX multipartsq}qXversioning_statusqNXrulesq]qXpolicyqNXwebsite_configurationqNhhh>h7)q}qh:h;)q}qh>]qsbsbXcorsq]qXloggingq}qXnotification_configurationqNubs.snakemake-5.10.0/setup.cfg000066400000000000000000000002121361131222100153400ustar00rootroot00000000000000[versioneer] VCS = git style = pep440 versionfile_source = snakemake/_version.py versionfile_build = snakemake/_version.py tag_prefix = v snakemake-5.10.0/setup.py000066400000000000000000000044171361131222100152440ustar00rootroot00000000000000# -*- coding: UTF-8 -*- from __future__ import print_function __author__ = "Johannes Köster" __copyright__ = "Copyright 2015, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import sys import versioneer if sys.version_info < (3, 5): print("At least Python 3.5 is required.\n", file=sys.stderr) exit(1) try: from setuptools import setup except ImportError: print("Please install setuptools before installing snakemake.", file=sys.stderr) exit(1) setup( name="snakemake", version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), author="Johannes Köster", author_email="johannes.koester@tu-dortmund.de", description="Snakemake is a workflow management system that aims to reduce the complexity " "of creating workflows by providing a fast and comfortable execution environment, " "together with a clean and modern specification language in python style. " "Snakemake workflows are essentially Python scripts extended by declarative " "code to define rules. Rules describe how to create output files from input files.", zip_safe=False, license="MIT", url="https://snakemake.readthedocs.io", packages=["snakemake", "snakemake.remote", "snakemake.report", "snakemake.caching", "snakemake.deployment"], entry_points={ "console_scripts": [ "snakemake = snakemake:main", "snakemake-bash-completion = snakemake:bash_completion", ] }, package_data={"": ["*.css", "*.sh", "*.html"]}, install_requires=[ "wrapt", "requests", "ratelimiter", "pyyaml", "configargparse", "appdirs", "datrie", "jsonschema", "docutils", "gitpython", "psutil", "nbformat", "toposort", ], extras_require={"reports": ["jinja2", "networkx", "pygments", "pygraphviz"], "messaging": ["slacker"]}, classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Science/Research", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Programming Language :: Python :: 3.5", "Topic :: Scientific/Engineering :: Bio-Informatics", ], ) snakemake-5.10.0/snakemake/000077500000000000000000000000001361131222100154635ustar00rootroot00000000000000snakemake-5.10.0/snakemake/__init__.py000066400000000000000000002552371361131222100176120ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import subprocess import glob from argparse import ArgumentError import logging as _logging import re import sys import inspect import threading import webbrowser from functools import partial import importlib import shutil from importlib.machinery import SourceFileLoader from snakemake.workflow import Workflow from snakemake.dag import Batch from snakemake.exceptions import print_exception, WorkflowError from snakemake.logging import setup_logger, logger, SlackLogger from snakemake.io import load_configfile from snakemake.shell import shell from snakemake.utils import update_config, available_cpu_count from snakemake.common import Mode, __version__ from snakemake.resources import parse_resources, DefaultResources SNAKEFILE_CHOICES = [ "Snakefile", "snakefile", "workflow/Snakefile", "workflow/snakefile", ] def snakemake( snakefile, batch=None, cache=None, report=None, listrules=False, list_target_rules=False, cores=1, nodes=1, local_cores=1, resources=dict(), overwrite_threads=dict(), default_resources=None, config=dict(), configfiles=None, config_args=None, workdir=None, targets=None, dryrun=False, touch=False, forcetargets=False, forceall=False, forcerun=[], until=[], omit_from=[], prioritytargets=[], stats=None, printreason=False, printshellcmds=False, debug_dag=False, printdag=False, printrulegraph=False, printfilegraph=False, printd3dag=False, nocolor=False, quiet=False, keepgoing=False, cluster=None, cluster_config=None, cluster_sync=None, drmaa=None, drmaa_log_dir=None, jobname="snakejob.{rulename}.{jobid}.sh", immediate_submit=False, standalone=False, ignore_ambiguity=False, snakemakepath=None, lock=True, unlock=False, cleanup_metadata=None, cleanup_conda=False, cleanup_shadow=False, cleanup_scripts=True, force_incomplete=False, ignore_incomplete=False, list_version_changes=False, list_code_changes=False, list_input_changes=False, list_params_changes=False, list_untracked=False, list_resources=False, summary=False, archive=None, delete_all_output=False, delete_temp_output=False, detailed_summary=False, latency_wait=3, wait_for_files=None, print_compilation=False, debug=False, notemp=False, keep_remote_local=False, nodeps=False, keep_target_files=False, allowed_rules=None, jobscript=None, greediness=None, no_hooks=False, overwrite_shellcmd=None, updated_files=None, log_handler=[], keep_logger=False, max_jobs_per_second=None, max_status_checks_per_second=100, restart_times=0, attempt=1, verbose=False, force_use_threads=False, use_conda=False, use_singularity=False, use_env_modules=False, singularity_args="", conda_prefix=None, list_conda_envs=False, singularity_prefix=None, shadow_prefix=None, create_envs_only=False, mode=Mode.default, wrapper_prefix=None, kubernetes=None, kubernetes_envvars=None, container_image=None, tibanna=False, tibanna_sfn=None, precommand="", default_remote_provider=None, default_remote_prefix="", assume_shared_fs=True, cluster_status=None, export_cwl=None, show_failed_logs=False, keep_incomplete=False, messaging=None, ): """Run snakemake on a given snakefile. This function provides access to the whole snakemake functionality. It is not thread-safe. Args: snakefile (str): the path to the snakefile batch (Batch): whether to compute only a partial DAG, defined by the given Batch object (default None) report (str): create an HTML report for a previous run at the given path listrules (bool): list rules (default False) list_target_rules (bool): list target rules (default False) cores (int): the number of provided cores (ignored when using cluster support) (default 1) nodes (int): the number of provided cluster nodes (ignored without cluster support) (default 1) local_cores (int): the number of provided local cores if in cluster mode (ignored without cluster support) (default 1) resources (dict): provided resources, a dictionary assigning integers to resource names, e.g. {gpu=1, io=5} (default {}) default_resources (DefaultResources): default values for resources not defined in rules (default None) config (dict): override values for workflow config workdir (str): path to working directory (default None) targets (list): list of targets, e.g. rule or file names (default None) dryrun (bool): only dry-run the workflow (default False) touch (bool): only touch all output files if present (default False) forcetargets (bool): force given targets to be re-created (default False) forceall (bool): force all output files to be re-created (default False) forcerun (list): list of files and rules that shall be re-created/re-executed (default []) prioritytargets (list): list of targets that shall be run with maximum priority (default []) stats (str): path to file that shall contain stats about the workflow execution (default None) printreason (bool): print the reason for the execution of each job (default false) printshellcmds (bool): print the shell command of each job (default False) printdag (bool): print the dag in the graphviz dot language (default False) printrulegraph (bool): print the graph of rules in the graphviz dot language (default False) printfilegraph (bool): print the graph of rules with their input and output files in the graphviz dot language (default False) printd3dag (bool): print a D3.js compatible JSON representation of the DAG (default False) nocolor (bool): do not print colored output (default False) quiet (bool): do not print any default job information (default False) keepgoing (bool): keep goind upon errors (default False) cluster (str): submission command of a cluster or batch system to use, e.g. qsub (default None) cluster_config (str,list): configuration file for cluster options, or list thereof (default None) cluster_sync (str): blocking cluster submission command (like SGE 'qsub -sync y') (default None) drmaa (str): if not None use DRMAA for cluster support, str specifies native args passed to the cluster when submitting a job drmaa_log_dir (str): the path to stdout and stderr output of DRMAA jobs (default None) jobname (str): naming scheme for cluster job scripts (default "snakejob.{rulename}.{jobid}.sh") immediate_submit (bool): immediately submit all cluster jobs, regardless of dependencies (default False) standalone (bool): kill all processes very rudely in case of failure (do not use this if you use this API) (default False) (deprecated) ignore_ambiguity (bool): ignore ambiguous rules and always take the first possible one (default False) snakemakepath (str): deprecated parameter whose value is ignored. Do not use. lock (bool): lock the working directory when executing the workflow (default True) unlock (bool): just unlock the working directory (default False) cleanup_metadata (list): just cleanup metadata of given list of output files (default None) cleanup_conda (bool): just cleanup unused conda environments (default False) cleanup_shadow (bool): just cleanup old shadow directories (default False) cleanup_scripts (bool): delete wrapper scripts used for execution (default True) force_incomplete (bool): force the re-creation of incomplete files (default False) ignore_incomplete (bool): ignore incomplete files (default False) list_version_changes (bool): list output files with changed rule version (default False) list_code_changes (bool): list output files with changed rule code (default False) list_input_changes (bool): list output files with changed input files (default False) list_params_changes (bool): list output files with changed params (default False) list_untracked (bool): list files in the workdir that are not used in the workflow (default False) summary (bool): list summary of all output files and their status (default False) archive (str): archive workflow into the given tarball delete_all_output (bool) remove all files generated by the workflow (default False) delete_temp_output (bool) remove all temporary files generated by the workflow (default False) latency_wait (int): how many seconds to wait for an output file to appear after the execution of a job, e.g. to handle filesystem latency (default 3) wait_for_files (list): wait for given files to be present before executing the workflow list_resources (bool): list resources used in the workflow (default False) summary (bool): list summary of all output files and their status (default False). If no option is specified a basic summary will be ouput. If 'detailed' is added as an option e.g --summary detailed, extra info about the input and shell commands will be included detailed_summary (bool): list summary of all input and output files and their status (default False) print_compilation (bool): print the compilation of the snakefile (default False) debug (bool): allow to use the debugger within rules notemp (bool): ignore temp file flags, e.g. do not delete output files marked as temp after use (default False) keep_remote_local (bool): keep local copies of remote files (default False) nodeps (bool): ignore dependencies (default False) keep_target_files (bool): do not adjust the paths of given target files relative to the working directory. allowed_rules (set): restrict allowed rules to the given set. If None or empty, all rules are used. jobscript (str): path to a custom shell script template for cluster jobs (default None) greediness (float): set the greediness of scheduling. This value between 0 and 1 determines how careful jobs are selected for execution. The default value (0.5 if prioritytargets are used, 1.0 else) provides the best speed and still acceptable scheduling quality. overwrite_shellcmd (str): a shell command that shall be executed instead of those given in the workflow. This is for debugging purposes only. updated_files(list): a list that will be filled with the files that are updated or created during the workflow execution verbose (bool): show additional debug output (default False) max_jobs_per_second (int): maximal number of cluster/drmaa jobs per second, None to impose no limit (default None) restart_times (int): number of times to restart failing jobs (default 0) attempt (int): initial value of Job.attempt. This is intended for internal use only (default 1). force_use_threads: whether to force use of threads over processes. helpful if shared memory is full or unavailable (default False) use_conda (bool): use conda environments for each job (defined with conda directive of rules) use_singularity (bool): run jobs in singularity containers (if defined with singularity directive) use_env_modules (bool): load environment modules if defined in rules singularity_args (str): additional arguments to pass to singularity conda_prefix (str): the directory in which conda environments will be created (default None) singularity_prefix (str): the directory to which singularity images will be pulled (default None) shadow_prefix (str): prefix for shadow directories. The job-specific shadow directories will be created in $SHADOW_PREFIX/shadow/ (default None) create_envs_only (bool): if specified, only builds the conda environments specified for each job, then exits. list_conda_envs (bool): list conda environments and their location on disk. mode (snakemake.common.Mode): execution mode wrapper_prefix (str): prefix for wrapper script URLs (default None) kubernetes (str): submit jobs to kubernetes, using the given namespace. kubernetes_envvars (list): environment variables that shall be passed to kubernetes jobs. container_image (str): Docker image to use, e.g., for kubernetes. default_remote_provider (str): default remote provider to use instead of local files (e.g. S3, GS) default_remote_prefix (str): prefix for default remote provider (e.g. name of the bucket). tibanna (str): submit jobs to AWS cloud using Tibanna. tibanna_sfn (str): Step function (Unicorn) name of Tibanna (e.g. tibanna_unicorn_monty). This must be deployed first using tibanna cli. precommand (str): commands to run on AWS cloud before the snakemake command (e.g. wget, git clone, unzip, etc). Use with --tibanna. assume_shared_fs (bool): assume that cluster nodes share a common filesystem (default true). cluster_status (str): status command for cluster execution. If None, Snakemake will rely on flag files. Otherwise, it expects the command to return "success", "failure" or "running" when executing with a cluster jobid as single argument. export_cwl (str): Compile workflow to CWL and save to given file log_handler (function): redirect snakemake output to this custom log handler, a function that takes a log message dictionary (see below) as its only argument (default None). The log message dictionary for the log handler has to following entries: keep_incomplete (bool): keep incomplete output files of failed jobs log_handler (list): redirect snakemake output to this list of custom log handler, each a function that takes a log message dictionary (see below) as its only argument (default []). The log message dictionary for the log handler has to following entries: :level: the log level ("info", "error", "debug", "progress", "job_info") :level="info", "error" or "debug": :msg: the log message :level="progress": :done: number of already executed jobs :total: number of total jobs :level="job_info": :input: list of input files of a job :output: list of output files of a job :log: path to log file of a job :local: whether a job is executed locally (i.e. ignoring cluster) :msg: the job message :reason: the job reason :priority: the job priority :threads: the threads of the job Returns: bool: True if workflow execution was successful. """ assert not immediate_submit or ( immediate_submit and notemp ), "immediate_submit has to be combined with notemp (it does not support temp file handling)" if tibanna: assume_shared_fs = False default_remote_provider = "S3" default_remote_prefix = default_remote_prefix.rstrip("/") assert ( default_remote_prefix ), "default_remote_prefix needed if tibanna is specified" assert tibanna_sfn, "tibanna_sfn needed if tibanna is specified" if updated_files is None: updated_files = list() if cluster or cluster_sync or drmaa or tibanna: cores = sys.maxsize else: nodes = sys.maxsize if isinstance(cluster_config, str): # Loading configuration from one file is still supported for # backward compatibility cluster_config = [cluster_config] if cluster_config: # Load all configuration files configs = [load_configfile(f) for f in cluster_config] # Merge in the order as specified, overriding earlier values with # later ones cluster_config_content = configs[0] for other in configs[1:]: update_config(cluster_config_content, other) else: cluster_config_content = dict() run_local = not (cluster or cluster_sync or drmaa or kubernetes or tibanna) if run_local and not dryrun: # clean up all previously recorded jobids. shell.cleanup() # force thread use for any kind of cluster use_threads = ( force_use_threads or (os.name != "posix") or cluster or cluster_sync or drmaa ) if not keep_logger: stdout = ( ( dryrun and not (printdag or printd3dag or printrulegraph or printfilegraph) ) or listrules or list_target_rules or list_resources ) setup_logger( handler=log_handler, quiet=quiet, printreason=printreason, printshellcmds=printshellcmds, debug_dag=debug_dag, nocolor=nocolor, stdout=stdout, debug=verbose, use_threads=use_threads, mode=mode, show_failed_logs=show_failed_logs, ) if greediness is None: greediness = 0.5 if prioritytargets else 1.0 else: if not (0 <= greediness <= 1.0): logger.error("Error: greediness must be a float between 0 and 1.") return False if not os.path.exists(snakefile): logger.error('Error: Snakefile "{}" not found.'.format(snakefile)) return False snakefile = os.path.abspath(snakefile) cluster_mode = ( (cluster is not None) + (cluster_sync is not None) + (drmaa is not None) ) if cluster_mode > 1: logger.error("Error: cluster and drmaa args are mutually exclusive") return False if debug and (cores > 1 or cluster_mode): logger.error( "Error: debug mode cannot be used with more than one core or cluster execution." ) return False overwrite_config = dict() if configfiles is None: configfiles = [] for f in configfiles: # get values to override. Later configfiles override earlier ones. overwrite_config.update(load_configfile(f)) # convert provided paths to absolute paths configfiles = list(map(os.path.abspath, configfiles)) # directly specified elements override any configfiles if config: overwrite_config.update(config) if config_args is None: config_args = unparse_config(config) if workdir: olddir = os.getcwd() if not os.path.exists(workdir): logger.info("Creating specified working directory {}.".format(workdir)) os.makedirs(workdir) workdir = os.path.abspath(workdir) os.chdir(workdir) logger.setup_logfile() try: # handle default remote provider _default_remote_provider = None if default_remote_provider is not None: try: rmt = importlib.import_module( "snakemake.remote." + default_remote_provider ) except ImportError as e: raise WorkflowError("Unknown default remote provider.") if rmt.RemoteProvider.supports_default: _default_remote_provider = rmt.RemoteProvider( keep_local=True, is_default=True ) else: raise WorkflowError( "Remote provider {} does not (yet) support to " "be used as default provider." ) workflow = Workflow( snakefile=snakefile, jobscript=jobscript, overwrite_shellcmd=overwrite_shellcmd, overwrite_config=overwrite_config, overwrite_workdir=workdir, overwrite_configfiles=configfiles, overwrite_clusterconfig=cluster_config_content, overwrite_threads=overwrite_threads, config_args=config_args, debug=debug, verbose=verbose, use_conda=use_conda or list_conda_envs or cleanup_conda, use_singularity=use_singularity, use_env_modules=use_env_modules, conda_prefix=conda_prefix, singularity_prefix=singularity_prefix, shadow_prefix=shadow_prefix, singularity_args=singularity_args, mode=mode, wrapper_prefix=wrapper_prefix, printshellcmds=printshellcmds, restart_times=restart_times, attempt=attempt, default_remote_provider=_default_remote_provider, default_remote_prefix=default_remote_prefix, run_local=run_local, default_resources=default_resources, cache=cache, cores=cores, nodes=nodes, resources=resources, ) success = True workflow.include( snakefile, overwrite_first_rule=True, print_compilation=print_compilation ) workflow.check() if not print_compilation: if listrules: workflow.list_rules() elif list_target_rules: workflow.list_rules(only_targets=True) elif list_resources: workflow.list_resources() else: # if not printdag and not printrulegraph: # handle subworkflows subsnakemake = partial( snakemake, local_cores=local_cores, cache=cache, default_resources=default_resources, dryrun=dryrun, touch=touch, printreason=printreason, printshellcmds=printshellcmds, debug_dag=debug_dag, nocolor=nocolor, quiet=quiet, keepgoing=keepgoing, cluster=cluster, cluster_sync=cluster_sync, drmaa=drmaa, drmaa_log_dir=drmaa_log_dir, jobname=jobname, immediate_submit=immediate_submit, standalone=standalone, ignore_ambiguity=ignore_ambiguity, restart_times=restart_times, attempt=attempt, lock=lock, unlock=unlock, cleanup_metadata=cleanup_metadata, cleanup_conda=cleanup_conda, cleanup_shadow=cleanup_shadow, cleanup_scripts=cleanup_scripts, force_incomplete=force_incomplete, ignore_incomplete=ignore_incomplete, latency_wait=latency_wait, verbose=verbose, notemp=notemp, keep_remote_local=keep_remote_local, nodeps=nodeps, jobscript=jobscript, greediness=greediness, no_hooks=no_hooks, overwrite_shellcmd=overwrite_shellcmd, config=config, config_args=config_args, cluster_config=cluster_config, keep_logger=True, force_use_threads=use_threads, use_conda=use_conda, use_singularity=use_singularity, use_env_modules=use_env_modules, conda_prefix=conda_prefix, singularity_prefix=singularity_prefix, shadow_prefix=shadow_prefix, singularity_args=singularity_args, list_conda_envs=list_conda_envs, kubernetes=kubernetes, kubernetes_envvars=kubernetes_envvars, container_image=container_image, create_envs_only=create_envs_only, default_remote_provider=default_remote_provider, default_remote_prefix=default_remote_prefix, tibanna=tibanna, tibanna_sfn=tibanna_sfn, precommand=precommand, assume_shared_fs=assume_shared_fs, cluster_status=cluster_status, max_jobs_per_second=max_jobs_per_second, max_status_checks_per_second=max_status_checks_per_second, ) success = workflow.execute( targets=targets, dryrun=dryrun, touch=touch, local_cores=local_cores, forcetargets=forcetargets, forceall=forceall, forcerun=forcerun, prioritytargets=prioritytargets, until=until, omit_from=omit_from, quiet=quiet, keepgoing=keepgoing, printshellcmds=printshellcmds, printreason=printreason, printrulegraph=printrulegraph, printfilegraph=printfilegraph, printdag=printdag, cluster=cluster, cluster_sync=cluster_sync, jobname=jobname, drmaa=drmaa, drmaa_log_dir=drmaa_log_dir, kubernetes=kubernetes, kubernetes_envvars=kubernetes_envvars, container_image=container_image, tibanna=tibanna, tibanna_sfn=tibanna_sfn, precommand=precommand, max_jobs_per_second=max_jobs_per_second, max_status_checks_per_second=max_status_checks_per_second, printd3dag=printd3dag, immediate_submit=immediate_submit, ignore_ambiguity=ignore_ambiguity, stats=stats, force_incomplete=force_incomplete, ignore_incomplete=ignore_incomplete, list_version_changes=list_version_changes, list_code_changes=list_code_changes, list_input_changes=list_input_changes, list_params_changes=list_params_changes, list_untracked=list_untracked, list_conda_envs=list_conda_envs, summary=summary, archive=archive, delete_all_output=delete_all_output, delete_temp_output=delete_temp_output, latency_wait=latency_wait, wait_for_files=wait_for_files, detailed_summary=detailed_summary, nolock=not lock, unlock=unlock, notemp=notemp, keep_remote_local=keep_remote_local, nodeps=nodeps, keep_target_files=keep_target_files, cleanup_metadata=cleanup_metadata, cleanup_conda=cleanup_conda, cleanup_shadow=cleanup_shadow, cleanup_scripts=cleanup_scripts, subsnakemake=subsnakemake, updated_files=updated_files, allowed_rules=allowed_rules, greediness=greediness, no_hooks=no_hooks, force_use_threads=use_threads, create_envs_only=create_envs_only, assume_shared_fs=assume_shared_fs, cluster_status=cluster_status, report=report, export_cwl=export_cwl, batch=batch, keepincomplete=keep_incomplete, ) except BrokenPipeError: # ignore this exception and stop. It occurs if snakemake output is piped into less and less quits before reading the whole output. # in such a case, snakemake shall stop scheduling and quit with error 1 success = False except (Exception, BaseException) as ex: if "workflow" in locals(): print_exception(ex, workflow.linemaps) else: print_exception(ex, dict()) success = False if workdir: os.chdir(olddir) if "workflow" in locals() and workflow.persistence: workflow.persistence.unlock() if not keep_logger: logger.cleanup() return success def parse_set_threads(args): errmsg = "Invalid threads definition: entries have to be defined as RULE=THREADS pairs (with THREADS being a positive integer)." overwrite_threads = dict() if args.set_threads is not None: for entry in args.set_threads: rule, threads = parse_key_value_arg(entry, errmsg=errmsg) try: threads = int(threads) except ValueError: raise ValueError(errmsg) if threads < 0: raise ValueError(errmsg) overwrite_threads[rule] = threads return overwrite_threads def parse_batch(args): errmsg = "Invalid batch definition: batch entry has to be defined as RULE=BATCH/BATCHES (with integers BATCH <= BATCHES, BATCH >= 1)." if args.batch is not None: rule, batchdef = parse_key_value_arg(args.batch, errmsg=errmsg) try: batch, batches = batchdef.split("/") batch = int(batch) batches = int(batches) except ValueError: raise ValueError(errmsg) if batch > batches or batch < 1: raise ValueError(errmsg) return Batch(rule, batch, batches) return None def parse_key_value_arg(arg, errmsg): try: key, val = arg.split("=", 1) except ValueError: raise ValueError(errmsg) return key, val def parse_config(args): """Parse config from args.""" parsers = [int, float, eval, str] config = dict() if args.config is not None: valid = re.compile(r"[a-zA-Z_]\w*$") for entry in args.config: key, val = parse_key_value_arg( entry, errmsg="Invalid config definition: Config entries have to be defined as name=value pairs.", ) if not valid.match(key): raise ValueError( "Invalid config definition: Config entry must start with a valid identifier." ) v = None for parser in parsers: try: v = parser(val) # avoid accidental interpretation as function if not callable(v): break except: pass assert v is not None config[key] = v return config def unparse_config(config): if not isinstance(config, dict): raise ValueError("config is not a dict") items = [] for key, value in config.items(): if isinstance(value, dict): raise ValueError("config may only be a flat dict") encoded = "'{}'".format(value) if isinstance(value, str) else value items.append("{}={}".format(key, encoded)) return items APPDIRS = None def get_appdirs(): global APPDIRS if APPDIRS is None: from appdirs import AppDirs APPDIRS = AppDirs("snakemake", "snakemake") return APPDIRS def get_profile_file(profile, file, return_default=False): dirs = get_appdirs() if os.path.isabs(profile): search_dirs = [os.path.dirname(profile)] profile = os.path.basename(profile) else: search_dirs = [os.getcwd(), dirs.user_config_dir, dirs.site_config_dir] get_path = lambda d: os.path.join(d, profile, file) for d in search_dirs: p = get_path(d) if os.path.exists(p): return p if return_default: return file return None def get_argument_parser(profile=None): """Generate and return argument parser.""" import configargparse from configargparse import YAMLConfigFileParser dirs = get_appdirs() config_files = [] if profile: if profile == "": print("Error: invalid profile name.", file=sys.stderr) exit(1) config_file = get_profile_file(profile, "config.yaml") if config_file is None: print( "Error: profile given but no config.yaml found. " "Profile has to be given as either absolute path, relative " "path or name of a directory available in either " "{site} or {user}.".format( site=dirs.site_config_dir, user=dirs.user_config_dir ), file=sys.stderr, ) exit(1) config_files = [config_file] parser = configargparse.ArgumentParser( description="Snakemake is a Python based language and execution " "environment for GNU Make-like workflows.", default_config_files=config_files, config_file_parser_class=YAMLConfigFileParser, ) group_exec = parser.add_argument_group("EXECUTION") group_exec.add_argument( "target", nargs="*", default=None, help="Targets to build. May be rules or files.", ) group_exec.add_argument( "--dry-run", "--dryrun", "-n", dest="dryrun", action="store_true", help="Do not execute anything, and display what would be done. " "If you have a very large workflow, use --dry-run --quiet to just " "print a summary of the DAG of jobs.", ) group_exec.add_argument( "--profile", help=""" Name of profile to use for configuring Snakemake. Snakemake will search for a corresponding folder in {} and {}. Alternatively, this can be an absolute or relative path. The profile folder has to contain a file 'config.yaml'. This file can be used to set default values for command line options in YAML format. For example, '--cluster qsub' becomes 'cluster: qsub' in the YAML file. Profiles can be obtained from https://github.com/snakemake-profiles. """.format( dirs.site_config_dir, dirs.user_config_dir ), ) group_exec.add_argument( "--cache", nargs="+", metavar="RULE", help="Store output files of given rules in a central cache given by the environment " "variable $SNAKEMAKE_OUTPUT_CACHE. Likewise, retrieve output files of the given rules " "from this cache if they have been created before (by anybody writing to the same cache), " "instead of actually executing the rules. Output files are identified by hashing all " "steps, parameters and software stack (conda envs or containers) needed to create them.", ) group_exec.add_argument( "--snakefile", "-s", metavar="FILE", help=( "The workflow definition in form of a snakefile." "Usually, you should not need to specify this. " "By default, Snakemake will search for {} " "beneath the current working " "directory, in this order. " "Only if you definitely want a different layout, " "you need to use this parameter." ).format(", ".join(map("'{}'".format, SNAKEFILE_CHOICES))), ) group_exec.add_argument( "--cores", "--jobs", "-j", action="store", const=available_cpu_count(), nargs="?", metavar="N", help=( "Use at most N cores in parallel. " "If N is omitted or 'all', the limit is set to the number of " "available cores." ), ) group_exec.add_argument( "--local-cores", action="store", default=available_cpu_count(), metavar="N", type=int, help=( "In cluster mode, use at most N cores of the host machine in parallel " " (default: number of CPU cores of the host). The cores are used to execute " "local rules. This option is ignored when not in cluster mode." ), ) group_exec.add_argument( "--resources", "--res", nargs="*", metavar="NAME=INT", help=( "Define additional resources that shall constrain the scheduling " "analogously to threads (see above). A resource is defined as " "a name and an integer value. E.g. --resources gpu=1. Rules can " "use resources by defining the resource keyword, e.g. " "resources: gpu=1. If now two rules require 1 of the resource " "'gpu' they won't be run in parallel by the scheduler." ), ) group_exec.add_argument( "--set-threads", metavar="RULE=THREADS", nargs="*", help="Overwrite thread usage of rules. This allows to fine-tune workflow " "parallelization. In particular, this is helpful to target certain cluster nodes " "by e.g. shifting a rule to use more, or less threads than defined in the workflow. " "Thereby, THREADS has to be a positive integer, and RULE has to be the name of the rule.", ) group_exec.add_argument( "--default-resources", "--default-res", nargs="*", metavar="NAME=INT", help=( "Define default values of resources for rules that do not define their own values. " "In addition to plain integers, python expressions over inputsize are allowed (e.g. '2*input.size')." "When specifying this without any arguments (--default-resources), it defines 'mem_mb=max(2*input.size, 1000)' " "'disk_mb=max(2*input.size, 1000)', i.e., default disk and mem usage is twice the input file size but at least 1GB." ), ) group_exec.add_argument( "--config", "-C", nargs="*", metavar="KEY=VALUE", help=( "Set or overwrite values in the workflow config object. " "The workflow config object is accessible as variable config inside " "the workflow. Default values can be set by providing a JSON file " "(see Documentation)." ), ) group_exec.add_argument( "--configfile", "--configfiles", nargs="+", metavar="FILE", help=( "Specify or overwrite the config file of the workflow (see the docs). " "Values specified in JSON or YAML format are available in the global config " "dictionary inside the workflow. Multiple files overwrite each other in " "the given order." ), ) group_exec.add_argument( "--directory", "-d", metavar="DIR", action="store", help=( "Specify working directory (relative paths in " "the snakefile will use this as their origin)." ), ) group_exec.add_argument( "--touch", "-t", action="store_true", help=( "Touch output files (mark them up to date without really " "changing them) instead of running their commands. This is " "used to pretend that the rules were executed, in order to " "fool future invocations of snakemake. Fails if a file does " "not yet exist. Note that this will only touch files that would " "otherwise be recreated by Snakemake (e.g. because their input " "files are newer). For enforcing a touch, combine this with " "--force, --forceall, or --forcerun. Note however that you loose " "the provenance information when the files have been created in " "realitiy. Hence, this should be used only as a last resort." ), ) group_exec.add_argument( "--keep-going", "-k", action="store_true", help="Go on with independent jobs if a job fails.", ) group_exec.add_argument( "--force", "-f", action="store_true", help=( "Force the execution of the selected target or the first rule " "regardless of already created output." ), ) group_exec.add_argument( "--forceall", "-F", action="store_true", help=( "Force the execution of the selected (or the first) rule and " "all rules it is dependent on regardless of already created " "output." ), ) group_exec.add_argument( "--forcerun", "-R", nargs="*", metavar="TARGET", help=( "Force the re-execution or creation of the given rules or files." " Use this option if you changed a rule and want to have all its " "output in your workflow updated." ), ) group_exec.add_argument( "--prioritize", "-P", nargs="+", metavar="TARGET", help=( "Tell the scheduler to assign creation of given targets " "(and all their dependencies) highest priority. (EXPERIMENTAL)" ), ) group_exec.add_argument( "--batch", metavar="RULE=BATCH/BATCHES", help=( "Only create the given BATCH of the input files of the given RULE. " "This can be used to iteratively run parts of very large workflows. " "Only the execution plan of the relevant part of the workflow has to " "be calculated, thereby speeding up DAG computation. " "It is recommended to provide the most suitable rule for batching when " "documenting a workflow. It should be some aggregating rule that " "would be executed only once, and has a large number of input files. " "For example, it can be a rule that aggregates over samples." ), ) group_exec.add_argument( "--until", "-U", nargs="+", metavar="TARGET", help=( "Runs the pipeline until it reaches the specified rules or " "files. Only runs jobs that are dependencies of the specified " "rule or files, does not run sibling DAGs. " ), ) group_exec.add_argument( "--omit-from", "-O", nargs="+", metavar="TARGET", help=( "Prevent the execution or creation of the given rules or files " "as well as any rules or files that are downstream of these targets " "in the DAG. Also runs jobs in sibling DAGs that are independent of the " "rules or files specified here." ), ) group_exec.add_argument( "--rerun-incomplete", "--ri", action="store_true", help=("Re-run all " "jobs the output of which is recognized as incomplete."), ) group_exec.add_argument( "--shadow-prefix", metavar="DIR", help=( "Specify a directory in which the 'shadow' directory is created. " "If not supplied, the value is set to the '.snakemake' directory relative " "to the working directory." ), ) group_utils = parser.add_argument_group("UTILITIES") group_utils.add_argument( "--report", nargs="?", const="report.html", metavar="HTMLFILE", help="Create an HTML report with results and statistics. " "If no filename is given, report.html is the default.", ) group_utils.add_argument( "--export-cwl", action="store", metavar="FILE", help="Compile workflow to CWL and store it in given FILE.", ) group_utils.add_argument( "--list", "-l", action="store_true", help="Show available rules in given Snakefile.", ) group_utils.add_argument( "--list-target-rules", "--lt", action="store_true", help="Show available target rules in given Snakefile.", ) group_utils.add_argument( "--dag", action="store_true", help="Do not execute anything and print the directed " "acyclic graph of jobs in the dot language. Recommended " "use on Unix systems: snakemake --dag | dot | display", ) group_utils.add_argument( "--rulegraph", action="store_true", help="Do not execute anything and print the dependency graph " "of rules in the dot language. This will be less " "crowded than above DAG of jobs, but also show less information. " "Note that each rule is displayed once, hence the displayed graph will be " "cyclic if a rule appears in several steps of the workflow. " "Use this if above option leads to a DAG that is too large. " "Recommended use on Unix systems: snakemake --rulegraph | dot | display", ) group_utils.add_argument( "--filegraph", action="store_true", help="Do not execute anything and print the dependency graph " "of rules with their input and output files in the dot language. " "This is an intermediate solution between above DAG of jobs and the rule graph. " "Note that each rule is displayed once, hence the displayed graph will be " "cyclic if a rule appears in several steps of the workflow. " "Use this if above option leads to a DAG that is too large. " "Recommended use on Unix systems: snakemake --filegraph | dot | display", ) group_utils.add_argument( "--d3dag", action="store_true", help="Print the DAG in D3.js compatible JSON format.", ) group_utils.add_argument( "--summary", "-S", action="store_true", help="Print a summary of all files created by the workflow. The " "has the following columns: filename, modification time, " "rule version, status, plan.\n" "Thereby rule version contains the version" "the file was created with (see the version keyword of rules), and " "status denotes whether the file is missing, its input files are " "newer or if version or implementation of the rule changed since " "file creation. Finally the last column denotes whether the file " "will be updated or created during the next workflow execution.", ) group_utils.add_argument( "--detailed-summary", "-D", action="store_true", help="Print a summary of all files created by the workflow. The " "has the following columns: filename, modification time, " "rule version, input file(s), shell command, status, plan.\n" "Thereby rule version contains the version " "the file was created with (see the version keyword of rules), and " "status denotes whether the file is missing, its input files are " "newer or if version or implementation of the rule changed since " "file creation. The input file and shell command columns are self " "explanatory. Finally the last column denotes whether the file " "will be updated or created during the next workflow execution.", ) group_utils.add_argument( "--archive", metavar="FILE", help="Archive the workflow into the given tar archive FILE. The archive " "will be created such that the workflow can be re-executed on a vanilla " "system. The function needs conda and git to be installed. " "It will archive every file that is under git version control. " "Note that it is best practice to have the Snakefile, config files, and " "scripts under version control. Hence, they will be included in the archive. " "Further, it will add input files that are not generated by " "by the workflow itself and conda environments. Note that symlinks are " "dereferenced. Supported " "formats are .tar, .tar.gz, .tar.bz2 and .tar.xz.", ) group_utils.add_argument( "--cleanup-metadata", "--cm", nargs="+", metavar="FILE", help="Cleanup the metadata " "of given files. That means that snakemake removes any tracked " "version info, and any marks that files are incomplete.", ) group_utils.add_argument( "--cleanup-shadow", action="store_true", help="Cleanup old shadow directories which have not been deleted due " "to failures or power loss.", ) group_utils.add_argument( "--skip-script-cleanup", action="store_true", help="Don't delete wrapper scripts used for execution", ) group_utils.add_argument( "--unlock", action="store_true", help="Remove a lock on the working directory." ) group_utils.add_argument( "--list-version-changes", "--lv", action="store_true", help="List all output files that have been created with " "a different version (as determined by the version keyword).", ) group_utils.add_argument( "--list-code-changes", "--lc", action="store_true", help="List all output files for which the rule body (run or shell) have " "changed in the Snakefile.", ) group_utils.add_argument( "--list-input-changes", "--li", action="store_true", help="List all output files for which the defined input files have changed " "in the Snakefile (e.g. new input files were added in the rule " "definition or files were renamed). For listing input file " "modification in the filesystem, use --summary.", ) group_utils.add_argument( "--list-params-changes", "--lp", action="store_true", help="List all output files for which the defined params have changed " "in the Snakefile.", ) group_utils.add_argument( "--list-untracked", "--lu", action="store_true", help="List all files in the working directory that are not used in the " "workflow. This can be used e.g. for identifying leftover files. Hidden files " "and directories are ignored.", ) group_utils.add_argument( "--delete-all-output", action="store_true", help="Remove all files generated by the workflow. Use together with --dry-run " "to list files without actually deleting anything. Note that this will " "not recurse into subworkflows. Write-protected files are not removed. " "Nevertheless, use with care!", ) group_utils.add_argument( "--delete-temp-output", action="store_true", help="Remove all temporary files generated by the workflow. Use together " "with --dry-run to list files without actually deleting anything. Note " "that this will not recurse into subworkflows.", ) group_utils.add_argument( "--bash-completion", action="store_true", help="Output code to register bash completion for snakemake. Put the " "following in your .bashrc (including the accents): " "`snakemake --bash-completion` or issue it in an open terminal " "session.", ) group_utils.add_argument( "--keep-incomplete", action="store_true", help="Do not remove incomplete output files by failed jobs.", ) group_utils.add_argument("--version", "-v", action="version", version=__version__) group_output = parser.add_argument_group("OUTPUT") group_output.add_argument( "--reason", "-r", action="store_true", help="Print the reason for each executed rule.", ) group_output.add_argument( "--gui", nargs="?", const="8000", metavar="PORT", type=str, help="Serve an HTML based user interface to the given network and " "port e.g. 168.129.10.15:8000. By default Snakemake is only " "available in the local network (default port: 8000). To make " "Snakemake listen to all ip addresses add the special host address " "0.0.0.0 to the url (0.0.0.0:8000). This is important if Snakemake " "is used in a virtualised environment like Docker. If possible, a " "browser window is opened.", ) group_output.add_argument( "--printshellcmds", "-p", action="store_true", help="Print out the shell commands that will be executed.", ) group_output.add_argument( "--debug-dag", action="store_true", help="Print candidate and selected jobs (including their wildcards) while " "inferring DAG. This can help to debug unexpected DAG topology or errors.", ) group_output.add_argument( "--stats", metavar="FILE", help="Write stats about Snakefile execution in JSON format to the given file.", ) group_output.add_argument( "--nocolor", action="store_true", help="Do not use a colored output." ) group_output.add_argument( "--quiet", "-q", action="store_true", help="Do not output any progress or rule information.", ) group_output.add_argument( "--print-compilation", action="store_true", help="Print the python representation of the workflow.", ) group_output.add_argument( "--verbose", action="store_true", help="Print debugging output." ) group_behavior = parser.add_argument_group("BEHAVIOR") group_behavior.add_argument( "--force-use-threads", dest="force_use_threads", action="store_true", help="Force threads rather than processes. Helpful if shared memory (/dev/shm) is full or unavailable.", ) group_behavior.add_argument( "--allow-ambiguity", "-a", action="store_true", help=( "Don't check for ambiguous rules and simply use the first if " "several can produce the same file. This allows the user to " "prioritize rules by their order in the snakefile." ), ) group_behavior.add_argument( "--nolock", action="store_true", help="Do not lock the working directory" ) group_behavior.add_argument( "--ignore-incomplete", "--ii", action="store_true", help="Do not check for incomplete output files.", ) group_behavior.add_argument( "--latency-wait", "--output-wait", "-w", type=int, default=5, metavar="SECONDS", help="Wait given seconds if an output file of a job is not present after " "the job finished. This helps if your filesystem " "suffers from latency (default 5).", ) group_behavior.add_argument( "--wait-for-files", nargs="*", metavar="FILE", help="Wait --latency-wait seconds for these " "files to be present before executing the workflow. " "This option is used internally to handle filesystem latency in cluster " "environments.", ) group_behavior.add_argument( "--notemp", "--nt", action="store_true", help="Ignore temp() declarations. This is useful when running only " "a part of the workflow, since temp() would lead to deletion of " "probably needed files by other parts of the workflow.", ) group_behavior.add_argument( "--keep-remote", action="store_true", help="Keep local copies of remote input files.", ) group_behavior.add_argument( "--keep-target-files", action="store_true", help="Do not adjust the paths of given target files relative to the working directory.", ) group_behavior.add_argument( "--allowed-rules", nargs="+", help="Only consider given rules. If omitted, all rules in Snakefile are " "used. Note that this is intended primarily for internal use and may " "lead to unexpected results otherwise.", ) group_behavior.add_argument( "--max-jobs-per-second", default=10, type=float, help="Maximal number of cluster/drmaa jobs per second, default is 10, " "fractions allowed.", ) group_behavior.add_argument( "--max-status-checks-per-second", default=10, type=float, help="Maximal number of job status checks per second, default is 10, " "fractions allowed.", ) group_behavior.add_argument( "--restart-times", default=0, type=int, help="Number of times to restart failing jobs (defaults to 0).", ) group_behavior.add_argument( "--attempt", default=1, type=int, help="Internal use only: define the initial value of the attempt " "parameter (default: 1).", ) group_behavior.add_argument( "--wrapper-prefix", default="https://github.com/snakemake/snakemake-wrappers/raw/", help="Prefix for URL created from wrapper directive (default: " "https://github.com/snakemake/snakemake-wrappers/raw/). Set this to " "a different URL to use your fork or a local clone of the repository, " "e.g., use a git URL like 'git+file://path/to/your/local/clone@'.", ) group_behavior.add_argument( "--default-remote-provider", choices=["S3", "GS", "FTP", "SFTP", "S3Mocked", "gfal", "gridftp", "iRODS"], help="Specify default remote provider to be used for " "all input and output files that don't yet specify " "one.", ) group_behavior.add_argument( "--default-remote-prefix", default="", help="Specify prefix for default remote provider. E.g. " "a bucket name.", ) group_behavior.add_argument( "--no-shared-fs", action="store_true", help="Do not assume that jobs share a common file " "system. When this flag is activated, Snakemake will " "assume that the filesystem on a cluster node is not " "shared with other nodes. For example, this will lead " "to downloading remote files on each cluster node " "separately. Further, it won't take special measures " "to deal with filesystem latency issues. This option " "will in most cases only make sense in combination with " "--default-remote-provider. Further, when using --cluster " "you will have to also provide --cluster-status. " "Only activate this if you " "know what you are doing.", ) group_behavior.add_argument( "--greediness", type=float, default=None, help="Set the greediness of scheduling. This value between 0 and 1 " "determines how careful jobs are selected for execution. The default " "value (1.0) provides the best speed and still acceptable scheduling " "quality.", ) group_behavior.add_argument( "--no-hooks", action="store_true", help="Do not invoke onstart, onsuccess or onerror hooks after execution.", ) group_behavior.add_argument( "--overwrite-shellcmd", help="Provide a shell command that shall be executed instead of those " "given in the workflow. " "This is for debugging purposes only.", ) group_behavior.add_argument( "--debug", action="store_true", help="Allow to debug rules with e.g. PDB. This flag " "allows to set breakpoints in run blocks.", ) group_behavior.add_argument( "--runtime-profile", metavar="FILE", help="Profile Snakemake and write the output to FILE. This requires yappi " "to be installed.", ) group_behavior.add_argument( "--mode", choices=[Mode.default, Mode.subprocess, Mode.cluster], default=Mode.default, type=int, help="Set execution mode of Snakemake (internal use only).", ) group_behavior.add_argument( "--show-failed-logs", action="store_true", help="Automatically display logs of failed jobs.", ) group_behavior.add_argument( "--log-handler-script", metavar="FILE", default=None, help="Provide a custom script containing a function 'def log_handler(msg):'. " "Snakemake will call this function for every logging output (given as a dictionary msg)" "allowing to e.g. send notifications in the form of e.g. slack messages or emails.", ) group_behavior.add_argument( "--log-service", default=None, choices=["none", "slack"], help="Set a specific messaging service for logging output." "Snakemake will notify the service on errors and completed execution." "Currently only slack is supported.", ) group_cluster = parser.add_argument_group("CLUSTER") # TODO extend below description to explain the wildcards that can be used cluster_mode_group = group_cluster.add_mutually_exclusive_group() cluster_mode_group.add_argument( "--cluster", "-c", metavar="CMD", help=( "Execute snakemake rules with the given submit command, " "e.g. qsub. Snakemake compiles jobs into scripts that are " "submitted to the cluster with the given command, once all input " "files for a particular job are present.\n" "The submit command can be decorated to make it aware of certain " "job properties (name, rulename, input, output, params, wildcards, log, threads " "and dependencies (see the argument below)), e.g.:\n" "$ snakemake --cluster 'qsub -pe threaded {threads}'." ), ), cluster_mode_group.add_argument( "--cluster-sync", metavar="CMD", help=( "cluster submission command will block, returning the remote exit" "status upon remote termination (for example, this should be used" "if the cluster command is 'qsub -sync y' (SGE)" ), ), cluster_mode_group.add_argument( "--drmaa", nargs="?", const="", metavar="ARGS", help="Execute snakemake on a cluster accessed via DRMAA, " "Snakemake compiles jobs into scripts that are " "submitted to the cluster with the given command, once all input " "files for a particular job are present. ARGS can be used to " "specify options of the underlying cluster system, " "thereby using the job properties name, rulename, input, output, params, wildcards, log, " "threads and dependencies, e.g.: " "--drmaa ' -pe threaded {threads}'. Note that ARGS must be given in quotes and " "with a leading whitespace.", ) group_cluster.add_argument( "--cluster-config", "-u", metavar="FILE", default=[], action="append", help=( "A JSON or YAML file that defines the wildcards used in 'cluster'" "for specific rules, instead of having them specified in the Snakefile. " "For example, for rule 'job' you may define: " "{ 'job' : { 'time' : '24:00:00' } } to specify the time for rule 'job'. " "You can specify more than one file. The configuration files are merged " "with later values overriding earlier ones. This option is deprecated in favor " "of using --profile, see docs." ), ), group_cluster.add_argument( "--immediate-submit", "--is", action="store_true", help="Immediately submit all jobs to the cluster instead of waiting " "for present input files. This will fail, unless you make " "the cluster aware of job dependencies, e.g. via:\n" "$ snakemake --cluster 'sbatch --dependency {dependencies}.\n" "Assuming that your submit script (here sbatch) outputs the " "generated job id to the first stdout line, {dependencies} will " "be filled with space separated job ids this job depends on.", ) group_cluster.add_argument( "--jobscript", "--js", metavar="SCRIPT", help="Provide a custom job script for submission to the cluster. " "The default script resides as 'jobscript.sh' in the " "installation directory.", ) group_cluster.add_argument( "--jobname", "--jn", default="snakejob.{name}.{jobid}.sh", metavar="NAME", help="Provide a custom name for the jobscript that is submitted to the " 'cluster (see --cluster). NAME is "snakejob.{name}.{jobid}.sh" ' "per default. The wildcard {jobid} has to be present in the name.", ) group_cluster.add_argument( "--cluster-status", help="Status command for cluster execution. This is only considered " "in combination with the --cluster flag. If provided, Snakemake will " "use the status command to determine if a job has finished successfully " "or failed. For this it is necessary that the submit command provided " "to --cluster returns the cluster job id. Then, the status command " "will be invoked with the job id. Snakemake expects it to return " "'success' if the job was successfull, 'failed' if the job failed and " "'running' if the job still runs.", ) group_cluster.add_argument( "--drmaa-log-dir", metavar="DIR", help="Specify a directory in which stdout and stderr files of DRMAA" " jobs will be written. The value may be given as a relative path," " in which case Snakemake will use the current invocation directory" " as the origin. If given, this will override any given '-o' and/or" " '-e' native specification. If not given, all DRMAA stdout and" " stderr files are written to the current working directory.", ) group_cloud = parser.add_argument_group("CLOUD") group_kubernetes = parser.add_argument_group("KUBERNETES") group_tibanna = parser.add_argument_group("TIBANNA") group_kubernetes.add_argument( "--kubernetes", metavar="NAMESPACE", nargs="?", const="default", help="Execute workflow in a kubernetes cluster (in the cloud). " "NAMESPACE is the namespace you want to use for your job (if nothing " "specified: 'default'). " "Usually, this requires --default-remote-provider and " "--default-remote-prefix to be set to a S3 or GS bucket where your . " "data shall be stored. It is further advisable to activate conda " "integration via --use-conda.", ) group_kubernetes.add_argument( "--kubernetes-env", nargs="+", metavar="ENVVAR", default=[], help="Specify environment variables to pass to the kubernetes job.", ) group_kubernetes.add_argument( "--container-image", metavar="IMAGE", help="Docker image to use, e.g., when submitting jobs to kubernetes. " "By default, this is 'https://hub.docker.com/r/snakemake/snakemake', tagged with " "the same version as the currently running Snakemake instance. " "Note that overwriting this value is up to your responsibility. " "Any used image has to contain a working snakemake installation " "that is compatible with (or ideally the same as) the currently " "running version.", ) group_tibanna.add_argument( "--tibanna", action="store_true", help="Execute workflow on AWS cloud using Tibanna. This requires " "--default-remote-prefix to be set to S3 bucket name and prefix" " (e.g. 'bucketname/subdirectory') where input is already stored" " and output will be sent to. Using --tibanna implies --default-resources" " is set as default. Optionally, use --precommand to" " specify any preparation command to run before snakemake command" " on the cloud (inside snakemake container on Tibanna VM)." " Also, --use-conda, --use-singularity, --config, --configfile are" " supported and will be carried over.", ) group_tibanna.add_argument( "--tibanna-sfn", help="Name of Tibanna Unicorn step function (e.g. tibanna_unicorn_monty)." "This works as serverless scheduler/resource allocator and must be " "deployed first using tibanna cli. (e.g. tibanna deploy_unicorn --usergroup=" "monty --buckets=bucketname)", ) group_tibanna.add_argument( "--precommand", help="Any command to execute before snakemake command on AWS cloud " "such as wget, git clone, unzip, etc. This is used with --tibanna." "Do not include input/output download/upload commands - file transfer" " between S3 bucket and the run environment (container) is automatically" " handled by Tibanna.", ) group_conda = parser.add_argument_group("CONDA") group_conda.add_argument( "--use-conda", action="store_true", help="If defined in the rule, run job in a conda environment. " "If this flag is not set, the conda directive is ignored.", ) group_conda.add_argument( "--list-conda-envs", action="store_true", help="List all conda environments and their location on " "disk.", ) group_conda.add_argument( "--cleanup-conda", action="store_true", help="Cleanup unused conda environments.", ) group_conda.add_argument( "--conda-prefix", metavar="DIR", help="Specify a directory in which the 'conda' and 'conda-archive' " "directories are created. These are used to store conda environments " "and their archives, respectively. If not supplied, the value is set " "to the '.snakemake' directory relative to the invocation directory. " "If supplied, the `--use-conda` flag must also be set. The value may " "be given as a relative path, which will be extrapolated to the " "invocation directory, or as an absolute path.", ) group_conda.add_argument( "--create-envs-only", action="store_true", help="If specified, only creates the job-specific " "conda environments then exits. The `--use-conda` " "flag must also be set.", ) group_singularity = parser.add_argument_group("SINGULARITY") group_singularity.add_argument( "--use-singularity", action="store_true", help="If defined in the rule, run job within a singularity container. " "If this flag is not set, the singularity directive is ignored.", ) group_singularity.add_argument( "--singularity-prefix", metavar="DIR", help="Specify a directory in which singularity images will be stored." "If not supplied, the value is set " "to the '.snakemake' directory relative to the invocation directory. " "If supplied, the `--use-singularity` flag must also be set. The value " "may be given as a relative path, which will be extrapolated to the " "invocation directory, or as an absolute path.", ) group_singularity.add_argument( "--singularity-args", default="", metavar="ARGS", help="Pass additional args to singularity.", ) group_env_modules = parser.add_argument_group("ENVIRONMENT MODULES") group_env_modules.add_argument( "--use-envmodules", action="store_true", help="If defined in the rule, run job within the given environment " "modules, loaded in the given order. This can be combined with " "--use-conda and --use-singularity, which will then be only used as a " "fallback for rules which don't define environment modules.", ) return parser def main(argv=None): """Main entry point.""" parser = get_argument_parser() args = parser.parse_args(argv) if args.profile: # reparse args while inferring config file from profile parser = get_argument_parser(args.profile) args = parser.parse_args(argv) def adjust_path(f): if os.path.exists(f) or os.path.isabs(f): return f else: return get_profile_file(args.profile, f, return_default=True) # update file paths to be relative to the profile # (if they do not exist relative to CWD) if args.jobscript: args.jobscript = adjust_path(args.jobscript) if args.cluster: args.cluster = adjust_path(args.cluster) if args.cluster_sync: args.cluster_sync = adjust_path(args.cluster_sync) if args.cluster_status: args.cluster_status = adjust_path(args.cluster_status) if args.bash_completion: cmd = b"complete -o bashdefault -C snakemake-bash-completion snakemake" sys.stdout.buffer.write(cmd) sys.exit(0) if args.batch is not None and args.forceall: print( "--batch may not be combined with --forceall, because recomputed upstream " "jobs in subsequent batches may render already obtained results outdated." ) try: resources = parse_resources(args.resources) config = parse_config(args) if (args.default_resources is not None and not args.default_resources) or ( args.tibanna and not args.default_resources ): args.default_resources = [ "mem_mb=max(2*input.size, 1000)", "disk_mb=max(2*input.size, 1000)", ] default_resources = DefaultResources(args.default_resources) batch = parse_batch(args) overwrite_threads = parse_set_threads(args) except ValueError as e: print(e, file=sys.stderr) print("", file=sys.stderr) sys.exit(1) if args.cores is not None: if args.cores == "all": args.cores = available_cpu_count() else: try: args.cores = int(args.cores) except ValueError: print( "Error parsing number of cores (--cores, --jobs, -j): must be integer, empty, or 'all'.", file=sys.stderr, ) sys.exit(1) if args.cluster or args.cluster_sync or args.drmaa: if args.cores is None: if args.dryrun: args.cores = 1 else: print( "Error: you need to specify the maximum number of jobs to " "be queued or executed at the same time with --jobs.", file=sys.stderr, ) sys.exit(1) elif args.cores is None: # if nothing specified, use all avaiable cores args.cores = available_cpu_count() if args.drmaa_log_dir is not None: if not os.path.isabs(args.drmaa_log_dir): args.drmaa_log_dir = os.path.abspath(os.path.expanduser(args.drmaa_log_dir)) if args.runtime_profile: import yappi yappi.start() if args.immediate_submit and not args.notemp: print( "Error: --immediate-submit has to be combined with --notemp, " "because temp file handling is not supported in this mode.", file=sys.stderr, ) sys.exit(1) if (args.conda_prefix or args.create_envs_only) and not args.use_conda: print( "Error: --use-conda must be set if --conda-prefix or " "--create-envs-only is set.", file=sys.stderr, ) sys.exit(1) if args.singularity_prefix and not args.use_singularity: print( "Error: --use_singularity must be set if --singularity-prefix " "is set.", file=sys.stderr, ) sys.exit(1) if args.kubernetes and ( not args.default_remote_provider or not args.default_remote_prefix ): print( "Error: --kubernetes must be combined with " "--default-remote-provider and --default-remote-prefix, see " "https://snakemake.readthedocs.io/en/stable/executable.html" "#executing-a-snakemake-workflow-via-kubernetes", file=sys.stderr, ) sys.exit(1) if args.tibanna: if not args.default_remote_prefix: print( "Error: --tibanna must be combined with --default-remote-prefix " "to provide bucket name and subdirectory (prefix) " "(e.g. 'bucketname/projectname'", file=sys.stderr, ) sys.exit(1) args.default_remote_prefix = args.default_remote_prefix.rstrip("/") if not args.tibanna_sfn: args.tibanna_sfn = os.environ.get("TIBANNA_DEFAULT_STEP_FUNCTION_NAME", "") if not args.tibanna_sfn: print( "Error: to use --tibanna, either --tibanna-sfn or environment variable " "TIBANNA_DEFAULT_STEP_FUNCTION_NAME must be set and exported " "to provide name of the tibanna unicorn step function " "(e.g. 'tibanna_unicorn_monty'). The step function must be deployed first " "using tibanna cli (e.g. tibanna deploy_unicorn --usergroup=monty " "--buckets=bucketname)", file=sys.stderr, ) sys.exit(1) if args.delete_all_output and args.delete_temp_output: print( "Error: --delete-all-output and --delete-temp-output are mutually exclusive.", file=sys.stderr, ) sys.exit(1) if args.snakefile is None: for p in SNAKEFILE_CHOICES: if os.path.exists(p): args.snakefile = p break if args.snakefile is None: print( "Error: no Snakefile found, tried {}.".format( ", ".join(SNAKEFILE_CHOICES), file=sys.stderr ) ) sys.exit(1) if args.gui is not None: try: import snakemake.gui as gui except ImportError: print( "Error: GUI needs Flask to be installed. Install " "with easy_install or contact your administrator.", file=sys.stderr, ) sys.exit(1) _logging.getLogger("werkzeug").setLevel(_logging.ERROR) _snakemake = partial(snakemake, os.path.abspath(args.snakefile)) gui.register(_snakemake, args) if ":" in args.gui: host, port = args.gui.split(":") else: port = args.gui host = "127.0.0.1" url = "http://{}:{}".format(host, port) print("Listening on {}.".format(url), file=sys.stderr) def open_browser(): try: webbrowser.open(url) except: pass print("Open this address in your browser to access the GUI.", file=sys.stderr) threading.Timer(0.5, open_browser).start() success = True try: gui.app.run(debug=False, threaded=True, port=int(port), host=host) except (KeyboardInterrupt, SystemExit): # silently close pass else: log_handler = [] if args.log_handler_script is not None: if not os.path.exists(args.log_handler_script): print( "Error: no log handler script found, {}.".format( args.log_handler_script ), file=sys.stderr, ) sys.exit(1) log_script = SourceFileLoader("log", args.log_handler_script).load_module() try: log_handler.append(log_script.log_handler) except: print( 'Error: Invalid log handler script, {}. Expect python function "log_handler(msg)".'.format( args.log_handler_script ), file=sys.stderr, ) sys.exit(1) if args.log_service == "slack": slack_logger = logging.SlackLogger() log_handler.append(slack_logger.log_handler) success = snakemake( args.snakefile, batch=batch, cache=args.cache, report=args.report, listrules=args.list, list_target_rules=args.list_target_rules, cores=args.cores, local_cores=args.local_cores, nodes=args.cores, resources=resources, overwrite_threads=overwrite_threads, default_resources=default_resources, config=config, configfiles=args.configfile, config_args=args.config, workdir=args.directory, targets=args.target, dryrun=args.dryrun, printshellcmds=args.printshellcmds, printreason=args.reason, debug_dag=args.debug_dag, printdag=args.dag, printrulegraph=args.rulegraph, printfilegraph=args.filegraph, printd3dag=args.d3dag, touch=args.touch, forcetargets=args.force, forceall=args.forceall, forcerun=args.forcerun, prioritytargets=args.prioritize, until=args.until, omit_from=args.omit_from, stats=args.stats, nocolor=args.nocolor, quiet=args.quiet, keepgoing=args.keep_going, cluster=args.cluster, cluster_config=args.cluster_config, cluster_sync=args.cluster_sync, drmaa=args.drmaa, drmaa_log_dir=args.drmaa_log_dir, kubernetes=args.kubernetes, kubernetes_envvars=args.kubernetes_env, container_image=args.container_image, tibanna=args.tibanna, tibanna_sfn=args.tibanna_sfn, precommand=args.precommand, jobname=args.jobname, immediate_submit=args.immediate_submit, standalone=True, ignore_ambiguity=args.allow_ambiguity, lock=not args.nolock, unlock=args.unlock, cleanup_metadata=args.cleanup_metadata, cleanup_conda=args.cleanup_conda, cleanup_shadow=args.cleanup_shadow, cleanup_scripts=not args.skip_script_cleanup, force_incomplete=args.rerun_incomplete, ignore_incomplete=args.ignore_incomplete, list_version_changes=args.list_version_changes, list_code_changes=args.list_code_changes, list_input_changes=args.list_input_changes, list_params_changes=args.list_params_changes, list_untracked=args.list_untracked, summary=args.summary, detailed_summary=args.detailed_summary, archive=args.archive, delete_all_output=args.delete_all_output, delete_temp_output=args.delete_temp_output, print_compilation=args.print_compilation, verbose=args.verbose, debug=args.debug, jobscript=args.jobscript, notemp=args.notemp, keep_remote_local=args.keep_remote, greediness=args.greediness, no_hooks=args.no_hooks, overwrite_shellcmd=args.overwrite_shellcmd, latency_wait=args.latency_wait, wait_for_files=args.wait_for_files, keep_target_files=args.keep_target_files, allowed_rules=args.allowed_rules, max_jobs_per_second=args.max_jobs_per_second, max_status_checks_per_second=args.max_status_checks_per_second, restart_times=args.restart_times, attempt=args.attempt, force_use_threads=args.force_use_threads, use_conda=args.use_conda, conda_prefix=args.conda_prefix, list_conda_envs=args.list_conda_envs, use_singularity=args.use_singularity, use_env_modules=args.use_envmodules, singularity_prefix=args.singularity_prefix, shadow_prefix=args.shadow_prefix, singularity_args=args.singularity_args, create_envs_only=args.create_envs_only, mode=args.mode, wrapper_prefix=args.wrapper_prefix, default_remote_provider=args.default_remote_provider, default_remote_prefix=args.default_remote_prefix, assume_shared_fs=not args.no_shared_fs, cluster_status=args.cluster_status, export_cwl=args.export_cwl, show_failed_logs=args.show_failed_logs, keep_incomplete=args.keep_incomplete, log_handler=log_handler, ) if args.runtime_profile: with open(args.runtime_profile, "w") as out: profile = yappi.get_func_stats() profile.sort("totaltime") profile.print_all(out=out) sys.exit(0 if success else 1) def bash_completion(snakefile="Snakefile"): """Entry point for bash completion.""" if not len(sys.argv) >= 2: print( "Calculate bash completion for snakemake. This tool shall not be invoked by hand." ) sys.exit(1) def print_candidates(candidates): if candidates: candidates = sorted(set(candidates)) ## Use bytes for avoiding '^M' under Windows. sys.stdout.buffer.write(b"\n".join(s.encode() for s in candidates)) prefix = sys.argv[2] if prefix.startswith("-"): print_candidates( action.option_strings[0] for action in get_argument_parser()._actions if action.option_strings and action.option_strings[0].startswith(prefix) ) else: candidates = [] files = glob.glob("{}*".format(prefix)) if files: candidates.extend(files) if os.path.exists(snakefile): workflow = Workflow(snakefile=snakefile) workflow.include(snakefile) candidates.extend( [file for file in workflow.concrete_files if file.startswith(prefix)] + [rule.name for rule in workflow.rules if rule.name.startswith(prefix)] ) if len(candidates) > 0: print_candidates(candidates) sys.exit(0) snakemake-5.10.0/snakemake/__main__.py000066400000000000000000000001631361131222100175550ustar00rootroot00000000000000# This script makes it possible to invoke snakemake with 'python3 -m snakemake' from snakemake import main main() snakemake-5.10.0/snakemake/_version.py000066400000000000000000000441251361131222100176670ustar00rootroot00000000000000# This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: v5.10.0)" git_full = "28674b1605d2cc0bd66e5e608bf15c7b407b2b95" git_date = "2020-01-20 12:52:49 +0100" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "v" cfg.parentdir_prefix = "None" cfg.versionfile_source = "snakemake/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen( [c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None), ) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return { "version": dirname[len(parentdir_prefix) :], "full-revisionid": None, "dirty": False, "error": None, "date": None, } else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print( "Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix) ) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG) :] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r"\d", r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix) :] if verbose: print("picking %s" % r) return { "version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date, } # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return { "version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None, } @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command( GITS, [ "describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix, ], cwd=root, ) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[: git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r"^(.+)-(\d+)-g([0-9a-f]+)$", git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = "unable to parse git-describe output: '%s'" % describe_out return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = "tag '%s' doesn't start with prefix '%s'" % ( full_tag, tag_prefix, ) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix) :] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[ 0 ].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return { "version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None, } if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return { "version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date"), } def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split("/"): root = os.path.dirname(root) except NameError: return { "version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None, } try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return { "version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None, } snakemake-5.10.0/snakemake/benchmark.py000066400000000000000000000232161361131222100177730ustar00rootroot00000000000000__author__ = "Manuel Holtgrewe" __copyright__ = "Copyright 2017, Manuel Holtgrewe" __email__ = "manuel.holtgrewe@bihealth.de" __license__ = "MIT" import contextlib import datetime from itertools import chain import os import sys import time import threading import psutil from snakemake.exceptions import WorkflowError #: Interval (in seconds) between measuring resource usage BENCHMARK_INTERVAL = 30 #: Interval (in seconds) between measuring resource usage before #: BENCHMARK_INTERVAL BENCHMARK_INTERVAL_SHORT = 0.5 class BenchmarkRecord: """Record type for benchmark times""" @classmethod def get_header(klass): return "\t".join( ( "s", "h:m:s", "max_rss", "max_vms", "max_uss", "max_pss", "io_in", "io_out", "mean_load", ) ) def __init__( self, running_time=None, max_rss=None, max_vms=None, max_uss=None, max_pss=None, io_in=None, io_out=None, cpu_seconds=None, ): #: Running time in seconds self.running_time = running_time #: Maximal RSS in MB self.max_rss = max_rss #: Maximal VMS in MB self.max_vms = max_vms #: Maximal USS in MB self.max_uss = max_uss #: Maximal PSS in MB self.max_pss = max_pss #: I/O read in bytes self.io_in = io_in #: I/O written in bytes self.io_out = io_out #: Count of CPU seconds, divide by running time to get mean load estimate self.cpu_seconds = cpu_seconds or 0 #: First time when we measured CPU load, for estimating total running time self.first_time = None #: Previous point when measured CPU load, for estimating total running time self.prev_time = None def to_tsv(self): """Return ``str`` with the TSV representation of this record""" def to_tsv_str(x): """Conversion of value to str for TSV (None becomes "-")""" if x is None: return "-" elif isinstance(x, float): return "{:.2f}".format(x) else: return str(x) def timedelta_to_str(x): """Conversion of timedelta to str without fractions of seconds""" mm, ss = divmod(x.seconds, 60) hh, mm = divmod(mm, 60) s = "%d:%02d:%02d" % (hh, mm, ss) if x.days: def plural(n): return n, abs(n) != 1 and "s" or "" s = ("%d day%s, " % plural(x.days)) + s return s return "\t".join( map( to_tsv_str, ( "{:.4f}".format(self.running_time), timedelta_to_str(datetime.timedelta(seconds=self.running_time)), self.max_rss, self.max_vms, self.max_uss, self.max_pss, self.io_in, self.io_out, 100.0 * self.cpu_seconds / self.running_time, ), ) ) class DaemonTimer(threading.Thread): """Variant of threading.Timer that is deaemonized""" def __init__(self, interval, function, args=None, kwargs=None): threading.Thread.__init__(self, daemon=True) self.interval = interval self.function = function self.args = args if args is not None else [] self.kwargs = kwargs if kwargs is not None else {} self.finished = threading.Event() def cancel(self): """Stop the timer if it hasn't finished yet.""" self.finished.set() def run(self): self.finished.wait(self.interval) if not self.finished.is_set(): self.function(*self.args, **self.kwargs) self.finished.set() class ScheduledPeriodicTimer: """Scheduling of periodic events Up to self._interval, schedule actions per second, above schedule events in self._interval second gaps. """ def __init__(self, interval): self._times_called = 0 self._interval = interval self._timer = None self._stopped = True def start(self): """Start the intervalic timer""" self.work() self._times_called += 1 self._stopped = False if self._times_called > self._interval: self._timer = DaemonTimer(self._interval, self._action) else: self._timer = DaemonTimer(BENCHMARK_INTERVAL_SHORT, self._action) self._timer.start() def _action(self): """Internally, called by timer""" self.work() self._times_called += 1 if self._times_called > self._interval: self._timer = DaemonTimer(self._interval, self._action) else: self._timer = DaemonTimer(BENCHMARK_INTERVAL_SHORT, self._action) self._timer.start() def work(self): """Override to perform the action""" raise NotImplementedError("Override me!") def cancel(self): """Call to cancel any events""" self._timer.cancel() self._stopped = True class BenchmarkTimer(ScheduledPeriodicTimer): """Allows easy observation of a given PID for resource usage""" def __init__(self, pid, bench_record, interval=BENCHMARK_INTERVAL): ScheduledPeriodicTimer.__init__(self, interval) #: PID of observed process self.pid = pid self.main = psutil.Process(self.pid) #: ``BenchmarkRecord`` to write results to self.bench_record = bench_record #: Cache of processes to keep track of cpu percent self.procs = {} def work(self): """Write statistics""" try: self._update_record() except psutil.NoSuchProcess: pass # skip, process died in flight except AttributeError: pass # skip, process died in flight def _update_record(self): """Perform the actual measurement""" # Memory measurements rss, vms, uss, pss = 0, 0, 0, 0 # I/O measurements io_in, io_out = 0, 0 check_io = True # CPU seconds cpu_seconds = 0 # Iterate over process and all children try: this_time = time.time() for proc in chain((self.main,), self.main.children(recursive=True)): proc = self.procs.setdefault(proc.pid, proc) with proc.oneshot(): if self.bench_record.prev_time: cpu_seconds += ( proc.cpu_percent() / 100 * (this_time - self.bench_record.prev_time) ) meminfo = proc.memory_full_info() rss += meminfo.rss vms += meminfo.vms uss += meminfo.uss pss += meminfo.pss if check_io: try: ioinfo = proc.io_counters() io_in += ioinfo.read_bytes io_out += ioinfo.write_bytes except NotImplementedError as nie: # OS doesn't track IO check_io = False self.bench_record.prev_time = this_time if not self.bench_record.first_time: self.bench_record.prev_time = this_time rss /= 1024 * 1024 vms /= 1024 * 1024 uss /= 1024 * 1024 pss /= 1024 * 1024 if check_io: io_in /= 1024 * 1024 io_out /= 1024 * 1024 else: io_in = None io_out = None except psutil.Error as e: return # Update benchmark record's RSS and VMS self.bench_record.max_rss = max(self.bench_record.max_rss or 0, rss) self.bench_record.max_vms = max(self.bench_record.max_vms or 0, vms) self.bench_record.max_uss = max(self.bench_record.max_uss or 0, uss) self.bench_record.max_pss = max(self.bench_record.max_pss or 0, pss) self.bench_record.io_in = io_in self.bench_record.io_out = io_out self.bench_record.cpu_seconds += cpu_seconds @contextlib.contextmanager def benchmarked(pid=None, benchmark_record=None, interval=BENCHMARK_INTERVAL): """Measure benchmark parameters while within the context manager Yields a ``BenchmarkRecord`` with the results (values are set after leaving context). If ``pid`` is ``None`` then the PID of the current process will be used. If ``benchmark_record`` is ``None`` then a new ``BenchmarkRecord`` is created and returned, otherwise, the object passed as this parameter is returned. Usage:: with benchmarked() as bench_result: pass """ result = benchmark_record or BenchmarkRecord() if pid is False: yield result else: start_time = time.time() bench_thread = BenchmarkTimer(int(pid or os.getpid()), result, interval) bench_thread.start() yield result bench_thread.cancel() result.running_time = time.time() - start_time def print_benchmark_records(records, file_): """Write benchmark records to file-like object""" print(BenchmarkRecord.get_header(), file=file_) for r in records: print(r.to_tsv(), file=file_) def write_benchmark_records(records, path): """Write benchmark records to file at path""" with open(path, "wt") as f: print_benchmark_records(records, f) snakemake-5.10.0/snakemake/caching/000077500000000000000000000000001361131222100170575ustar00rootroot00000000000000snakemake-5.10.0/snakemake/caching/__init__.py000066400000000000000000000041671361131222100212000ustar00rootroot00000000000000__authors__ = "Johannes Köster, Sven Nahnsen" __copyright__ = "Copyright 2019, Johannes Köster, Sven Nahnsen" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" from abc import ABCMeta, abstractmethod import os from snakemake.jobs import Job from snakemake.io import is_flagged, get_flag_value, apply_wildcards from snakemake.exceptions import WorkflowError, CacheMissException from snakemake.caching.hash import ProvenanceHashMap LOCATION_ENVVAR = "SNAKEMAKE_OUTPUT_CACHE" class AbstractOutputFileCache: __metaclass__ = ABCMeta def __init__(self): try: self.cache_location = os.environ[LOCATION_ENVVAR] except KeyError: raise WorkflowError( "Output file cache activated (--cache), but no cache " "location specified. Please set the environment variable " "${}.".format(LOCATION_ENVVAR) ) self.provenance_hash_map = ProvenanceHashMap() @abstractmethod def store(self, job: Job): pass @abstractmethod def fetch(self, job: Job): pass @abstractmethod def exists(self, job: Job): pass def get_outputfiles(self, job: Job): if job.rule.output[0].is_multiext: prefix_len = len( apply_wildcards(job.rule.output[0].multiext_prefix, job.wildcards) ) yield from ((f, f[prefix_len:]) for f in job.output) else: yield (job.output[0], "") def raise_write_error(self, entry, exception=None): raise WorkflowError( "Given output cache entry {} ($SNAKEMAKE_OUTPUT_CACHE={}) is not writeable.".format( entry, self.cache_location ), *[exception], ) def raise_read_error(self, entry, exception=None): raise WorkflowError( "Given output cache entry {} ($SNAKEMAKE_OUTPUT_CACHE={}) is not readable.".format( entry, self.cache_location ), *[exception], ) def raise_cache_miss_exception(self, job): raise CacheMissException("Job {} not yet cached.".format(job)) snakemake-5.10.0/snakemake/caching/hash.py000066400000000000000000000070741361131222100203640ustar00rootroot00000000000000__authors__ = "Johannes Köster, Sven Nahnsen" __copyright__ = "Copyright 2019, Johannes Köster, Sven Nahnsen" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" import hashlib import json from snakemake.jobs import Job from snakemake import script from snakemake import wrapper # ATTENTION: increase version number whenever the hashing algorithm below changes! __version__ = "0.1" class ProvenanceHashMap: def __init__(self): self._hashes = dict() def get_provenance_hash(self, job: Job): versioned_hash = hashlib.sha256() # Ensure that semantic version changes in this module versioned_hash.update(self._get_provenance_hash(job).encode()) versioned_hash.update(__version__.encode()) return versioned_hash.hexdigest() def _get_provenance_hash(self, job: Job): """ Recursively calculate hash for the output of the given job and all upstream jobs in a blockchain fashion. This is based on an idea of Sven Nahnsen. Fails if job has more than one output file. The reason is that there is no way to generate a per-output file hash without generating the files. This hash, however, shall work without having to generate the files, just by describing all steps down to a given job. """ if job in self._hashes: return self._hashes[job] workflow = job.dag.workflow h = hashlib.sha256() # Hash shell command or script. if job.is_shell: # We cannot use the formatted shell command, because it also contains threads, # resources, and filenames (which shall be irrelevant for the hash). h.update(job.rule.shellcmd.encode()) elif job.is_script: _, source, _ = script.get_source(job.rule.script) h.update(source) elif job.is_wrapper: _, source, _ = script.get_source( wrapper.get_script(job.rule.wrapper, prefix=workflow.wrapper_prefix) ) h.update(source) # Hash params. for key, value in sorted(job.params._allitems()): h.update(key.encode()) # If this raises a TypeError, we cannot calculate a reliable hash. h.update(json.dumps(value, sort_keys=True).encode()) # Hash input files that are not generated by other jobs. for f in job.input: if not any( f in depfiles for depfiles in job.dag.dependencies[job].values() ): with open(f, "b") as f: # Read and update hash string value in blocks of 4K for byte_block in iter(lambda: f.read(4096), b""): h.update(byte_block) # Hash used containers or conda environments. if workflow.use_conda and job.conda_env: if workflow.use_singularity and job.conda_env.singularity_img_url: h.update(job.conda_env.singularity_img_url.encode()) h.update(job.conda_env.content) elif workflow.use_singularity and job.singularity_img_url: h.update(job.singularity_img_url.encode()) # Generate hashes of dependencies, and add them in a blockchain fashion (as input to the current hash). for dep_hash in sorted( self._get_provenance_hash(dep) for dep in set(job.dag.dependencies[job].keys()) ): h.update(dep_hash.encode()) provenance_hash = h.hexdigest() # Store for re-use. self._hashes[job] = provenance_hash return provenance_hash snakemake-5.10.0/snakemake/caching/local.py000066400000000000000000000105761361131222100205340ustar00rootroot00000000000000__authors__ = "Johannes Köster, Sven Nahnsen" __copyright__ = "Copyright 2019, Johannes Köster, Sven Nahnsen" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" from tempfile import TemporaryDirectory from pathlib import Path import os import shutil import stat from snakemake.logging import logger from snakemake.jobs import Job from snakemake.exceptions import WorkflowError from snakemake.caching.hash import ProvenanceHashMap from snakemake.caching import LOCATION_ENVVAR, AbstractOutputFileCache class OutputFileCache(AbstractOutputFileCache): """ A cache for output files that uses a provenance hash value that describes all steps, parameters, and software needed to generate each output file. """ def __init__(self): super().__init__() self.path = Path(self.cache_location) def check_writeable(self, cachefile): if not (os.access(cachefile.parent, os.W_OK) or os.access(cachefile, os.W_OK)): self.raise_write_error(cachefile) def check_readable(self, cachefile): if not os.access(cachefile, os.R_OK): self.raise_read_error(cachefile) def store(self, job: Job): """ Store generated job output in the cache. """ with TemporaryDirectory(dir=self.path) as tmpdirname: tmpdir = Path(tmpdirname) for outputfile, cachefile in self.get_outputfiles_and_cachefiles(job): self.check_writeable(cachefile) logger.info("Moving output file {} to cache.".format(outputfile)) tmp = tmpdir / cachefile.name # First move is performed into a tempdir (it might involve a copy if not on the same FS). # This is important, such that network filesystem latency # does not lead to concurrent writes to the same file. # We can use the plain copy method of shutil, because we do not care about the metadata. shutil.move(outputfile, tmp, copy_function=shutil.copy) # make readable/writeable for all os.chmod( tmp, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH, ) # Move to the actual path (now we are on the same FS, hence move is atomic). # Here we use the default copy function, also copying metadata (which is important here). # It will always work, because we are guaranteed to be in the same FS. shutil.move(tmp, cachefile) # now restore the outputfile via a symlink self.symlink(cachefile, outputfile, utime=False) def fetch(self, job: Job): """ Retrieve cached output file and copy to the place where the job expects it's output. """ for outputfile, cachefile in self.get_outputfiles_and_cachefiles(job): if not cachefile.exists(): self.raise_cache_miss_exception(job) self.check_readable(cachefile) self.symlink(cachefile, outputfile) def exists(self, job: Job): """ Return True if job is already cached """ for outputfile, cachefile in self.get_outputfiles_and_cachefiles(job): if not cachefile.exists(): return False self.check_readable(cachefile) return True def get_outputfiles_and_cachefiles(self, job: Job): provenance_hash = self.provenance_hash_map.get_provenance_hash(job) base_path = self.path / provenance_hash return ( (outputfile, base_path.with_suffix(ext)) for outputfile, ext in self.get_outputfiles(job) ) def symlink(self, path, outputfile, utime=True): if os.utime in os.supports_follow_symlinks or not utime: logger.info("Symlinking output file {} from cache.".format(outputfile)) os.symlink(path, outputfile) if utime: os.utime(outputfile, follow_symlinks=False) else: logger.info( "Copying output file {} from cache (OS does not support updating the modification date of symlinks).".format( outputfile ) ) shutil.copyfile(path, outputfile) snakemake-5.10.0/snakemake/caching/remote.py000066400000000000000000000040111361131222100207200ustar00rootroot00000000000000__authors__ = "Johannes Köster, Sven Nahnsen" __copyright__ = "Copyright 2019, Johannes Köster, Sven Nahnsen" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" from snakemake.caching.hash import ProvenanceHashMap from snakemake.caching import AbstractOutputFileCache from snakemake.jobs import Job from snakemake.io import get_flag_value, IOFile class OutputFileCache(AbstractOutputFileCache): """ A cache for output files that uses a provenance hash value that describes all steps, parameters, and software needed to generate each output file. This is the remote version. """ def __init__(self, remote_provider): super().__init__() self.remote_provider = remote_provider def store(self, job: Job): for entry in self._get_remotes(job): # upload to remote try: entry.upload() except Exception as e: self.raise_write_error(entry, exception=e) def fetch(self, job: Job): for entry in self._get_remotes(job): if not entry.exists(): self.raise_cache_miss_exception(job) # download to outputfile try: entry.download() except Exception as e: self.raise_read_error(entry, exception=e) def exists(self, job: Job): for entry in self._get_remotes(job): try: return entry.exists() except Exception as e: self.raise_read_error(entry, exception=e) def _get_remotes(self, job: Job, check_output_exists=False): provenance_hash = self.provenance_hash_map.get_provenance_hash(job) for outputfile, ext in self.get_outputfiles(job): f = self.remote_provider.remote( "{}/{}{}".format(self.cache_location, provenance_hash, ext) ) remote = get_flag_value(f, "remote_object") # set local copy of the remote file remote._iofile = outputfile yield remote snakemake-5.10.0/snakemake/checkpoints.py000066400000000000000000000024101361131222100203440ustar00rootroot00000000000000from snakemake.exceptions import IncompleteCheckpointException, WorkflowError from snakemake.io import checkpoint_target class Checkpoints: """ A namespace for checkpoints so that they can be accessed via dot notation. """ def __init__(self): self.future_output = None def register(self, rule): setattr(self, rule.name, Checkpoint(rule, self)) class Checkpoint: __slots__ = ["rule", "checkpoints"] def __init__(self, rule, checkpoints): self.rule = rule self.checkpoints = checkpoints def get(self, **wildcards): missing = self.rule.wildcard_names.difference(wildcards.keys()) if missing: raise WorkflowError( "Missing wildcard values for {}".format(", ".join(missing)) ) output, _ = self.rule.expand_output(wildcards) if self.checkpoints.future_output is None or any( (not f.exists or f in self.checkpoints.future_output) for f in output ): raise IncompleteCheckpointException(self.rule, checkpoint_target(output[0])) return CheckpointJob(self.rule, output) class CheckpointJob: __slots__ = ["rule", "output"] def __init__(self, rule, output): self.output = output self.rule = rule snakemake-5.10.0/snakemake/common.py000066400000000000000000000045061361131222100173320ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2016-2019, Johannes Köster" __email__ = "johannes.koester@protonmail.com" __license__ = "MIT" from functools import update_wrapper import inspect import uuid import os from ._version import get_versions __version__ = get_versions()["version"] del get_versions MIN_PY_VERSION = (3, 5) DYNAMIC_FILL = "__snakemake_dynamic__" SNAKEMAKE_SEARCHPATH = os.path.dirname(os.path.dirname(__file__)) UUID_NAMESPACE = uuid.uuid5(uuid.NAMESPACE_URL, "https://snakemake.readthedocs.io") class TBDInt(int): """An integer that prints into """ def __str__(self): return "" # A string that prints as TBD TBDString = "" def num_if_possible(s): """Convert string to number if possible, otherwise return string.""" try: return int(s) except ValueError: try: return float(s) except ValueError: return s def get_last_stable_version(): return __version__.split("+")[0] def get_container_image(): return "snakemake/snakemake:v{}".format(get_last_stable_version()) def get_uuid(name): return uuid.uuid5(UUID_NAMESPACE, name) class Mode: """ Enum for execution mode of Snakemake. This handles the behavior of e.g. the logger. """ default = 0 subprocess = 1 cluster = 2 class lazy_property(property): __slots__ = ["method", "cached", "__doc__"] @staticmethod def clean(instance, method): delattr(instance, method) def __init__(self, method): self.method = method self.cached = "_{}".format(method.__name__) super().__init__(method, doc=method.__doc__) def __get__(self, instance, owner): cached = ( getattr(instance, self.cached) if hasattr(instance, self.cached) else None ) if cached is not None: return cached value = self.method(instance) setattr(instance, self.cached, value) return value def strip_prefix(text, prefix): if text.startswith(prefix): return text[len(prefix) :] return text def log_location(msg): callerframerecord = inspect.stack()[1] frame = callerframerecord[0] info = inspect.getframeinfo(frame) logger.debug( "{}: {info.filename}, {info.function}, {info.lineno}".format(msg, info=info) ) snakemake-5.10.0/snakemake/cwl.py000066400000000000000000000206011361131222100166210ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2018-2019, Johannes Köster" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" from urllib.request import pathname2url import os import subprocess import tempfile import json import shutil import uuid from itertools import chain from snakemake.utils import format from snakemake.logging import logger from snakemake.exceptions import WorkflowError from snakemake.shell import shell from snakemake.common import get_container_image, Mode from snakemake.io import is_flagged def cwl( path, basedir, input, output, params, wildcards, threads, resources, log, config, rulename, use_singularity, bench_record, jobid, ): """ Load cwl from the given basedir + path and execute it. """ if shutil.which("cwltool") is None: raise WorkflowError( "'cwltool' must be in PATH in order to execute cwl directive." ) if not path.startswith("http"): if path.startswith("file://"): path = path[7:] elif path.startswith("file:"): path = path[5:] if not os.path.isabs(path): path = os.path.abspath(os.path.join(basedir, path)) path = "file://" + path path = format(path, stepout=1) if path.startswith("file://"): sourceurl = "file:" + pathname2url(path[7:]) else: sourceurl = path def file_spec(f): if isinstance(f, str): return {"path": os.path.abspath(f), "class": "File"} return [file_spec(f_) for f_ in f] inputs = dict() inputs.update({name: file_spec(f) for name, f in input.items()}) inputs.update({name: p for name, p in params.items()}) inputs.update({name: f for name, f in output.items()}) inputs.update({name: f for name, f in log.items()}) args = "--singularity" if use_singularity else "" with tempfile.NamedTemporaryFile(mode="w") as input_file: json.dump(inputs, input_file) input_file.flush() cmd = "cwltool {} {} {}".format(args, sourceurl, input_file.name) shell(cmd, bench_record=bench_record) def job_to_cwl(job, dag, outputs, inputs): """Convert a job with its dependencies to a CWL workflow step. """ if job.dynamic_output: raise WorkflowError("Dynamic output is not supported by CWL conversion.") for f in job.output: if os.path.isabs(f): raise WorkflowError( "All output files have to be relative to the " "working directory." ) get_output_id = lambda job, i: "#main/job-{}/{}".format(job.jobid, i) dep_ids = { o: get_output_id(dep, i) for dep, files in dag.dependencies[job].items() for i, o in enumerate(dep.output) if o in files } files = [f for f in job.input if f not in dep_ids] if job.conda_env_file: files.add(os.path.relpath(job.conda_env_file)) out = [get_output_id(job, i) for i, _ in enumerate(job.output)] def workdir_entry(i, f): location = "??inputs.input_files[{}].location??".format(i) if f.is_directory: entry = { "class": "Directory", "basename": os.path.basename(f), "location": location, } else: entry = { "class": "File", "basename": os.path.basename(f), "location": location, } return "$({})".format( json.dumps(outer_entry(f, entry)).replace('"??', "").replace('??"', "") ).replace('"', "'") def outer_entry(f, entry): parent = os.path.dirname(f) if parent: return outer_entry( parent, { "class": "Directory", "basename": os.path.basename(parent), "listing": [entry], }, ) else: return entry if job in dag.targetjobs: # TODO this maps output files into the cwd after the workflow is complete. # We need to find a way to define subdirectories though. Otherwise, # there can be name clashes, and it will also become very crowded. outputs.append( { "type": {"type": "array", "items": "File"}, "outputSource": "#main/job-{}/output_files".format(job.jobid), "id": "#main/output/job-{}".format(job.jobid), } ) cwl = { "run": "#snakemake-job", "requirements": { "InitialWorkDirRequirement": { "listing": [ {"writable": True, "entry": workdir_entry(i, f)} for i, f in enumerate( chain( files, (f for dep in dag.dependencies[job] for f in dep.output), ) ) ] } }, "in": { "cores": {"default": job.threads}, "target_files": {"default": job.output._plainstrings()}, "rules": {"default": [job.rule.name]}, }, "out": ["output_files"], "id": "#main/job-{}".format(job.jobid), } if files: inputs.append( { "type": {"type": "array", "items": "File"}, "default": [{"class": "File", "location": f} for f in files], "id": "#main/input/job-{}".format(job.jobid), } ) input_files = [] if files: input_files.append("#main/input/job-{}".format(job.jobid)) input_files.extend( "#main/job-{}/output_files".format(dep.jobid) for dep in dag.dependencies[job] ) cwl["in"]["input_files"] = {"source": input_files, "linkMerge": "merge_flattened"} return cwl def dag_to_cwl(dag): """Convert a given DAG to a CWL workflow, which is returned as JSON object. """ snakemake_cwl = { "class": "CommandLineTool", "id": "#snakemake-job", "label": "Snakemake job executor", "hints": [{"dockerPull": get_container_image(), "class": "DockerRequirement"}], "baseCommand": "snakemake", "requirements": {"ResourceRequirement": {"coresMin": "$(inputs.cores)"}}, "arguments": [ "--force", "--keep-target-files", "--keep-remote", "--force-use-threads", "--wrapper-prefix", dag.workflow.wrapper_prefix, "--notemp", "--quiet", "--use-conda", "--no-hooks", "--nolock", "--mode", str(Mode.subprocess), ], "inputs": { "snakefile": { "type": "File", "default": { "class": "File", "location": os.path.relpath(dag.workflow.snakefile), }, "inputBinding": {"prefix": "--snakefile"}, }, "sources": { "type": "File[]", "default": [ {"class": "File", "location": f} for f in dag.workflow.get_sources() ], }, "cores": { "type": "int", "default": 1, "inputBinding": {"prefix": "--cores"}, }, "rules": { "type": "string[]?", "inputBinding": {"prefix": "--allowed-rules"}, }, "input_files": {"type": "File[]", "default": []}, "target_files": {"type": "string[]?", "inputBinding": {"position": 0}}, }, "outputs": { "output_files": { "type": {"type": "array", "items": "File"}, "outputBinding": {"glob": "$(inputs.target_files)"}, } }, } groups = dag.get_jobs_or_groups() outputs = [] inputs = [] dag_cwl = [job_to_cwl(job, dag, outputs, inputs) for job in groups] return { "cwlVersion": "v1.0", "$graph": [ snakemake_cwl, { "class": "Workflow", "requirements": { "InlineJavascriptRequirement": {}, "MultipleInputFeatureRequirement": {}, }, "steps": dag_cwl, "inputs": inputs, "outputs": outputs, "id": "#main", }, ], } snakemake-5.10.0/snakemake/dag.py000077500000000000000000002133261361131222100166020ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import html import os import shutil import textwrap import time import tarfile from collections import defaultdict, Counter from itertools import chain, filterfalse, groupby from functools import partial from pathlib import Path import uuid import math from snakemake.io import PeriodicityDetector, wait_for_files, is_flagged from snakemake.jobs import Job, Reason, GroupJob from snakemake.exceptions import MissingInputException from snakemake.exceptions import MissingRuleException, AmbiguousRuleException from snakemake.exceptions import CyclicGraphException, MissingOutputException from snakemake.exceptions import IncompleteFilesException, ImproperOutputException from snakemake.exceptions import PeriodicWildcardError from snakemake.exceptions import RemoteFileException, WorkflowError, ChildIOException from snakemake.exceptions import InputFunctionException from snakemake.logging import logger from snakemake.common import DYNAMIC_FILL from snakemake.deployment import conda, singularity from snakemake.output_index import OutputIndex from snakemake import workflow class Batch: """Definition of a batch for calculating only a partial DAG.""" def __init__(self, rulename: str, idx: int, batches: int): assert idx <= batches assert idx > 0 self.rulename = rulename self.idx = idx self.batches = batches def get_batch(self, items: list): """Return the defined batch of the given items. Items are usually input files.""" # make sure that we always consider items in the same order if len(items) < self.batches: raise WorkflowError( "Batching rule {} has less input files than batches. " "Please choose a smaller number of batches.".format(self.rulename) ) items = sorted(items) batch_len = math.floor(len(items) / self.batches) # self.batch is one-based, hence we have to subtract 1 idx = self.idx - 1 i = idx * batch_len if self.is_final: # extend the last batch to cover rest of list return items[i:] else: return items[i : i + batch_len] @property def is_final(self): return self.idx == self.batches def __str__(self): return "{}/{} (rule {})".format(self.idx, self.batches, self.rulename) class DAG: """Directed acyclic graph of jobs.""" def __init__( self, workflow, rules=None, dryrun=False, targetfiles=None, targetrules=None, forceall=False, forcerules=None, forcefiles=None, priorityfiles=None, priorityrules=None, untilfiles=None, untilrules=None, omitfiles=None, omitrules=None, ignore_ambiguity=False, force_incomplete=False, ignore_incomplete=False, notemp=False, keep_remote_local=False, batch=None, ): self.dryrun = dryrun self.dependencies = defaultdict(partial(defaultdict, set)) self.depending = defaultdict(partial(defaultdict, set)) self._needrun = set() self._priority = dict() self._reason = defaultdict(Reason) self._finished = set() self._dynamic = set() self._len = 0 self.workflow = workflow self.rules = set(rules) self.ignore_ambiguity = ignore_ambiguity self.targetfiles = targetfiles self.targetrules = targetrules self.priorityfiles = priorityfiles self.priorityrules = priorityrules self.targetjobs = set() self.prioritytargetjobs = set() self._ready_jobs = set() self.notemp = notemp self.keep_remote_local = keep_remote_local self._jobid = dict() self.job_cache = dict() self.conda_envs = dict() self.singularity_imgs = dict() self._progress = 0 self._group = dict() self.forcerules = set() self.forcefiles = set() self.untilrules = set() self.untilfiles = set() self.omitrules = set() self.omitfiles = set() self.updated_subworkflow_files = set() if forceall: self.forcerules.update(self.rules) elif forcerules: self.forcerules.update(forcerules) if forcefiles: self.forcefiles.update(forcefiles) if untilrules: self.untilrules.update(set(rule.name for rule in untilrules)) if untilfiles: self.untilfiles.update(untilfiles) if omitrules: self.omitrules.update(set(rule.name for rule in omitrules)) if omitfiles: self.omitfiles.update(omitfiles) self.omitforce = set() self.batch = batch if batch is not None and not batch.is_final: # Since not all input files of a batching rule are considered, we cannot run # beyond that rule. # For the final batch, we do not need to omit anything. self.omitrules.add(batch.rulename) self.force_incomplete = force_incomplete self.ignore_incomplete = ignore_incomplete self.periodic_wildcard_detector = PeriodicityDetector() self.update_output_index() def init(self, progress=False): """ Initialise the DAG. """ for job in map(self.rule2job, self.targetrules): job = self.update([job], progress=progress) self.targetjobs.add(job) for file in self.targetfiles: job = self.update(self.file2jobs(file), file=file, progress=progress) self.targetjobs.add(job) self.cleanup() self.update_needrun() self.set_until_jobs() self.delete_omitfrom_jobs() self.update_jobids() self.check_directory_outputs() # check if remaining jobs are valid for i, job in enumerate(self.jobs): job.is_valid() def check_directory_outputs(self): """Check that no output file is contained in a directory output of the same or another rule.""" outputs = sorted( { path(f) for job in self.jobs for f in job.output for path in (os.path.abspath, os.path.realpath) } ) for i in range(len(outputs) - 1): a, b = outputs[i : i + 2] try: common = os.path.commonpath([a, b]) except ValueError: # commonpath raises error if windows drives are different. continue if common == os.path.commonpath([a]): raise ChildIOException(parent=outputs[i], child=outputs[i + 1]) @property def checkpoint_jobs(self): for job in self.needrun_jobs: if job.is_checkpoint: yield job def update_checkpoint_outputs(self): workflow.checkpoints.future_output = set( f for job in self.checkpoint_jobs for f in job.output ) def update_jobids(self): for job in self.jobs: if job not in self._jobid: self._jobid[job] = len(self._jobid) def cleanup(self): self.job_cache.clear() final_jobs = set(self.jobs) todelete = [job for job in self.dependencies if job not in final_jobs] for job in todelete: del self.dependencies[job] try: del self.depending[job] except KeyError: pass def create_conda_envs( self, dryrun=False, forceall=False, init_only=False, quiet=False ): # First deduplicate based on job.conda_env_file jobs = self.jobs if forceall else self.needrun_jobs env_set = { (job.conda_env_file, job.singularity_img_url) for job in jobs if job.conda_env_file } # Then based on md5sum values self.conda_envs = dict() for (env_file, simg_url) in env_set: simg = None if simg_url: assert ( simg_url in self.singularity_imgs ), "bug: must first pull singularity images" simg = self.singularity_imgs[simg_url] env = conda.Env(env_file, self, singularity_img=simg) self.conda_envs[(env_file, simg_url)] = env if not init_only: for env in self.conda_envs.values(): if not dryrun or not quiet: env.create(dryrun) def pull_singularity_imgs(self, dryrun=False, forceall=False, quiet=False): # First deduplicate based on job.conda_env_file jobs = self.jobs if forceall else self.needrun_jobs img_set = {job.singularity_img_url for job in jobs if job.singularity_img_url} for img_url in img_set: img = singularity.Image(img_url, self) if not dryrun or not quiet: img.pull(dryrun) self.singularity_imgs[img_url] = img def update_output_index(self): """Update the OutputIndex.""" self.output_index = OutputIndex(self.rules) def check_incomplete(self): """Check if any output files are incomplete. This is done by looking up markers in the persistence module.""" if not self.ignore_incomplete: incomplete = self.incomplete_files if incomplete: if self.force_incomplete: logger.debug("Forcing incomplete files:") logger.debug("\t" + "\n\t".join(incomplete)) self.forcefiles.update(incomplete) else: raise IncompleteFilesException(incomplete) def incomplete_external_jobid(self, job): """Return the external jobid of the job if it is marked as incomplete. Returns None, if job is not incomplete, or if no external jobid has been registered or if force_incomplete is True. """ if self.force_incomplete: return None jobids = self.workflow.persistence.external_jobids(job) if len(jobids) == 1: return jobids[0] elif len(jobids) > 1: raise WorkflowError( "Multiple different external jobids registered " "for output files of incomplete job {} ({}). This job " "cannot be resumed. Execute Snakemake with --rerun-incomplete " "to fix this issue.".format(job.jobid, jobids) ) def check_dynamic(self): """Check dynamic output and update downstream rules if necessary.""" for job in filter( lambda job: (job.dynamic_output and not self.needrun(job)), self.jobs ): self.update_dynamic(job) self.postprocess() @property def dynamic_output_jobs(self): """Iterate over all jobs with dynamic output files.""" return (job for job in self.jobs if job.dynamic_output) @property def jobs(self): """ All jobs in the DAG. """ for job in self.bfs(self.dependencies, *self.targetjobs): yield job @property def needrun_jobs(self): """ Jobs that need to be executed. """ for job in filter( self.needrun, self.bfs(self.dependencies, *self.targetjobs, stop=self.noneedrun_finished), ): yield job @property def local_needrun_jobs(self): """Iterate over all jobs that need to be run and are marked as local.""" return filter(lambda job: job.is_local, self.needrun_jobs) @property def finished_jobs(self): """ Iterate over all jobs that have been finished.""" for job in filter(self.finished, self.bfs(self.dependencies, *self.targetjobs)): yield job @property def ready_jobs(self): """Jobs that are ready to execute.""" return self._ready_jobs def needrun(self, job): """Return whether a given job needs to be executed.""" return job in self._needrun def priority(self, job): """Return priority of given job.""" return self._priority[job] def noneedrun_finished(self, job): """ Return whether a given job is finished or was not required to run at all. """ return not self.needrun(job) or self.finished(job) def reason(self, job): """ Return the reason of the job execution. """ return self._reason[job] def finished(self, job): """ Return whether a job is finished. """ return job in self._finished def dynamic(self, job): """ Return whether a job is dynamic (i.e. it is only a placeholder for those that are created after the job with dynamic output has finished. """ if job.is_group(): for j in job: if j in self._dynamic: return True else: return job in self._dynamic def requested_files(self, job): """Return the files a job requests.""" return set(*self.depending[job].values()) @property def incomplete_files(self): """Return list of incomplete files.""" return list( chain( *( job.output for job in filter( self.workflow.persistence.incomplete, filterfalse(self.needrun, self.jobs), ) ) ) ) @property def newversion_files(self): """Return list of files where the current version is newer than the recorded version. """ return list( chain( *( job.output for job in filter(self.workflow.persistence.newversion, self.jobs) ) ) ) def missing_temp(self, job): """ Return whether a temp file that is input of the given job is missing. """ for job_, files in self.depending[job].items(): if self.needrun(job_) and any(not f.exists for f in files): return True return False def check_and_touch_output( self, job, wait=3, ignore_missing_output=False, no_touch=False, force_stay_on_remote=False, ): """ Raise exception if output files of job are missing. """ expanded_output = [job.shadowed_path(path) for path in job.expanded_output] if job.benchmark: expanded_output.append(job.benchmark) if not ignore_missing_output: try: wait_for_files( expanded_output, latency_wait=wait, force_stay_on_remote=force_stay_on_remote, ignore_pipe=True, ) except IOError as e: raise MissingOutputException( str(e) + "\nThis might be due to " "filesystem latency. If that is the case, consider to increase the " "wait time with --latency-wait.", rule=job.rule, ) # Ensure that outputs are of the correct type (those flagged with directory() # are directories and not files and vice versa). for f in expanded_output: if (f.is_directory and not os.path.isdir(f)) or ( os.path.isdir(f) and not f.is_directory ): raise ImproperOutputException(job.rule, [f]) # It is possible, due to archive expansion or cluster clock skew, that # the files appear older than the input. But we know they must be new, # so touch them to update timestamps. This also serves to touch outputs # when using the --touch flag. # Note that if the input files somehow have a future date then this will # not currently be spotted and the job will always be re-run. if not no_touch: for f in expanded_output: # This won't create normal files if missing, but will create # the flag file for directories. if f.exists_local: f.touch() def unshadow_output(self, job, only_log=False): """ Move files from shadow directory to real output paths. """ if not job.shadow_dir or not job.expanded_output: return files = job.log if only_log else chain(job.expanded_output, job.log) for real_output in files: shadow_output = job.shadowed_path(real_output).file # Remake absolute symlinks as relative if os.path.islink(shadow_output): dest = os.readlink(shadow_output) if os.path.isabs(dest): rel_dest = os.path.relpath(dest, job.shadow_dir) os.remove(shadow_output) os.symlink(rel_dest, shadow_output) if os.path.realpath(shadow_output) == os.path.realpath(real_output): continue logger.debug( "Moving shadow output {} to destination {}".format( shadow_output, real_output ) ) shutil.move(shadow_output, real_output) shutil.rmtree(job.shadow_dir) def check_periodic_wildcards(self, job): """ Raise an exception if a wildcard of the given job appears to be periodic, indicating a cyclic dependency. """ for wildcard, value in job.wildcards_dict.items(): periodic_substring = self.periodic_wildcard_detector.is_periodic(value) if periodic_substring is not None: raise PeriodicWildcardError( "The value {} in wildcard {} is periodically repeated ({}). " "This would lead to an infinite recursion. " "To avoid this, e.g. restrict the wildcards in this rule to certain values.".format( periodic_substring, wildcard, value ), rule=job.rule, ) def handle_protected(self, job): """ Write-protect output files that are marked with protected(). """ for f in job.expanded_output: if f in job.protected_output: logger.info("Write-protecting output file {}.".format(f)) f.protect() def handle_touch(self, job): """ Touches those output files that are marked for touching. """ for f in job.expanded_output: if f in job.touch_output: f = job.shadowed_path(f) logger.info("Touching output file {}.".format(f)) f.touch_or_create() assert os.path.exists(f) def temp_input(self, job): for job_, files in self.dependencies[job].items(): for f in filter(job_.temp_output.__contains__, files): yield f def temp_size(self, job): """Return the total size of temporary input files of the job. If none, return 0. """ return sum(f.size for f in self.temp_input(job)) def handle_temp(self, job): """ Remove temp files if they are no longer needed. Update temp_mtimes. """ if self.notemp: return is_temp = lambda f: is_flagged(f, "temp") # handle temp input needed = lambda job_, f: any( f in files for j, files in self.depending[job_].items() if not self.finished(j) and self.needrun(j) and j != job ) def unneeded_files(): # temp input for job_, files in self.dependencies[job].items(): tempfiles = set(f for f in job_.expanded_output if is_temp(f)) yield from filterfalse(partial(needed, job_), tempfiles & files) # temp output if not job.dynamic_output and ( job not in self.targetjobs or job.rule.name == self.workflow.first_rule ): tempfiles = ( f for f in job.expanded_output if is_temp(f) and f not in self.targetfiles ) yield from filterfalse(partial(needed, job), tempfiles) for f in unneeded_files(): logger.info("Removing temporary output file {}.".format(f)) f.remove(remove_non_empty_dir=True) def handle_log(self, job, upload_remote=True): for f in job.log: if not f.exists_local: # If log file was not created during job, create an empty one. f.touch_or_create() if upload_remote and f.is_remote and not f.should_stay_on_remote: f.upload_to_remote() if not f.exists_remote: raise RemoteFileException( "The file upload was attempted, but it does not " "exist on remote. Check that your credentials have " "read AND write permissions." ) def handle_remote(self, job, upload=True): """ Remove local files if they are no longer needed and upload. """ if upload: # handle output files files = list(job.expanded_output) if job.benchmark: files.append(job.benchmark) for f in files: if f.is_remote and not f.should_stay_on_remote: f.upload_to_remote() remote_mtime = f.mtime # immediately force local mtime to match remote, # since conversions from S3 headers are not 100% reliable # without this, newness comparisons may fail down the line f.touch(times=(remote_mtime, remote_mtime)) if not f.exists_remote: raise RemoteFileException( "The file upload was attempted, but it does not " "exist on remote. Check that your credentials have " "read AND write permissions." ) if not self.keep_remote_local: # handle input files needed = lambda job_, f: any( f in files for j, files in self.depending[job_].items() if not self.finished(j) and self.needrun(j) and j != job ) def unneeded_files(): putative = ( lambda f: f.is_remote and not f.protected and not f.should_keep_local ) generated_input = set() for job_, files in self.dependencies[job].items(): generated_input |= files for f in filter(putative, files): if not needed(job_, f): yield f for f, f_ in zip(job.output, job.rule.output): if putative(f) and not needed(job, f) and not f in self.targetfiles: if f in job.dynamic_output: for f_ in job.expand_dynamic(f_): yield f_ else: yield f for f in filter(putative, job.input): # TODO what about remote inputs that are used by multiple jobs? if f not in generated_input: yield f for f in unneeded_files(): if f.exists_local: logger.info("Removing local output file: {}".format(f)) f.remove() job.rmdir_empty_remote_dirs() def jobid(self, job): """Return job id of given job.""" if job.is_group(): return job.jobid else: return self._jobid[job] def update( self, jobs, file=None, visited=None, skip_until_dynamic=False, progress=False ): """ Update the DAG by adding given jobs and their dependencies. """ if visited is None: visited = set() producer = None exceptions = list() jobs = sorted(jobs, reverse=not self.ignore_ambiguity) cycles = list() for job in jobs: logger.dag_debug(dict(status="candidate", job=job)) if file in job.input: cycles.append(job) continue if job in visited: cycles.append(job) continue try: self.check_periodic_wildcards(job) self.update_( job, visited=set(visited), skip_until_dynamic=skip_until_dynamic, progress=progress, ) # TODO this might fail if a rule discarded here is needed # elsewhere if producer: if job < producer or self.ignore_ambiguity: break elif producer is not None: raise AmbiguousRuleException(file, job, producer) producer = job except ( MissingInputException, CyclicGraphException, PeriodicWildcardError, ) as ex: exceptions.append(ex) except RecursionError as e: raise WorkflowError( e, "If building the DAG exceeds the recursion limit, " "this is likely due to a cyclic dependency." "E.g. you might have a sequence of rules that " "can generate their own input. Try to make " "the output files more specific. " "A common pattern is to have different prefixes " "in the output files of different rules." + "\nProblematic file pattern: {}".format(file) if file else "", ) if producer is None: if cycles: job = cycles[0] raise CyclicGraphException(job.rule, file, rule=job.rule) if exceptions: raise exceptions[0] else: logger.dag_debug(dict(status="selected", job=producer)) n = len(self.dependencies) if progress and n % 1000 == 0 and n and self._progress != n: logger.info("Processed {} potential jobs.".format(n)) self._progress = n return producer def update_(self, job, visited=None, skip_until_dynamic=False, progress=False): """ Update the DAG by adding the given job and its dependencies. """ if job in self.dependencies: return if visited is None: visited = set() visited.add(job) dependencies = self.dependencies[job] potential_dependencies = self.collect_potential_dependencies(job) skip_until_dynamic = skip_until_dynamic and not job.dynamic_output missing_input = set() producer = dict() exceptions = dict() for file, jobs in potential_dependencies.items(): if not jobs: # no producing job found if not file.exists: # file not found, hence missing input missing_input.add(file) # file found, no problem continue try: selected_job = self.update( jobs, file=file, visited=visited, skip_until_dynamic=skip_until_dynamic or file in job.dynamic_input, progress=progress, ) producer[file] = selected_job except ( MissingInputException, CyclicGraphException, PeriodicWildcardError, ) as ex: if not file.exists: self.delete_job(job, recursive=False) # delete job from tree raise ex for file, job_ in producer.items(): dependencies[job_].add(file) self.depending[job_][job].add(file) if self.is_batch_rule(job.rule) and self.batch.is_final: # For the final batch, ensure that all input files from # previous batches are present on disk. if any( f for f in job.input if f not in potential_dependencies and not f.exists ): raise WorkflowError( "Unable to execute batch {} because not all previous batches " "have been completed before or files have been deleted.".format( self.batch ) ) if missing_input: self.delete_job(job, recursive=False) # delete job from tree raise MissingInputException(job.rule, missing_input) if skip_until_dynamic: self._dynamic.add(job) def update_needrun(self): """ Update the information whether a job needs to be executed. """ def output_mintime(job): for job_ in self.bfs(self.depending, job): t = job_.output_mintime if t: return t def needrun(job): reason = self.reason(job) noinitreason = not reason updated_subworkflow_input = self.updated_subworkflow_files.intersection( job.input ) if ( job not in self.omitforce and job.rule in self.forcerules or not self.forcefiles.isdisjoint(job.output) ): reason.forced = True elif updated_subworkflow_input: reason.updated_input.update(updated_subworkflow_input) elif job in self.targetjobs: # TODO find a way to handle added/removed input files here? if not job.output and not job.benchmark: if job.input: if job.rule.norun: reason.updated_input_run.update( [f for f in job.input if not f.exists] ) else: reason.nooutput = True else: reason.noio = True else: if job.rule in self.targetrules: missing_output = job.missing_output() else: missing_output = job.missing_output( requested=set(chain(*self.depending[job].values())) | self.targetfiles ) reason.missing_output.update(missing_output) if not reason: output_mintime_ = output_mintime(job) if output_mintime_: updated_input = [ f for f in job.input if f.exists and f.is_newer(output_mintime_) ] reason.updated_input.update(updated_input) if noinitreason and reason: reason.derived = False return job reason = self.reason _needrun = self._needrun dependencies = self.dependencies depending = self.depending _needrun.clear() candidates = set(self.jobs) queue = list(filter(reason, map(needrun, candidates))) visited = set(queue) while queue: job = queue.pop(0) _needrun.add(job) for job_, files in dependencies[job].items(): missing_output = job_.missing_output(requested=files) reason(job_).missing_output.update(missing_output) if missing_output and not job_ in visited: visited.add(job_) queue.append(job_) for job_, files in depending[job].items(): if job_ in candidates: reason(job_).updated_input_run.update(files) if not job_ in visited: visited.add(job_) queue.append(job_) # update len including finished jobs (because they have already increased the job counter) self._len = len(self._finished | self._needrun) def in_until(self, job): """Return whether given job has been specified via --until.""" return job.rule.name in self.untilrules or not self.untilfiles.isdisjoint( job.output ) def in_omitfrom(self, job): """Return whether given job has been specified via --omit-from.""" return job.rule.name in self.omitrules or not self.omitfiles.isdisjoint( job.output ) def until_jobs(self): """Returns a generator of jobs specified by untiljobs.""" return (job for job in self.jobs if self.in_until(job)) def omitfrom_jobs(self): """Returns a generator of jobs specified by omitfromjobs.""" return (job for job in self.jobs if self.in_omitfrom(job)) def downstream_of_omitfrom(self): """Returns the downstream of --omit-from rules or files and themselves.""" return self.bfs(self.depending, *self.omitfrom_jobs()) def delete_omitfrom_jobs(self): """Removes jobs downstream of jobs specified by --omit-from.""" if not self.omitrules and not self.omitfiles: return downstream_jobs = list( self.downstream_of_omitfrom() ) # need to cast as list before deleting jobs for job in downstream_jobs: self.delete_job(job, recursive=False, add_dependencies=True) def set_until_jobs(self): """Removes jobs downstream of jobs specified by --omit-from.""" if not self.untilrules and not self.untilfiles: return self.targetjobs = set(self.until_jobs()) def update_priority(self): """ Update job priorities. """ prioritized = ( lambda job: job.rule in self.priorityrules or not self.priorityfiles.isdisjoint(job.output) ) for job in self.needrun_jobs: self._priority[job] = job.rule.priority for job in self.bfs( self.dependencies, *filter(prioritized, self.needrun_jobs), stop=self.noneedrun_finished, ): self._priority[job] = Job.HIGHEST_PRIORITY def update_groups(self): groups = dict() for job in self.needrun_jobs: if job.group is None: continue stop = lambda j: j.group != job.group # BFS into depending needrun jobs if in same group # Note: never go up here (into depending), because it may contain # jobs that have been sorted out due to e.g. ruleorder. group = GroupJob( job.group, ( job for job in self.bfs(self.dependencies, job, stop=stop) if self.needrun(job) ), ) # merge with previously determined groups if present for j in group: if j in groups: other = groups[j] other.merge(group) group = other # update assignment for j in group: if j not in groups: groups[j] = group self._group = groups def update_ready(self, jobs=None): """ Update information whether a job is ready to execute. Given jobs must be needrun jobs! """ if jobs is None: jobs = self.needrun_jobs candidate_groups = set() for job in jobs: if not self.finished(job) and self._ready(job): if job.group is None: self._ready_jobs.add(job) else: group = self._group[job] group.finalize() candidate_groups.add(group) self._ready_jobs.update( group for group in candidate_groups if all(self._ready(job) for job in group) ) def get_jobs_or_groups(self): visited_groups = set() for job in self.jobs: if job.group is None: yield job else: group = self._group[job] if group in visited_groups: continue visited_groups.add(group) yield group def close_remote_objects(self): """Close all remote objects.""" for job in self.jobs: if not self.needrun(job): job.close_remote() def postprocess(self): """Postprocess the DAG. This has to be invoked after any change to the DAG topology.""" self.update_jobids() self.update_needrun() self.update_priority() self.handle_pipes() self.update_groups() self.update_ready() self.close_remote_objects() self.update_checkpoint_outputs() def handle_pipes(self): """Use pipes to determine job groups. Check if every pipe has exactly one consumer""" for job in self.needrun_jobs: candidate_groups = set() if job.group is not None: candidate_groups.add(job.group) all_depending = set() has_pipe = False for f in job.output: if is_flagged(f, "pipe"): if job.is_run: raise WorkflowError( "Rule defines pipe output but " "uses a 'run' directive. This is " "not possible for technical " "reasons. Consider using 'shell' or " "'script'.", rule=job.rule, ) has_pipe = True depending = [ j for j, files in self.depending[job].items() if f in files ] if len(depending) > 1: raise WorkflowError( "Output file {} is marked as pipe " "but more than one job depends on " "it. Make sure that any pipe " "output is only consumed by one " "job".format(f), rule=job.rule, ) elif len(depending) == 0: raise WorkflowError( "Output file {} is marked as pipe " "but it has no consumer. This is " "invalid because it can lead to " "a dead lock.".format(f), rule=job.rule, ) depending = depending[0] if depending.is_run: raise WorkflowError( "Rule consumes pipe input but " "uses a 'run' directive. This is " "not possible for technical " "reasons. Consider using 'shell' or " "'script'.", rule=job.rule, ) all_depending.add(depending) if depending.group is not None: candidate_groups.add(depending.group) if not has_pipe: continue if len(candidate_groups) > 1: raise WorkflowError( "An output file is marked as " "pipe, but consuming jobs " "are part of conflicting " "groups.", rule=job.rule, ) elif candidate_groups: # extend the candidate group to all involved jobs group = candidate_groups.pop() else: # generate a random unique group name group = str(uuid.uuid4()) job.group = group for j in all_depending: j.group = group def _ready(self, job): """Return whether the given job is ready to execute.""" group = self._group.get(job, None) if group is None: is_external_needrun_dep = self.needrun else: def is_external_needrun_dep(j): g = self._group.get(j, None) return self.needrun(j) and (g is None or g != group) return self._finished.issuperset( filter(is_external_needrun_dep, self.dependencies[job]) ) def update_checkpoint_dependencies(self, jobs=None): """Update dependencies of checkpoints.""" updated = False self.update_checkpoint_outputs() if jobs is None: jobs = [job for job in self.jobs if not self.needrun(job)] for job in jobs: if job.is_checkpoint: depending = list(self.depending[job]) # re-evaluate depending jobs, replace and update DAG for j in depending: logger.info("Updating job {} ({}).".format(self.jobid(j), j)) newjob = j.updated() self.replace_job(j, newjob, recursive=False) updated = True if updated: # This has to be done for each checkpoint, # otherwise, jobs may be missing in the end. self.postprocess() return updated def finish(self, job, update_dynamic=True): """Finish a given job (e.g. remove from ready jobs, mark depending jobs as ready).""" try: self._ready_jobs.remove(job) except KeyError: pass if job.is_group(): jobs = job else: jobs = [job] self._finished.update(jobs) updated_dag = False if update_dynamic: updated_dag = self.update_checkpoint_dependencies(jobs) # mark depending jobs as ready # skip jobs that are marked as until jobs self.update_ready( j for job in jobs for j in self.depending[job] if not self.in_until(job) and self.needrun(j) ) for job in jobs: if update_dynamic and job.dynamic_output: logger.info("Dynamically updating jobs") newjob = self.update_dynamic(job) if newjob: # simulate that this job ran and was finished before self.omitforce.add(newjob) self._needrun.add(newjob) self._finished.add(newjob) updated_dag = True self.postprocess() self.handle_protected(newjob) self.handle_touch(newjob) if updated_dag: # We might have new jobs, so we need to ensure that all conda envs # and singularity images are set up. if self.workflow.use_singularity: self.pull_singularity_imgs() if self.workflow.use_conda: self.create_conda_envs() def new_job(self, rule, targetfile=None, format_wildcards=None): """Create new job for given rule and (optional) targetfile. This will reuse existing jobs with the same wildcards.""" key = (rule, targetfile) if key in self.job_cache: assert targetfile is not None return self.job_cache[key] wildcards_dict = rule.get_wildcards(targetfile) job = Job( rule, self, wildcards_dict=wildcards_dict, format_wildcards=format_wildcards, targetfile=targetfile, ) self.cache_job(job) return job def cache_job(self, job): for f in job.products: self.job_cache[(job.rule, f)] = job def update_dynamic(self, job): """Update the DAG by evaluating the output of the given job that contains dynamic output files.""" dynamic_wildcards = job.dynamic_wildcards if not dynamic_wildcards: # this happens e.g. in dryrun if output is not yet present return depending = list( filter(lambda job_: not self.finished(job_), self.bfs(self.depending, job)) ) newrule, non_dynamic_wildcards = job.rule.dynamic_branch( dynamic_wildcards, input=False ) self.specialize_rule(job.rule, newrule) # no targetfile needed for job newjob = self.new_job(newrule, format_wildcards=non_dynamic_wildcards) self.replace_job(job, newjob) for job_ in depending: needs_update = any( f.get_wildcard_names() & dynamic_wildcards.keys() for f in job_.rule.dynamic_input ) if needs_update: newrule_ = job_.rule.dynamic_branch(dynamic_wildcards) if newrule_ is not None: self.specialize_rule(job_.rule, newrule_) if not self.dynamic(job_): logger.debug("Updating job {}.".format(job_)) newjob_ = self.new_job( newrule_, targetfile=job_.output[0] if job_.output else None ) unexpected_output = self.reason( job_ ).missing_output.intersection(newjob.existing_output) if unexpected_output: logger.warning( "Warning: the following output files of rule {} were not " "present when the DAG was created:\n{}".format( newjob_.rule, unexpected_output ) ) self.replace_job(job_, newjob_) return newjob def delete_job(self, job, recursive=True, add_dependencies=False): """Delete given job from DAG.""" if job in self.targetjobs: self.targetjobs.remove(job) if add_dependencies: for _job in self.dependencies[job]: self.targetjobs.add(_job) for job_ in self.depending[job]: del self.dependencies[job_][job] del self.depending[job] for job_ in self.dependencies[job]: depending = self.depending[job_] del depending[job] if not depending and recursive: self.delete_job(job_) del self.dependencies[job] if job in self._needrun: self._len -= 1 self._needrun.remove(job) del self._reason[job] if job in self._finished: self._finished.remove(job) if job in self._dynamic: self._dynamic.remove(job) if job in self._ready_jobs: self._ready_jobs.remove(job) # remove from cache for f in job.output: try: del self.job_cache[(job.rule, f)] except KeyError: pass def replace_job(self, job, newjob, recursive=True): """Replace given job with new job.""" add_to_targetjobs = job in self.targetjobs depending = list(self.depending[job].items()) if self.finished(job): self._finished.add(newjob) self.delete_job(job, recursive=recursive) if add_to_targetjobs: self.targetjobs.add(newjob) self.cache_job(newjob) self.update([newjob]) logger.debug("Replace {} with dynamic branch {}".format(job, newjob)) for job_, files in depending: # if not job_.dynamic_input: logger.debug("updating depending job {}".format(job_)) self.dependencies[job_][newjob].update(files) self.depending[newjob][job_].update(files) def specialize_rule(self, rule, newrule): """Specialize the given rule by inserting newrule into the DAG.""" assert newrule is not None self.rules.add(newrule) self.update_output_index() def is_batch_rule(self, rule): """Return True if the underlying rule is to be used for batching the DAG.""" return self.batch is not None and rule.name == self.batch.rulename def collect_potential_dependencies(self, job): """Collect all potential dependencies of a job. These might contain ambiguities. The keys of the returned dict represent the files to be considered.""" dependencies = defaultdict(list) # use a set to circumvent multiple jobs for the same file # if user specified it twice file2jobs = self.file2jobs input_files = list(job.unique_input) if self.is_batch_rule(job.rule): # only consider the defined partition of the input files input_batch = self.batch.get_batch(input_files) if len(input_batch) != len(input_files): logger.info( "Considering only batch {} for DAG computation.\n" "All jobs beyond the batching rule are omitted until the final batch.\n" "Don't forget to run the other batches too.".format(self.batch) ) input_files = input_batch for file in input_files: # omit the file if it comes from a subworkflow if file in job.subworkflow_input: continue try: if file in job.dependencies: jobs = [self.new_job(job.dependencies[file], targetfile=file)] else: jobs = file2jobs(file) dependencies[file].extend(jobs) except MissingRuleException as ex: # no dependency found dependencies[file] = [] return dependencies def bfs(self, direction, *jobs, stop=lambda job: False): """Perform a breadth-first traversal of the DAG.""" queue = list(jobs) visited = set(queue) while queue: job = queue.pop(0) if stop(job): # stop criterion reached for this node continue yield job for job_, _ in direction[job].items(): if not job_ in visited: queue.append(job_) visited.add(job_) def level_bfs(self, direction, *jobs, stop=lambda job: False): """Perform a breadth-first traversal of the DAG, but also yield the level together with each job.""" queue = [(job, 0) for job in jobs] visited = set(jobs) while queue: job, level = queue.pop(0) if stop(job): # stop criterion reached for this node continue yield level, job level += 1 for job_, _ in direction[job].items(): if not job_ in visited: queue.append((job_, level)) visited.add(job_) def dfs(self, direction, *jobs, stop=lambda job: False, post=True): """Perform depth-first traversal of the DAG.""" visited = set() def _dfs(job): """Inner function for DFS traversal.""" if stop(job): return if not post: yield job for job_ in direction[job]: if not job_ in visited: visited.add(job_) for j in _dfs(job_): yield j if post: yield job for job in jobs: for job_ in self._dfs(direction, job, visited, stop=stop, post=post): yield job_ def new_wildcards(self, job): """Return wildcards that are newly introduced in this job, compared to its ancestors.""" new_wildcards = set(job.wildcards.items()) for job_ in self.dependencies[job]: if not new_wildcards: return set() for wildcard in job_.wildcards.items(): new_wildcards.discard(wildcard) return new_wildcards def rule2job(self, targetrule): """Generate a new job from a given rule.""" if targetrule.has_wildcards(): raise WorkflowError( "Target rules may not contain wildcards. Please specify concrete files or a rule without wildcards." ) return self.new_job(targetrule) def file2jobs(self, targetfile): rules = self.output_index.match(targetfile) jobs = [] exceptions = list() for rule in rules: if rule.is_producer(targetfile): try: jobs.append(self.new_job(rule, targetfile=targetfile)) except InputFunctionException as e: exceptions.append(e) if not jobs: if exceptions: raise exceptions[0] raise MissingRuleException(targetfile) return jobs def rule_dot2(self): dag = defaultdict(list) visited = set() preselect = set() def preselect_parents(job): for parent in self.depending[job]: if parent in preselect: continue preselect.add(parent) preselect_parents(parent) def build_ruledag(job, key=lambda job: job.rule.name): if job in visited: return visited.add(job) deps = sorted(self.dependencies[job], key=key) deps = [ ( group[0] if preselect.isdisjoint(group) else preselect.intersection(group).pop() ) for group in (list(g) for _, g in groupby(deps, key)) ] dag[job].extend(deps) preselect_parents(job) for dep in deps: build_ruledag(dep) for job in self.targetjobs: build_ruledag(job) return self._dot(dag.keys(), print_wildcards=False, print_types=False, dag=dag) def rule_dot(self): graph = defaultdict(set) for job in self.jobs: graph[job.rule].update(dep.rule for dep in self.dependencies[job]) return self._dot(graph) def dot(self): def node2style(job): if not self.needrun(job): return "rounded,dashed" if self.dynamic(job) or job.dynamic_input: return "rounded,dotted" return "rounded" def format_wildcard(wildcard): name, value = wildcard if DYNAMIC_FILL in value: value = "..." return "{}: {}".format(name, value) node2rule = lambda job: job.rule node2label = lambda job: "\\n".join( chain( [job.rule.name], sorted(map(format_wildcard, self.new_wildcards(job))) ) ) dag = {job: self.dependencies[job] for job in self.jobs} return self._dot( dag, node2rule=node2rule, node2style=node2style, node2label=node2label ) def _dot( self, graph, node2rule=lambda node: node, node2style=lambda node: "rounded", node2label=lambda node: node, ): # color rules huefactor = 2 / (3 * len(self.rules)) rulecolor = { rule: "{:.2f} 0.6 0.85".format(i * huefactor) for i, rule in enumerate(self.rules) } # markup node_markup = '\t{}[label = "{}", color = "{}", style="{}"];'.format edge_markup = "\t{} -> {}".format # node ids ids = {node: i for i, node in enumerate(graph)} # calculate nodes nodes = [ node_markup( ids[node], node2label(node), rulecolor[node2rule(node)], node2style(node), ) for node in graph ] # calculate edges edges = [ edge_markup(ids[dep], ids[node]) for node, deps in graph.items() for dep in deps ] return textwrap.dedent( """\ digraph snakemake_dag {{ graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, \ fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; {items} }}\ """ ).format(items="\n".join(nodes + edges)) def filegraph_dot( self, node2rule=lambda node: node, node2style=lambda node: "rounded", node2label=lambda node: node, ): # NOTE: This is code from the rule_dot method. # This method could be split like there as well, however, # it cannot easily reuse the _dot method due to the different node type graph = defaultdict(set) for job in self.jobs: graph[job.rule].update(dep.rule for dep in self.dependencies[job]) # node ids ids = {node: i for i, node in enumerate(graph)} # Compute colors for rules def hsv_to_htmlhexrgb(h, s, v): """Convert hsv colors to hex-encoded rgb colors usable by html.""" import colorsys hex_r, hex_g, hex_b = (round(255 * x) for x in colorsys.hsv_to_rgb(h, s, v)) return "#{hex_r:0>2X}{hex_g:0>2X}{hex_b:0>2X}".format( hex_r=hex_r, hex_g=hex_g, hex_b=hex_b ) huefactor = 2 / (3 * len(self.rules)) rulecolor = { rule: hsv_to_htmlhexrgb(i * huefactor, 0.6, 0.85) for i, rule in enumerate(self.rules) } def resolve_input_functions(input_files): """Iterate over all input files and replace input functions with a fixed string. """ files = [] for f in input_files: if callable(f): files.append("") # NOTE: This is a workaround. It would be more informative # to show the code of the input function here (if it is # short enough). This cannot be easily done with the inspect # module, since the line numbers in the Snakefile do not # behave as expected. One (complicated) solution for this # would be to find the Snakefile and directly extract the # code of the function. else: files.append(repr(f).strip("'")) return files def html_node(node_id, node, color): """Assemble a html style node for graphviz""" input_files = resolve_input_functions(node._input) output_files = [repr(f).strip("'") for f in node._output] input_header = ( '↪ input' if input_files else "" ) output_header = ( 'output →' if output_files else "" ) html_node = [ '{node_id} [ shape=none, margin=0, label=<'.format( node_id=node_id, color=color ), "", "
", ''.format( input_header=input_header ), ] for filename in sorted(input_files): # Escape html relevant chars like '<' and '>' in filenames # These can be added by input functions etc. and cannot be # displayed in graphviz HTML nodes. in_file = html.escape(filename) html_node.extend( [ "", ''.format( in_file=in_file ), "", ] ) html_node.append("
") html_node.append( ''.format( output_header=output_header ) ) for filename in sorted(output_files): out_file = html.escape(filename) html_node.extend( [ "", '' "".format(out_file=out_file), ] ) html_node.append("
", '{node.name}'.format(node=node), "
{input_header}
{in_file}
{output_header}
{out_file}
>]") return "\n".join(html_node) nodes = [ html_node(ids[node], node, rulecolor[node2rule(node)]) for node in graph ] # calculate edges edge_markup = "\t{} -> {}".format edges = [ edge_markup(ids[dep], ids[node], ids[dep], ids[node]) for node, deps in graph.items() for dep in deps ] return textwrap.dedent( """\ digraph snakemake_dag {{ graph[bgcolor=white, margin=0]; node[shape=box, style=rounded, fontname=sans, \ fontsize=10, penwidth=2]; edge[penwidth=2, color=grey]; {items} }}\ """ ).format(items="\n".join(nodes + edges)) def summary(self, detailed=False): if detailed: yield "output_file\tdate\trule\tversion\tlog-file(s)\tinput-file(s)\tshellcmd\tstatus\tplan" else: yield "output_file\tdate\trule\tversion\tlog-file(s)\tstatus\tplan" for job in self.jobs: output = job.rule.output if self.dynamic(job) else job.expanded_output for f in output: rule = self.workflow.persistence.rule(f) rule = "-" if rule is None else rule version = self.workflow.persistence.version(f) version = "-" if version is None else str(version) date = time.ctime(f.mtime) if f.exists else "-" pending = "update pending" if self.reason(job) else "no update" log = self.workflow.persistence.log(f) log = "-" if log is None else ",".join(log) input = self.workflow.persistence.input(f) input = "-" if input is None else ",".join(input) shellcmd = self.workflow.persistence.shellcmd(f) shellcmd = "-" if shellcmd is None else shellcmd # remove new line characters, leading and trailing whitespace shellcmd = shellcmd.strip().replace("\n", "; ") status = "ok" if not f.exists: status = "missing" elif self.reason(job).updated_input: status = "updated input files" elif self.workflow.persistence.version_changed(job, file=f): status = "version changed to {}".format(job.rule.version) elif self.workflow.persistence.code_changed(job, file=f): status = "rule implementation changed" elif self.workflow.persistence.input_changed(job, file=f): status = "set of input files changed" elif self.workflow.persistence.params_changed(job, file=f): status = "params changed" if detailed: yield "\t".join( (f, date, rule, version, log, input, shellcmd, status, pending) ) else: yield "\t".join((f, date, rule, version, log, status, pending)) def archive(self, path): """Archives workflow such that it can be re-run on a different system. Archiving includes git versioned files (i.e. Snakefiles, config files, ...), ancestral input files and conda environments. """ if path.endswith(".tar"): mode = "x" elif path.endswith("tar.bz2"): mode = "x:bz2" elif path.endswith("tar.xz"): mode = "x:xz" elif path.endswith("tar.gz"): mode = "x:gz" else: raise WorkflowError( "Unsupported archive format " "(supported: .tar, .tar.gz, .tar.bz2, .tar.xz)" ) if os.path.exists(path): raise WorkflowError("Archive already exists:\n" + path) self.create_conda_envs(forceall=True) try: workdir = Path(os.path.abspath(os.getcwd())) with tarfile.open(path, mode=mode, dereference=True) as archive: archived = set() def add(path): if workdir not in Path(os.path.abspath(path)).parents: logger.warning( "Path {} cannot be archived: " "not within working directory.".format(path) ) else: f = os.path.relpath(path) if f not in archived: archive.add(f) archived.add(f) logger.info("archived " + f) logger.info( "Archiving snakefiles, scripts and files under " "version control..." ) for f in self.workflow.get_sources(): add(f) logger.info("Archiving external input files...") for job in self.jobs: # input files for f in job.input: if not any( f in files for files in self.dependencies[job].values() ): # this is an input file that is not created by any job add(f) logger.info("Archiving conda environments...") envs = set() for job in self.jobs: if job.conda_env_file: env_archive = job.archive_conda_env() envs.add(env_archive) for env in envs: add(env) except (Exception, BaseException) as e: os.remove(path) raise e def clean(self, only_temp=False, dryrun=False): """Removes files generated by the workflow. """ for job in self.jobs: for f in job.output: if not only_temp or is_flagged(f, "temp"): # The reason for the second check is that dangling # symlinks fail f.exists. if f.exists or os.path.islink(f): if f.protected: logger.error("Skipping write-protected file {}.".format(f)) else: msg = "Deleting {}" if not dryrun else "Would delete {}" logger.info(msg.format(f)) if not dryrun: # Remove non-empty dirs if flagged as temp() f.remove(remove_non_empty_dir=only_temp) def list_untracked(self): """List files in the workdir that are not in the dag. """ used_files = set() files_in_cwd = set() for job in self.jobs: used_files.update( os.path.relpath(file) for file in chain(job.local_input, job.local_output, job.log) ) for root, dirs, files in os.walk(os.getcwd()): # Ignore hidden files and don't traverse into hidden dirs files_in_cwd.update( [ os.path.relpath(os.path.join(root, f)) for f in files if not f[0] == "." ] ) dirs[:] = [d for d in dirs if not d[0] == "."] for f in sorted(list(files_in_cwd - used_files)): logger.info(f) def d3dag(self, max_jobs=10000): def node(job): jobid = self.jobid(job) return { "id": jobid, "value": { "jobid": jobid, "label": job.rule.name, "rule": job.rule.name, }, } def edge(a, b): return {"u": self.jobid(a), "v": self.jobid(b)} jobs = list(self.jobs) if len(jobs) > max_jobs: logger.info( "Job-DAG is too large for visualization (>{} jobs).".format(max_jobs) ) else: logger.d3dag( nodes=[node(job) for job in jobs], edges=[ edge(dep, job) for job in jobs for dep in self.dependencies[job] if self.needrun(dep) ], ) def stats(self): rules = Counter() rules.update(job.rule for job in self.needrun_jobs) rules.update(job.rule for job in self.finished_jobs) yield "Job counts:" yield "\tcount\tjobs" for rule, count in sorted(rules.most_common(), key=lambda item: item[0].name): yield "\t{}\t{}".format(count, rule) yield "\t{}".format(len(self)) def __str__(self): return self.dot() def __len__(self): return self._len snakemake-5.10.0/snakemake/decorators.py000066400000000000000000000007461361131222100202110ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import functools import inspect def dec_all_methods(decorator, prefix="test_"): def dec_class(cls): for name, m in inspect.getmembers(cls, inspect.isfunction): if prefix == None or name.startswith(prefix): setattr(cls, name, decorator(m)) return cls return dec_class snakemake-5.10.0/snakemake/deployment/000077500000000000000000000000001361131222100176435ustar00rootroot00000000000000snakemake-5.10.0/snakemake/deployment/__init__.py000066400000000000000000000000001361131222100217420ustar00rootroot00000000000000snakemake-5.10.0/snakemake/deployment/conda.py000066400000000000000000000372711361131222100213130ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" import os import re import subprocess import tempfile from urllib.request import urlopen from urllib.parse import urlparse from urllib.error import URLError import hashlib import shutil from distutils.version import StrictVersion import json from glob import glob import tarfile import uuid from snakemake.exceptions import CreateCondaEnvironmentException, WorkflowError from snakemake.logging import logger from snakemake.common import strip_prefix from snakemake import utils from snakemake.deployment import singularity from snakemake.io import git_content def content(env_file): if env_file.startswith("git+file:"): return git_content(env_file).encode("utf-8") elif urlparse(env_file).scheme: try: return urlopen(env_file).read() except URLError as e: raise WorkflowError( "Failed to open environment file {}:".format(env_file), e ) else: if not os.path.exists(env_file): raise WorkflowError("Conda env file does not " "exist: {}".format(env_file)) with open(env_file, "rb") as f: return f.read() class Env: """Conda environment from a given specification file.""" def __init__(self, env_file, dag, singularity_img=None): self.file = env_file self._env_dir = dag.workflow.persistence.conda_env_path self._env_archive_dir = dag.workflow.persistence.conda_env_archive_path self._hash = None self._content_hash = None self._content = None self._path = None self._archive_file = None self._singularity_img = singularity_img @property def singularity_img_url(self): return self._singularity_img.url if self._singularity_img else None @property def content(self): if self._content is None: self._content = content(self.file) return self._content @property def hash(self): if self._hash is None: md5hash = hashlib.md5() # Include the absolute path of the target env dir into the hash. # By this, moving the working directory around automatically # invalidates all environments. This is necessary, because binaries # in conda environments can contain hardcoded absolute RPATHs. env_dir = os.path.realpath(self._env_dir) md5hash.update(env_dir.encode()) if self._singularity_img: md5hash.update(self._singularity_img.url.encode()) md5hash.update(self.content) self._hash = md5hash.hexdigest() return self._hash @property def content_hash(self): if self._content_hash is None: md5hash = hashlib.md5() md5hash.update(self.content) self._content_hash = md5hash.hexdigest() return self._content_hash @property def path(self): """Path to directory of the conda environment. First tries full hash, if it does not exist, (8-prefix) is used as default. """ hash = self.hash env_dir = self._env_dir for h in [hash, hash[:8]]: path = os.path.join(env_dir, h) if os.path.exists(path): return path return path @property def archive_file(self): """Path to archive of the conda environment, which may or may not exist.""" if self._archive_file is None: self._archive_file = os.path.join(self._env_archive_dir, self.content_hash) return self._archive_file def create_archive(self): """Create self-contained archive of environment.""" from snakemake.shell import shell try: import yaml except ImportError: raise WorkflowError( "Error importing PyYAML. " "Please install PyYAML to archive workflows." ) # importing requests locally because it interferes with instantiating conda environments import requests env_archive = self.archive_file if os.path.exists(env_archive): return env_archive try: # Download logger.info( "Downloading packages for conda environment {}...".format(self.file) ) os.makedirs(env_archive, exist_ok=True) try: out = shell.check_output( "conda list --explicit --prefix '{}'".format(self.path), stderr=subprocess.STDOUT, ) logger.debug(out.decode()) except subprocess.CalledProcessError as e: raise WorkflowError( "Error exporting conda packages:\n" + e.output.decode() ) with open(os.path.join(env_archive, "packages.txt"), "w") as pkg_list: for l in out.decode().split("\n"): if l and not l.startswith("#") and not l.startswith("@"): pkg_url = l logger.info(pkg_url) parsed = urlparse(pkg_url) pkg_name = os.path.basename(parsed.path) # write package name to list print(pkg_name, file=pkg_list) # download package pkg_path = os.path.join(env_archive, pkg_name) with open(pkg_path, "wb") as copy: r = requests.get(pkg_url) r.raise_for_status() copy.write(r.content) try: tarfile.open(pkg_path) except: raise WorkflowError( "Package is invalid tar archive: {}".format(pkg_url) ) except ( requests.exceptions.ChunkedEncodingError, requests.exceptions.HTTPError, ) as e: shutil.rmtree(env_archive) raise WorkflowError("Error downloading conda package {}.".format(pkg_url)) except (Exception, BaseException) as e: shutil.rmtree(env_archive) raise e return env_archive def create(self, dryrun=False): """ Create the conda enviroment.""" from snakemake.shell import shell # Read env file and create hash. env_file = self.file tmp_file = None url_scheme, *_ = urlparse(env_file) if (url_scheme and not url_scheme == "file") or ( not url_scheme and env_file.startswith("git+file:/") ): with tempfile.NamedTemporaryFile(delete=False, suffix=".yaml") as tmp: tmp.write(self.content) env_file = tmp.name tmp_file = tmp.name env_hash = self.hash env_path = self.path # Check for broken environment if os.path.exists( os.path.join(env_path, "env_setup_start") ) and not os.path.exists(os.path.join(env_path, "env_setup_done")): if dryrun: logger.info( "Incomplete Conda environment {} will be recreated.".format( utils.simplify_path(self.file) ) ) else: logger.info( "Removing incomplete Conda environment {}...".format( utils.simplify_path(self.file) ) ) shutil.rmtree(env_path, ignore_errors=True) # Create environment if not already present. if not os.path.exists(env_path): if dryrun: logger.info( "Conda environment {} will be created.".format( utils.simplify_path(self.file) ) ) return env_path conda = Conda(self._singularity_img) logger.info( "Creating conda environment {}...".format( utils.simplify_path(self.file) ) ) # Check if env archive exists. Use that if present. env_archive = self.archive_file try: # Touch "start" flag file os.makedirs(env_path, exist_ok=True) with open(os.path.join(env_path, "env_setup_start"), "a") as f: pass if os.path.exists(env_archive): logger.info("Installing archived conda packages.") pkg_list = os.path.join(env_archive, "packages.txt") if os.path.exists(pkg_list): # read pacakges in correct order # this is for newer env archives where the package list # was stored packages = [ os.path.join(env_archive, pkg.rstrip()) for pkg in open(pkg_list) ] else: # guess order packages = glob(os.path.join(env_archive, "*.tar.bz2")) # install packages manually from env archive cmd = " ".join( ["conda", "create", "--copy", "--prefix '{}'".format(env_path)] + packages ) if self._singularity_img: cmd = singularity.shellcmd( self._singularity_img.path, cmd, envvars=self.get_singularity_envvars(), ) out = shell.check_output(cmd, stderr=subprocess.STDOUT) else: # Copy env file to env_path (because they can be on # different volumes and singularity should only mount one). # In addition, this allows to immediately see what an # environment in .snakemake/conda contains. target_env_file = env_path + ".yaml" shutil.copy(env_file, target_env_file) logger.info("Downloading and installing remote packages.") cmd = " ".join( [ "conda", "env", "create", "--file '{}'".format(target_env_file), "--prefix '{}'".format(env_path), ] ) if self._singularity_img: cmd = singularity.shellcmd( self._singularity_img.path, cmd, envvars=self.get_singularity_envvars(), ) out = shell.check_output(cmd, stderr=subprocess.STDOUT) # Touch "done" flag file with open(os.path.join(env_path, "env_setup_done"), "a") as f: pass logger.debug(out.decode()) logger.info( "Environment for {} created (location: {})".format( os.path.relpath(env_file), os.path.relpath(env_path) ) ) except subprocess.CalledProcessError as e: # remove potential partially installed environment shutil.rmtree(env_path, ignore_errors=True) raise CreateCondaEnvironmentException( "Could not create conda environment from {}:\n".format(env_file) + e.output.decode() ) if tmp_file: # temporary file was created os.remove(tmp_file) return env_path @classmethod def get_singularity_envvars(self): return {"CONDA_PKGS_DIRS": "/tmp/conda/{}".format(uuid.uuid4())} def __hash__(self): # this hash is only for object comparison, not for env paths return hash(self.file) def __eq__(self, other): if isinstance(other, Env): return self.file == other.file return False class Conda: instances = dict() def __new__(cls, singularity_img=None): if singularity_img not in cls.instances: inst = super().__new__(cls) inst.__init__(singularity_img=singularity_img) cls.instances[singularity_img] = inst return inst else: return cls.instances[singularity_img] def __init__(self, singularity_img=None): from snakemake.shell import shell from snakemake.deployment import singularity if isinstance(singularity_img, singularity.Image): singularity_img = singularity_img.path self.singularity_img = singularity_img self._check() self.info = json.loads(shell.check_output(self._get_cmd("conda info --json"))) def _get_cmd(self, cmd): if self.singularity_img: return singularity.shellcmd(self.singularity_img, cmd) return cmd def _check(self): from snakemake.shell import shell try: # Use type here since conda now is a function. # type allows to check for both functions and regular commands. shell.check_output(self._get_cmd("type conda"), stderr=subprocess.STDOUT) except subprocess.CalledProcessError as e: if self.singularity_img: raise CreateCondaEnvironmentException( "The 'conda' command is not " "available inside " "your singularity container " "image. Snakemake mounts " "your conda installation " "into singularity. " "Sometimes, this can fail " "because of shell restrictions. " "It has been tested to work " "with docker://ubuntu, but " "it e.g. fails with " "docker://bash " ) else: raise CreateCondaEnvironmentException( "The 'conda' command is not " "available in the " "shell {} that will be " "used by Snakemake. You have " "to ensure that it is in your " "PATH, e.g., first activating " "the conda base environment " "with `conda activate base`.".format(shell.get_executable()) ) try: version = shell.check_output( self._get_cmd("conda --version"), stderr=subprocess.STDOUT ).decode() version = version.split()[1] if StrictVersion(version) < StrictVersion("4.2"): raise CreateCondaEnvironmentException( "Conda must be version 4.2 or later, found version {}.".format( version ) ) except subprocess.CalledProcessError as e: raise CreateCondaEnvironmentException( "Unable to check conda version:\n" + e.output.decode() ) def prefix_path(self): return self.info["conda_prefix"] def bin_path(self): return os.path.join(self.prefix_path(), "bin") def shellcmd(self, env_path, cmd): from snakemake.shell import shell # get path to activate script activate = os.path.join(self.bin_path(), "activate") return "source {} '{}'; {}".format(activate, env_path, cmd) snakemake-5.10.0/snakemake/deployment/env_modules.py000066400000000000000000000011201361131222100225270ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" class EnvModules: def __init__(self, *module_names): self.names = module_names def shellcmd(self, cmd): """Return shell command with given modules loaded.""" loads = " ".join(self._load_module(name) for name in self.names) return "{} {}".format(loads, cmd) def _load_module(self, name): return "module load {};".format(name) def __str__(self): return ", ".join(self.names) snakemake-5.10.0/snakemake/deployment/singularity.py000066400000000000000000000103471361131222100225740ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "johannes.koester@uni-due.de" __license__ = "MIT" import subprocess import shutil import os from urllib.parse import urlparse import hashlib from distutils.version import LooseVersion import snakemake from snakemake.deployment.conda import Conda from snakemake.common import lazy_property, SNAKEMAKE_SEARCHPATH from snakemake.exceptions import WorkflowError from snakemake.logging import logger SNAKEMAKE_MOUNTPOINT = "/mnt/snakemake" class Image: def __init__(self, url, dag): if " " in url: raise WorkflowError( "Invalid singularity image URL containing " "whitespace." ) if not shutil.which("singularity"): raise WorkflowError( "The singularity command has to be " "available in order to use singularity " "integration." ) try: v = subprocess.check_output( ["singularity", "--version"], stderr=subprocess.PIPE ).decode() except subprocess.CalledProcessError as e: raise WorkflowError( "Failed to get singularity version:\n{}".format(e.stderr.decode()) ) v = v.rsplit(" ", 1)[-1] if v.startswith("v"): v = v[1:] if not LooseVersion(v) >= LooseVersion("2.4.1"): raise WorkflowError("Minimum singularity version is 2.4.1.") self.url = url self._img_dir = dag.workflow.persistence.singularity_img_path @property def is_local(self): scheme = urlparse(self.url).scheme return not scheme or scheme == "file" @lazy_property def hash(self): md5hash = hashlib.md5() md5hash.update(self.url.encode()) return md5hash.hexdigest() def pull(self, dryrun=False): if self.is_local: return if dryrun: logger.info("Singularity image {} will be pulled.".format(self.url)) return logger.debug("Singularity image location: {}".format(self.path)) if not os.path.exists(self.path): logger.info("Pulling singularity image {}.".format(self.url)) try: p = subprocess.check_output( [ "singularity", "pull", "--name", "{}.simg".format(self.hash), self.url, ], cwd=self._img_dir, stderr=subprocess.STDOUT, ) except subprocess.CalledProcessError as e: raise WorkflowError( "Failed to pull singularity image " "from {}:\n{}".format(self.url, e.stdout.decode()) ) @property def path(self): if self.is_local: return urlparse(self.url).path return os.path.join(self._img_dir, self.hash) + ".simg" def __hash__(self): return hash(self.hash) def __eq__(self, other): return self.url == other.url def shellcmd( img_path, cmd, args="", envvars=None, shell_executable=None, container_workdir=None ): """Execute shell command inside singularity container given optional args and environment variables to be passed.""" if envvars: envvars = " ".join( "SINGULARITYENV_{}={}".format(k, v) for k, v in envvars.items() ) else: envvars = "" if shell_executable is None: shell_executable = "sh" else: # Ensure to just use the name of the executable, not a path, # because we cannot be sure where it is located in the container. shell_executable = os.path.split(shell_executable)[-1] # mount host snakemake module into container args += " --bind {}:{}".format(SNAKEMAKE_SEARCHPATH, SNAKEMAKE_MOUNTPOINT) if container_workdir: args += " --pwd {}".format(container_workdir) cmd = "{} singularity exec --home {} {} {} {} -c '{}'".format( envvars, os.getcwd(), args, img_path, shell_executable, cmd.replace("'", r"'\''"), ) logger.debug(cmd) return cmd snakemake-5.10.0/snakemake/exceptions.py000066400000000000000000000362511361131222100202250ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import traceback from tokenize import TokenError from snakemake.logging import logger def format_error(ex, lineno, linemaps=None, snakefile=None, show_traceback=False): if linemaps is None: linemaps = dict() msg = str(ex) if linemaps and snakefile and snakefile in linemaps: lineno = linemaps[snakefile][lineno] if isinstance(ex, SyntaxError): msg = ex.msg location = ( " in line {} of {}".format(lineno, snakefile) if lineno and snakefile else "" ) tb = "" if show_traceback: tb = "\n".join(format_traceback(cut_traceback(ex), linemaps=linemaps)) return "{}{}{}{}".format( ex.__class__.__name__, location, ":\n" + msg if msg else ".", "\n{}".format(tb) if show_traceback and tb else "", ) def get_exception_origin(ex, linemaps): for file, lineno, _, _ in reversed(traceback.extract_tb(ex.__traceback__)): if file in linemaps: return lineno, file def cut_traceback(ex): snakemake_path = os.path.dirname(__file__) for line in traceback.extract_tb(ex.__traceback__): dir = os.path.dirname(line[0]) if not dir: dir = "." if not os.path.isdir(dir) or not os.path.samefile(snakemake_path, dir): yield line def format_traceback(tb, linemaps): for file, lineno, function, code in tb: if file in linemaps: lineno = linemaps[file][lineno] if code is not None: yield ' File "{}", line {}, in {}'.format(file, lineno, function) def log_verbose_traceback(ex): tb = "Full " + "".join(traceback.format_exception(type(ex), ex, ex.__traceback__)) logger.debug(tb) def print_exception(ex, linemaps): """ Print an error message for a given exception. Arguments ex -- the exception linemaps -- a dict of a dict that maps for each snakefile the compiled lines to source code lines in the snakefile. """ log_verbose_traceback(ex) if isinstance(ex, SyntaxError) or isinstance(ex, IndentationError): logger.error( format_error( ex, ex.lineno, linemaps=linemaps, snakefile=ex.filename, show_traceback=True, ) ) return origin = get_exception_origin(ex, linemaps) if origin is not None: lineno, file = origin logger.error( format_error( ex, lineno, linemaps=linemaps, snakefile=file, show_traceback=True ) ) return elif isinstance(ex, TokenError): logger.error(format_error(ex, None, show_traceback=False)) elif isinstance(ex, MissingRuleException): logger.error( format_error( ex, None, linemaps=linemaps, snakefile=ex.filename, show_traceback=False ) ) elif isinstance(ex, RuleException): for e in ex._include + [ex]: if not e.omit: logger.error( format_error( e, e.lineno, linemaps=linemaps, snakefile=e.filename, show_traceback=True, ) ) elif isinstance(ex, WorkflowError): logger.error( format_error( ex, ex.lineno, linemaps=linemaps, snakefile=ex.snakefile, show_traceback=True, ) ) elif isinstance(ex, KeyboardInterrupt): logger.info("Cancelling snakemake on user request.") else: traceback.print_exception(type(ex), ex, ex.__traceback__) class WorkflowError(Exception): @staticmethod def format_arg(arg): if isinstance(arg, str): return arg else: return "{}: {}".format(arg.__class__.__name__, str(arg)) def __init__(self, *args, lineno=None, snakefile=None, rule=None): super().__init__("\n".join(self.format_arg(arg) for arg in args)) if rule is not None: self.lineno = rule.lineno self.snakefile = rule.snakefile else: self.lineno = lineno self.snakefile = snakefile self.rule = rule class WildcardError(WorkflowError): pass class RuleException(Exception): """ Base class for exception occuring within the execution or definition of rules. """ def __init__( self, message=None, include=None, lineno=None, snakefile=None, rule=None ): """ Creates a new instance of RuleException. Arguments message -- the exception message include -- iterable of other exceptions to be included lineno -- the line the exception originates snakefile -- the file the exception originates """ super(RuleException, self).__init__(message) self._include = set() if include: for ex in include: self._include.add(ex) self._include.update(ex._include) if rule is not None: if lineno is None: lineno = rule.lineno if snakefile is None: snakefile = rule.snakefile self._include = list(self._include) self.lineno = lineno self.filename = snakefile self.omit = not message @property def messages(self): return map(str, (ex for ex in self._include + [self] if not ex.omit)) class InputFunctionException(WorkflowError): def __init__(self, msg, wildcards=None, lineno=None, snakefile=None, rule=None): msg = ( self.format_arg(msg) + "\nWildcards:\n" + "\n".join( "{}={}".format(name, value) for name, value in wildcards.items() ) ) super().__init__(msg, lineno=lineno, snakefile=snakefile, rule=rule) class ChildIOException(WorkflowError): def __init__( self, parent=None, child=None, wildcards=None, lineno=None, snakefile=None, rule=None, ): msg = "File/directory is a child to another output:\n" + "{}\n{}".format( parent, child ) super().__init__(msg, lineno=lineno, snakefile=snakefile, rule=rule) class MissingOutputException(RuleException): pass class IOException(RuleException): def __init__(self, prefix, rule, files, include=None, lineno=None, snakefile=None): message = ( "{} for rule {}:\n{}".format(prefix, rule, "\n".join(files)) if files else "" ) super().__init__( message=message, include=include, lineno=lineno, snakefile=snakefile, rule=rule, ) class MissingInputException(IOException): def __init__(self, rule, files, include=None, lineno=None, snakefile=None): msg = "Missing input files" if any(map(lambda f: f.startswith("~"), files)): msg += ( "(Using '~' in your paths is not allowed as such platform " "specific syntax is not resolved by Snakemake. In general, " "try sticking to relative paths for everything inside the " "working directory.)" ) super().__init__(msg, rule, files, include, lineno=lineno, snakefile=snakefile) class PeriodicWildcardError(RuleException): pass class ProtectedOutputException(IOException): def __init__(self, rule, files, include=None, lineno=None, snakefile=None): super().__init__( "Write-protected output files", rule, files, include, lineno=lineno, snakefile=snakefile, ) class ImproperOutputException(IOException): def __init__(self, rule, files, include=None, lineno=None, snakefile=None): super().__init__( "Outputs of incorrect type (directories when expecting files or vice versa). " "Output directories must be flagged with directory().", rule, files, include, lineno=lineno, snakefile=snakefile, ) class UnexpectedOutputException(IOException): def __init__(self, rule, files, include=None, lineno=None, snakefile=None): super().__init__( "Unexpectedly present output files " "(accidentally created by other rule?)", rule, files, include, lineno=lineno, snakefile=snakefile, ) class ImproperShadowException(RuleException): def __init__(self, rule, lineno=None, snakefile=None): super().__init__( "Rule cannot shadow if using ThreadPoolExecutor", rule=rule, lineno=lineno, snakefile=snakefile, ) class AmbiguousRuleException(RuleException): def __init__(self, filename, job_a, job_b, lineno=None, snakefile=None): from snakemake import utils wildcards_a = utils.format("{}", job_a._format_wildcards) wildcards_b = utils.format("{}", job_b._format_wildcards) super().__init__( "Rules {job_a} and {job_b} are ambiguous for the file {f}.\n" "Consider starting rule output with a unique prefix, constrain " "your wildcards, or use the ruleorder directive.\n" "Wildcards:\n" "\t{job_a}: {wildcards_a}\n" "\t{job_b}: {wildcards_b}\n" "Expected input files:\n" "\t{job_a}: {job_a.input}\n" "\t{job_b}: {job_b.input}" "Expected output files:\n" "\t{job_a}: {job_a.output}\n" "\t{job_b}: {job_b.output}".format( job_a=job_a, job_b=job_b, f=filename, wildcards_a=wildcards_a, wildcards_b=wildcards_b, ), lineno=lineno, snakefile=snakefile, ) self.rule1, self.rule2 = job_a.rule, job_b.rule class CyclicGraphException(RuleException): def __init__(self, repeatedrule, file, rule=None): super().__init__( "Cyclic dependency on rule {}.".format(repeatedrule), rule=rule ) self.file = file class MissingRuleException(RuleException): def __init__(self, file, lineno=None, snakefile=None): super().__init__( "No rule to produce {} (if you use input functions make sure that they don't raise unexpected exceptions).".format( file ), lineno=lineno, snakefile=snakefile, ) class UnknownRuleException(RuleException): def __init__(self, name, prefix="", lineno=None, snakefile=None): msg = "There is no rule named {}.".format(name) if prefix: msg = "{} {}".format(prefix, msg) super().__init__(msg, lineno=lineno, snakefile=snakefile) class NoRulesException(RuleException): def __init__(self, lineno=None, snakefile=None): super().__init__( "There has to be at least one rule.", lineno=lineno, snakefile=snakefile ) class IncompleteFilesException(RuleException): def __init__(self, files): super().__init__( "The files below seem to be incomplete. " "If you are sure that certain files are not incomplete, " "mark them as complete with\n\n" " snakemake --cleanup-metadata \n\n" "To re-generate the files rerun your command with the " "--rerun-incomplete flag.\nIncomplete files:\n{}".format("\n".join(files)) ) class IOFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class RemoteFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class HTTPFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class FTPFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class S3FileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class AzureFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class SFTPFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class DropboxFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class XRootDFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class NCBIFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class WebDAVFileException(RuleException): def __init__(self, msg, lineno=None, snakefile=None): super().__init__(msg, lineno=lineno, snakefile=snakefile) class ClusterJobException(RuleException): def __init__(self, job_info, jobid): super().__init__( "Error executing rule {} on cluster (jobid: {}, external: {}, jobscript: {}). " "For detailed error see the cluster log.".format( job_info.job.rule.name, jobid, job_info.jobid, job_info.jobscript ), lineno=job_info.job.rule.lineno, snakefile=job_info.job.rule.snakefile, ) class CreateRuleException(RuleException): pass class TerminatedException(Exception): pass class CreateCondaEnvironmentException(WorkflowError): pass class SpawnedJobError(Exception): pass class IncompleteCheckpointException(Exception): def __init__(self, rule, targetfile): super().__init__( "The requested checkpoint output is not yet created." "If you see this error, you have likely tried to use " "checkpoint output outside of an input function, or " "you have tried to call an input function directly " "via (). Please check the docs at " "https://snakemake.readthedocs.io/en/stable/" "snakefiles/rules.html#data-dependent-conditional-execution " "and note that the input function in the example rule " "'aggregate' is NOT called, but passed to the rule " "by name, such that Snakemake can call it internally " "once the checkpoint is finished." ) self.rule = rule from snakemake.io import checkpoint_target self.targetfile = checkpoint_target(targetfile) class CacheMissException(Exception): pass snakemake-5.10.0/snakemake/executors.py000066400000000000000000002230401361131222100200570ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import sys import contextlib import time import datetime import json import textwrap import stat import shutil import shlex import threading import concurrent.futures import subprocess import signal from functools import partial from itertools import chain from collections import namedtuple from tempfile import mkdtemp from snakemake.io import _IOFile import random import base64 import uuid import re import math from snakemake.jobs import Job from snakemake.shell import shell from snakemake.logging import logger from snakemake.stats import Stats from snakemake.utils import format, Unformattable, makedirs from snakemake.io import get_wildcard_names, Wildcards from snakemake.exceptions import print_exception, get_exception_origin from snakemake.exceptions import format_error, RuleException, log_verbose_traceback from snakemake.exceptions import ( ProtectedOutputException, WorkflowError, ImproperShadowException, SpawnedJobError, CacheMissException, ) from snakemake.common import Mode, __version__, get_container_image, get_uuid def sleep(): # do not sleep on CI. In that case we just want to quickly test everything. if os.environ.get("CI") != "true": time.sleep(10) class AbstractExecutor: def __init__( self, workflow, dag, printreason=False, quiet=False, printshellcmds=False, printthreads=True, latency_wait=3, keepincomplete=False, ): self.workflow = workflow self.dag = dag self.quiet = quiet self.printreason = printreason self.printshellcmds = printshellcmds self.printthreads = printthreads self.latency_wait = latency_wait self.keepincomplete = keepincomplete def get_default_remote_provider_args(self): if self.workflow.default_remote_provider: return ( " --default-remote-provider {} " "--default-remote-prefix {} " ).format( self.workflow.default_remote_provider.__module__.split(".")[-1], self.workflow.default_remote_prefix, ) return "" def get_default_resources_args(self): if self.workflow.default_resources.args is not None: args = " --default-resources {} ".format( " ".join(map('"{}"'.format, self.workflow.default_resources.args)) ) return args return "" def run(self, job, callback=None, submit_callback=None, error_callback=None): self._run(job) callback(job) def shutdown(self): pass def cancel(self): pass def _run(self, job): job.check_protected_output() self.printjob(job) def rule_prefix(self, job): return "local " if job.is_local else "" def printjob(self, job): job.log_info(skip_dynamic=True) def print_job_error(self, job, msg=None, **kwargs): job.log_error(msg, **kwargs) def handle_job_success(self, job): pass def handle_job_error(self, job): pass class DryrunExecutor(AbstractExecutor): def printjob(self, job): super().printjob(job) if job.is_group(): for j in job.jobs: self.printcache(j) else: self.printcache(job) def printcache(self, job): if self.workflow.is_cached_rule(job.rule): if self.workflow.output_file_cache.exists(job): logger.info( "Output file {} will be obtained from cache.".format(job.output[0]) ) else: logger.info( "Output file {} will be written to cache.".format(job.output[0]) ) class RealExecutor(AbstractExecutor): def __init__( self, workflow, dag, printreason=False, quiet=False, printshellcmds=False, latency_wait=3, assume_shared_fs=True, keepincomplete=False, ): super().__init__( workflow, dag, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, keepincomplete=keepincomplete, ) self.assume_shared_fs = assume_shared_fs self.stats = Stats() self.snakefile = workflow.snakefile def register_job(self, job): job.register() def _run(self, job, callback=None, error_callback=None): super()._run(job) self.stats.report_job_start(job) try: self.register_job(job) except IOError as e: logger.info( "Failed to set marker file for job started ({}). " "Snakemake will work, but cannot ensure that output files " "are complete in case of a kill signal or power loss. " "Please ensure write permissions for the " "directory {}".format(e, self.workflow.persistence.path) ) def handle_job_success( self, job, upload_remote=True, handle_log=True, handle_touch=True, ignore_missing_output=False, ): job.postprocess( upload_remote=upload_remote, handle_log=handle_log, handle_touch=handle_touch, ignore_missing_output=ignore_missing_output, latency_wait=self.latency_wait, assume_shared_fs=self.assume_shared_fs, ) self.stats.report_job_end(job) def handle_job_error(self, job, upload_remote=True): job.postprocess( error=True, assume_shared_fs=self.assume_shared_fs, latency_wait=self.latency_wait, ) def format_job_pattern(self, pattern, job=None, **kwargs): overwrite_workdir = [] if self.workflow.overwrite_workdir: overwrite_workdir.extend(("--directory", self.workflow.overwrite_workdir)) overwrite_config = [] if self.workflow.overwrite_configfiles: # add each of the overwriting configfiles in the original order if self.workflow.overwrite_configfiles: overwrite_config.append("--configfiles") overwrite_config.extend(self.workflow.overwrite_configfiles) if self.workflow.config_args: overwrite_config.append("--config") overwrite_config.extend(self.workflow.config_args) printshellcmds = "" if self.workflow.printshellcmds: printshellcmds = "-p" if not job.is_branched and not job.is_updated: # Restrict considered rules. This does not work for updated jobs # because they need to be updated in the spawned process as well. rules = ["--allowed-rules"] rules.extend(job.rules) else: rules = [] target = kwargs.get("target", job.get_targets()) snakefile = kwargs.get("snakefile", self.snakefile) cores = kwargs.get("cores", self.cores) if "target" in kwargs: del kwargs["target"] if "snakefile" in kwargs: del kwargs["snakefile"] if "cores" in kwargs: del kwargs["cores"] return format( pattern, job=job, attempt=job.attempt, overwrite_workdir=overwrite_workdir, overwrite_config=overwrite_config, printshellcmds=printshellcmds, workflow=self.workflow, snakefile=snakefile, cores=cores, benchmark_repeats=job.benchmark_repeats if not job.is_group() else None, target=target, rules=rules, **kwargs ) class TouchExecutor(RealExecutor): def run(self, job, callback=None, submit_callback=None, error_callback=None): super()._run(job) try: # Touching of output files will be done by handle_job_success time.sleep(0.1) callback(job) except OSError as ex: print_exception(ex, self.workflow.linemaps) error_callback(job) def handle_job_success(self, job): super().handle_job_success(job, ignore_missing_output=True) _ProcessPoolExceptions = (KeyboardInterrupt,) try: from concurrent.futures.process import BrokenProcessPool _ProcessPoolExceptions = (KeyboardInterrupt, BrokenProcessPool) except ImportError: pass class CPUExecutor(RealExecutor): def __init__( self, workflow, dag, workers, printreason=False, quiet=False, printshellcmds=False, use_threads=False, latency_wait=3, cores=1, keepincomplete=False, ): super().__init__( workflow, dag, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, keepincomplete=keepincomplete, ) self.exec_job = "\\\n".join( ( "cd {workflow.workdir_init} && ", "{sys.executable} -m snakemake {target} --snakefile {snakefile} ", "--force -j{cores} --keep-target-files --keep-remote ", "--attempt {attempt} ", "--force-use-threads --wrapper-prefix {workflow.wrapper_prefix} ", "--latency-wait {latency_wait} ", self.get_default_remote_provider_args(), self.get_default_resources_args(), "{overwrite_workdir} {overwrite_config} {printshellcmds} {rules} ", "--notemp --quiet --no-hooks --nolock --mode {} ".format( Mode.subprocess ), ) ) if self.workflow.shadow_prefix: self.exec_job += " --shadow-prefix {} ".format(self.workflow.shadow_prefix) if self.workflow.use_conda: self.exec_job += " --use-conda " if self.workflow.conda_prefix: self.exec_job += " --conda-prefix {} ".format( self.workflow.conda_prefix ) if self.workflow.use_singularity: self.exec_job += " --use-singularity " if self.workflow.singularity_prefix: self.exec_job += " --singularity-prefix {} ".format( self.workflow.singularity_prefix ) if self.workflow.singularity_args: self.exec_job += ' --singularity-args "{}"'.format( self.workflow.singularity_args ) self.use_threads = use_threads self.cores = cores self.pool = concurrent.futures.ThreadPoolExecutor(max_workers=workers + 1) def run(self, job, callback=None, submit_callback=None, error_callback=None): super()._run(job) if job.is_group(): # the future waits for the entire group job future = self.pool.submit(self.run_group_job, job) else: future = self.run_single_job(job) future.add_done_callback(partial(self._callback, job, callback, error_callback)) def job_args_and_prepare(self, job): job.prepare() conda_env = job.conda_env_path singularity_img = job.singularity_img_path env_modules = job.env_modules benchmark = None benchmark_repeats = job.benchmark_repeats or 1 if job.benchmark is not None: benchmark = str(job.benchmark) return ( job.rule, job.input._plainstrings(), job.output._plainstrings(), job.params, job.wildcards, job.threads, job.resources, job.log._plainstrings(), benchmark, benchmark_repeats, conda_env, singularity_img, self.workflow.singularity_args, env_modules, self.workflow.use_singularity, self.workflow.linemaps, self.workflow.debug, self.workflow.cleanup_scripts, job.shadow_dir, job.jobid, ) def run_single_job(self, job): if self.use_threads or (not job.is_shadow and not job.is_run): future = self.pool.submit( self.cached_or_run, job, run_wrapper, *self.job_args_and_prepare(job) ) else: # run directive jobs are spawned into subprocesses future = self.pool.submit(self.cached_or_run, job, self.spawn_job, job) return future def run_group_job(self, job): """Run a pipe group job. This lets all items run simultaneously.""" # we only have to consider pipe groups because in local running mode, # these are the only groups that will occur futures = [self.run_single_job(j) for j in job] while True: k = 0 for f in futures: if f.done(): ex = f.exception() if ex is not None: # kill all shell commands of the other group jobs # there can be only shell commands because the # run directive is not allowed for pipe jobs for j in job: shell.kill(j.jobid) raise ex else: k += 1 if k == len(futures): return time.sleep(1) def spawn_job(self, job): exec_job = self.exec_job cmd = self.format_job_pattern( exec_job, job=job, _quote_all=True, latency_wait=self.latency_wait ) try: subprocess.check_call(cmd, shell=True) except subprocess.CalledProcessError as e: raise SpawnedJobError() def cached_or_run(self, job, run_func, *args): """ Either retrieve result from cache, or run job with given function. """ to_cache = self.workflow.is_cached_rule(job.rule) try: if to_cache: self.workflow.output_file_cache.fetch(job) return except CacheMissException: pass run_func(*args) if to_cache: self.workflow.output_file_cache.store(job) def shutdown(self): self.pool.shutdown() def cancel(self): self.pool.shutdown() def _callback(self, job, callback, error_callback, future): try: ex = future.exception() if ex is not None: raise ex callback(job) except _ProcessPoolExceptions: self.handle_job_error(job) # no error callback, just silently ignore the interrupt as the main scheduler is also killed except SpawnedJobError: # don't print error message, this is done by the spawned subprocess error_callback(job) except (Exception, BaseException) as ex: self.print_job_error(job) if not (job.is_group() or job.shellcmd) or self.workflow.verbose: print_exception(ex, self.workflow.linemaps) error_callback(job) def handle_job_success(self, job): super().handle_job_success(job) def handle_job_error(self, job): super().handle_job_error(job) if not self.keepincomplete: job.cleanup() self.workflow.persistence.cleanup(job) class ClusterExecutor(RealExecutor): default_jobscript = "jobscript.sh" def __init__( self, workflow, dag, cores, jobname="snakejob.{name}.{jobid}.sh", printreason=False, quiet=False, printshellcmds=False, latency_wait=3, cluster_config=None, local_input=None, restart_times=None, exec_job=None, assume_shared_fs=True, max_status_checks_per_second=1, disable_default_remote_provider_args=False, keepincomplete=False, ): from ratelimiter import RateLimiter local_input = local_input or [] super().__init__( workflow, dag, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, assume_shared_fs=assume_shared_fs, ) if not self.assume_shared_fs: # use relative path to Snakefile self.snakefile = os.path.relpath(workflow.snakefile) jobscript = workflow.jobscript if jobscript is None: jobscript = os.path.join(os.path.dirname(__file__), self.default_jobscript) try: with open(jobscript) as f: self.jobscript = f.read() except IOError as e: raise WorkflowError(e) if not "jobid" in get_wildcard_names(jobname): raise WorkflowError( 'Defined jobname ("{}") has to contain the wildcard {jobid}.' ) if exec_job is None: self.exec_job = "\\\n".join( ( "cd {workflow.workdir_init} && " if assume_shared_fs else "", "{sys.executable} " if assume_shared_fs else "python ", "-m snakemake {target} --snakefile {snakefile} ", "--force -j{cores} --keep-target-files --keep-remote ", "--wait-for-files {wait_for_files} --latency-wait {latency_wait} ", " --attempt {attempt} {use_threads} ", "--wrapper-prefix {workflow.wrapper_prefix} ", "{overwrite_workdir} {overwrite_config} {printshellcmds} {rules} " "--nocolor --notemp --no-hooks --nolock ", "--mode {} ".format(Mode.cluster), ) ) else: self.exec_job = exec_job if self.workflow.shadow_prefix: self.exec_job += " --shadow-prefix {} ".format(self.workflow.shadow_prefix) if self.workflow.use_conda: self.exec_job += " --use-conda " if self.workflow.conda_prefix: self.exec_job += " --conda-prefix {} ".format( self.workflow.conda_prefix ) if self.workflow.use_singularity: self.exec_job += " --use-singularity " if self.workflow.singularity_prefix: self.exec_job += " --singularity-prefix {} ".format( self.workflow.singularity_prefix ) if self.workflow.singularity_args: self.exec_job += ' --singularity-args "{}"'.format( self.workflow.singularity_args ) if not disable_default_remote_provider_args: self.exec_job += self.get_default_remote_provider_args() self.exec_job += self.get_default_resources_args() self.jobname = jobname self._tmpdir = None self.cores = cores if cores else "" self.cluster_config = cluster_config if cluster_config else dict() self.restart_times = restart_times self.active_jobs = list() self.lock = threading.Lock() self.wait = True self.wait_thread = threading.Thread(target=self._wait_for_jobs) self.wait_thread.daemon = True self.wait_thread.start() self.max_status_checks_per_second = max_status_checks_per_second self.status_rate_limiter = RateLimiter( max_calls=self.max_status_checks_per_second, period=1 ) def shutdown(self): with self.lock: self.wait = False self.wait_thread.join() if not self.workflow.immediate_submit: # Only delete tmpdir (containing jobscripts) if not using # immediate_submit. With immediate_submit, jobs can be scheduled # after this method is completed. Hence we have to keep the # directory. shutil.rmtree(self.tmpdir) def cancel(self): self.shutdown() def _run(self, job, callback=None, error_callback=None): if self.assume_shared_fs: job.remove_existing_output() job.download_remote_input() super()._run(job, callback=callback, error_callback=error_callback) @property def tmpdir(self): if self._tmpdir is None: self._tmpdir = mkdtemp(dir=".snakemake", prefix="tmp.") return os.path.abspath(self._tmpdir) def get_jobscript(self, job): f = job.format_wildcards(self.jobname, cluster=self.cluster_wildcards(job)) if os.path.sep in f: raise WorkflowError( "Path separator ({}) found in job name {}. " "This is not supported.".format(os.path.sep, f) ) return os.path.join(self.tmpdir, f) def format_job(self, pattern, job, **kwargs): wait_for_files = [] if self.assume_shared_fs: wait_for_files.append(self.tmpdir) wait_for_files.extend(job.get_wait_for_files()) format_p = partial( self.format_job_pattern, job=job, properties=job.properties(cluster=self.cluster_params(job)), latency_wait=self.latency_wait, wait_for_files=wait_for_files, **kwargs ) try: return format_p(pattern) except KeyError as e: raise WorkflowError( "Error formatting jobscript: {} not found\n" "Make sure that your custom jobscript is up to date.".format(e) ) def write_jobscript(self, job, jobscript, **kwargs): # only force threads if this is not a group job # otherwise we want proper process handling use_threads = "--force-use-threads" if not job.is_group() else "" exec_job = self.format_job( self.exec_job, job, _quote_all=True, use_threads=use_threads, **kwargs ) content = self.format_job(self.jobscript, job, exec_job=exec_job, **kwargs) logger.debug("Jobscript:\n{}".format(content)) with open(jobscript, "w") as f: print(content, file=f) os.chmod(jobscript, os.stat(jobscript).st_mode | stat.S_IXUSR) def cluster_params(self, job): """Return wildcards object for job from cluster_config.""" cluster = self.cluster_config.get("__default__", dict()).copy() cluster.update(self.cluster_config.get(job.name, dict())) # Format values with available parameters from the job. for key, value in list(cluster.items()): if isinstance(value, str): try: cluster[key] = job.format_wildcards(value) except NameError as e: if job.is_group(): msg = ( "Failed to format cluster config for group job. " "You have to ensure that your default entry " "does not contain any items that group jobs " "cannot provide, like {rule}, {wildcards}." ) else: msg = ( "Failed to format cluster config " "entry for job {}.".format(job.rule.name) ) raise WorkflowError(msg, e) return cluster def cluster_wildcards(self, job): return Wildcards(fromdict=self.cluster_params(job)) def handle_job_success(self, job): super().handle_job_success( job, upload_remote=False, handle_log=False, handle_touch=False ) def handle_job_error(self, job): # TODO what about removing empty remote dirs?? This cannot be decided # on the cluster node. super().handle_job_error(job, upload_remote=False) logger.debug("Cleanup job metadata.") # We have to remove metadata here as well. # It will be removed by the CPUExecutor in case of a shared FS, # but we might not see the removal due to filesystem latency. # By removing it again, we make sure that it is gone on the host FS. if not self.keepincomplete: self.workflow.persistence.cleanup(job) def print_cluster_job_error(self, job_info, jobid): job = job_info.job kind = ( "rule {}".format(job.rule.name) if not job.is_group() else "group job {}".format(job.groupid) ) logger.error( "Error executing {} on cluster (jobid: {}, external: " "{}, jobscript: {}). For error details see the cluster " "log and the log files of the involved rule(s).".format( kind, jobid, job_info.jobid, job_info.jobscript ) ) GenericClusterJob = namedtuple( "GenericClusterJob", "job jobid callback error_callback jobscript jobfinished jobfailed", ) class GenericClusterExecutor(ClusterExecutor): def __init__( self, workflow, dag, cores, submitcmd="qsub", statuscmd=None, cluster_config=None, jobname="snakejob.{rulename}.{jobid}.sh", printreason=False, quiet=False, printshellcmds=False, latency_wait=3, restart_times=0, assume_shared_fs=True, max_status_checks_per_second=1, keepincomplete=False, ): self.submitcmd = submitcmd if not assume_shared_fs and statuscmd is None: raise WorkflowError( "When no shared filesystem can be assumed, a " "status command must be given." ) self.statuscmd = statuscmd self.external_jobid = dict() super().__init__( workflow, dag, cores, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, restart_times=restart_times, assume_shared_fs=assume_shared_fs, max_status_checks_per_second=max_status_checks_per_second, ) if statuscmd: self.exec_job += " && exit 0 || exit 1" elif assume_shared_fs: # TODO wrap with watch and touch {jobrunning} # check modification date of {jobrunning} in the wait_for_job method self.exec_job += ( ' && touch "{jobfinished}" || (touch "{jobfailed}"; exit 1)' ) else: raise WorkflowError( "If no shared filesystem is used, you have to " "specify a cluster status command." ) def cancel(self): logger.info("Will exit after finishing currently running jobs.") self.shutdown() def register_job(self, job): # Do not register job here. # Instead do it manually once the jobid is known. pass def run(self, job, callback=None, submit_callback=None, error_callback=None): super()._run(job) workdir = os.getcwd() jobid = job.jobid jobscript = self.get_jobscript(job) jobfinished = os.path.join(self.tmpdir, "{}.jobfinished".format(jobid)) jobfailed = os.path.join(self.tmpdir, "{}.jobfailed".format(jobid)) self.write_jobscript( job, jobscript, jobfinished=jobfinished, jobfailed=jobfailed ) if self.statuscmd: ext_jobid = self.dag.incomplete_external_jobid(job) if ext_jobid: # Job is incomplete and still running. # We simply register it and wait for completion or failure. logger.info( "Resuming incomplete job {} with external jobid '{}'.".format( jobid, ext_jobid ) ) submit_callback(job) with self.lock: self.active_jobs.append( GenericClusterJob( job, ext_jobid, callback, error_callback, jobscript, jobfinished, jobfailed, ) ) return deps = " ".join( self.external_jobid[f] for f in job.input if f in self.external_jobid ) try: submitcmd = job.format_wildcards( self.submitcmd, dependencies=deps, cluster=self.cluster_wildcards(job) ) except AttributeError as e: raise WorkflowError(str(e), rule=job.rule if not job.is_group() else None) try: ext_jobid = ( subprocess.check_output( '{submitcmd} "{jobscript}"'.format( submitcmd=submitcmd, jobscript=jobscript ), shell=True, ) .decode() .split("\n") ) except subprocess.CalledProcessError as ex: logger.error( "Error submitting jobscript (exit code {}):\n{}".format( ex.returncode, ex.output.decode() ) ) error_callback(job) return if ext_jobid and ext_jobid[0]: ext_jobid = ext_jobid[0] self.external_jobid.update((f, ext_jobid) for f in job.output) logger.info( "Submitted {} {} with external jobid '{}'.".format( "group job" if job.is_group() else "job", jobid, ext_jobid ) ) self.workflow.persistence.started(job, external_jobid=ext_jobid) submit_callback(job) with self.lock: self.active_jobs.append( GenericClusterJob( job, ext_jobid, callback, error_callback, jobscript, jobfinished, jobfailed, ) ) def _wait_for_jobs(self): success = "success" failed = "failed" running = "running" if self.statuscmd is not None: def job_status(job): try: # this command shall return "success", "failed" or "running" return ( subprocess.check_output( "{statuscmd} {jobid}".format( jobid=job.jobid, statuscmd=self.statuscmd ), shell=True, ) .decode() .split("\n")[0] ) except subprocess.CalledProcessError as e: if e.returncode < 0: # Ignore SIGINT and all other issues due to signals # because it will be caused by hitting e.g. # Ctrl-C on the main process or sending killall to # snakemake. # Snakemake will handle the signal in # the master process. pass else: raise WorkflowError( "Failed to obtain job status. " "See above for error message." ) else: def job_status(job): if os.path.exists(active_job.jobfinished): os.remove(active_job.jobfinished) os.remove(active_job.jobscript) return success if os.path.exists(active_job.jobfailed): os.remove(active_job.jobfailed) os.remove(active_job.jobscript) return failed return running while True: with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() logger.debug("Checking status of {} jobs.".format(len(active_jobs))) for active_job in active_jobs: with self.status_rate_limiter: status = job_status(active_job) if status == success: active_job.callback(active_job.job) elif status == failed: self.print_job_error( active_job.job, cluster_jobid=active_job.jobid if active_job.jobid else "unknown", ) self.print_cluster_job_error( active_job, self.dag.jobid(active_job.job) ) active_job.error_callback(active_job.job) else: still_running.append(active_job) with self.lock: self.active_jobs.extend(still_running) sleep() SynchronousClusterJob = namedtuple( "SynchronousClusterJob", "job jobid callback error_callback jobscript process" ) class SynchronousClusterExecutor(ClusterExecutor): """ invocations like "qsub -sync y" (SGE) or "bsub -K" (LSF) are synchronous, blocking the foreground thread and returning the remote exit code at remote exit. """ def __init__( self, workflow, dag, cores, submitcmd="qsub", cluster_config=None, jobname="snakejob.{rulename}.{jobid}.sh", printreason=False, quiet=False, printshellcmds=False, latency_wait=3, restart_times=0, assume_shared_fs=True, keepincomplete=False, ): super().__init__( workflow, dag, cores, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, restart_times=restart_times, assume_shared_fs=assume_shared_fs, max_status_checks_per_second=10, ) self.submitcmd = submitcmd self.external_jobid = dict() def cancel(self): logger.info("Will exit after finishing currently running jobs.") self.shutdown() def run(self, job, callback=None, submit_callback=None, error_callback=None): super()._run(job) workdir = os.getcwd() jobid = job.jobid jobscript = self.get_jobscript(job) self.write_jobscript(job, jobscript) deps = " ".join( self.external_jobid[f] for f in job.input if f in self.external_jobid ) try: submitcmd = job.format_wildcards( self.submitcmd, dependencies=deps, cluster=self.cluster_wildcards(job) ) except AttributeError as e: raise WorkflowError(str(e), rule=job.rule if not job.is_group() else None) process = subprocess.Popen( '{submitcmd} "{jobscript}"'.format( submitcmd=submitcmd, jobscript=jobscript ), shell=True, ) submit_callback(job) with self.lock: self.active_jobs.append( SynchronousClusterJob( job, process.pid, callback, error_callback, jobscript, process ) ) def _wait_for_jobs(self): while True: with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() for active_job in active_jobs: with self.status_rate_limiter: exitcode = active_job.process.poll() if exitcode is None: # job not yet finished still_running.append(active_job) elif exitcode == 0: # job finished successfully os.remove(active_job.jobscript) active_job.callback(active_job.job) else: # job failed os.remove(active_job.jobscript) self.print_job_error(active_job.job) self.print_cluster_job_error( active_job, self.dag.jobid(active_job.job) ) active_job.error_callback(active_job.job) with self.lock: self.active_jobs.extend(still_running) sleep() DRMAAClusterJob = namedtuple( "DRMAAClusterJob", "job jobid callback error_callback jobscript" ) class DRMAAExecutor(ClusterExecutor): def __init__( self, workflow, dag, cores, jobname="snakejob.{rulename}.{jobid}.sh", printreason=False, quiet=False, printshellcmds=False, drmaa_args="", drmaa_log_dir=None, latency_wait=3, cluster_config=None, restart_times=0, assume_shared_fs=True, max_status_checks_per_second=1, keepincomplete=False, ): super().__init__( workflow, dag, cores, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, restart_times=restart_times, assume_shared_fs=assume_shared_fs, max_status_checks_per_second=max_status_checks_per_second, ) try: import drmaa except ImportError: raise WorkflowError( "Python support for DRMAA is not installed. " "Please install it, e.g. with easy_install3 --user drmaa" ) except RuntimeError as e: raise WorkflowError("Error loading drmaa support:\n{}".format(e)) self.session = drmaa.Session() self.drmaa_args = drmaa_args self.drmaa_log_dir = drmaa_log_dir self.session.initialize() self.submitted = list() def cancel(self): from drmaa.const import JobControlAction from drmaa.errors import InvalidJobException, InternalException for jobid in self.submitted: try: self.session.control(jobid, JobControlAction.TERMINATE) except (InvalidJobException, InternalException): # This is common - logging a warning would probably confuse the user. pass self.shutdown() def run(self, job, callback=None, submit_callback=None, error_callback=None): super()._run(job) jobscript = self.get_jobscript(job) self.write_jobscript(job, jobscript) try: drmaa_args = job.format_wildcards( self.drmaa_args, cluster=self.cluster_wildcards(job) ) except AttributeError as e: raise WorkflowError(str(e), rule=job.rule) import drmaa if self.drmaa_log_dir: makedirs(self.drmaa_log_dir) try: jt = self.session.createJobTemplate() jt.remoteCommand = jobscript jt.nativeSpecification = drmaa_args if self.drmaa_log_dir: jt.outputPath = ":" + self.drmaa_log_dir jt.errorPath = ":" + self.drmaa_log_dir jt.jobName = os.path.basename(jobscript) jobid = self.session.runJob(jt) except ( drmaa.DeniedByDrmException, drmaa.InternalException, drmaa.InvalidAttributeValueException, ) as e: print_exception( WorkflowError("DRMAA Error: {}".format(e)), self.workflow.linemaps ) error_callback(job) return logger.info( "Submitted DRMAA job {} with external jobid {}.".format(job.jobid, jobid) ) self.submitted.append(jobid) self.session.deleteJobTemplate(jt) submit_callback(job) with self.lock: self.active_jobs.append( DRMAAClusterJob(job, jobid, callback, error_callback, jobscript) ) def shutdown(self): super().shutdown() self.session.exit() def _wait_for_jobs(self): import drmaa while True: with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() for active_job in active_jobs: with self.status_rate_limiter: try: retval = self.session.wait( active_job.jobid, drmaa.Session.TIMEOUT_NO_WAIT ) except drmaa.ExitTimeoutException as e: # job still active still_running.append(active_job) continue except (drmaa.InternalException, Exception) as e: print_exception( WorkflowError("DRMAA Error: {}".format(e)), self.workflow.linemaps, ) os.remove(active_job.jobscript) active_job.error_callback(active_job.job) continue # job exited os.remove(active_job.jobscript) if ( not retval.wasAborted and retval.hasExited and retval.exitStatus == 0 ): active_job.callback(active_job.job) else: self.print_job_error(active_job.job) self.print_cluster_job_error( active_job, self.dag.jobid(active_job.job) ) active_job.error_callback(active_job.job) with self.lock: self.active_jobs.extend(still_running) sleep() @contextlib.contextmanager def change_working_directory(directory=None): """ Change working directory in execution context if provided. """ if directory: try: saved_directory = os.getcwd() logger.info("Changing to shadow directory: {}".format(directory)) os.chdir(directory) yield finally: os.chdir(saved_directory) else: yield KubernetesJob = namedtuple( "KubernetesJob", "job jobid callback error_callback kubejob jobscript" ) class KubernetesExecutor(ClusterExecutor): def __init__( self, workflow, dag, namespace, envvars, container_image=None, jobname="{rulename}.{jobid}", printreason=False, quiet=False, printshellcmds=False, latency_wait=3, cluster_config=None, local_input=None, restart_times=None, keepincomplete=False, ): exec_job = ( "cp -rf /source/. . && " "snakemake {target} --snakefile {snakefile} " "--force -j{cores} --keep-target-files --keep-remote " "--latency-wait 0 " " --attempt {attempt} {use_threads} " "--wrapper-prefix {workflow.wrapper_prefix} " "{overwrite_config} {printshellcmds} {rules} --nocolor " "--notemp --no-hooks --nolock " ) super().__init__( workflow, dag, None, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, local_input=local_input, restart_times=restart_times, exec_job=exec_job, assume_shared_fs=False, max_status_checks_per_second=10, ) # use relative path to Snakefile self.snakefile = os.path.relpath(workflow.snakefile) try: from kubernetes import config except ImportError: raise WorkflowError( "The Python 3 package 'kubernetes' " "must be installed to use Kubernetes" ) config.load_kube_config() import kubernetes.client self.kubeapi = kubernetes.client.CoreV1Api() self.batchapi = kubernetes.client.BatchV1Api() self.namespace = namespace self.envvars = envvars or [] self.secret_files = {} self.run_namespace = str(uuid.uuid4()) self.secret_envvars = {} self.register_secret() self.container_image = container_image or get_container_image() def register_secret(self): import kubernetes.client secret = kubernetes.client.V1Secret() secret.metadata = kubernetes.client.V1ObjectMeta() # create a random uuid secret.metadata.name = self.run_namespace secret.type = "Opaque" secret.data = {} for i, f in enumerate(self.workflow.get_sources()): if f.startswith(".."): logger.warning( "Ignoring source file {}. Only files relative " "to the working directory are allowed.".format(f) ) continue with open(f, "br") as content: key = "f{}".format(i) self.secret_files[key] = f secret.data[key] = base64.b64encode(content.read()).decode() for e in self.envvars: try: key = e.lower() secret.data[key] = base64.b64encode(os.environ[e].encode()).decode() self.secret_envvars[key] = e except KeyError: continue self.kubeapi.create_namespaced_secret(self.namespace, secret) def unregister_secret(self): import kubernetes.client self.kubeapi.delete_namespaced_secret( self.run_namespace, self.namespace, body=kubernetes.client.V1DeleteOptions() ) def shutdown(self): self.unregister_secret() super().shutdown() def cancel(self): import kubernetes.client body = kubernetes.client.V1DeleteOptions() with self.lock: for j in self.active_jobs: self.kubeapi.delete_namespaced_pod(j.jobid, self.namespace, body=body) self.shutdown() def run(self, job, callback=None, submit_callback=None, error_callback=None): import kubernetes.client super()._run(job) exec_job = self.format_job( self.exec_job, job, _quote_all=True, use_threads="--force-use-threads" if not job.is_group() else "", ) # Kubernetes silently does not submit a job if the name is too long # therefore, we ensure that it is not longer than snakejob+uuid. jobid = "snakejob-{}".format( get_uuid("{}-{}-{}".format(self.run_namespace, job.jobid, job.attempt)) ) body = kubernetes.client.V1Pod() body.metadata = kubernetes.client.V1ObjectMeta(labels={"app": "snakemake"}) body.metadata.name = jobid # container container = kubernetes.client.V1Container(name=jobid) container.image = self.container_image container.command = shlex.split("/bin/sh") container.args = ["-c", exec_job] container.working_dir = "/workdir" container.volume_mounts = [ kubernetes.client.V1VolumeMount(name="workdir", mount_path="/workdir") ] container.volume_mounts = [ kubernetes.client.V1VolumeMount(name="source", mount_path="/source") ] body.spec = kubernetes.client.V1PodSpec(containers=[container]) # fail on first error body.spec.restart_policy = "Never" # source files as a secret volume # we copy these files to the workdir before executing Snakemake too_large = [ path for path in self.secret_files.values() if os.path.getsize(path) > 1000000 ] if too_large: raise WorkflowError( "The following source files exceed the maximum " "file size (1MB) that can be passed from host to " "kubernetes. These are likely not source code " "files. Consider adding them to your " "remote storage instead or (if software) use " "Conda packages or container images:\n{}".format("\n".join(too_large)) ) secret_volume = kubernetes.client.V1Volume(name="source") secret_volume.secret = kubernetes.client.V1SecretVolumeSource() secret_volume.secret.secret_name = self.run_namespace secret_volume.secret.items = [ kubernetes.client.V1KeyToPath(key=key, path=path) for key, path in self.secret_files.items() ] # workdir as an emptyDir volume of undefined size workdir_volume = kubernetes.client.V1Volume(name="workdir") workdir_volume.empty_dir = kubernetes.client.V1EmptyDirVolumeSource() body.spec.volumes = [secret_volume, workdir_volume] # env vars container.env = [] for key, e in self.secret_envvars.items(): envvar = kubernetes.client.V1EnvVar(name=e) envvar.value_from = kubernetes.client.V1EnvVarSource() envvar.value_from.secret_key_ref = kubernetes.client.V1SecretKeySelector( key=key, name=self.run_namespace ) container.env.append(envvar) # request resources container.resources = kubernetes.client.V1ResourceRequirements() container.resources.requests = {} container.resources.requests["cpu"] = job.resources["_cores"] if "mem_mb" in job.resources.keys(): container.resources.requests["memory"] = "{}M".format( job.resources["mem_mb"] ) # capabilities if job.needs_singularity and self.workflow.use_singularity: # TODO this should work, but it doesn't currently because of # missing loop devices # singularity inside docker requires SYS_ADMIN capabilities # see https://groups.google.com/a/lbl.gov/forum/#!topic/singularity/e9mlDuzKowc # container.capabilities = kubernetes.client.V1Capabilities() # container.capabilities.add = ["SYS_ADMIN", # "DAC_OVERRIDE", # "SETUID", # "SETGID", # "SYS_CHROOT"] # Running in priviledged mode always works container.security_context = kubernetes.client.V1SecurityContext( privileged=True ) pod = self._kubernetes_retry( lambda: self.kubeapi.create_namespaced_pod(self.namespace, body) ) logger.info( "Get status with:\n" "kubectl describe pod {jobid}\n" "kubectl logs {jobid}".format(jobid=jobid) ) self.active_jobs.append( KubernetesJob(job, jobid, callback, error_callback, pod, None) ) def _kubernetes_retry(self, func): import kubernetes with self.lock: try: return func() except kubernetes.client.rest.ApiException as e: if e.status == 401: # Unauthorized. # Reload config in order to ensure token is # refreshed. Then try again. logger.info("trying to reauthenticate") kubernetes.config.load_kube_config() subprocess.run(["kubectl", "get", "nodes"]) self.kubeapi = kubernetes.client.CoreV1Api() self.batchapi = kubernetes.client.BatchV1Api() self.register_secret() try: return func() except kubernetes.client.rest.ApiException as e: # Both attempts failed, raise error. raise WorkflowError( e, "This is likely a bug in " "https://github.com/kubernetes-client/python.", ) # Handling timeout that may occur in case of GKE master upgrade except urllib3.exceptions.MaxRetryError as e: logger.info( "Request time out! " "check your connection to Kubernetes master" "Workflow will pause for 5 minutes to allow any update operations to complete" ) time.sleep(300) try: return func() except: # Still can't reach the server after 5 minutes raise WorkflowErroe( e, "Error 111 connection timeout, please check" " that the k8 cluster master is reachable!", ) def _wait_for_jobs(self): import kubernetes while True: with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() for j in active_jobs: with self.status_rate_limiter: logger.debug("Checking status for pod {}".format(j.jobid)) job_not_found = False try: res = self._kubernetes_retry( lambda: self.kubeapi.read_namespaced_pod_status( j.jobid, self.namespace ) ) except kubernetes.client.rest.ApiException as e: if e.status == 404: # Jobid not found # The job is likely already done and was deleted on # the server. j.callback(j.job) continue except WorkflowError as e: print_exception(e, self.workflow.linemaps) j.error_callback(j.job) continue if res is None: msg = ( "Unknown pod {jobid}. " "Has the pod been deleted " "manually?" ).format(jobid=j.jobid) self.print_job_error(j.job, msg=msg, jobid=j.jobid) j.error_callback(j.job) elif res.status.phase == "Failed": msg = ( "For details, please issue:\n" "kubectl describe pod {jobid}\n" "kubectl logs {jobid}" ).format(jobid=j.jobid) # failed self.print_job_error(j.job, msg=msg, jobid=j.jobid) j.error_callback(j.job) elif res.status.phase == "Succeeded": # finished j.callback(j.job) else: # still active still_running.append(j) with self.lock: self.active_jobs.extend(still_running) sleep() TibannaJob = namedtuple( "TibannaJob", "job jobname jobid exec_arn callback error_callback" ) class TibannaExecutor(ClusterExecutor): def __init__( self, workflow, dag, cores, tibanna_sfn, precommand="", container_image=None, printreason=False, quiet=False, printshellcmds=False, latency_wait=3, local_input=None, restart_times=None, exec_job=None, max_status_checks_per_second=1, keepincomplete=False, ): self.workflow_sources = [] for wfs in workflow.get_sources(): if os.path.isdir(wfs): for (dirpath, dirnames, filenames) in os.walk(wfs): self.workflow_sources.extend( [os.path.join(dirpath, f) for f in filenames] ) else: self.workflow_sources.append(os.path.abspath(wfs)) log = "sources=" for f in self.workflow_sources: log += f logger.debug(log) self.snakefile = workflow.snakefile self.tibanna_sfn = tibanna_sfn if precommand: self.precommand = precommand else: self.precommand = "" self.s3_bucket = workflow.default_remote_prefix.split("/")[0] self.s3_subdir = re.sub( "^{}/".format(self.s3_bucket), "", workflow.default_remote_prefix ) logger.debug("precommand= " + self.precommand) logger.debug("bucket=" + self.s3_bucket) logger.debug("subdir=" + self.s3_subdir) self.quiet = quiet exec_job = ( "snakemake {target} --snakefile {snakefile} " "--force -j{cores} --keep-target-files --keep-remote " "--latency-wait 0 " "--attempt 1 {use_threads} " "{overwrite_config} {rules} --nocolor " "--notemp --no-hooks --nolock " ) super().__init__( workflow, dag, cores, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, local_input=local_input, restart_times=restart_times, exec_job=exec_job, assume_shared_fs=False, max_status_checks_per_second=max_status_checks_per_second, disable_default_remote_provider_args=True, ) self.container_image = container_image or get_container_image() def shutdown(self): # perform additional steps on shutdown if necessary logger.debug("shutting down Tibanna executor") super().shutdown() def cancel(self): from tibanna.core import API for j in self.active_jobs: logger.info("killing job {}".format(j.jobname)) while True: try: res = API().kill(j.exec_arn) if not self.quiet: print(res) break except KeyboardInterrupt: pass self.shutdown() def split_filename(self, filename, checkdir=None): f = os.path.abspath(filename) if checkdir: checkdir = checkdir.rstrip("/") if f.startswith(checkdir): fname = re.sub("^{}/".format(checkdir), "", f) fdir = checkdir else: direrrmsg = ( "All source files including Snakefile, " + "conda env files, and rule script files " + "must be in the same working directory: {} vs {}" ) raise WorkflowError(direrrmsg.format(checkdir, f)) else: fdir, fname = os.path.split(f) return fname, fdir def remove_prefix(self, s): return re.sub("^{}/{}/".format(self.s3_bucket, self.s3_subdir), "", s) def handle_remote(self, target): if isinstance(target, _IOFile) and target.remote_object.provider.is_default: return self.remove_prefix(target) else: return target def add_command(self, job, tibanna_args, tibanna_config): # snakefile, with file name remapped snakefile_fname = tibanna_args.snakemake_main_filename # targets, with file name remapped targets = job.get_targets() if not isinstance(targets, list): targets = [targets] targets_default = " ".join([self.handle_remote(t) for t in targets]) # use_threads use_threads = "--force-use-threads" if not job.is_group() else "" # format command command = self.format_job_pattern( self.exec_job, job, target=targets_default, snakefile=snakefile_fname, use_threads=use_threads, cores=tibanna_config["cpu"], ) if self.precommand: command = self.precommand + "; " + command logger.debug("command = " + str(command)) tibanna_args.command = command def add_workflow_files(self, job, tibanna_args): snakefile_fname, snakemake_dir = self.split_filename(self.snakefile) snakemake_child_fnames = [] for src in self.workflow_sources: src_fname, _ = self.split_filename(src, snakemake_dir) if src_fname != snakefile_fname: # redundant snakemake_child_fnames.append(src_fname) # change path for config files self.workflow.overwrite_configfiles = [ self.split_filename(cf, snakemake_dir)[0] for cf in self.workflow.overwrite_configfiles ] tibanna_args.snakemake_directory_local = snakemake_dir tibanna_args.snakemake_main_filename = snakefile_fname tibanna_args.snakemake_child_filenames = list(set(snakemake_child_fnames)) def adjust_filepath(self, f): if not hasattr(f, "remote_object"): rel = self.remove_prefix(f) # log/benchmark elif ( hasattr(f.remote_object, "provider") and f.remote_object.provider.is_default ): rel = self.remove_prefix(f) else: rel = f return rel def make_tibanna_input(self, job): from tibanna import ec2_utils, core as tibanna_core # input & output # Local snakemake command here must be run with --default-remote-prefix # and --default-remote-provider (forced) but on VM these options will be removed. # The snakemake on the VM will consider these input and output as not remote. # They files are transferred to the container by Tibanna before running snakemake. # In short, the paths on VM must be consistent with what's in Snakefile. # but the actual location of the files is on the S3 bucket/prefix. # This mapping info must be passed to Tibanna. for i in job.input: logger.debug("job input " + str(i)) logger.debug("job input is remote= " + ("true" if i.is_remote else "false")) if hasattr(i.remote_object, "provider"): logger.debug( " is remote default= " + ("true" if i.remote_object.provider.is_default else "false") ) for o in job.expanded_output: logger.debug("job output " + str(o)) logger.debug( "job output is remote= " + ("true" if o.is_remote else "false") ) if hasattr(o.remote_object, "provider"): logger.debug( " is remote default= " + ("true" if o.remote_object.provider.is_default else "false") ) file_prefix = ( "file:///data1/snakemake" # working dir inside snakemake container on VM ) input_source = dict() for ip in job.input: ip_rel = self.adjust_filepath(ip) input_source[os.path.join(file_prefix, ip_rel)] = "s3://" + ip output_target = dict() output_all = [eo for eo in job.expanded_output] if job.log: output_all.append(str(job.log)) if hasattr(job, "benchmark") and job.benchmark: output_all.append(str(job.benchmark)) for op in output_all: op_rel = self.adjust_filepath(op) output_target[os.path.join(file_prefix, op_rel)] = "s3://" + op # mem & cpu mem = job.resources["mem_mb"] / 1024 if "mem_mb" in job.resources else 1 cpu = job.threads # jobid, grouping, run_name jobid = tibanna_core.create_jobid() if job.is_group(): run_name = "snakemake-job-%s-group-%s" % (str(jobid), str(job.groupid)) else: run_name = "snakemake-job-%s-rule-%s" % (str(jobid), str(job.rule)) # tibanna input tibanna_config = { "run_name": run_name, "mem": mem, "cpu": cpu, "ebs_size": math.ceil(job.resources["disk_mb"] / 1024), "log_bucket": self.s3_bucket, } tibanna_args = ec2_utils.Args( output_S3_bucket=self.s3_bucket, language="snakemake", container_image=self.container_image, input_files=input_source, output_target=output_target, ) self.add_workflow_files(job, tibanna_args) self.add_command(job, tibanna_args, tibanna_config) tibanna_input = { "jobid": jobid, "config": tibanna_config, "args": tibanna_args.as_dict(), } logger.debug(json.dumps(tibanna_input, indent=4)) return tibanna_input def run(self, job, callback=None, submit_callback=None, error_callback=None): logger.info("running job using Tibanna...") from tibanna.core import API super()._run(job) # submit job here, and obtain job ids from the backend tibanna_input = self.make_tibanna_input(job) jobid = tibanna_input["jobid"] exec_info = API().run_workflow( tibanna_input, sfn=self.tibanna_sfn, verbose=not self.quiet, jobid=jobid ) exec_arn = exec_info.get("_tibanna", {}).get("exec_arn", "") jobname = tibanna_input["config"]["run_name"] jobid = tibanna_input["jobid"] # register job as active, using your own namedtuple. # The namedtuple must at least contain the attributes # job, jobid, callback, error_callback. self.active_jobs.append( TibannaJob(job, jobname, jobid, exec_arn, callback, error_callback) ) def _wait_for_jobs(self): # busy wait on job completion # This is only needed if your backend does not allow to use callbacks # for obtaining job status. from tibanna.core import API while True: # always use self.lock to avoid race conditions with self.lock: if not self.wait: return active_jobs = self.active_jobs self.active_jobs = list() still_running = list() for j in active_jobs: # use self.status_rate_limiter to avoid too many API calls. with self.status_rate_limiter: if j.exec_arn: status = API().check_status(j.exec_arn) else: status = "FAILED_AT_SUBMISSION" if not self.quiet or status != "RUNNING": logger.debug("job %s: %s" % (j.jobname, status)) if status == "RUNNING": still_running.append(j) elif status == "SUCCEEDED": j.callback(j.job) else: j.error_callback(j.job) with self.lock: self.active_jobs.extend(still_running) sleep() def run_wrapper( job_rule, input, output, params, wildcards, threads, resources, log, benchmark, benchmark_repeats, conda_env, singularity_img, singularity_args, env_modules, use_singularity, linemaps, debug, cleanup_scripts, shadow_dir, jobid, ): """ Wrapper around the run method that handles exceptions and benchmarking. Arguments job_rule -- the ``job.rule`` member input -- list of input files output -- list of output files wildcards -- so far processed wildcards threads -- usable threads log -- list of log files shadow_dir -- optional shadow directory root """ # get shortcuts to job_rule members run = job_rule.run_func version = job_rule.version rule = job_rule.name is_shell = job_rule.shellcmd is not None if os.name == "posix" and debug: sys.stdin = open("/dev/stdin") if benchmark is not None: from snakemake.benchmark import ( BenchmarkRecord, benchmarked, write_benchmark_records, ) # Change workdir if shadow defined and not using singularity. # Otherwise, we do the change from inside the container. passed_shadow_dir = None if use_singularity and singularity_img: passed_shadow_dir = shadow_dir shadow_dir = None try: with change_working_directory(shadow_dir): if benchmark: bench_records = [] for bench_iteration in range(benchmark_repeats): # Determine whether to benchmark this process or do not # benchmarking at all. We benchmark this process unless the # execution is done through the ``shell:``, ``script:``, or # ``wrapper:`` stanza. is_sub = ( job_rule.shellcmd or job_rule.script or job_rule.wrapper or job_rule.cwl ) if is_sub: # The benchmarking through ``benchmarked()`` is started # in the execution of the shell fragment, script, wrapper # etc, as the child PID is available there. bench_record = BenchmarkRecord() run( input, output, params, wildcards, threads, resources, log, version, rule, conda_env, singularity_img, singularity_args, use_singularity, env_modules, bench_record, jobid, is_shell, bench_iteration, cleanup_scripts, passed_shadow_dir, ) else: # The benchmarking is started here as we have a run section # and the generated Python function is executed in this # process' thread. with benchmarked() as bench_record: run( input, output, params, wildcards, threads, resources, log, version, rule, conda_env, singularity_img, singularity_args, use_singularity, env_modules, bench_record, jobid, is_shell, bench_iteration, cleanup_scripts, passed_shadow_dir, ) # Store benchmark record for this iteration bench_records.append(bench_record) else: run( input, output, params, wildcards, threads, resources, log, version, rule, conda_env, singularity_img, singularity_args, use_singularity, env_modules, None, jobid, is_shell, None, cleanup_scripts, passed_shadow_dir, ) except (KeyboardInterrupt, SystemExit) as e: # Re-raise the keyboard interrupt in order to record an error in the # scheduler but ignore it raise e except (Exception, BaseException) as ex: log_verbose_traceback(ex) # this ensures that exception can be re-raised in the parent thread lineno, file = get_exception_origin(ex, linemaps) raise RuleException( format_error( ex, lineno, linemaps=linemaps, snakefile=file, show_traceback=True ) ) if benchmark is not None: try: write_benchmark_records(bench_records, benchmark) except (Exception, BaseException) as ex: raise WorkflowError(ex) snakemake-5.10.0/snakemake/gui.html000066400000000000000000000304711361131222100171420ustar00rootroot00000000000000 Snakemake

Resources

{% for resource in resources %}
{% endfor %}
snakemake-5.10.0/snakemake/gui.py000066400000000000000000000105021361131222100166170ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import json import os import threading from flask import Flask, render_template, request from snakemake.common import __version__ LOCK = threading.Lock() app = Flask("snakemake", template_folder=os.path.dirname(__file__)) # app.debug=True app.extensions = { "dag": None, "run_snakemake": None, "progress": "", "log": [], "status": {"running": False}, "args": None, "targets": [], "rule_info": [], "resources": [], } def register(run_snakemake, args): app.extensions["run_snakemake"] = run_snakemake app.extensions["args"] = dict( targets=args.target, cluster=args.cluster, workdir=args.directory, touch=args.touch, forcetargets=args.force, forceall=args.forceall, forcerun=args.forcerun, prioritytargets=args.prioritize, stats=args.stats, keepgoing=args.keep_going, jobname=args.jobname, immediate_submit=args.immediate_submit, ignore_ambiguity=args.allow_ambiguity, lock=not args.nolock, force_incomplete=args.rerun_incomplete, ignore_incomplete=args.ignore_incomplete, jobscript=args.jobscript, notemp=args.notemp, latency_wait=args.latency_wait, ) target_rules = [] def log_handler(msg): if msg["level"] == "rule_info": target_rules.append(msg["name"]) run_snakemake(list_target_rules=True, log_handler=log_handler) for target in args.target: target_rules.remove(target) app.extensions["targets"] = args.target + target_rules resources = [] def log_handler(msg): if msg["level"] == "info": resources.append(msg["msg"]) run_snakemake(list_resources=True, log_handler=log_handler) app.extensions["resources"] = resources app.extensions["snakefilepath"] = os.path.abspath(args.snakefile) def run_snakemake(**kwargs): args = dict(app.extensions["args"]) args.update(kwargs) app.extensions["run_snakemake"](**args) @app.route("/") def index(): args = app.extensions["args"] return render_template( "gui.html", targets=app.extensions["targets"], cores_label="Nodes" if args["cluster"] else "Cores", resources=app.extensions["resources"], snakefilepath=app.extensions["snakefilepath"], version=__version__, node_width=15, node_padding=10, ) @app.route("/dag") def dag(): if app.extensions["dag"] is None: def record(msg): if msg["level"] == "d3dag": app.extensions["dag"] = msg elif msg["level"] in ("error", "info"): app.extensions["log"].append(msg) run_snakemake(printd3dag=True, log_handler=record) return json.dumps(app.extensions["dag"]) @app.route("/log/") def log(id): log = app.extensions["log"][id:] return json.dumps(log) @app.route("/progress") def progress(): return json.dumps(app.extensions["progress"]) def _run(dryrun=False): def log_handler(msg): level = msg["level"] if level == "progress": app.extensions["progress"] = msg elif level in ("info", "error", "job_info", "job_finished"): app.extensions["log"].append(msg) with LOCK: app.extensions["status"]["running"] = True run_snakemake(log_handler=log_handler, dryrun=dryrun) with LOCK: app.extensions["status"]["running"] = False return "" @app.route("/run") def run(): _run() @app.route("/dryrun") def dryrun(): _run(dryrun=True) @app.route("/status") def status(): with LOCK: return json.dumps(app.extensions["status"]) @app.route("/targets") def targets(): return json.dumps(app.extensions["targets"]) @app.route("/get_args") def get_args(): return json.dumps(app.extensions["args"]) @app.route("/set_args", methods=["POST"]) def set_args(): app.extensions["args"].update( {name: value for name, value in request.form.items() if not name.endswith("[]")} ) targets = request.form.getlist("targets[]") if targets != app.extensions["args"]["targets"]: app.extensions["dag"] = None app.extensions["args"]["targets"] = targets return "" snakemake-5.10.0/snakemake/io.py000077500000000000000000001261271361131222100164600ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import collections import os import shutil from pathlib import Path import re import stat import time import datetime import json import copy import functools import subprocess as sp from itertools import product, chain from contextlib import contextmanager import string import collections from snakemake.exceptions import ( MissingOutputException, WorkflowError, WildcardError, RemoteFileException, ) from snakemake.logging import logger from inspect import isfunction, ismethod from snakemake.common import DYNAMIC_FILL def lutime(f, times): # In some cases, we have a platform where os.supports_follow_symlink includes stat() # but not utime(). This leads to an anomaly. In any case we never want to touch the # target of a link. if os.utime in os.supports_follow_symlinks: # ...utime is well behaved os.utime(f, times, follow_symlinks=False) elif not os.path.islink(f): # ...symlinks not an issue here os.utime(f, times) else: try: # try the system command if times: fmt_time = lambda sec: datetime.fromtimestamp(sec).strftime( "%Y%m%d%H%M.%S" ) atime, mtime = times sp.check_call(["touch", "-h", f, "-a", "-t", fmt_time(atime)]) sp.check_call(["touch", "-h", f, "-m", "-t", fmt_time(mtime)]) else: sp.check_call(["touch", "-h", f]) except sp.CalledProcessError: pass # ...problem system. Do nothing. logger.warning( "Unable to set utime on symlink {}. Your Python build does not support it.".format( f ) ) return None if os.chmod in os.supports_follow_symlinks: def lchmod(f, mode): os.chmod(f, mode, follow_symlinks=False) else: def lchmod(f, mode): os.chmod(f, mode) class IOCache: def __init__(self): self.mtime = dict() self.exists_local = dict() self.exists_remote = dict() self.size = dict() self.active = True def clear(self): self.mtime.clear() self.size.clear() self.exists_local.clear() self.exists_remote.clear() def deactivate(self): self.clear() self.active = False def IOFile(file, rule=None): assert rule is not None f = _IOFile(file) f.rule = rule return f class _IOFile(str): """ A file that is either input or output of a rule. """ __slots__ = ["_is_function", "_file", "rule", "_regex"] def __new__(cls, file): obj = str.__new__(cls, file) obj._is_function = isfunction(file) or ismethod(file) obj._is_function = obj._is_function or ( isinstance(file, AnnotatedString) and bool(file.callable) ) obj._file = file obj.rule = None obj._regex = None if obj.is_remote: obj.remote_object._iofile = obj return obj def iocache(func): @functools.wraps(func) def wrapper(self, *args, **kwargs): if self.rule.workflow.iocache.active: cache = getattr(self.rule.workflow.iocache, func.__name__) if self in cache: return cache[self] v = func(self, *args, **kwargs) cache[self] = v return v else: return func(self, *args, **kwargs) return wrapper def _refer_to_remote(func): """ A decorator so that if the file is remote and has a version of the same file-related function, call that version instead. """ @functools.wraps(func) def wrapper(self, *args, **kwargs): if self.is_remote: if hasattr(self.remote_object, func.__name__): return getattr(self.remote_object, func.__name__)(*args, **kwargs) return func(self, *args, **kwargs) return wrapper @contextmanager def open(self, mode="r", buffering=-1, encoding=None, errors=None, newline=None): """Open this file. If necessary, download it from remote first. This can (and should) be used in a `with`-statement. """ if not self.exists: raise WorkflowError( "File {} cannot be opened, since it does not exist.".format(self) ) if not self.exists_local and self.is_remote: self.download_from_remote() f = open(self) try: yield f finally: f.close() @property def is_remote(self): return is_flagged(self._file, "remote_object") @property def is_ancient(self): return is_flagged(self._file, "ancient") @property def is_directory(self): return is_flagged(self._file, "directory") @property def is_multiext(self): return is_flagged(self._file, "multiext") @property def multiext_prefix(self): return get_flag_value(self._file, "multiext") def update_remote_filepath(self): # if the file string is different in the iofile, update the remote object # (as in the case of wildcard expansion) remote_object = self.remote_object if remote_object._file != self._file: remote_object._iofile = self @property def should_keep_local(self): return self.remote_object.keep_local @property def should_stay_on_remote(self): return self.remote_object.stay_on_remote @property def remote_object(self): return get_flag_value(self._file, "remote_object") @property @_refer_to_remote def file(self): if not self._is_function: return self._file else: raise ValueError( "This IOFile is specified as a function and " "may not be used directly." ) def check(self): hint = ( "It can also lead to inconsistent results of the file-matching " "approach used by Snakemake." ) if self._file.startswith("./"): logger.warning( "Relative file path '{}' starts with './'. This is redundant " "and strongly discouraged. {} You can simply omit the './' " "for relative file paths.".format(self._file, hint) ) if self._file.startswith(" "): logger.warning( "File path '{}' starts with whitespace. " "This is likely unintended. {}".format(self._file, hint) ) if self._file.endswith(" "): logger.warning( "File path '{}' ends with whitespace. " "This is likely unintended. {}".format(self._file, hint) ) if "\n" in self._file: logger.warning( "File path '{}' contains line break. " "This is likely unintended. {}".format(self._file, hint) ) if _double_slash_regex.search(self._file) is not None and not self.is_remote: logger.warning( "File path {} contains double '{}'. " "This is likely unintended. {}".format(self._file, os.path.sep, hint) ) @property def exists(self): if self.is_remote: return self.exists_remote else: return self.exists_local def parents(self, omit=0): """Yield all parent paths, omitting the given number of ancestors.""" for p in list(Path(self.file).parents)[::-1][omit:]: p = IOFile(str(p), rule=self.rule) p.clone_flags(self) yield p @property @iocache def exists_local(self): if self.rule.workflow.iocache.active: # The idea is to first check existence of parent directories and # cache the results. # We omit the last ancestor, because this is always "." or "/" or a # drive letter. for p in self.parents(omit=1): try: if not p.exists_local: return False except: # In case of an error, we continue, because it can be that # we simply don't have the permissions to access a parent # directory. continue return os.path.exists(self.file) @property @iocache def exists_remote(self): if not self.is_remote: return False if ( self.rule.workflow.iocache.active and self.remote_object.provider.allows_directories ): # The idea is to first check existence of parent directories and # cache the results. # We omit the last 2 ancestors, because these are "." and the host # name of the remote location. for p in self.parents(omit=2): try: if not p.exists_remote: return False except: # In case of an error, we continue, because it can be that # we simply don't have the permissions to access a parent # directory in the remote. continue return self.remote_object.exists() @property def protected(self): """Returns True if the file is protected. Always False for symlinks.""" # symlinks are never regarded as protected return ( self.exists_local and not os.access(self.file, os.W_OK) and not os.path.islink(self.file) ) @property @iocache @_refer_to_remote def mtime(self): return self.mtime_local @property def mtime_local(self): # do not follow symlinks for modification time if os.path.isdir(self.file) and os.path.exists( os.path.join(self.file, ".snakemake_timestamp") ): return os.lstat(os.path.join(self.file, ".snakemake_timestamp")).st_mtime else: return os.lstat(self.file).st_mtime @property def flags(self): return getattr(self._file, "flags", {}) @property @iocache @_refer_to_remote def size(self): return self.size_local @property def size_local(self): # follow symlinks but throw error if invalid self.check_broken_symlink() return os.path.getsize(self.file) def check_broken_symlink(self): """ Raise WorkflowError if file is a broken symlink. """ if not self.exists_local and os.lstat(self.file): raise WorkflowError( "File {} seems to be a broken symlink.".format(self.file) ) @_refer_to_remote def is_newer(self, time): """ Returns true of the file is newer than time, or if it is a symlink that points to a file newer than time. """ if self.is_ancient: return False elif self.is_remote: # If file is remote but provider does not override the implementation this # is the best we can do. return self.mtime > time else: if os.path.isdir(self.file) and os.path.exists( os.path.join(self.file, ".snakemake_timestamp") ): st_mtime_file = os.path.join(self.file, ".snakemake_timestamp") else: st_mtime_file = self.file try: return os.stat(st_mtime_file).st_mtime > time or self.mtime > time except FileNotFoundError: raise WorkflowError( "File {} not found although it existed before. Is there another active process that might have deleted it?".format( self.file ) ) def download_from_remote(self): if self.is_remote and self.remote_object.exists(): if not self.should_stay_on_remote: logger.info("Downloading from remote: {}".format(self.file)) self.remote_object.download() logger.info("Finished download.") else: raise RemoteFileException( "The file to be downloaded does not seem to exist remotely." ) def upload_to_remote(self): if self.is_remote: logger.info("Uploading to remote: {}".format(self.file)) self.remote_object.upload() logger.info("Finished upload.") def prepare(self): path_until_wildcard = re.split(DYNAMIC_FILL, self.file)[0] dir = os.path.dirname(path_until_wildcard) if len(dir) > 0: try: os.makedirs(dir, exist_ok=True) except OSError as e: # ignore Errno 17 "File exists" (reason: multiprocessing) if e.errno != 17: raise e if is_flagged(self._file, "pipe"): os.mkfifo(self._file) def protect(self): mode = ( os.lstat(self.file).st_mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH ) if os.path.isdir(self.file): for root, dirs, files in os.walk(self.file): for d in dirs: lchmod(os.path.join(self.file, d), mode) for f in files: lchmod(os.path.join(self.file, f), mode) lchmod(self.file, mode) def remove(self, remove_non_empty_dir=False): if self.is_directory: remove(self, remove_non_empty_dir=True) else: remove(self, remove_non_empty_dir=remove_non_empty_dir) def touch(self, times=None): """ times must be 2-tuple: (atime, mtime) """ try: if self.is_directory: file = os.path.join(self.file, ".snakemake_timestamp") # Create the flag file if it doesn't exist if not os.path.exists(file): with open(file, "w") as f: pass lutime(file, times) else: lutime(self.file, times) except OSError as e: if e.errno == 2: raise MissingOutputException( "Output file {} of rule {} shall be touched but " "does not exist.".format(self.file, self.rule.name), lineno=self.rule.lineno, snakefile=self.rule.snakefile, ) else: raise e def touch_or_create(self): try: self.touch() except MissingOutputException: # first create directory if it does not yet exist dir = self.file if self.is_directory else os.path.dirname(self.file) if dir: os.makedirs(dir, exist_ok=True) # create empty file file = ( os.path.join(self.file, ".snakemake_timestamp") if self.is_directory else self.file ) with open(file, "w") as f: pass def apply_wildcards(self, wildcards, fill_missing=False, fail_dynamic=False): f = self._file if self._is_function: f = self._file(Namedlist(fromdict=wildcards)) # this bit ensures flags are transferred over to files after # wildcards are applied file_with_wildcards_applied = IOFile( apply_wildcards( f, wildcards, fill_missing=fill_missing, fail_dynamic=fail_dynamic, dynamic_fill=DYNAMIC_FILL, ), rule=self.rule, ) file_with_wildcards_applied.clone_flags(self) return file_with_wildcards_applied def get_wildcard_names(self): return get_wildcard_names(self.file) def contains_wildcard(self): return contains_wildcard(self.file) def regex(self): if self._regex is None: # compile a regular expression self._regex = re.compile(regex(self.file)) return self._regex def constant_prefix(self): first_wildcard = _wildcard_regex.search(self.file) if first_wildcard: return self.file[: first_wildcard.start()] return self.file def constant_suffix(self): m = None for m in _wildcard_regex.finditer(self.file): pass last_wildcard = m if last_wildcard: return self.file[last_wildcard.end() :] return self.file def match(self, target): return self.regex().match(target) or None def format_dynamic(self): return self.replace(DYNAMIC_FILL, "{*}") def clone_flags(self, other): if isinstance(self._file, str): self._file = AnnotatedString(self._file) if isinstance(other._file, AnnotatedString): self._file.flags = getattr(other._file, "flags", {}).copy() if "remote_object" in self._file.flags: self._file.flags["remote_object"] = copy.copy( self._file.flags["remote_object"] ) self.update_remote_filepath() def clone_remote_object(self, other): if ( isinstance(other._file, AnnotatedString) and "remote_object" in other._file.flags ): self._file.flags["remote_object"] = copy.copy( other._file.flags["remote_object"] ) self.update_remote_filepath() def set_flags(self, flags): if isinstance(self._file, str): self._file = AnnotatedString(self._file) self._file.flags = flags def __eq__(self, other): f = other._file if isinstance(other, _IOFile) else other return self._file == f def __hash__(self): return self._file.__hash__() _double_slash_regex = ( re.compile(r"([^:]//|^//)") if os.path.sep == "/" else re.compile(r"\\\\") ) _wildcard_regex = re.compile( r""" \{ (?=( # This lookahead assertion emulates an 'atomic group' # which is required for performance \s*(?P\w+) # wildcard name (\s*,\s* (?P # an optional constraint ([^{}]+ | \{\d+(,\d+)?\})* # allow curly braces to nest one level ) # ... as in '{w,a{3,5}}' )?\s* ))\1 \} """, re.VERBOSE, ) def wait_for_files( files, latency_wait=3, force_stay_on_remote=False, ignore_pipe=False ): """Wait for given files to be present in filesystem.""" files = list(files) def get_missing(): return [ f for f in files if not ( f.exists_remote if ( isinstance(f, _IOFile) and f.is_remote and (force_stay_on_remote or f.should_stay_on_remote) ) else os.path.exists(f) if not (is_flagged(f, "pipe") and ignore_pipe) else True ) ] missing = get_missing() if missing: logger.info( "Waiting at most {} seconds for missing files.".format(latency_wait) ) for _ in range(latency_wait): if not get_missing(): return time.sleep(1) raise IOError( "Missing files after {} seconds:\n{}".format( latency_wait, "\n".join(get_missing()) ) ) def get_wildcard_names(pattern): return set(match.group("name") for match in _wildcard_regex.finditer(pattern)) def contains_wildcard(path): return _wildcard_regex.search(path) is not None def contains_wildcard_constraints(pattern): return any(match.group("constraint") for match in _wildcard_regex.finditer(pattern)) def remove(file, remove_non_empty_dir=False): if file.is_remote and file.should_stay_on_remote: if file.exists_remote: file.remote_object.remove() elif os.path.isdir(file) and not os.path.islink(file): if remove_non_empty_dir: shutil.rmtree(file) else: try: os.removedirs(file) except OSError as e: # skip non empty directories if e.errno == 39: logger.info( "Skipped removing non-empty directory {}".format(e.filename) ) else: logger.warning(str(e)) # Remember that dangling symlinks fail the os.path.exists() test, but # we definitely still want to zap them. try/except is the safest way. # Also, we don't want to remove the null device if it is an output. elif os.devnull != str(file): try: os.remove(file) except FileNotFoundError: pass def regex(filepattern): f = [] last = 0 wildcards = set() for match in _wildcard_regex.finditer(filepattern): f.append(re.escape(filepattern[last : match.start()])) wildcard = match.group("name") if wildcard in wildcards: if match.group("constraint"): raise ValueError( "Constraint regex must be defined only in the first " "occurence of the wildcard in a string." ) f.append("(?P={})".format(wildcard)) else: wildcards.add(wildcard) f.append( "(?P<{}>{})".format( wildcard, match.group("constraint") if match.group("constraint") else ".+", ) ) last = match.end() f.append(re.escape(filepattern[last:])) f.append("$") # ensure that the match spans the whole file return "".join(f) def apply_wildcards( pattern, wildcards, fill_missing=False, fail_dynamic=False, dynamic_fill=None, keep_dynamic=False, ): def format_match(match): name = match.group("name") try: value = wildcards[name] if fail_dynamic and value == dynamic_fill: raise WildcardError(name) return str(value) # convert anything into a str except KeyError as ex: if keep_dynamic: return "{{{}}}".format(name) elif fill_missing: return dynamic_fill else: raise WildcardError(str(ex)) return re.sub(_wildcard_regex, format_match, pattern) def not_iterable(value): return ( isinstance(value, str) or isinstance(value, dict) or not isinstance(value, collections.abc.Iterable) ) def is_callable(value): return ( callable(value) or (isinstance(value, _IOFile) and value._is_function) or (isinstance(value, AnnotatedString) and value.callable is not None) ) class AnnotatedString(str): def __init__(self, value): self.flags = dict() self.callable = value if is_callable(value) else None def flag(value, flag_type, flag_value=True): if isinstance(value, AnnotatedString): value.flags[flag_type] = flag_value return value if not_iterable(value): value = AnnotatedString(value) value.flags[flag_type] = flag_value return value return [flag(v, flag_type, flag_value=flag_value) for v in value] def is_flagged(value, flag): if isinstance(value, AnnotatedString): return flag in value.flags and value.flags[flag] if isinstance(value, _IOFile): return flag in value.flags and value.flags[flag] return False def get_flag_value(value, flag_type): if isinstance(value, AnnotatedString) or isinstance(value, _IOFile): if flag_type in value.flags: return value.flags[flag_type] else: return None def ancient(value): """ A flag for an input file that shall be considered ancient; i.e. its timestamp shall have no effect on which jobs to run. """ return flag(value, "ancient") def directory(value): """ A flag to specify that an output is a directory, rather than a file or named pipe. """ if is_flagged(value, "pipe"): raise SyntaxError("Pipe and directory flags are mutually exclusive.") if is_flagged(value, "remote"): raise SyntaxError("Remote and directory flags are mutually exclusive.") if is_flagged(value, "dynamic"): raise SyntaxError("Dynamic and directory flags are mutually exclusive.") return flag(value, "directory") def temp(value): """ A flag for an input or output file that shall be removed after usage. """ if is_flagged(value, "protected"): raise SyntaxError("Protected and temporary flags are mutually exclusive.") if is_flagged(value, "remote"): raise SyntaxError("Remote and temporary flags are mutually exclusive.") return flag(value, "temp") def pipe(value): if is_flagged(value, "protected"): raise SyntaxError("Pipes may not be protected.") if is_flagged(value, "remote"): raise SyntaxError("Pipes may not be remote files.") return flag(value, "pipe") def temporary(value): """ An alias for temp. """ return temp(value) def protected(value): """ A flag for a file that shall be write protected after creation. """ if is_flagged(value, "temp"): raise SyntaxError("Protected and temporary flags are mutually exclusive.") if is_flagged(value, "remote"): raise SyntaxError("Remote and protected flags are mutually exclusive.") return flag(value, "protected") def dynamic(value): """ A flag for a file that shall be dynamic, i.e. the multiplicity (and wildcard values) will be expanded after a certain rule has been run """ logger.warning( "Dynamic output is deprecated in favor of checkpoints (see docs). " "It will be removed in Snakemake 6.0." ) annotated = flag(value, "dynamic", True) tocheck = [annotated] if not_iterable(annotated) else annotated for file in tocheck: matches = list(_wildcard_regex.finditer(file)) # if len(matches) != 1: # raise SyntaxError("Dynamic files need exactly one wildcard.") for match in matches: if match.group("constraint"): raise SyntaxError( "The wildcards in dynamic files cannot be constrained." ) return annotated def touch(value): return flag(value, "touch") def unpack(value): return flag(value, "unpack") def repeat(value, n_repeat): """Flag benchmark records with the number of repeats.""" return flag(value, "repeat", n_repeat) def checkpoint_target(value): return flag(value, "checkpoint_target") ReportObject = collections.namedtuple("ReportObject", ["caption", "category"]) def report(value, caption=None, category=None): """Flag output file as to be included into reports.""" return flag(value, "report", ReportObject(caption, category)) def local(value): """Mark a file as local file. This disables application of a default remote provider. """ if is_flagged(value, "remote"): raise SyntaxError("Remote and local flags are mutually exclusive.") return flag(value, "local") def expand(*args, **wildcards): """ Expand wildcards in given filepatterns. Arguments *args -- first arg: filepatterns as list or one single filepattern, second arg (optional): a function to combine wildcard values (itertools.product per default) **wildcards -- the wildcards as keyword arguments with their values as lists. If allow_missing=True is included wildcards in filepattern without values will stay unformatted. """ filepatterns = args[0] if len(args) == 1: combinator = product elif len(args) == 2: combinator = args[1] if isinstance(filepatterns, str): filepatterns = [filepatterns] def path_to_str(f): if isinstance(f, Path): return str(f) return f filepatterns = list(map(path_to_str, filepatterns)) if any(map(lambda f: getattr(f, "flags", {}), filepatterns)): raise WorkflowError( "Flags in file patterns given to expand() are invalid. " "Flags (e.g. temp(), directory()) have to be applied outside " "of expand (e.g. 'temp(expand(\"plots/{sample}.pdf\", sample=SAMPLES))')." ) # check if remove missing is provided format_dict = dict if "allow_missing" in wildcards and wildcards["allow_missing"] is True: class FormatDict(dict): def __missing__(self, key): return "{" + key + "}" format_dict = FormatDict # check that remove missing is not a wildcard in the filepatterns for filepattern in filepatterns: if "allow_missing" in re.findall(r"{([^}\.[!:]+)", filepattern): format_dict = dict break # remove unused wildcards to avoid duplicate filepatterns wildcards = { filepattern: { k: v for k, v in wildcards.items() if k in re.findall(r"{([^}\.[!:]+)", filepattern) } for filepattern in filepatterns } def flatten(wildcards): for wildcard, values in wildcards.items(): if isinstance(values, str) or not isinstance( values, collections.abc.Iterable ): values = [values] yield [(wildcard, value) for value in values] formatter = string.Formatter() try: return [ formatter.vformat(filepattern, (), comb) for filepattern in filepatterns for comb in map(format_dict, combinator(*flatten(wildcards[filepattern]))) ] except KeyError as e: raise WildcardError("No values given for wildcard {}.".format(e)) def multiext(prefix, *extensions): """Expand a given prefix with multiple extensions (e.g. .txt, .csv, ...).""" if any( ("/" in ext or "\\" in ext or not ext.startswith(".")) for ext in extensions ): raise WorkflowError( "Extensions for multiext may not contain path delimiters " "(/,\) and must start with '.' (e.g. .txt)." ) return [flag(prefix + ext, "multiext", flag_value=prefix) for ext in extensions] def limit(pattern, **wildcards): """ Limit wildcards to the given values. Arguments: **wildcards -- the wildcards as keyword arguments with their values as lists """ return pattern.format( **{ wildcard: "{{{},{}}}".format(wildcard, "|".join(values)) for wildcard, values in wildcards.items() } ) def glob_wildcards(pattern, files=None, followlinks=False): """ Glob the values of the wildcards by matching the given pattern to the filesystem. Returns a named tuple with a list of values for each wildcard. """ pattern = os.path.normpath(pattern) first_wildcard = re.search("{[^{]", pattern) dirname = ( os.path.dirname(pattern[: first_wildcard.start()]) if first_wildcard else os.path.dirname(pattern) ) if not dirname: dirname = "." names = [match.group("name") for match in _wildcard_regex.finditer(pattern)] Wildcards = collections.namedtuple("Wildcards", names) wildcards = Wildcards(*[list() for name in names]) pattern = re.compile(regex(pattern)) if files is None: files = ( os.path.normpath(os.path.join(dirpath, f)) for dirpath, dirnames, filenames in os.walk( dirname, followlinks=followlinks ) for f in chain(filenames, dirnames) ) for f in files: match = re.match(pattern, f) if match: for name, value in match.groupdict().items(): getattr(wildcards, name).append(value) return wildcards def update_wildcard_constraints( pattern, wildcard_constraints, global_wildcard_constraints ): """Update wildcard constraints Args: pattern (str): pattern on which to update constraints wildcard_constraints (dict): dictionary of wildcard:constraint key-value pairs global_wildcard_constraints (dict): dictionary of wildcard:constraint key-value pairs """ def replace_constraint(match): name = match.group("name") constraint = match.group("constraint") newconstraint = wildcard_constraints.get( name, global_wildcard_constraints.get(name) ) if name in examined_names: return match.group(0) examined_names.add(name) # Don't override if constraint already set if constraint is not None: return match.group(0) # Only update if a new constraint has actually been set elif newconstraint is not None: return "{{{},{}}}".format(name, newconstraint) else: return match.group(0) examined_names = set() updated = re.sub(_wildcard_regex, replace_constraint, pattern) # inherit flags if isinstance(pattern, AnnotatedString): updated = AnnotatedString(updated) updated.flags = dict(pattern.flags) return updated def split_git_path(path): file_sub = re.sub(r"^git\+file:/+", "/", path) (file_path, version) = file_sub.split("@") file_path = os.path.realpath(file_path) root_path = get_git_root(file_path) if file_path.startswith(root_path): file_path = file_path[len(root_path) :].lstrip("/") return (root_path, file_path, version) def get_git_root(path): """ Args: path: (str) Path a to a directory/file that is located inside the repo Returns: path to root folder for git repo """ import git try: git_repo = git.Repo(path, search_parent_directories=True) return git_repo.git.rev_parse("--show-toplevel") except git.exc.NoSuchPathError as e: tail, head = os.path.split(path) return get_git_root_parent_directory(tail, path) def get_git_root_parent_directory(path, input_path): """ This function will recursively go through parent directories until a git repository is found or until no parent directories are left, in which case a error will be raised. This is needed when providing a path to a file/folder that is located on a branch/tag no currently checked out. Args: path: (str) Path a to a directory that is located inside the repo input_path: (str) origin path, used when raising WorkflowError Returns: path to root folder for git repo """ import git try: git_repo = git.Repo(path, search_parent_directories=True) return git_repo.git.rev_parse("--show-toplevel") except git.exc.NoSuchPathError as e: tail, head = os.path.split(path) if tail is None: raise WorkflowError( "Neither provided git path ({}) ".format(input_path) + "or parent directories contain a valid git repo." ) else: return get_git_root_parent_directory(tail, input_path) def git_content(git_file): """ This function will extract a file from a git repository, one located on the filesystem. Expected format is git+file:///path/to/your/repo/path_to_file@@version Args: env_file (str): consist of path to repo, @, version and file information Ex: git+file:////home/smeds/snakemake-wrappers/bio/fastqc/wrapper.py@0.19.3 Returns: file content or None if the expected format isn't meet """ import git if git_file.startswith("git+file:"): (root_path, file_path, version) = split_git_path(git_file) return git.Repo(root_path).git.show("{}:{}".format(version, file_path)) else: raise WorkflowError( "Provided git path ({}) doesn't meet the " "expected format:".format(git_file) + ", expected format is " "git+file://PATH_TO_REPO/PATH_TO_FILE_INSIDE_REPO@VERSION" ) return None def strip_wildcard_constraints(pattern): """Return a string that does not contain any wildcard constraints.""" def strip_constraint(match): return "{{{}}}".format(match.group("name")) return _wildcard_regex.sub(strip_constraint, pattern) class Namedlist(list): """ A list that additionally provides functions to name items. Further, it is hashable, however the hash does not consider the item names. """ def __init__( self, toclone=None, fromdict=None, plainstr=False, strip_constraints=False, custom_map=None, ): """ Create the object. Arguments toclone -- another Namedlist that shall be cloned fromdict -- a dict that shall be converted to a Namedlist (keys become names) """ list.__init__(self) self._names = dict() if toclone: if custom_map is not None: self.extend(map(custom_map, toclone)) elif plainstr: self.extend(map(str, toclone)) elif strip_constraints: self.extend(map(strip_wildcard_constraints, toclone)) else: self.extend(toclone) if isinstance(toclone, Namedlist): self._take_names(toclone._get_names()) if fromdict: for key, item in fromdict.items(): self.append(item) self._add_name(key) def _add_name(self, name): """ Add a name to the last item. Arguments name -- a name """ self._set_name(name, len(self) - 1) def _set_name(self, name, index, end=None): """ Set the name of an item. Arguments name -- a name index -- the item index """ if name == "items" or name == "keys" or name == "get": raise AttributeError( "invalid name for input, output, wildcard, " "params or log: 'items', 'keys', and 'get' are reserved for internal use" ) self._names[name] = (index, end) if end is None: setattr(self, name, self[index]) else: setattr(self, name, Namedlist(toclone=self[index:end])) def _get_names(self): """ Get the defined names as (name, index) pairs. """ for name, index in self._names.items(): yield name, index def _take_names(self, names): """ Take over the given names. Arguments names -- the given names as (name, index) pairs """ for name, (i, j) in names: self._set_name(name, i, end=j) def items(self): for name in self._names: yield name, getattr(self, name) def _allitems(self): next = 0 for name, index in sorted( self._names.items(), key=lambda item: ( item[1][0], item[1][0] + 1 if item[1][1] is None else item[1][1], ), ): start, end = index if end is None: end = start + 1 if start > next: for item in self[next:start]: yield None, item yield name, getattr(self, name) next = end for item in self[next:]: yield None, item def _insert_items(self, index, items): self[index : index + 1] = items add = len(items) - 1 for name, (i, j) in self._names.items(): if i > index: self._names[name] = (i + add, None if j is None else j + add) elif i == index: self._set_name(name, i, end=i + len(items)) def keys(self): return self._names.keys() def _plainstrings(self): return self.__class__.__call__(toclone=self, plainstr=True) def _stripped_constraints(self): return self.__class__.__call__(toclone=self, strip_constraints=True) def _clone(self): return self.__class__.__call__(toclone=self) def get(self, key, default_value=None): return self.__dict__.get(key, default_value) def __getitem__(self, key): try: return super().__getitem__(key) except TypeError: pass return getattr(self, key) def __hash__(self): return hash(tuple(self)) def __str__(self): return " ".join(map(str, self)) class InputFiles(Namedlist): @property def size(self): return sum(f.size for f in self) class OutputFiles(Namedlist): pass class Wildcards(Namedlist): pass class Params(Namedlist): pass class Resources(Namedlist): pass class Log(Namedlist): pass def _load_configfile(configpath, filetype="Config"): "Tries to load a configfile first as JSON, then as YAML, into a dict." import yaml try: with open(configpath) as f: try: return json.load(f, object_pairs_hook=collections.OrderedDict) except ValueError: f.seek(0) # try again try: # From http://stackoverflow.com/a/21912744/84349 class OrderedLoader(yaml.Loader): pass def construct_mapping(loader, node): loader.flatten_mapping(node) return collections.OrderedDict(loader.construct_pairs(node)) OrderedLoader.add_constructor( yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, construct_mapping ) return yaml.load(f, Loader=OrderedLoader) except yaml.YAMLError: raise WorkflowError( "Config file is not valid JSON or YAML. " "In case of YAML, make sure to not mix " "whitespace and tab indentation.".format(filetype) ) except FileNotFoundError: raise WorkflowError("{} file {} not found.".format(filetype, configpath)) def load_configfile(configpath): "Loads a JSON or YAML configfile as a dict, then checks that it's a dict." config = _load_configfile(configpath) if not isinstance(config, dict): raise WorkflowError( "Config file must be given as JSON or YAML " "with keys at top level." ) return config ##### Wildcard pumping detection ##### class PeriodicityDetector: def __init__(self, min_repeat=20, max_repeat=100): """ Args: max_repeat (int): The maximum length of the periodic substring. min_repeat (int): The minimum length of the periodic substring. """ self.regex = re.compile( "((?P.+)(?P=value){{{min_repeat},{max_repeat}}})$".format( min_repeat=min_repeat - 1, max_repeat=max_repeat - 1 ) ) def is_periodic(self, value): """Returns the periodic substring or None if not periodic.""" m = self.regex.search(value) # search for a periodic suffix. if m is not None: return m.group("value") snakemake-5.10.0/snakemake/jobs.py000066400000000000000000001316041361131222100167770ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import hashlib import os import sys import base64 import tempfile import subprocess import json from collections import defaultdict from itertools import chain, filterfalse from functools import partial from operator import attrgetter from urllib.request import urlopen from urllib.parse import urlparse from snakemake.io import ( IOFile, Wildcards, Resources, _IOFile, is_flagged, get_flag_value, contains_wildcard, ) from snakemake.utils import format, listfiles from snakemake.exceptions import RuleException, ProtectedOutputException, WorkflowError from snakemake.exceptions import ( UnexpectedOutputException, CreateCondaEnvironmentException, ) from snakemake.logging import logger from snakemake.common import DYNAMIC_FILL, lazy_property, get_uuid from snakemake.deployment import conda from snakemake import wrapper def format_files(job, io, dynamicio): for f in io: if f in dynamicio: yield "{} (dynamic)".format(f.format_dynamic()) elif is_flagged(f, "pipe"): yield "{} (pipe)".format(f) elif is_flagged(f, "checkpoint_target"): yield "" else: yield f def jobfiles(jobs, type): return chain(*map(attrgetter(type), jobs)) class AbstractJob: def is_group(self): raise NotImplementedError() def log_info(self, skip_dynamic=False): raise NotImplementedError() def log_error(self, msg=None, **kwargs): raise NotImplementedError() def remove_existing_output(self): raise NotImplementedError() def download_remote_input(self): raise NotImplementedError() def properties(self, omit_resources=["_cores", "_nodes"], **aux_properties): raise NotImplementedError() class Job(AbstractJob): HIGHEST_PRIORITY = sys.maxsize __slots__ = [ "rule", "dag", "wildcards_dict", "wildcards", "_format_wildcards", "input", "dependencies", "output", "_params", "_log", "_benchmark", "_resources", "_conda_env_file", "_conda_env", "shadow_dir", "_inputsize", "dynamic_output", "dynamic_input", "temp_output", "protected_output", "touch_output", "subworkflow_input", "_hash", "_attempt", "_group", "targetfile", ] def __init__( self, rule, dag, wildcards_dict=None, format_wildcards=None, targetfile=None ): self.rule = rule self.dag = dag # the targetfile that led to the job # it is important to record this, since we need it to submit the # job on a cluster. In contrast, an arbitrary targetfile could # lead to a different composition of wildcard values (in case of # ambiguity in matching). self.targetfile = targetfile self.wildcards_dict = wildcards_dict self.wildcards = Wildcards(fromdict=self.wildcards_dict) self._format_wildcards = ( self.wildcards if format_wildcards is None else Wildcards(fromdict=format_wildcards) ) self.input, input_mapping, self.dependencies = self.rule.expand_input( self.wildcards_dict ) self.output, output_mapping = self.rule.expand_output(self.wildcards_dict) # other properties are lazy to be able to use additional parameters and check already existing files self._params = None self._log = None self._benchmark = None self._resources = None self._conda_env_file = None self._conda_env = None self._group = None self.shadow_dir = None self._inputsize = None self.is_updated = False self._attempt = self.dag.workflow.attempt # TODO get rid of these self.dynamic_output, self.dynamic_input = set(), set() self.temp_output, self.protected_output = set(), set() self.touch_output = set() self.subworkflow_input = dict() for f in self.output: f_ = output_mapping[f] if f_ in self.rule.dynamic_output: self.dynamic_output.add(f) if f_ in self.rule.temp_output: self.temp_output.add(f) if f_ in self.rule.protected_output: self.protected_output.add(f) if f_ in self.rule.touch_output: self.touch_output.add(f) for f in self.input: f_ = input_mapping[f] if f_ in self.rule.dynamic_input: self.dynamic_input.add(f) if f_ in self.rule.subworkflow_input: self.subworkflow_input[f] = self.rule.subworkflow_input[f_] elif "subworkflow" in f.flags: sub = f.flags["subworkflow"] if f in self.subworkflow_input: other = self.subworkflow_input[f] if sub != other: raise WorkflowError( "The input file {} is ambiguously " "associated with two subworkflows {} " "and {}.".format(f, sub, other), rule=self.rule, ) self.subworkflow_input[f] = sub self._hash = self.rule.__hash__() for wildcard_value in self.wildcards_dict.values(): self._hash ^= wildcard_value.__hash__() def updated(self): job = Job( self.rule, self.dag, wildcards_dict=self.wildcards_dict, targetfile=self.targetfile, ) job.is_updated = True return job def is_valid(self): """Check if job is valid""" # these properties have to work in dry-run as well. Hence we check them here: self.rule.expand_benchmark(self.wildcards_dict) self.rule.expand_log(self.wildcards_dict) def outputs_older_than_script_or_notebook(self): """return output that's older than script, i.e. script has changed""" path = self.rule.script or self.rule.notebook if not path: return assert os.path.exists(path) # to make sure lstat works script_mtime = os.lstat(path).st_mtime for f in self.expanded_output: if f.exists: if not f.is_newer(script_mtime): yield f @property def threads(self): return self.resources._cores @property def params(self): if self._params is None: self._params = self.rule.expand_params( self.wildcards_dict, self.input, self.output, self.resources ) return self._params @property def log(self): if self._log is None: self._log = self.rule.expand_log(self.wildcards_dict) return self._log @property def benchmark(self): if self._benchmark is None: self._benchmark = self.rule.expand_benchmark(self.wildcards_dict) return self._benchmark @property def benchmark_repeats(self): if self.benchmark is not None: return get_flag_value(self.benchmark, "repeat") or 1 @property def group(self): if self._group is None: self._group = self.rule.expand_group(self.wildcards_dict) return self._group @group.setter def group(self, group): self._group = group @property def attempt(self): return self._attempt @attempt.setter def attempt(self, attempt): # reset resources self._resources = None self._attempt = attempt @property def resources(self): if self._resources is None: self._resources = self.rule.expand_resources( self.wildcards_dict, self.input, self.attempt ) return self._resources @property def conda_env_file(self): if self._conda_env_file is None: expanded_env = self.rule.expand_conda_env(self.wildcards_dict) if expanded_env is not None: scheme, _, path, *_ = urlparse(expanded_env) # Normalize 'file:///my/path.yml' to '/my/path.yml' if scheme == "file" or not scheme: self._conda_env_file = path else: self._conda_env_file = expanded_env return self._conda_env_file @property def conda_env(self): if self.conda_env_file: if self._conda_env is None: self._conda_env = self.dag.conda_envs.get( (self.conda_env_file, self.singularity_img_url) ) return self._conda_env return None @property def conda_env_path(self): return self.conda_env.path if self.conda_env else None def archive_conda_env(self): """Archive a conda environment into a custom local channel.""" if self.conda_env_file: return self.conda_env.create_archive() return None @property def needs_singularity(self): return self.singularity_img is not None @property def singularity_img_url(self): return self.rule.singularity_img @property def singularity_img(self): if self.singularity_img_url: return self.dag.singularity_imgs[self.singularity_img_url] return None @property def env_modules(self): return self.rule.env_modules @property def singularity_img_path(self): return self.singularity_img.path if self.singularity_img else None @property def is_shadow(self): return self.rule.shadow_depth is not None @property def priority(self): return self.dag.priority(self) @property def b64id(self): return base64.b64encode( (self.rule.name + "".join(self.output)).encode("utf-8") ).decode("utf-8") @property def inputsize(self): """ Return the size of the input files. Input files need to be present. """ if self._inputsize is None: self._inputsize = sum(f.size for f in self.input) return self._inputsize @property def message(self): """ Return the message for this job. """ try: return ( self.format_wildcards(self.rule.message) if self.rule.message else None ) except AttributeError as ex: raise RuleException(str(ex), rule=self.rule) except KeyError as ex: raise RuleException( "Unknown variable in message " "of shell command: {}".format(str(ex)), rule=self.rule, ) @property def shellcmd(self): """ Return the shell command. """ try: return ( self.format_wildcards(self.rule.shellcmd) if self.rule.shellcmd else None ) except AttributeError as ex: raise RuleException(str(ex), rule=self.rule) except KeyError as ex: raise RuleException( "Unknown variable when printing " "shell command: {}".format(str(ex)), rule=self.rule, ) @property def is_shell(self): return self.rule.shellcmd is not None @property def is_norun(self): return self.rule.norun @property def is_script(self): return self.rule.script is not None @property def is_notebook(self): return self.rule.notebook is not None @property def is_wrapper(self): return self.rule.wrapper is not None @property def is_cwl(self): return self.rule.cwl is not None @property def is_run(self): return not ( self.is_shell or self.is_norun or self.is_script or self.is_wrapper or self.is_cwl ) @property def expanded_output(self): """ Iterate over output files while dynamic output is expanded. """ for f, f_ in zip(self.output, self.rule.output): if f in self.dynamic_output: expansion = self.expand_dynamic(f_) if not expansion: yield f_ for f, _ in expansion: file_to_yield = IOFile(f, self.rule) file_to_yield.clone_flags(f_) yield file_to_yield else: yield f def shadowed_path(self, f): """ Get the shadowed path of IOFile f. """ if not self.shadow_dir: return f f_ = IOFile(os.path.join(self.shadow_dir, f), self.rule) f_.clone_flags(f) return f_ @property def dynamic_wildcards(self): """ Return all wildcard values determined from dynamic output. """ combinations = set() for f, f_ in zip(self.output, self.rule.output): if f in self.dynamic_output: for f, w in self.expand_dynamic(f_): combinations.add(tuple(w.items())) wildcards = defaultdict(list) for combination in combinations: for name, value in combination: wildcards[name].append(value) return wildcards @property def missing_input(self): """ Return missing input files. """ # omit file if it comes from a subworkflow return set( f for f in self.input if not f.exists and not f in self.subworkflow_input ) @property def existing_remote_input(self): files = set() for f in self.input: if f.is_remote: if f.exists_remote: files.add(f) return files @property def existing_remote_output(self): files = set() for f in self.remote_output: if f.exists_remote: files.add(f) return files @property def missing_remote_input(self): return self.remote_input - self.existing_remote_input @property def missing_remote_output(self): return self.remote_output - self.existing_remote_output @property def output_mintime(self): """ Return oldest output file. """ existing = [f.mtime for f in self.expanded_output if f.exists] if self.benchmark and self.benchmark.exists: existing.append(self.benchmark.mtime) if existing: return min(existing) return None @property def output_mintime_local(self): existing = [f.mtime_local for f in self.expanded_output if f.exists] if self.benchmark and self.benchmark.exists: existing.append(self.benchmark.mtime_local) if existing: return min(existing) return None @property def input_maxtime(self): """ Return newest input file. """ existing = [f.mtime for f in self.input if f.exists] if existing: return max(existing) return None def missing_output(self, requested=None): """ Return missing output files. """ files = set() if self.benchmark and (requested is None or self.benchmark in requested): if not self.benchmark.exists: files.add(self.benchmark) for f, f_ in zip(self.output, self.rule.output): if requested is None or f in requested: if f in self.dynamic_output: if not self.expand_dynamic(f_): files.add("{} (dynamic)".format(f_)) elif not f.exists: files.add(f) for f in self.log: if requested and f in requested and not f.exists: files.add(f) return files @property def local_input(self): for f in self.input: if not f.is_remote: yield f @property def unique_input(self): seen = set() for element in filterfalse(seen.__contains__, self.input): seen.add(element) yield element @property def local_output(self): for f in self.output: if not f.is_remote: yield f @property def remote_input(self): for f in self.input: if f.is_remote: yield f @property def remote_output(self): for f in self.output: if f.is_remote: yield f @property def remote_input_newer_than_local(self): files = set() for f in self.remote_input: if (f.exists_remote and f.exists_local) and (f.mtime > f.mtime_local): files.add(f) return files @property def remote_input_older_than_local(self): files = set() for f in self.remote_input: if (f.exists_remote and f.exists_local) and (f.mtime < f.mtime_local): files.add(f) return files @property def remote_output_newer_than_local(self): files = set() for f in self.remote_output: if (f.exists_remote and f.exists_local) and (f.mtime > f.mtime_local): files.add(f) return files @property def remote_output_older_than_local(self): files = set() for f in self.remote_output: if (f.exists_remote and f.exists_local) and (f.mtime < f.mtime_local): files.add(f) return files @property def files_to_download(self): toDownload = set() for f in self.input: if f.is_remote: if (not f.exists_local and f.exists_remote) and ( not self.rule.norun or f.remote_object.keep_local ): toDownload.add(f) toDownload = toDownload | self.remote_input_newer_than_local return toDownload @property def files_to_upload(self): return self.missing_remote_input & self.remote_input_older_than_local @property def existing_output(self): return filter(lambda f: f.exists, self.expanded_output) def check_protected_output(self): protected = list(filter(lambda f: f.protected, self.expanded_output)) if protected: raise ProtectedOutputException(self.rule, protected) def remove_existing_output(self): """Clean up both dynamic and regular output before rules actually run """ if self.dynamic_output: for f, _ in chain(*map(self.expand_dynamic, self.rule.dynamic_output)): os.remove(f) for f, f_ in zip(self.output, self.rule.output): try: # remove_non_empty_dir only applies to directories which aren't # flagged with directory(). f.remove(remove_non_empty_dir=False) except FileNotFoundError: # No file == no problem pass for f in self.log: f.remove(remove_non_empty_dir=False) def download_remote_input(self): for f in self.files_to_download: f.download_from_remote() def prepare(self): """ Prepare execution of job. This includes creation of directories and deletion of previously created dynamic files. Creates a shadow directory for the job if specified. """ self.check_protected_output() unexpected_output = self.dag.reason(self).missing_output.intersection( self.existing_output ) if unexpected_output: logger.warning( "Warning: the following output files of rule {} were not " "present when the DAG was created:\n{}".format( self.rule, unexpected_output ) ) self.remove_existing_output() for f, f_ in zip(self.output, self.rule.output): f.prepare() self.download_remote_input() for f in self.log: f.prepare() if self.benchmark: self.benchmark.prepare() if not self.is_shadow: return # Create shadow directory structure self.shadow_dir = tempfile.mkdtemp( dir=self.rule.workflow.persistence.shadow_path ) cwd = os.getcwd() if self.rule.shadow_depth == "minimal": # Re-create the directory structure in the shadow directory for (f, d) in set( [ (item, os.path.dirname(item)) for sublist in [self.input, self.output, self.log] if sublist is not None for item in sublist ] ): if d and not os.path.isabs(d): rel_path = os.path.relpath(d) # Only create subdirectories if not rel_path.split(os.path.sep)[0] == "..": os.makedirs( os.path.join(self.shadow_dir, rel_path), exist_ok=True ) else: raise RuleException( "The following file name references a parent directory relative to your workdir.\n" 'This isn\'t supported for shadow: "minimal". Consider using an absolute path instead.\n{}'.format( f ), rule=self.rule, ) # Symlink the input files for rel_path in set( [os.path.relpath(f) for f in self.input if not os.path.isabs(f)] ): link = os.path.join(self.shadow_dir, rel_path) original = os.path.relpath(rel_path, os.path.dirname(link)) os.symlink(original, link) # Shallow simply symlink everything in the working directory. elif self.rule.shadow_depth == "shallow": for source in os.listdir(cwd): link = os.path.join(self.shadow_dir, source) os.symlink(os.path.abspath(source), link) elif self.rule.shadow_depth == "full": snakemake_dir = os.path.join(cwd, ".snakemake") for dirpath, dirnames, filenames in os.walk(cwd): # Must exclude .snakemake and its children to avoid infinite # loop of symlinks. if os.path.commonprefix([snakemake_dir, dirpath]) == snakemake_dir: continue for dirname in dirnames: if dirname == ".snakemake": continue relative_source = os.path.relpath(os.path.join(dirpath, dirname)) shadow = os.path.join(self.shadow_dir, relative_source) os.mkdir(shadow) for filename in filenames: source = os.path.join(dirpath, filename) relative_source = os.path.relpath(source) link = os.path.join(self.shadow_dir, relative_source) os.symlink(source, link) def close_remote(self): for f in self.input + self.output: if f.is_remote: f.remote_object.close() def cleanup(self): """ Cleanup output files. """ to_remove = [f for f in self.expanded_output if f.exists] to_remove.extend([f for f in self.remote_input if f.exists_local]) to_remove.extend( [ f for f in self.remote_output if ( f.exists_remote if (f.is_remote and f.should_stay_on_remote) else f.exists_local ) ] ) if to_remove: logger.info( "Removing output files of failed job {}" " since they might be corrupted:\n{}".format(self, ", ".join(to_remove)) ) for f in to_remove: f.remove() self.rmdir_empty_remote_dirs() @property def empty_remote_dirs(self): for f in set(self.output) | set(self.input): if f.is_remote and not f.should_stay_on_remote: if os.path.exists(os.path.dirname(f)) and not len( os.listdir(os.path.dirname(f)) ): yield os.path.dirname(f) def rmdir_empty_remote_dirs(self): for d in self.empty_remote_dirs: try: os.removedirs(d) except: pass # it's ok if we can't remove the leaf def format_wildcards(self, string, **variables): """ Format a string with variables from the job. """ _variables = dict() _variables.update(self.rule.workflow.globals) _variables.update( dict( input=self.input, output=self.output, params=self.params, wildcards=self._format_wildcards, threads=self.threads, resources=self.resources, log=self.log, jobid=self.jobid, version=self.rule.version, name=self.name, rule=self.rule.name, rulename=self.rule.name, bench_iteration=None, ) ) _variables.update(variables) try: return format(string, **_variables) except NameError as ex: raise RuleException("NameError: " + str(ex), rule=self.rule) except IndexError as ex: raise RuleException("IndexError: " + str(ex), rule=self.rule) def properties(self, omit_resources=["_cores", "_nodes"], **aux_properties): resources = { name: res for name, res in self.resources.items() if name not in omit_resources } params = {name: value for name, value in self.params.items()} properties = { "type": "single", "rule": self.rule.name, "local": self.is_local, "input": self.input, "output": self.output, "wildcards": self.wildcards_dict, "params": params, "log": self.log, "threads": self.threads, "resources": resources, "jobid": self.dag.jobid(self), } properties.update(aux_properties) try: return json.dumps(properties) except TypeError: del properties["params"] return json.dumps(properties) @property def is_local(self): return self.dag.workflow.is_local(self.rule) def __repr__(self): return self.rule.name def __eq__(self, other): if other is None: return False return ( self.rule == other.rule and (self.wildcards_dict == other.wildcards_dict) and (self.input == other.input) ) def __lt__(self, other): return self.rule.__lt__(other.rule) def __gt__(self, other): return self.rule.__gt__(other.rule) def __hash__(self): return self._hash def expand_dynamic(self, pattern): """ Expand dynamic files. """ return list( listfiles(pattern, restriction=self.wildcards, omit_value=DYNAMIC_FILL) ) def is_group(self): return False def log_info(self, skip_dynamic=False, indent=False, printshellcmd=True): # skip dynamic jobs that will be "executed" only in dryrun mode if skip_dynamic and self.dag.dynamic(self): return priority = self.priority logger.job_info( jobid=self.dag.jobid(self), msg=self.message, name=self.rule.name, local=self.dag.workflow.is_local(self.rule), input=list(format_files(self, self.input, self.dynamic_input)), output=list(format_files(self, self.output, self.dynamic_output)), log=list(self.log), benchmark=self.benchmark, wildcards=self.wildcards_dict, reason=str(self.dag.reason(self)), resources=self.resources, priority="highest" if priority == Job.HIGHEST_PRIORITY else priority, threads=self.threads, indent=indent, is_checkpoint=self.rule.is_checkpoint, printshellcmd=printshellcmd, ) logger.shellcmd(self.shellcmd, indent=indent) if self.dynamic_output: logger.info( "Subsequent jobs will be added dynamically " "depending on the output of this job", indent=True, ) def log_error(self, msg=None, indent=False, **kwargs): logger.job_error( name=self.rule.name, jobid=self.dag.jobid(self), output=list(format_files(self, self.output, self.dynamic_output)), log=list(self.log), conda_env=self.conda_env.path if self.conda_env else None, aux=kwargs, indent=indent, shellcmd=self.shellcmd, ) if msg is not None: logger.error(msg) def register(self): self.dag.workflow.persistence.started(self) def get_wait_for_files(self): wait_for_files = [] wait_for_files.extend(self.local_input) wait_for_files.extend( f for f in self.remote_input if not f.should_stay_on_remote ) if self.shadow_dir: wait_for_files.append(self.shadow_dir) if self.dag.workflow.use_conda and self.conda_env: wait_for_files.append(self.conda_env_path) return wait_for_files @property def jobid(self): return self.dag.jobid(self) def postprocess( self, upload_remote=True, handle_log=True, handle_touch=True, handle_temp=True, error=False, ignore_missing_output=False, assume_shared_fs=True, latency_wait=None, ): if assume_shared_fs: if not error and handle_touch: self.dag.handle_touch(self) if handle_log: self.dag.handle_log(self) if not error: self.dag.check_and_touch_output( self, wait=latency_wait, ignore_missing_output=ignore_missing_output ) self.dag.unshadow_output(self, only_log=error) if not error: self.dag.handle_remote(self, upload=upload_remote) self.dag.handle_protected(self) self.close_remote() else: if not error: self.dag.check_and_touch_output( self, wait=latency_wait, no_touch=True, force_stay_on_remote=True ) if not error: try: self.dag.workflow.persistence.finished(self) except IOError as e: logger.warning( "Error recording metadata for finished job " "({}). Please ensure write permissions for the " "directory {}".format(e, self.dag.workflow.persistence.path) ) if handle_temp: # temp handling has to happen after calling finished(), # because we need to access temp output files to record # start and end times. self.dag.handle_temp(self) @property def name(self): return self.rule.name @property def priority(self): return self.dag.priority(self) @property def products(self): products = list(self.output) if self.benchmark: products.append(self.benchmark) products.extend(self.log) return products def get_targets(self): return self.targetfile or [self.rule.name] @property def is_branched(self): return self.rule.is_branched @property def rules(self): return [self.rule.name] @property def restart_times(self): return self.rule.restart_times @property def is_checkpoint(self): return self.rule.is_checkpoint def __len__(self): return 1 class GroupJob(AbstractJob): __slots__ = [ "groupid", "jobs", "_resources", "_input", "_output", "_log", "_inputsize", "_all_products", "_attempt", "toposorted", ] def __init__(self, id, jobs): self.groupid = id self.jobs = frozenset(jobs) self.toposorted = None self._resources = None self._input = None self._output = None self._log = None self._inputsize = None self._all_products = None self._attempt = self.dag.workflow.attempt @property def dag(self): return next(iter(self.jobs)).dag def merge(self, other): assert other.groupid == self.groupid self.jobs = self.jobs | other.jobs def finalize(self): from toposort import toposort if self.toposorted is None: dag = { job: {dep for dep in self.dag.dependencies[job] if dep in self.jobs} for job in self.jobs } self.toposorted = list(toposort(dag)) @property def all_products(self): if self._all_products is None: self._all_products = set(f for job in self.jobs for f in job.products) return self._all_products def __iter__(self): if self.toposorted is None: yield from self.jobs else: yield from chain.from_iterable(self.toposorted) def __repr__(self): return "JobGroup({},{})".format(self.groupid, repr(self.jobs)) def __contains__(self, job): return job in self.jobs def is_group(self): return True @property def is_checkpoint(self): return any(job.is_checkpoint for job in self.jobs) @property def is_updated(self): return any(job.is_updated for job in self.jobs) def log_info(self, skip_dynamic=False): logger.group_info(groupid=self.groupid) for job in sorted(self.jobs, key=lambda j: j.rule.name): job.log_info(skip_dynamic, indent=True) def log_error(self, msg=None, **kwargs): logger.group_error(groupid=self.groupid) for job in self.jobs: job.log_error(msg=msg, indent=True, **kwargs) def register(self): for job in self.jobs: job.register() def remove_existing_output(self): for job in self.jobs: job.remove_existing_output() def download_remote_input(self): for job in self.jobs: job.download_remote_input() def get_wait_for_files(self): local_input = [ f for job in self.jobs for f in job.local_input if f not in self.all_products ] remote_input = [ f for job in self.jobs for f in job.remote_input if f not in self.all_products ] wait_for_files = [] wait_for_files.extend(local_input) wait_for_files.extend(f for f in remote_input if not f.should_stay_on_remote) for job in self.jobs: if job.shadow_dir: wait_for_files.append(job.shadow_dir) if self.dag.workflow.use_conda and job.conda_env: wait_for_files.append(job.conda_env_path) return wait_for_files @property def resources(self): if self._resources is None: self._resources = defaultdict(int) pipe_group = any( [any([is_flagged(o, "pipe") for o in job.output]) for job in self.jobs] ) # iterate over siblings that can be executed in parallel for siblings in self.toposorted: sibling_resources = defaultdict(int) for job in siblings: try: job_resources = job.resources except FileNotFoundError: # Skip job if resource evaluation leads to a file not found error. # This will be caused by an inner job, which needs files created by the same group. # All we can do is to ignore such jobs for now. continue for res, value in job_resources.items(): if res != "_nodes": sibling_resources[res] += value for res, value in sibling_resources.items(): if res != "_nodes": if self.dag.workflow.run_local or pipe_group: # in case of local execution, this must be a # group of jobs that are connected with pipes # and have to run simultaneously self._resources[res] += value else: # take the maximum with previous values self._resources[res] = max( self._resources.get(res, 0), value ) return Resources(fromdict=self._resources) @property def input(self): if self._input is None: self._input = [ f for job in self.jobs for f in job.input if f not in self.all_products ] return self._input @property def output(self): all_input = set(f for job in self.jobs for f in job.input) if self._output is None: self._output = [ f for job in self.jobs for f in job.output if f not in all_input ] return self._output @property def log(self): if self._log is None: self._log = [f for job in self.jobs for f in job.log] return self._log @property def products(self): all_input = set(f for job in self.jobs for f in job.input) return [f for job in self.jobs for f in job.products if f not in all_input] def properties(self, omit_resources=["_cores", "_nodes"], **aux_properties): resources = { name: res for name, res in self.resources.items() if name not in omit_resources } properties = { "type": "group", "groupid": self.groupid, "local": self.is_local, "input": self.input, "output": self.output, "threads": self.threads, "resources": resources, "jobid": self.jobid, } properties.update(aux_properties) return json.dumps(properties) @property def jobid(self): return str(get_uuid(",".join(str(job.jobid) for job in self.jobs))) def cleanup(self): for job in self.jobs: job.cleanup() def postprocess(self, error=False, **kwargs): for job in self.jobs: job.postprocess(handle_temp=False, error=error, **kwargs) # Handle temp after per-job postprocess. # This is necessary because group jobs are not topologically sorted, # and we might otherwise delete a temp input file before it has been # postprocessed by the outputting job in the same group. if not error: for job in self.jobs: self.dag.handle_temp(job) # remove all pipe outputs since all jobs of this group are done and the # pipes are no longer needed for job in self.jobs: for f in job.output: if is_flagged(f, "pipe"): f.remove() @property def name(self): return str(self.groupid) def check_protected_output(self): for job in self.jobs: job.check_protected_output() @property def dynamic_input(self): return [ f for job in self.jobs for f in job.dynamic_input if f not in self.all_products ] @property def inputsize(self): if self._inputsize is None: self._inputsize = sum(f.size for f in self.input) return self._inputsize @property def priority(self): return max(self.dag.priority(job) for job in self.jobs) @property def is_local(self): return all(job.is_local for job in self.jobs) def format_wildcards(self, string, **variables): """ Format a string with variables from the job. """ _variables = dict() _variables.update(self.dag.workflow.globals) _variables.update( dict( input=self.input, output=self.output, threads=self.threads, jobid=self.jobid, name=self.name, rule="GROUP", rulename="GROUP", resources=self.resources, ) ) _variables.update(variables) try: return format(string, **_variables) except NameError as ex: raise WorkflowError( "NameError with group job {}: {}".format(self.jobid, str(ex)) ) except IndexError as ex: raise WorkflowError( "IndexError with group job {}: {}".format(self.jobid, str(ex)) ) @property def threads(self): return self.resources["_cores"] def get_targets(self): # jobs without output are targeted by rule name targets = [job.rule.name for job in self.jobs if not job.products] targets.extend(self.products) return targets @property def attempt(self): return self._attempt @attempt.setter def attempt(self, attempt): # reset resources self._resources = None self._attempt = attempt @property def is_branched(self): return any(job.is_branched for job in self.jobs) @property def needs_singularity(self): return any(job.needs_singularity for job in self.jobs) @property def rules(self): return [job.rule.name for job in self.jobs] @property def expanded_output(self): """Yields the entire expanded output of all jobs""" for job in self.jobs: yield from job.expanded_output @property def restart_times(self): return max(job.restart_times for job in self.jobs) def __len__(self): return len(self.jobs) def __hash__(self): return hash(self.jobs) def __eq__(self, other): if other.is_group(): return self.jobs == other.jobs else: return False class Reason: __slots__ = [ "_updated_input", "_updated_input_run", "_missing_output", "_incomplete_output", "forced", "noio", "nooutput", "derived", ] def __init__(self): self._updated_input = None self._updated_input_run = None self._missing_output = None self._incomplete_output = None self.forced = False self.noio = False self.nooutput = False self.derived = True @lazy_property def updated_input(self): return set() @lazy_property def updated_input_run(self): return set() @lazy_property def missing_output(self): return set() @lazy_property def incomplete_output(self): return set() def __str__(self): s = list() if self.forced: s.append("Forced execution") else: if self.noio: s.append( "Rules with neither input nor " "output files are always executed." ) elif self.nooutput: s.append( "Rules with a run or shell declaration but no output " "are always executed." ) else: if self._missing_output: s.append( "Missing output files: {}".format( ", ".join(self.missing_output) ) ) if self._incomplete_output: s.append( "Incomplete output files: {}".format( ", ".join(self.incomplete_output) ) ) if self._updated_input: updated_input = self.updated_input - self.updated_input_run s.append("Updated input files: {}".format(", ".join(updated_input))) if self._updated_input_run: s.append( "Input files updated by another job: {}".format( ", ".join(self.updated_input_run) ) ) s = "; ".join(s) return s def __bool__(self): return bool( self.updated_input or self.missing_output or self.forced or self.updated_input_run or self.noio or self.nooutput ) snakemake-5.10.0/snakemake/jobscript.sh000066400000000000000000000000611361131222100200130ustar00rootroot00000000000000#!/bin/sh # properties = {properties} {exec_job} snakemake-5.10.0/snakemake/logging.py000066400000000000000000000376251361131222100175000ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import logging as _logging import platform import time import datetime import sys import os import json import threading import tempfile from functools import partial import inspect from snakemake.common import DYNAMIC_FILL from snakemake.common import Mode class ColorizingStreamHandler(_logging.StreamHandler): BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = range(8) RESET_SEQ = "\033[0m" COLOR_SEQ = "\033[%dm" BOLD_SEQ = "\033[1m" colors = { "WARNING": YELLOW, "INFO": GREEN, "DEBUG": BLUE, "CRITICAL": RED, "ERROR": RED, } def __init__( self, nocolor=False, stream=sys.stderr, use_threads=False, mode=Mode.default ): super().__init__(stream=stream) self._output_lock = threading.Lock() self.nocolor = nocolor or not self.can_color_tty(mode) def can_color_tty(self, mode): if "TERM" in os.environ and os.environ["TERM"] == "dumb": return False if mode == Mode.subprocess: return True return self.is_tty and not platform.system() == "Windows" @property def is_tty(self): isatty = getattr(self.stream, "isatty", None) return isatty and isatty() def emit(self, record): with self._output_lock: try: self.format(record) # add the message to the record self.stream.write(self.decorate(record)) self.stream.write(getattr(self, "terminator", "\n")) self.flush() except BrokenPipeError as e: raise e except (KeyboardInterrupt, SystemExit): # ignore any exceptions in these cases as any relevant messages have been printed before pass except Exception as e: self.handleError(record) def decorate(self, record): message = record.message message = [message] if not self.nocolor and record.levelname in self.colors: message.insert(0, self.COLOR_SEQ % (30 + self.colors[record.levelname])) message.append(self.RESET_SEQ) return "".join(message) class SlackLogger: def __init__(self): from slacker import Slacker self.token = os.getenv("SLACK_TOKEN") if not self.token: print( "The use of slack logging requires the user to set a user specific slack legacy token to the SLACK_TOKEN environment variable. Set this variable by 'export SLACK_TOKEN=your_token'. To generate your token please visit https://api.slack.com/custom-integrations/legacy-tokens." ) exit(-1) self.slack = Slacker(self.token) # Check for success try: auth = self.slack.auth.test().body except Exception: print( "Slack connection failed. Please compare your provided slack token exported in the SLACK_TOKEN environment variable with your online token at https://api.slack.com/custom-integrations/legacy-tokens. A different token can be set up by 'export SLACK_TOKEN=your_token'." ) exit(-1) self.own_id = auth["user_id"] self.error_occured = False def log_handler(self, msg): if msg["level"] == "error" and not self.error_occured: self.slack.chat.post_message( self.own_id, text="At least one error occured.", username="snakemake" ) self.error_occured = True if msg["level"] == "progress" and msg["done"] == msg["total"]: # workflow finished self.slack.chat.post_message( self.own_id, text="Workflow complete.", username="snakemake" ) class Logger: def __init__(self): self.logger = _logging.getLogger(__name__) self.log_handler = [self.text_handler] self.stream_handler = None self.printshellcmds = False self.printreason = False self.debug_dag = False self.quiet = False self.logfile = None self.last_msg_was_job_info = False self.mode = Mode.default self.show_failed_logs = False self.logfile_handler = None def setup_logfile(self): if self.mode == Mode.default: os.makedirs(os.path.join(".snakemake", "log"), exist_ok=True) self.logfile = os.path.abspath( os.path.join( ".snakemake", "log", datetime.datetime.now().isoformat().replace(":", "") + ".snakemake.log", ) ) self.logfile_handler = _logging.FileHandler(self.logfile) self.logger.addHandler(self.logfile_handler) def cleanup(self): if self.mode == Mode.default and self.logfile_handler is not None: self.logger.removeHandler(self.logfile_handler) self.logfile_handler.close() self.log_handler = [self.text_handler] def get_logfile(self): if self.logfile is not None: self.logfile_handler.flush() return self.logfile def remove_logfile(self): if self.mode == Mode.default: self.logfile_handler.close() os.remove(self.logfile) def handler(self, msg): for handler in self.log_handler: handler(msg) def set_stream_handler(self, stream_handler): if self.stream_handler is not None: self.logger.removeHandler(self.stream_handler) self.stream_handler = stream_handler self.logger.addHandler(stream_handler) def set_level(self, level): self.logger.setLevel(level) def logfile_hint(self): if self.mode == Mode.default: logfile = self.get_logfile() self.info("Complete log: {}".format(logfile)) def location(self, msg): callerframerecord = inspect.stack()[1] frame = callerframerecord[0] info = inspect.getframeinfo(frame) self.debug( "{}: {info.filename}, {info.function}, {info.lineno}".format(msg, info=info) ) def info(self, msg, indent=False): self.handler(dict(level="info", msg=msg, indent=indent)) def warning(self, msg): self.handler(dict(level="warning", msg=msg)) def debug(self, msg): self.handler(dict(level="debug", msg=msg)) def error(self, msg): self.handler(dict(level="error", msg=msg)) def progress(self, done=None, total=None): self.handler(dict(level="progress", done=done, total=total)) def resources_info(self, msg): self.handler(dict(level="resources_info", msg=msg)) def run_info(self, msg): self.handler(dict(level="run_info", msg=msg)) def group_info(self, **msg): msg["level"] = "group_info" self.handler(msg) def job_info(self, **msg): msg["level"] = "job_info" self.handler(msg) def job_error(self, **msg): msg["level"] = "job_error" self.handler(msg) def group_error(self, **msg): msg["level"] = "group_error" self.handler(msg) def dag_debug(self, msg): self.handler(dict(level="dag_debug", **msg)) def shellcmd(self, msg, indent=False): if msg is not None: msg = dict(level="shellcmd", msg=msg) msg["indent"] = indent self.handler(msg) def job_finished(self, **msg): msg["level"] = "job_finished" self.handler(msg) def rule_info(self, **msg): msg["level"] = "rule_info" self.handler(msg) def d3dag(self, **msg): msg["level"] = "d3dag" self.handler(msg) def text_handler(self, msg): """The default snakemake log handler. Prints the output to the console. Args: msg (dict): the log message dictionary """ def job_info(msg): def format_item(item, omit=None, valueformat=str): value = msg[item] if value != omit: return " {}: {}".format(item, valueformat(value)) yield "{}{} {}:".format( "local" if msg["local"] else "", "checkpoint" if msg["is_checkpoint"] else "rule", msg["name"], ) for item in ["input", "output", "log"]: fmt = format_item(item, omit=[], valueformat=", ".join) if fmt != None: yield fmt singleitems = ["jobid", "benchmark"] if self.printreason: singleitems.append("reason") for item in singleitems: fmt = format_item(item, omit=None) if fmt != None: yield fmt wildcards = format_wildcards(msg["wildcards"]) if wildcards: yield " wildcards: " + wildcards for item, omit in zip("priority threads".split(), [0, 1]): fmt = format_item(item, omit=omit) if fmt != None: yield fmt resources = format_resources(msg["resources"]) if resources: yield " resources: " + resources def indent(item): if msg.get("indent"): return " " + item else: return item def timestamp(): self.logger.info(indent("[{}]".format(time.asctime()))) level = msg["level"] if level == "job_info" and not self.quiet: if not self.last_msg_was_job_info: self.logger.info("") timestamp() if msg["msg"] is not None: self.logger.info(indent("Job {}: {}".format(msg["jobid"], msg["msg"]))) if self.printreason: self.logger.info(indent("Reason: {}".format(msg["reason"]))) else: self.logger.info("\n".join(map(indent, job_info(msg)))) if msg["is_checkpoint"]: self.logger.warning( indent("Downstream jobs will be updated " "after completion.") ) self.logger.info("") self.last_msg_was_job_info = True elif level == "group_info" and not self.quiet: timestamp() if not self.last_msg_was_job_info: self.logger.info("") self.logger.info( "group job {} (jobs in lexicogr. order):".format(msg["groupid"]) ) elif level == "job_error": timestamp() self.logger.error(indent("Error in rule {}:".format(msg["name"]))) self.logger.error(indent(" jobid: {}".format(msg["jobid"]))) if msg["output"]: self.logger.error( indent(" output: {}".format(", ".join(msg["output"]))) ) if msg["log"]: self.logger.error( indent( " log: {} (check log file(s) for error message)".format( ", ".join(msg["log"]) ) ) ) if msg["conda_env"]: self.logger.error(indent(" conda-env: {}".format(msg["conda_env"]))) if msg["shellcmd"]: self.logger.error( indent( " shell:\n {}\n (one of the commands exited with non-zero exit code; note that snakemake uses bash strict mode!)".format( msg["shellcmd"] ) ) ) for item in msg["aux"].items(): self.logger.error(indent(" {}: {}".format(*item))) if self.show_failed_logs and msg["log"]: for f in msg["log"]: try: self.logger.error("Logfile {}:\n{}".format(f, open(f).read())) except FileNotFoundError: self.logger.error("Logfile {} not found.".format(f)) self.logger.error("") elif level == "group_error": timestamp() self.logger.error("Error in group job {}:".format(msg["groupid"])) else: if level == "info" and not self.quiet: self.logger.warning(msg["msg"]) if level == "warning": self.logger.warning(msg["msg"]) elif level == "error": self.logger.error(msg["msg"]) elif level == "debug": self.logger.debug(msg["msg"]) elif level == "resources_info" and not self.quiet: self.logger.warning(msg["msg"]) elif level == "run_info": self.logger.warning(msg["msg"]) elif level == "progress" and not self.quiet: done = msg["done"] total = msg["total"] p = done / total percent_fmt = ("{:.2%}" if p < 0.01 else "{:.0%}").format(p) self.logger.info( "{} of {} steps ({}) done".format(done, total, percent_fmt) ) elif level == "shellcmd": if self.printshellcmds: self.logger.warning(indent(msg["msg"])) elif level == "job_finished" and not self.quiet: timestamp() self.logger.info("Finished job {}.".format(msg["jobid"])) pass elif level == "rule_info": self.logger.info(msg["name"]) if msg["docstring"]: self.logger.info(" " + msg["docstring"]) elif level == "d3dag": print(json.dumps({"nodes": msg["nodes"], "links": msg["edges"]})) elif level == "dag_debug": if self.debug_dag: job = msg["job"] self.logger.warning( "{status} job {name}\n\twildcards: {wc}".format( status=msg["status"], name=job.rule.name, wc=format_wildcards(job.wildcards), ) ) self.last_msg_was_job_info = False def format_dict(dict_like, omit_keys=[], omit_values=[]): from snakemake.io import Namedlist if isinstance(dict_like, Namedlist): items = dict_like.items() elif isinstance(dict_like, dict): items = dict_like.items() else: raise ValueError( "bug: format_dict applied to something neither a dict nor a Namedlist" ) return ", ".join( "{}={}".format(name, str(value)) for name, value in items if name not in omit_keys and value not in omit_values ) format_resources = partial(format_dict, omit_keys={"_cores", "_nodes"}) format_wildcards = partial(format_dict, omit_values={DYNAMIC_FILL}) def format_resource_names(resources, omit_resources="_cores _nodes".split()): return ", ".join(name for name in resources if name not in omit_resources) logger = Logger() def setup_logger( handler=[], quiet=False, printshellcmds=False, printreason=False, debug_dag=False, nocolor=False, stdout=False, debug=False, use_threads=False, mode=Mode.default, show_failed_logs=False, ): logger.log_handler.extend(handler) # console output only if no custom logger was specified stream_handler = ColorizingStreamHandler( nocolor=nocolor, stream=sys.stdout if stdout else sys.stderr, use_threads=use_threads, mode=mode, ) logger.set_stream_handler(stream_handler) logger.set_level(_logging.DEBUG if debug else _logging.INFO) logger.quiet = quiet logger.printshellcmds = printshellcmds logger.printreason = printreason logger.debug_dag = debug_dag logger.mode = mode logger.show_failed_logs = show_failed_logs snakemake-5.10.0/snakemake/notebook.py000066400000000000000000000074411361131222100176630ustar00rootroot00000000000000import os from snakemake.exceptions import WorkflowError from snakemake.shell import shell from snakemake.script import get_source, ScriptBase, PythonScript, RScript class JupyterNotebook(ScriptBase): def write_script(self, preamble, fd): import nbformat nb = nbformat.reads(self.source, as_version=4) # nbformat.NO_CONVERT preamble_cell = nbformat.v4.new_code_cell(preamble) nb["cells"].insert(0, preamble_cell) fd.write(nbformat.writes(nb).encode()) def execute_script(self, fname): fname_out = self.log.get("notebook", None) if fname_out is None: output_parameter = "" else: fname_out = os.path.join(os.getcwd(), fname_out) output_parameter = "--output {fname_out:q}" cmd_tmp = "jupyter-nbconvert --execute {output_parameter} --to notebook --ExecutePreprocessor.timeout=-1 {{fname:q}}".format( output_parameter=output_parameter ) self._execute_cmd(cmd_tmp, fname_out=fname_out, fname=fname) class PythonJupyterNotebook(JupyterNotebook): def get_preamble(self): preamble_addendum = "import os; os.chdir('{cwd}');".format(cwd=os.getcwd()) return PythonScript.generate_preamble( self.path, self.source, self.basedir, self.input, self.output, self.params, self.wildcards, self.threads, self.resources, self.log, self.config, self.rulename, self.conda_env, self.singularity_img, self.singularity_args, self.env_modules, self.bench_record, self.jobid, self.bench_iteration, self.cleanup_scripts, self.shadow_dir, preamble_addendum=preamble_addendum, ) class RJupyterNotebook(JupyterNotebook): def get_preamble(self): preamble_addendum = "setwd('{cwd}');".format(cwd=os.getcwd()) return RScript.generate_preamble( self.path, self.source, self.basedir, self.input, self.output, self.params, self.wildcards, self.threads, self.resources, self.log, self.config, self.rulename, self.conda_env, self.singularity_img, self.singularity_args, self.env_modules, self.bench_record, self.jobid, self.bench_iteration, self.cleanup_scripts, self.shadow_dir, preamble_addendum=preamble_addendum, ) def notebook( path, basedir, input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ): """ Load a script from the given basedir + path and execute it. """ path, source, language = get_source(path, basedir) ExecClass = { "jupyter_python": PythonJupyterNotebook, "jupyter_r": RJupyterNotebook, }.get(language, None) if ExecClass is None: raise ValueError("Unsupported notebook: Expecting Jupyter Notebook (.ipynb).") executor = ExecClass( path, source, basedir, input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ) executor.evaluate() snakemake-5.10.0/snakemake/output_index.py000066400000000000000000000030761361131222100205720ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2018-2019, Johannes Köster" __email__ = "johannes.koester@protonmail.com" __license__ = "MIT" from itertools import chain from snakemake.io import _IOFile class OutputIndex: def __init__(self, rules): import datrie def prefixes(rule): return (str(o.constant_prefix()) for o in rule.products) def reverse_suffixes(rule): return (str(o.constant_suffix())[::-1] for o in rule.products) def calc_trie(subpatterns): t = datrie.Trie("".join(p for rule in rules for p in subpatterns(rule))) empty = list() for rule in rules: has_empty = False for p in subpatterns(rule): if not p: has_empty = True if p not in t: t[p] = [rule] else: t[p].append(rule) if has_empty: empty.append(rule) return t, empty self.prefix_trie, self.empty_prefix = calc_trie(prefixes) self.suffix_trie, self.empty_suffix = calc_trie(reverse_suffixes) def match(self, targetfile): def match_pattern(pattern, trie, empty): return chain(chain.from_iterable(trie.iter_prefix_values(pattern)), empty) f = str(targetfile) hits = set(match_pattern(f, self.prefix_trie, self.empty_prefix)) return hits.intersection( match_pattern(f[::-1], self.suffix_trie, self.empty_suffix) ) snakemake-5.10.0/snakemake/parser.py000066400000000000000000000534771361131222100173510ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import tokenize import textwrap import os from urllib.error import HTTPError, URLError, ContentTooShortError import urllib.request from io import TextIOWrapper from snakemake.exceptions import WorkflowError dd = textwrap.dedent INDENT = "\t" def is_newline(token, newline_tokens=set((tokenize.NEWLINE, tokenize.NL))): return token.type in newline_tokens def is_indent(token): return token.type == tokenize.INDENT def is_dedent(token): return token.type == tokenize.DEDENT def is_op(token): return token.type == tokenize.OP def is_greater(token): return is_op(token) and token.string == ">" def is_comma(token): return is_op(token) and token.string == "," def is_name(token): return token.type == tokenize.NAME def is_colon(token): return is_op(token) and token.string == ":" def is_comment(token): return token.type == tokenize.COMMENT def is_string(token): return token.type == tokenize.STRING def is_eof(token): return token.type == tokenize.ENDMARKER def lineno(token): return token.start[0] class StopAutomaton(Exception): def __init__(self, token): self.token = token class TokenAutomaton: subautomata = dict() def __init__(self, snakefile, base_indent=0, dedent=0, root=True): self.root = root self.snakefile = snakefile self.state = None self.base_indent = base_indent self.line = 0 self.indent = 0 self.was_indented = False self.lasttoken = None self._dedent = dedent @property def dedent(self): return self._dedent @property def effective_indent(self): return self.base_indent + self.indent - self.dedent def indentation(self, token): if is_indent(token) or is_dedent(token): self.indent = token.end[1] - self.base_indent self.was_indented |= self.indent > 0 def consume(self): for token in self.snakefile: self.indentation(token) try: for t, orig in self.state(token): if self.lasttoken == "\n" and not t.isspace(): yield INDENT * self.effective_indent, orig yield t, orig self.lasttoken = t except tokenize.TokenError as e: self.error( str(e).split(",")[0].strip("()''"), token ) # TODO the inferred line number seems to be wrong sometimes def error(self, msg, token): raise SyntaxError(msg, (self.snakefile.path, lineno(token), None, None)) def subautomaton(self, automaton, *args, **kwargs): return self.subautomata[automaton]( self.snakefile, *args, base_indent=self.base_indent + self.indent, dedent=self.dedent, root=False, **kwargs ) class KeywordState(TokenAutomaton): prefix = "" def __init__(self, snakefile, base_indent=0, dedent=0, root=True): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.line = 0 self.state = self.colon @property def keyword(self): return self.__class__.__name__.lower()[len(self.prefix) :] def end(self): yield ")" def decorate_end(self, token): for t in self.end(): yield t, token def colon(self, token): if is_colon(token): self.state = self.block for t in self.start(): yield t, token else: self.error("Colon expected after keyword {}.".format(self.keyword), token) def is_block_end(self, token): return (self.line and self.indent <= 0) or is_eof(token) def block(self, token): if self.lasttoken == "\n" and is_comment(token): # ignore lines containing only comments self.line -= 1 if self.is_block_end(token): for t, token_ in self.decorate_end(token): yield t, token_ yield "\n", token raise StopAutomaton(token) if is_newline(token): self.line += 1 yield token.string, token elif not (is_indent(token) or is_dedent(token)): if is_comment(token): yield token.string, token else: for t in self.block_content(token): yield t def yield_indent(self, token): return token.string, token def block_content(self, token): yield token.string, token class GlobalKeywordState(KeywordState): def start(self): yield "workflow.{keyword}(".format(keyword=self.keyword) class DecoratorKeywordState(KeywordState): decorator = None args = list() def start(self): yield "@workflow.{}".format(self.decorator) yield "\n" yield "def __{}({}):".format(self.decorator, ", ".join(self.args)) def end(self): yield "" class RuleKeywordState(KeywordState): def __init__(self, snakefile, base_indent=0, dedent=0, root=True, rulename=None): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.rulename = rulename def start(self): yield "\n" yield "@workflow.{keyword}(".format(keyword=self.keyword) class SubworkflowKeywordState(KeywordState): prefix = "Subworkflow" def start(self): yield ", {keyword}=".format(keyword=self.keyword) def end(self): # no end needed return list() # Global keyword states class Include(GlobalKeywordState): pass class Workdir(GlobalKeywordState): pass class Configfile(GlobalKeywordState): pass class Report(GlobalKeywordState): pass class Ruleorder(GlobalKeywordState): def block_content(self, token): if is_greater(token): yield ",", token elif is_name(token): yield repr(token.string), token else: self.error( "Expected a descending order of rule names, " "e.g. rule1 > rule2 > rule3 ...", token, ) class GlobalWildcardConstraints(GlobalKeywordState): @property def keyword(self): return "global_wildcard_constraints" class GlobalSingularity(GlobalKeywordState): @property def keyword(self): return "global_singularity" # subworkflows class SubworkflowSnakefile(SubworkflowKeywordState): pass class SubworkflowWorkdir(SubworkflowKeywordState): pass class SubworkflowConfigfile(SubworkflowKeywordState): pass class Subworkflow(GlobalKeywordState): subautomata = dict( snakefile=SubworkflowSnakefile, workdir=SubworkflowWorkdir, configfile=SubworkflowConfigfile, ) def __init__(self, snakefile, base_indent=0, dedent=0, root=True): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.state = self.name self.has_snakefile = False self.has_workdir = False self.has_name = False self.primary_token = None def end(self): if not (self.has_snakefile or self.has_workdir): self.error( "A subworkflow needs either a path to a Snakefile or to a workdir.", self.primary_token, ) yield ")" def name(self, token): if is_name(token): yield "workflow.subworkflow({name!r}".format(name=token.string), token self.has_name = True elif is_colon(token) and self.has_name: self.primary_token = token self.state = self.block else: self.error("Expected name after subworkflow keyword.", token) def block_content(self, token): if is_name(token): try: if token.string == "snakefile": self.has_snakefile = True if token.string == "workdir": self.has_workdir = True for t in self.subautomaton(token.string).consume(): yield t except KeyError: self.error( "Unexpected keyword {} in " "subworkflow definition".format(token.string), token, ) except StopAutomaton as e: self.indentation(e.token) for t in self.block(e.token): yield t elif is_comment(token): yield "\n", token yield token.string, token elif is_string(token): # ignore docstring pass else: self.error( "Expecting subworkflow keyword, comment or docstrings " "inside a subworkflow definition.", token, ) class Localrules(GlobalKeywordState): def block_content(self, token): if is_comma(token): yield ",", token elif is_name(token): yield repr(token.string), token else: self.error( "Expected a comma separated list of rules that shall " "not be executed by the cluster command.", token, ) # Rule keyword states class Input(RuleKeywordState): pass class Output(RuleKeywordState): pass class Params(RuleKeywordState): pass class Threads(RuleKeywordState): pass class Shadow(RuleKeywordState): pass class Resources(RuleKeywordState): pass class Priority(RuleKeywordState): pass class Version(RuleKeywordState): pass class Log(RuleKeywordState): pass class Message(RuleKeywordState): pass class Benchmark(RuleKeywordState): pass class Conda(RuleKeywordState): pass class Singularity(RuleKeywordState): pass class EnvModules(RuleKeywordState): pass class Group(RuleKeywordState): pass class WildcardConstraints(RuleKeywordState): @property def keyword(self): return "wildcard_constraints" class Run(RuleKeywordState): def __init__(self, snakefile, rulename, base_indent=0, dedent=0, root=True): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.rulename = rulename self.content = 0 def start(self): yield "@workflow.run" yield "\n" yield ( "def __rule_{rulename}(input, output, params, wildcards, threads, " "resources, log, version, rule, conda_env, singularity_img, " "singularity_args, use_singularity, env_modules, bench_record, jobid, " "is_shell, bench_iteration, cleanup_scripts, shadow_dir):".format( rulename=self.rulename if self.rulename is not None else self.snakefile.rulecount ) ) def end(self): yield "" def block_content(self, token): self.content += 1 yield token.string, token def is_block_end(self, token): return (self.content and self.line and self.indent <= 0) or is_eof(token) class AbstractCmd(Run): overwrite_cmd = None start_func = None end_func = None def __init__(self, snakefile, rulename, base_indent=0, dedent=0, root=True): super().__init__( snakefile, rulename, base_indent=base_indent, dedent=dedent, root=root ) self.cmd = list() self.token = None if self.overwrite_cmd is not None: self.block_content = self.overwrite_block_content def is_block_end(self, token): return (self.line and self.indent <= 0) or is_eof(token) def start(self): if self.start_func is not None: yield self.start_func yield "(" def args(self): yield from [] def end(self): # the end is detected. So we can savely reset the indent to zero here self.indent = 0 yield "\n" yield ")" yield "\n" for t in super().start(): yield t yield "\n" yield INDENT * (self.effective_indent + 1) yield self.end_func yield "(" yield "\n".join(self.cmd) yield from self.args() yield "\n" yield ")" for t in super().end(): yield t def decorate_end(self, token): if self.token is None: # no block after shell keyword self.error( "Command must be given as string after the shell keyword.", token ) for t in self.end(): yield t, self.token def block_content(self, token): self.token = token self.cmd.append(token.string) yield token.string, token def overwrite_block_content(self, token): if self.token is None: self.token = token cmd = repr(self.overwrite_cmd) self.cmd.append(cmd) yield cmd, token class Shell(AbstractCmd): start_func = "@workflow.shellcmd" end_func = "shell" def args(self): yield ", bench_record=bench_record, bench_iteration=bench_iteration" class Script(AbstractCmd): start_func = "@workflow.script" end_func = "script" def args(self): # basedir yield ", {!r}".format(os.path.abspath(os.path.dirname(self.snakefile.path))) # other args yield ( ", input, output, params, wildcards, threads, resources, log, " "config, rule, conda_env, singularity_img, singularity_args, env_modules, " "bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir" ) class Notebook(Script): start_func = "@workflow.notebook" end_func = "notebook" class Wrapper(Script): start_func = "@workflow.wrapper" end_func = "wrapper" def args(self): yield ( ", input, output, params, wildcards, threads, resources, log, " "config, rule, conda_env, singularity_img, singularity_args, env_modules, " "bench_record, workflow.wrapper_prefix, jobid, bench_iteration, " "cleanup_scripts, shadow_dir" ) class CWL(Script): start_func = "@workflow.cwl" end_func = "cwl" def args(self): # basedir yield ", {!r}".format(os.path.abspath(os.path.dirname(self.snakefile.path))) # other args yield ( ", input, output, params, wildcards, threads, resources, log, " "config, rule, use_singularity, bench_record, jobid" ) class Rule(GlobalKeywordState): subautomata = dict( input=Input, output=Output, params=Params, threads=Threads, resources=Resources, priority=Priority, version=Version, log=Log, message=Message, benchmark=Benchmark, conda=Conda, singularity=Singularity, envmodules=EnvModules, wildcard_constraints=WildcardConstraints, shadow=Shadow, group=Group, run=Run, shell=Shell, script=Script, notebook=Notebook, wrapper=Wrapper, cwl=CWL, ) def __init__(self, snakefile, base_indent=0, dedent=0, root=True): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.state = self.name self.lineno = None self.rulename = None self.run = False self.snakefile.rulecount += 1 def start(self, aux=""): yield ( "@workflow.rule(name={rulename!r}, lineno={lineno}, " "snakefile={snakefile!r}{aux})".format( rulename=self.rulename, lineno=self.lineno, snakefile=self.snakefile.path, aux=aux, ) ) def end(self): if not self.run: yield "@workflow.norun()" yield "\n" for t in self.subautomaton("run", rulename=self.rulename).start(): yield t # the end is detected. # So we can savely reset the indent to zero here self.indent = 0 yield "\n" yield INDENT * (self.effective_indent + 1) yield "pass" def name(self, token): if is_name(token): self.rulename = token.string elif is_colon(token): self.lineno = self.snakefile.lines + 1 self.state = self.block for t in self.start(): yield t, token else: self.error("Expected name or colon after rule keyword.", token) def block_content(self, token): if is_name(token): try: if ( token.string == "run" or token.string == "shell" or token.string == "script" or token.string == "wrapper" or token.string == "cwl" ): if self.run: raise self.error( "Multiple run or shell keywords in rule {}.".format( self.rulename ), token, ) self.run = True elif self.run: raise self.error( "No rule keywords allowed after " "run/shell/script/wrapper/cwl in " "rule {}.".format(self.rulename), token, ) for t in self.subautomaton( token.string, rulename=self.rulename ).consume(): yield t except KeyError: self.error( "Unexpected keyword {} in rule definition".format(token.string), token, ) except StopAutomaton as e: self.indentation(e.token) for t in self.block(e.token): yield t elif is_comment(token): yield "\n", token yield token.string, token elif is_string(token): yield "\n", token yield "@workflow.docstring({})".format(token.string), token else: self.error( "Expecting rule keyword, comment or docstrings " "inside a rule definition.", token, ) @property def dedent(self): return self.indent class Checkpoint(Rule): def start(self): yield from super().start(aux=", checkpoint=True") class OnSuccess(DecoratorKeywordState): decorator = "onsuccess" args = ["log"] class OnError(DecoratorKeywordState): decorator = "onerror" args = ["log"] class OnStart(DecoratorKeywordState): decorator = "onstart" args = ["log"] class Python(TokenAutomaton): subautomata = dict( include=Include, workdir=Workdir, configfile=Configfile, report=Report, ruleorder=Ruleorder, rule=Rule, checkpoint=Checkpoint, subworkflow=Subworkflow, localrules=Localrules, onsuccess=OnSuccess, onerror=OnError, onstart=OnStart, wildcard_constraints=GlobalWildcardConstraints, singularity=GlobalSingularity, ) def __init__(self, snakefile, base_indent=0, dedent=0, root=True): super().__init__(snakefile, base_indent=base_indent, dedent=dedent, root=root) self.state = self.python def python(self, token): if not (is_indent(token) or is_dedent(token)): if self.lasttoken is None or self.lasttoken.isspace(): try: for t in self.subautomaton(token.string).consume(): yield t except KeyError: yield token.string, token except StopAutomaton as e: self.indentation(e.token) for t in self.python(e.token): yield t else: yield token.string, token class Snakefile: def __init__(self, path, rulecount=0): self.path = path try: self.file = open(self.path, encoding="utf-8") except (FileNotFoundError, OSError) as e: try: self.file = TextIOWrapper( urllib.request.urlopen(self.path), encoding="utf-8" ) except (HTTPError, URLError, ContentTooShortError, ValueError): raise WorkflowError("Failed to open {}.".format(path)) self.tokens = tokenize.generate_tokens(self.file.readline) self.rulecount = rulecount self.lines = 0 def __next__(self): return next(self.tokens) def __iter__(self): return self def __enter__(self): return self def __exit__(self, *args): self.file.close() def format_tokens(tokens): t_ = None for t in tokens: if t_ and not t.isspace() and not t_.isspace(): yield " " yield t t_ = t def parse(path, overwrite_shellcmd=None, rulecount=0): Shell.overwrite_cmd = overwrite_shellcmd with Snakefile(path, rulecount=rulecount) as snakefile: automaton = Python(snakefile) linemap = dict() compilation = list() for t, orig_token in automaton.consume(): l = lineno(orig_token) linemap.update( dict( (i, l) for i in range( snakefile.lines + 1, snakefile.lines + t.count("\n") + 1 ) ) ) snakefile.lines += t.count("\n") compilation.append(t) compilation = "".join(format_tokens(compilation)) if linemap: last = max(linemap) linemap[last + 1] = linemap[last] return compilation, linemap, snakefile.rulecount snakemake-5.10.0/snakemake/persistence.py000077500000000000000000000315061361131222100203710ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import shutil import signal import marshal import pickle import json import time from base64 import urlsafe_b64encode, b64encode from functools import lru_cache, partial from itertools import filterfalse, count from snakemake.logging import logger from snakemake.jobs import jobfiles from snakemake.utils import listfiles class Persistence: def __init__( self, nolock=False, dag=None, conda_prefix=None, singularity_prefix=None, shadow_prefix=None, warn_only=False, ): self.path = os.path.abspath(".snakemake") if not os.path.exists(self.path): os.mkdir(self.path) self._lockdir = os.path.join(self.path, "locks") if not os.path.exists(self._lockdir): os.mkdir(self._lockdir) self.dag = dag self._lockfile = dict() self._metadata_path = os.path.join(self.path, "metadata") self.conda_env_archive_path = os.path.join(self.path, "conda-archive") self.benchmark_path = os.path.join(self.path, "benchmarks") if conda_prefix is None: self.conda_env_path = os.path.join(self.path, "conda") else: self.conda_env_path = os.path.abspath(os.path.expanduser(conda_prefix)) if singularity_prefix is None: self.singularity_img_path = os.path.join(self.path, "singularity") else: self.singularity_img_path = os.path.abspath( os.path.expanduser(singularity_prefix) ) if shadow_prefix is None: self.shadow_path = os.path.join(self.path, "shadow") else: self.shadow_path = os.path.join(shadow_prefix, "shadow") for d in ( self._metadata_path, self.shadow_path, self.conda_env_archive_path, self.conda_env_path, self.singularity_img_path, ): os.makedirs(d, exist_ok=True) if nolock: self.lock = self.noop self.unlock = self.noop if warn_only: self.lock = self.lock_warn_only self.unlock = self.noop self._read_record = self._read_record_cached @property def files(self): if self._files is None: self._files = set(self.dag.output_files) return self._files @property def locked(self): inputfiles = set(self.all_inputfiles()) outputfiles = set(self.all_outputfiles()) if os.path.exists(self._lockdir): for lockfile in self._locks("input"): with open(lockfile) as lock: for f in lock: f = f.strip() if f in outputfiles: return True for lockfile in self._locks("output"): with open(lockfile) as lock: for f in lock: f = f.strip() if f in outputfiles or f in inputfiles: return True return False def lock_warn_only(self): if self.locked: logger.info( "Error: Directory cannot be locked. This usually " "means that another Snakemake instance is running on this directory. " "Another possibility is that a previous run exited unexpectedly." ) def lock(self): if self.locked: raise IOError("Another snakemake process " "has locked this directory.") self._lock(self.all_inputfiles(), "input") self._lock(self.all_outputfiles(), "output") def unlock(self, *args): logger.debug("unlocking") for lockfile in self._lockfile.values(): try: logger.debug("removing lock") os.remove(lockfile) except OSError as e: if e.errno != 2: # missing file raise e logger.debug("removed all locks") def cleanup_locks(self): shutil.rmtree(self._lockdir) def cleanup_metadata(self, path): self._delete_record(self._metadata_path, path) def cleanup_shadow(self): if os.path.exists(self.shadow_path): shutil.rmtree(self.shadow_path) os.mkdir(self.shadow_path) def cleanup_conda(self): # cleanup envs in_use = set(env.hash[:8] for env in self.dag.conda_envs.values()) for d in os.listdir(self.conda_env_path): if len(d) >= 8 and d[:8] not in in_use: if os.path.isdir(os.path.join(self.conda_env_path, d)): shutil.rmtree(os.path.join(self.conda_env_path, d)) else: os.remove(os.path.join(self.conda_env_path, d)) # cleanup env archives in_use = set(env.content_hash for env in self.dag.conda_envs.values()) for d in os.listdir(self.conda_env_archive_path): if d not in in_use: shutil.rmtree(os.path.join(self.conda_env_archive_path, d)) def started(self, job, external_jobid=None): for f in job.output: self._record( self._metadata_path, {"incomplete": True, "external_jobid": external_jobid}, f, ) def finished(self, job): version = str(job.rule.version) if job.rule.version is not None else None code = self._code(job.rule) input = self._input(job) log = self._log(job) params = self._params(job) shellcmd = job.shellcmd conda_env = self._conda_env(job) fallback_time = time.time() for f in job.expanded_output: rec_path = self._record_path(self._metadata_path, f) starttime = os.path.getmtime(rec_path) if os.path.exists(rec_path) else None endtime = f.mtime if os.path.exists(f) else fallback_time self._record( self._metadata_path, { "version": version, "code": code, "rule": job.rule.name, "input": input, "log": log, "params": params, "shellcmd": shellcmd, "incomplete": False, "starttime": starttime, "endtime": endtime, "job_hash": hash(job), "conda_env": conda_env, "singularity_img_url": job.singularity_img_url, }, f, ) def cleanup(self, job): for f in job.expanded_output: self._delete_record(self._metadata_path, f) def incomplete(self, job): def marked_incomplete(f): return self._read_record(self._metadata_path, f).get("incomplete", False) return any(map(lambda f: f.exists and marked_incomplete(f), job.output)) def external_jobids(self, job): return list( set( self._read_record(self._metadata_path, f).get("external_jobid", None) for f in job.output ) ) def metadata(self, path): return self._read_record(self._metadata_path, path) def version(self, path): return self.metadata(path).get("version") def rule(self, path): return self.metadata(path).get("rule") def input(self, path): return self.metadata(path).get("input") def log(self, path): return self.metadata(path).get("log") def shellcmd(self, path): return self.metadata(path).get("shellcmd") def params(self, path): return self.metadata(path).get("params") def code(self, path): return self.metadata(path).get("code") def version_changed(self, job, file=None): """Yields output files with changed versions of bool if file given.""" return _bool_or_gen(self._version_changed, job, file=file) def code_changed(self, job, file=None): """Yields output files with changed code of bool if file given.""" return _bool_or_gen(self._code_changed, job, file=file) def input_changed(self, job, file=None): """Yields output files with changed input of bool if file given.""" return _bool_or_gen(self._input_changed, job, file=file) def params_changed(self, job, file=None): """Yields output files with changed params of bool if file given.""" return _bool_or_gen(self._params_changed, job, file=file) def _version_changed(self, job, file=None): assert file is not None return self.version(file) != job.rule.version def _code_changed(self, job, file=None): assert file is not None return self.code(file) != self._code(job.rule) def _input_changed(self, job, file=None): assert file is not None return self.input(file) != self._input(job) def _params_changed(self, job, file=None): assert file is not None return self.params(file) != self._params(job) def noop(self, *args): pass def _b64id(self, s): return urlsafe_b64encode(str(s).encode()).decode() @lru_cache() def _code(self, rule): code = rule.run_func.__code__ return b64encode(pickle_code(code)).decode() @lru_cache() def _conda_env(self, job): if job.conda_env: return b64encode(job.conda_env.content).decode() @lru_cache() def _input(self, job): return sorted(job.input) @lru_cache() def _log(self, job): return sorted(job.log) @lru_cache() def _params(self, job): return sorted(map(repr, job.params)) @lru_cache() def _output(self, job): return sorted(job.output) def _record(self, subject, json_value, id): recpath = self._record_path(subject, id) os.makedirs(os.path.dirname(recpath), exist_ok=True) with open(recpath, "w") as f: json.dump(json_value, f) def _delete_record(self, subject, id): try: recpath = self._record_path(subject, id) os.remove(recpath) recdirs = os.path.relpath(os.path.dirname(recpath), start=subject) if recdirs != ".": os.removedirs(recdirs) except OSError as e: if e.errno != 2: # not missing raise e @lru_cache() def _read_record_cached(self, subject, id): return self._read_record_uncached(subject, id) def _read_record_uncached(self, subject, id): if not self._exists_record(subject, id): return dict() with open(self._record_path(subject, id), "r") as f: return json.load(f) def _exists_record(self, subject, id): return os.path.exists(self._record_path(subject, id)) def _locks(self, type): return ( f for f, _ in listfiles( os.path.join(self._lockdir, "{{n,[0-9]+}}.{}.lock".format(type)) ) if not os.path.isdir(f) ) def _lock(self, files, type): for i in count(0): lockfile = os.path.join(self._lockdir, "{}.{}.lock".format(i, type)) if not os.path.exists(lockfile): self._lockfile[type] = lockfile with open(lockfile, "w") as lock: print(*files, sep="\n", file=lock) return def _record_path(self, subject, id): max_len = ( os.pathconf(subject, "PC_NAME_MAX") if os.name == "posix" else 255 ) # maximum NTFS and FAT32 filename length if max_len == 0: max_len = 255 b64id = self._b64id(id) # split into chunks of proper length b64id = [b64id[i : i + max_len - 1] for i in range(0, len(b64id), max_len - 1)] # prepend dirs with @ (does not occur in b64) to avoid conflict with b64-named files in the same dir b64id = ["@" + s for s in b64id[:-1]] + [b64id[-1]] path = os.path.join(subject, *b64id) return path def all_outputfiles(self): # we only look at output files that will be updated return jobfiles(self.dag.needrun_jobs, "output") def all_inputfiles(self): # we consider all input files, also of not running jobs return jobfiles(self.dag.jobs, "input") def deactivate_cache(self): self._read_record_cached.cache_clear() self._read_record = self._read_record_uncached def _bool_or_gen(func, job, file=None): if file is None: return (f for f in job.expanded_output if func(job, file=f)) else: return func(job, file=file) def pickle_code(code): consts = [ (pickle_code(const) if type(const) == type(code) else const) for const in code.co_consts ] return pickle.dumps((code.co_code, code.co_varnames, consts, code.co_names)) snakemake-5.10.0/snakemake/remote/000077500000000000000000000000001361131222100167565ustar00rootroot00000000000000snakemake-5.10.0/snakemake/remote/AzureStorage.py000066400000000000000000000315201361131222100217440ustar00rootroot00000000000000__author__ = "Sebastian Kurscheid" __copyright__ = "Copyright 2019, Sebastian Kurscheid" __email__ = "sebastian.kurscheid@anu.edu.au" __license__ = "MIT" # built-ins import os import re import math import functools import concurrent.futures # snakemake specific from snakemake.common import lazy_property # module specific from snakemake.exceptions import WorkflowError, AzureFileException from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider # service provider support try: from azure.storage.common.cloudstorageaccount import ( CloudStorageAccount as AzureStorageAccount, ) except ImportError as e: raise WorkflowError( "The Python 3 packages 'azure-storage' and 'azure-storage-common' " "need to be installed to use Azure Storage remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): supports_default = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self._as = AzureStorageHelper(*args, **kwargs) def remote_interface(self): return self._as @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "ab://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["ab://"] class RemoteObject(AbstractRemoteObject): def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) if provider: self._as = provider.remote_interface() else: self._as = AzureStorageHelper(*args, **kwargs) # === Implementations of abstract class members === def exists(self): if self._matched_as_path: return self._as.exists_in_container(self.container_name, self.blob_name) else: raise AzureFileException( "The file cannot be parsed as an Azure Blob path in form 'container/blob': %s" % self.local_file() ) def mtime(self): if self.exists(): t = self._as.blob_last_modified(self.container_name, self.blob_name) return t else: raise AzureFileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def size(self): if self.exists(): return self._as.blob_size(self.container_name, self.blob_name) else: return self._iofile.size_local def download(self): if self.exists(): os.makedirs(os.path.dirname(self.local_file()), exist_ok=True) self._as.download_from_azure_storage( self.container_name, self.blob_name, destination_path=self.local_file() ) os.sync() return self.local_file() return None def upload(self): self._as.upload_to_azure_storage( container_name=self.container_name, blob_name=self.blob_name, file_path=self.local_file(), ) @property def list(self): return self._as.list_blobs(self.container_name) # # === Related methods === @property def _matched_as_path(self): return re.search( "(?P[^/]*)/(?P.*)", self.local_file() ) def as_create_stub(self): if self._matched_as_path: if not self.exists: self._as.download_from_azure_storage( self.container_name, self.blob_name, self.file, create_stub_only=True, ) else: raise AzureFileException( "The file to be downloaded cannot be parsed as an Azure Storage path in form 'container/blob': %s" % self.local_file() ) @property def container_name(self): if len(self._matched_as_path.groups()) == 2: return self._matched_as_path.group("container_name") return None @property def name(self): return self.blob_name @property def blob_name(self): if len(self._matched_as_path.groups()) == 2: return self._matched_as_path.group("blob_name") # Actual Azure specific functions, adapted from S3.py class AzureStorageHelper(object): def __init__(self, *args, **kwargs): if "stay_on_remote" in kwargs: del kwargs["stay_on_remote"] self.azure = AzureStorageAccount(**kwargs).create_block_blob_service() def container_exists(self, container_name): try: self.azure.exists(container_name=container_name) return True except: return False def upload_to_azure_storage( self, container_name, file_path, blob_name=None, use_relative_path_for_blob_name=True, relative_start_dir=None, extra_args=None, ): """ Upload a file to Azure Storage This function uploads a file to an Azure Storage Container as a blob. Args: container_name: the name of the Azure container to use file_path: The path to the file to upload. blob_name: The name to set for the blob on Azure. If not specified, this will default to the name of the file. Returns: The blob_name of the file on Azure if written, None otherwise """ file_path = os.path.realpath(os.path.expanduser(file_path)) assert container_name, "container_name must be specified" assert os.path.exists(file_path), ( "The file path specified does not exist: %s" % file_path ) assert os.path.isfile(file_path), ( "The file path specified does not appear to be a file: %s" % file_path ) if not self.azure.exists(container_name): self.azure.create_container(container_name=container_name) if not blob_name: if use_relative_path_for_blob_name: if relative_start_dir: path_blob_name = os.path.relpath(file_path, relative_start_dir) else: path_blob_name = os.path.relpath(file_path) else: path_blob_name = os.path.basename(file_path) blob_name = path_blob_name b = self.azure try: b.create_blob_from_path( container_name, file_path=file_path, blob_name=blob_name ) return b.get_blob_properties(container_name, blob_name=blob_name).name except: raise WorkflowError("Error in creating blob. %s" % e.msg) # return None def download_from_azure_storage( self, container_name, blob_name, destination_path=None, expandBlobNameIntoDirs=True, make_dest_dirs=True, create_stub_only=False, ): """ Download a file from Azure Storage This function downloads an object from a specified Azure Storage container. Args: container_name: the name of the Azure Storage container to use (container name only) destination_path: If specified, the file will be saved to this path, otherwise cwd. expandBlobNameIntoDirs: Since Azure blob names can include slashes, if this is True (defult) then Azure blob names with slashes are expanded into directories on the receiving end. If it is False, the blob name is passed to os.path.basename() to get the substring following the last slash. make_dest_dirs: If this is True (default) and the destination path includes directories that do not exist, they will be created. Returns: The destination path of the downloaded file on the receiving end, or None if the destination_path could not be downloaded """ assert container_name, "container_name must be specified" assert blob_name, "blob_name must be specified" if destination_path: destination_path = os.path.realpath(os.path.expanduser(destination_path)) else: if expandBlobNameIntoDirs: destination_path = os.path.join(os.getcwd(), blob_name) else: destination_path = os.path.join( os.getcwd(), os.path.basename(blob_name) ) # if the destination path does not exist if make_dest_dirs: os.makedirs(os.path.dirname(destination_path), exist_ok=True) b = self.azure try: if not create_stub_only: b.get_blob_to_path( container_name=container_name, blob_name=blob_name, file_path=destination_path, ) else: # just create an empty file with the right timestamps with open(destination_path, "wb") as fp: os.utime( fp.name, (k.last_modified.timestamp(), k.last_modified.timestamp()), ) return destination_path except: return None def delete_from_container(self, container_name, blob_name): """ Delete a file from Azure Storage container This function deletes an object from a specified Azure Storage container. Args: container_name: the name of the Azure Storage container to use (container name only, not endpoint) blob_name: the name of the blob to delete from the container Returns: nothing """ assert container_name, "container_name must be specified" assert blob_name, "blob_name must be specified" b = self.azure b.delete_blob(container_name, blob_name) def exists_in_container(self, container_name, blob_name): """ Returns whether the blob exists in the container Args: container_name: the name of the Azure Storage container (container name only, not endpoint) blob_name: the blob_name of the object to delete from the container Returns: True | False """ assert container_name, "container_name must be specified" assert blob_name, "blob_name must be specified" try: return self.azure.exists(container_name, blob_name) except: return None def blob_size(self, container_name, blob_name): """ Returns the size of a blob Args: container_name: the name of the Azure Storage container (container name only, not endpoint) blob_name: the blob_name of the object to delete from the container Returns: Size in kb """ assert container_name, "container_name must be specified" assert blob_name, "blob_name must be specified" try: b = self.azure.get_blob_properties(container_name, blob_name) return b.properties.content_length // 1024 except: print("blob or container do not exist") return None def blob_last_modified(self, container_name, blob_name): """ Returns a timestamp of a blob Args: container_name: the name of the Azure Storage container (container name only, not endpoint) blob_name: the blob_name of the object to delete from the container Returns: timestamp """ assert container_name, "container_name must be specified" assert blob_name, "blob_name must be specified" try: b = self.azure.get_blob_properties(container_name, blob_name) return b.properties.last_modified.timestamp() except: print("blob or container do not exist") return None def list_blobs(self, container_name): """ Returns a list of blobs from the container Args: container_name: the name of the Azure Storage container (container name only, not endpoint) Returns: list of blobs """ assert container_name, "container_name must be specified" try: b = self.azure.list_blobs(container_name) return [o.name for o in b] except: print("Did you provide a valid container_name?") return None snakemake-5.10.0/snakemake/remote/EGA.py000066400000000000000000000162241361131222100177310ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2018-2019, Johannes Köster" __email__ = "johannes.koester@tu-dortmund.de" __license__ = "MIT" import os import json import time import uuid from collections import namedtuple import hashlib import requests from requests.auth import HTTPBasicAuth from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError from snakemake.common import lazy_property EGAFileInfo = namedtuple("EGAFileInfo", ["size", "status", "id", "checksum"]) EGAFile = namedtuple("EGAFile", ["dataset", "path"]) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, retry=5, **kwargs ): super().__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self.retry = retry self._token = None self._expires = None self._file_cache = dict() def _login(self): if self._expires is not None and self._expires > time.time(): # token is still valid return # token will expire in 10 minutes # (we stop using it 10 seconds earlier to be sure) self._expires = time.time() + 10 * 60 * 60 - 10 data = { "grant_type": "password", "client_id": self._client_id(), "scope": "openid", "client_secret": self._client_secret(), "username": self._username(), "password": self._password(), } for i in range(3): try: r = requests.post( "https://ega.ebi.ac.uk:8443/ega-openid-connect-server/token", headers={"Content-Type": "application/x-www-form-urlencoded"}, data=data, ) except requests.exceptions.ConnectionError as e: time.sleep(5) if i == 2: raise WorkflowError("Error contacting EGA.", e) if r.status_code != 200: raise WorkflowError("Login to EGA failed with:\n{}".format(r.text)) r = r.json() # store session token try: self._token = r["access_token"] except KeyError: raise WorkflowError("Login to EGA failed:\n{}".format(r)) def _expire_token(self): self._expires = None @property def token(self): self._login() return self._token def api_request( self, url_suffix, url_prefix="https://ega.ebi.ac.uk:8051/elixir/", json=True, post=False, **params ): """Make an API request. Args: url_suffix (str): Part of REST API URL right of https://ega.ebi.ac.uk:8051/elixir/ params (dict): Parameters to pass, except session """ url = url_prefix + url_suffix headers = ( {"Accept": "application/json"} if json else {"Accept": "application/octet-stream"} ) headers["Authorization"] = "Bearer {}".format(self.token) for i in range(3): try: if post: r = requests.post( url, stream=not json, data=params, params={"session": self.token}, headers=headers, ) else: params = dict(params) params["session"] = self.token r = requests.get( url, stream=not json, params=params, headers=headers ) except requests.exceptions.ConnectionError as e: time.sleep(5) if i == 2: raise WorkflowError("Error contacting EGA.", e) if r.status_code != 200: raise WorkflowError( "Access to EGA API endpoint {} failed " "with:\n{}".format(url, r.text) ) if json: msg = r.json() return msg else: return r def get_files(self, dataset): if dataset not in self._file_cache: files = self.api_request( "data/metadata/datasets/{dataset}/files".format(dataset=dataset) ) self._file_cache[dataset] = { os.path.basename(f["fileName"])[:-4]: EGAFileInfo( int(f["fileSize"]), f["fileStatus"], f["fileId"], f["checksum"] ) for f in files } return self._file_cache[dataset] @property def default_protocol(self): return "ega://" @property def available_protocols(self): return ["ega://"] @classmethod def _username(cls): return self._credentials("EGA_USERNAME") @classmethod def _password(cls): return self._credentials("EGA_PASSWORD") @classmethod def _client_id(cls): return self._credentials("EGA_CLIENT_ID") @classmethod def _client_secret(cls): return self._credentials("EGA_CLIENT_SECRET") @classmethod def _credentials(cls, name): try: return os.environ[name] except KeyError: raise WorkflowError( "$EGA_USERNAME, $EGA_PASSWORD, $EGA_CLIENT_ID, " "$EGA_CLIENT_SECRET must be given " "as environment variables." ) class RemoteObject(AbstractRemoteObject): # === Implementations of abstract class members === def _stats(self): return self.provider.get_files(self.parts.dataset)[self.parts.path] def exists(self): return self.parts.path in self.provider.get_files(self.parts.dataset) def size(self): return self._stats().size def mtime(self): # There is no mtime info provided by EGA # Hence, the files are always considered to be "ancient". return 0 def download(self): stats = self._stats() r = self.provider.api_request( "data/files/{}?destinationFormat=plain".format(stats.id), json=False ) local_md5 = hashlib.md5() # download file in chunks and calculate md5 on the fly os.makedirs(os.path.dirname(self.local_file()), exist_ok=True) with open(self.local_file(), "wb") as f: for chunk in r.iter_content(chunk_size=1024 * 1024 * 10): local_md5.update(chunk) f.write(chunk) local_md5 = local_md5.hexdigest() if local_md5 != stats.checksum: raise WorkflowError( "File checksums do not match for: {}".format(self.remote_file()) ) @lazy_property def parts(self): parts = self.local_file().split("/") if parts[0] != "ega": raise WorkflowError( "Invalid EGA remote file name. Must be 'ega//'" ) _, dataset, path = self.local_file().split("/", 2) return EGAFile(dataset, path) snakemake-5.10.0/snakemake/remote/FTP.py000066400000000000000000000215161361131222100177660ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import os import re import ftplib import collections from itertools import chain from contextlib import contextmanager # module-specific from snakemake.remote import AbstractRemoteProvider, DomainObject from snakemake.exceptions import FTPFileException, WorkflowError try: # third-party modules import ftputil import ftputil.session except ImportError as e: raise WorkflowError( "The Python 3 package 'ftputil' " + "must be installed to use SFTP remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): supports_default = True allows_directories = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, immediate_close=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self.immediate_close = immediate_close @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "ftp://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["ftp://", "ftps://"] def remote( self, value, *args, encrypt_data_channel=None, immediate_close=None, **kwargs ): if isinstance(value, str): values = [value] elif isinstance(value, collections.abc.Iterable): values = value else: raise TypeError( "Invalid type ({}) passed to remote: {}".format(type(value), value) ) for i, file in enumerate(values): match = re.match("^(ftps?)://.+", file) if match: (protocol,) = match.groups() if protocol == "ftps" and encrypt_data_channel: raise SyntaxError( "encrypt_data_channel=False cannot be used with a ftps:// url" ) if protocol == "ftp" and encrypt_data_channel not in [None, False]: raise SyntaxError( "encrypt_data_channel=Trie cannot be used with a ftp:// url" ) else: if encrypt_data_channel: values[i] = "ftps://" + file else: values[i] = "ftp://" + file should_close = immediate_close if immediate_close else self.immediate_close values = [ super(RemoteProvider, self).remote( value, *args, encrypt_data_channel=encrypt_data_channel, immediate_close=should_close, **kwargs ) for value in values ] if len(values) == 1: return values[0] else: return values class RemoteObject(DomainObject): """ This is a class to interact with an FTP server. """ def __init__( self, *args, keep_local=False, provider=None, encrypt_data_channel=False, immediate_close=False, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) self.encrypt_data_channel = encrypt_data_channel self.immediate_close = immediate_close def close(self): if ( hasattr(self, "conn") and isinstance(self.conn, ftputil.FTPHost) and not self.immediate_close ): try: self.conn.keep_alive() self.conn.close() except: pass # === Implementations of abstract class members === @contextmanager # makes this a context manager. after 'yield' is __exit__() def ftpc(self): if ( not hasattr(self, "conn") or (hasattr(self, "conn") and not isinstance(self.conn, ftputil.FTPHost)) ) or self.immediate_close: # if args have been provided to remote(), use them over those given to RemoteProvider() args_to_use = self.provider.args if len(self.args): args_to_use = self.args # use kwargs passed in to remote() to override those given to the RemoteProvider() # default to the host and port given as part of the file, falling back to one specified # as a kwarg to remote() or the RemoteProvider (overriding the latter with the former if both) kwargs_to_use = {} kwargs_to_use["host"] = self.host kwargs_to_use["username"] = None kwargs_to_use["password"] = None kwargs_to_use["port"] = int(self.port) if self.port else 21 kwargs_to_use["encrypt_data_channel"] = self.encrypt_data_channel for k, v in self.provider.kwargs.items(): kwargs_to_use[k] = v for k, v in self.kwargs.items(): kwargs_to_use[k] = v ftp_base_class = ( ftplib.FTP_TLS if kwargs_to_use["encrypt_data_channel"] else ftplib.FTP ) ftp_session_factory = ftputil.session.session_factory( base_class=ftp_base_class, port=kwargs_to_use["port"], encrypt_data_channel=kwargs_to_use["encrypt_data_channel"], debug_level=None, ) conn = ftputil.FTPHost( kwargs_to_use["host"], kwargs_to_use["username"], kwargs_to_use["password"], session_factory=ftp_session_factory, ) if self.immediate_close: yield conn else: self.conn = conn yield self.conn elif not self.immediate_close: yield self.conn # after returning from the context manager, close the connection if the scope is local if self.immediate_close: try: conn.keep_alive() conn.close() except: pass def exists(self): if self._matched_address: with self.ftpc() as ftpc: return ftpc.path.exists(self.remote_path) return False else: raise FTPFileException( "The file cannot be parsed as an FTP path in form 'host:port/abs/path/to/file': %s" % self.local_file() ) def mtime(self): if self.exists(): with self.ftpc() as ftpc: try: # requires write access ftpc.synchronize_times() except: pass return ftpc.path.getmtime(self.remote_path) else: raise FTPFileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def size(self): if self.exists(): with self.ftpc() as ftpc: return ftpc.path.getsize(self.remote_path) else: return self._iofile.size_local def download(self, make_dest_dirs=True): with self.ftpc() as ftpc: if self.exists(): # if the destination path does not exist if make_dest_dirs: os.makedirs(os.path.dirname(self.local_path), exist_ok=True) try: # requires write access ftpc.synchronize_times() except: pass ftpc.download(source=self.remote_path, target=self.local_path) os.sync() # ensure flush to disk else: raise FTPFileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def upload(self): with self.ftpc() as ftpc: ftpc.synchronize_times() ftpc.upload(source=self.local_path, target=self.remote_path) @property def list(self): file_list = [] first_wildcard = self._iofile.constant_prefix() dirname = first_wildcard.replace(self.path_prefix, "") with self.ftpc() as ftpc: file_list = [ (os.path.join(dirpath, f) if dirpath != "." else f) for dirpath, dirnames, filenames in ftpc.walk(dirname) for f in chain(filenames, dirnames) ] file_list = [ file_path[1:] if file_path[0] == "/" else file_path for file_path in file_list ] return file_list snakemake-5.10.0/snakemake/remote/GS.py000066400000000000000000000106171361131222100176460ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2017-2019, Johannes Köster" __email__ = "johannes.koester@tu-dortmund.de" __license__ = "MIT" import os import re from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError from snakemake.common import lazy_property try: import google.cloud from google.cloud import storage except ImportError as e: raise WorkflowError( "The Python 3 package 'google-cloud-sdk' " "needs to be installed to use GS remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): supports_default = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self.client = storage.Client(*args, **kwargs) def remote_interface(self): return self.client @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "gs://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["gs://"] class RemoteObject(AbstractRemoteObject): def __init__( self, *args, keep_local=False, provider=None, user_project=None, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) if provider: self.client = provider.remote_interface() else: self.client = storage.Client(*args, **kwargs) # keep user_project available for when bucket is initialized self._user_project = user_project self._key = None self._bucket_name = None self._bucket = None self._blob = None # === Implementations of abstract class members === def exists(self): return self.blob.exists() def mtime(self): if self.exists(): self.update_blob() t = self.blob.updated return t.timestamp() else: raise WorkflowError( "The file does not seem to exist remotely: %s" % self.local_file() ) def size(self): if self.exists(): self.update_blob() return self.blob.size // 1024 else: return self._iofile.size_local def download(self): if self.exists(): os.makedirs(os.path.dirname(self.local_file()), exist_ok=True) self.blob.download_to_filename(self.local_file()) os.sync() return self.local_file() return None def upload(self): try: if not self.bucket.exists(): self.bucket.create() self.update_blob() self.blob.upload_from_filename(self.local_file()) except google.cloud.exceptions.Forbidden as e: raise WorkflowError( e, "When running locally, make sure that you are authenticated " "via gcloud (see Snakemake documentation). When running in a " "kubernetes cluster, make sure that storage-rw is added to " "--scopes (see Snakemake documentation).", ) @property def name(self): return self.key @property def list(self): return [k.name for k in self.bucket.list_blobs()] # ========= Helpers =============== def update_blob(self): self._blob = self.bucket.get_blob(self.key) @lazy_property def bucket(self): return self.client.bucket(self.bucket_name, user_project=self._user_project) @lazy_property def blob(self): return self.bucket.blob(self.key) @lazy_property def bucket_name(self): return self.parse().group("bucket") @lazy_property def key(self): return self.parse().group("key") def parse(self): m = re.search("(?P[^/]*)/(?P.*)", self.local_file()) if len(m.groups()) != 2: raise WorkflowError( "GS remote file {} does not have the form " "/.".format(self.local_file()) ) return m snakemake-5.10.0/snakemake/remote/HTTP.py000066400000000000000000000230631361131222100201130ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import os import re import collections import shutil import email.utils from contextlib import contextmanager # module-specific from snakemake.remote import AbstractRemoteProvider, DomainObject from snakemake.exceptions import HTTPFileException, WorkflowError from snakemake.logging import logger try: # third-party modules import requests except ImportError as e: raise WorkflowError( "The Python 3 package 'requests' " + "must be installed to use HTTP(S) remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "https://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["http://", "https://"] def remote(self, value, *args, insecure=None, **kwargs): if isinstance(value, str): values = [value] elif isinstance(value, collections.abc.Iterable): values = value else: raise TypeError( "Invalid type ({}) passed to remote: {}".format(type(value), value) ) for i, file in enumerate(values): match = re.match("^(https?)://.+", file) if match: (protocol,) = match.groups() if protocol == "https" and insecure: raise SyntaxError( "insecure=True cannot be used with a https:// url" ) if protocol == "http" and insecure not in [None, False]: raise SyntaxError( "insecure=False cannot be used with a http:// url" ) else: if insecure: values[i] = "http://" + file else: values[i] = "https://" + file return super(RemoteProvider, self).remote(values, *args, **kwargs) class RemoteObject(DomainObject): """ This is a class to interact with an HTTP server. """ def __init__( self, *args, keep_local=False, provider=None, additional_request_string="", allow_redirects=True, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, allow_redirects=allow_redirects, **kwargs ) self.additional_request_string = additional_request_string # === Implementations of abstract class members === @contextmanager # makes this a context manager. after 'yield' is __exit__() def httpr(self, verb="GET", stream=False): # if args have been provided to remote(), use them over those given to RemoteProvider() args_to_use = self.provider.args if len(self.args): args_to_use = self.args # use kwargs passed in to remote() to override those given to the RemoteProvider() # default to the host and port given as part of the file, falling back to one specified # as a kwarg to remote() or the RemoteProvider (overriding the latter with the former if both) kwargs_to_use = {} kwargs_to_use["username"] = None kwargs_to_use["password"] = None kwargs_to_use["auth"] = None for k, v in self.provider.kwargs.items(): kwargs_to_use[k] = v for k, v in self.kwargs.items(): kwargs_to_use[k] = v # Check that in case authentication kwargs are provided, they are either ("username", "password") combination # or "auth", but not both. if ( kwargs_to_use["username"] and kwargs_to_use["password"] and kwargs_to_use["auth"] ): raise TypeError( "Authentication accepts either username and password or requests.auth object" ) # If "username" and "password" kwargs are provided, use those to construct a tuple for "auth". Neither # requests.head() nor requests.get() accept them as-is. if kwargs_to_use["username"] and kwargs_to_use["password"]: kwargs_to_use["auth"] = ( kwargs_to_use["username"], kwargs_to_use["password"], ) # Delete "username" and "password" from kwargs del kwargs_to_use["username"] del kwargs_to_use["password"] url = self.remote_file() + self.additional_request_string if verb.upper() == "GET": r = requests.get(url, *args_to_use, stream=stream, **kwargs_to_use) if verb.upper() == "HEAD": r = requests.head(url, *args_to_use, **kwargs_to_use) yield r r.close() def exists(self): if self._matched_address: with self.httpr(verb="HEAD") as httpr: # if a file redirect was found if httpr.status_code in range(300, 308): raise HTTPFileException( "The file specified appears to have been moved (HTTP %s), check the URL or try adding 'allow_redirects=True' to the remote() file object: %s" % (httpr.status_code, httpr.url) ) return httpr.status_code == requests.codes.ok return False else: raise HTTPFileException( "The file cannot be parsed as an HTTP path in form 'host:port/abs/path/to/file': %s" % self.local_file() ) def mtime(self): if self.exists(): with self.httpr(verb="HEAD") as httpr: file_mtime = self.get_header_item(httpr, "last-modified", default=None) logger.debug("HTTP last-modified: {}".format(file_mtime)) epochTime = 0 if file_mtime is not None: modified_tuple = email.utils.parsedate_tz(file_mtime) if modified_tuple is None: logger.debug( "HTTP last-modified not in RFC2822 format: `{}`".format( file_mtime ) ) else: epochTime = email.utils.mktime_tz(modified_tuple) return epochTime else: raise HTTPFileException( "The file does not seem to exist remotely: %s" % self.remote_file() ) def size(self): if self.exists(): with self.httpr(verb="HEAD") as httpr: content_size = int( self.get_header_item(httpr, "content-size", default=0) ) return content_size else: return self._iofile.size_local def download(self, make_dest_dirs=True): with self.httpr(stream=True) as httpr: if self.exists(): # Find out if the source file is gzip compressed in order to keep # compression intact after the download. # Per default requests decompresses .gz files. # More detials can be found here: https://stackoverflow.com/questions/25749345/how-to-download-gz-files-with-requests-in-python-without-decoding-it?noredirect=1&lq=1 # Since data transferred with HTTP compression need to be decompressed automatically # check the header and decode if the content is encoded. if ( not self.name.endswith(".gz") and httpr.headers.get("Content-Encoding") == "gzip" ): # Decode non-gzipped sourcefiles automatically. # This is needed to decompress uncompressed files that are compressed # for the transfer by HTTP compression. httpr.raw.decode_content = True # if the destination path does not exist if make_dest_dirs: os.makedirs(os.path.dirname(self.local_path), exist_ok=True) with open(self.local_path, "wb") as f: shutil.copyfileobj(httpr.raw, f) os.sync() # ensure flush to disk else: raise HTTPFileException( "The file does not seem to exist remotely: %s" % self.remote_file() ) def upload(self): raise HTTPFileException( "Upload is not permitted for the HTTP remote provider. Is an output set to HTTP.remote()?" ) def get_header_item(self, httpr, header_name, default): """ Since HTTP header capitalization may differ, this returns a header value regardless of case """ header_value = default for k, v in httpr.headers.items(): if k.lower() == header_name: header_value = v return header_value @property def list(self): raise HTTPFileException( "The HTTP Remote Provider does not currently support list-based operations like glob_wildcards()." ) snakemake-5.10.0/snakemake/remote/NCBI.py000066400000000000000000000704201361131222100200460ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2017, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" # built-ins import time import os import re import json import logging import xml.etree.ElementTree as ET # module-specific from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError, NCBIFileException from snakemake.logging import logger try: # third-party modules from Bio import Entrez except ImportError as e: raise WorkflowError( "The Python package 'biopython' needs to be installed to use NCBI Entrez remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, email=None, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, email=email, **kwargs ) self._ncbi = NCBIHelper(*args, email=email, **kwargs) def remote_interface(self): return self._ncbi @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "ncbi://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["ncbi://"] def search( self, query, *args, db="nuccore", idtype="acc", retmode="json", **kwargs ): return list( self._ncbi.search( query, *args, db=db, idtype=idtype, retmode=retmode, **kwargs ) ) class RemoteObject(AbstractRemoteObject): """ This is a class to interact with NCBI / GenBank. """ def __init__( self, *args, keep_local=False, stay_on_remote=False, provider=None, email=None, db=None, rettype=None, retmode=None, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, provider=provider, email=email, db=db, rettype=rettype, retmode=retmode, **kwargs ) if provider: self._ncbi = provider.remote_interface() else: self._ncbi = NCBIHelper(*args, email=email, **kwargs) if db and not self._ncbi.is_valid_db(db): raise NCBIFileException( "DB specified is not valid. Options include: {dbs}".format( dbs=", ".join(self._ncbi.valid_dbs) ) ) else: self.db = db self.rettype = rettype self.retmode = retmode self.kwargs = kwargs # === Implementations of abstract class members === def exists(self): if not self.retmode or not self.rettype: likely_request_options = self._ncbi.guess_db_options_for_extension( self.file_ext, db=self.db, rettype=self.rettype, retmode=self.retmode ) self.db = likely_request_options["db"] self.retmode = likely_request_options["retmode"] self.rettype = likely_request_options["rettype"] return self._ncbi.exists(self.accession, db=self.db) def mtime(self): if self.exists(): return self._ncbi.mtime(self.accession, db=self.db) else: raise NCBIFileException( "The record does not seem to exist remotely: %s" % self.accession ) def size(self): if self.exists(): return self._ncbi.size(self.accession, db=self.db) else: return self._iofile.size_local def download(self): if self.exists(): self._ncbi.fetch_from_ncbi( [self.accession], os.path.dirname(self.accession), rettype=self.rettype, retmode=self.retmode, file_ext=self.file_ext, db=self.db, **self.kwargs ) else: raise NCBIFileException( "The record does not seem to exist remotely: %s" % self.accession ) def upload(self): raise NCBIFileException( "Upload is not permitted for the NCBI remote provider. Is an output set to NCBI.RemoteProvider.remote()?" ) @property def list(self): raise NCBIFileException( "The NCBI Remote Provider does not currently support list-based operations like glob_wildcards()." ) @property def accession(self): accession, version, file_ext = self._ncbi.parse_accession_str(self.local_file()) return accession + "." + version @property def file_ext(self): accession, version, file_ext = self._ncbi.parse_accession_str(self.local_file()) return file_ext @property def version(self): accession, version, file_ext = self._ncbi.parse_accession_str(self.local_file()) return version class NCBIHelper(object): def __init__(self, *args, email=None, **kwargs): if not email: raise NCBIFileException( "An e-mail address must be provided to either the remote file or the RemoteProvider() as email=. The NCBI requires e-mail addresses for queries." ) self.email = email self.entrez = Entrez self.entrez.email = self.email self.entrez.tool = "Snakemake" # valid NCBI Entrez efetch options # via https://www.ncbi.nlm.nih.gov/books/NBK25499/table/chapter4.T._valid_values_of__retmode_and/?report=objectonly self.efetch_options = { "bioproject": [{"rettype": "xml", "retmode": "xml", "ext": "xml"}], "biosample": [ {"rettype": "full", "retmode": "xml", "ext": "xml"}, {"rettype": "full", "retmode": "text", "ext": "txt"}, ], "biosystems": [{"rettype": "xml", "retmode": "xml", "ext": "xml"}], "gds": [{"rettype": "summary", "retmode": "text", "ext": "txt"}], "gene": [ {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "null", "retmode": "xml", "ext": "xml"}, {"rettype": "gene_table", "retmode": "text", "ext": "gene_table"}, ], "homologene": [ {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "null", "retmode": "xml", "ext": "xml"}, { "rettype": "alignmentscores", "retmode": "text", "ext": "alignmentscores", }, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "homologene", "retmode": "text", "ext": "homologene"}, ], "mesh": [{"rettype": "full", "retmode": "text", "ext": "txt"}], "nlmcatalog": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "xml", "ext": "xml"}, ], "nuccore": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "native", "retmode": "xml", "ext": "xml"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "fasta", "retmode": "xml", "ext": "fasta.xml"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, {"rettype": "gb", "retmode": "text", "ext": "gb"}, {"rettype": "gb", "retmode": "xml", "ext": "gb.xml"}, {"rettype": "gbc", "retmode": "xml", "ext": "gbc"}, {"rettype": "ft", "retmode": "text", "ext": "ft"}, {"rettype": "gbwithparts", "retmode": "text", "ext": "gbwithparts"}, {"rettype": "fasta_cds_na", "retmode": "text", "ext": "fasta_cds_na"}, {"rettype": "fasta_cds_aa", "retmode": "text", "ext": "fasta_cds_aa"}, ], "nucest": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "native", "retmode": "xml", "ext": "xml"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "fasta", "retmode": "xml", "ext": "fasta.xml"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, {"rettype": "gb", "retmode": "text", "ext": "gb"}, {"rettype": "gb", "retmode": "xml", "ext": "gb.xml"}, {"rettype": "gbc", "retmode": "xml", "ext": "gbc"}, {"rettype": "est", "retmode": "text", "ext": "est"}, ], "nucgss": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "native", "retmode": "xml", "ext": "xml"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "fasta", "retmode": "xml", "ext": "fasta.xml"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, {"rettype": "gb", "retmode": "text", "ext": "gb"}, {"rettype": "gb", "retmode": "xml", "ext": "gb.xml"}, {"rettype": "gbc", "retmode": "xml", "ext": "gbc"}, {"rettype": "gss", "retmode": "text", "ext": "gss"}, ], "protein": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "native", "retmode": "xml", "ext": "xml"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "fasta", "retmode": "xml", "ext": "fasta.xml"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, {"rettype": "ft", "retmode": "text", "ext": "ft"}, {"rettype": "gp", "retmode": "text", "ext": "gp"}, {"rettype": "gp", "retmode": "xml", "ext": "gp.xml"}, {"rettype": "gpc", "retmode": "xml", "ext": "gpc"}, {"rettype": "ipg", "retmode": "xml", "ext": "xml"}, ], "popset": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "native", "retmode": "xml", "ext": "xml"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "fasta", "retmode": "xml", "ext": "fasta.xml"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, {"rettype": "gb", "retmode": "text", "ext": "gb"}, {"rettype": "gb", "retmode": "xml", "ext": "gb.xml"}, {"rettype": "gbc", "retmode": "xml", "ext": "gbc"}, ], "pmc": [ {"rettype": "null", "retmode": "xml", "ext": "xml"}, {"rettype": "medline", "retmode": "text", "ext": "medline"}, ], "pubmed": [ {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "null", "retmode": "xml", "ext": "xml"}, {"rettype": "medline", "retmode": "text", "ext": "medline"}, {"rettype": "uilist", "retmode": "text", "ext": "uilist"}, {"rettype": "abstract", "retmode": "text", "ext": "abstract"}, ], "sequences": [ {"rettype": "null", "retmode": "text", "ext": "txt"}, {"rettype": "acc", "retmode": "text", "ext": "acc"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "seqid", "retmode": "text", "ext": "seqid"}, ], "snp": [ {"rettype": "null", "retmode": "asn.1", "ext": "asn1"}, {"rettype": "null", "retmode": "xml", "ext": "xml"}, {"rettype": "flt", "retmode": "text", "ext": "flt"}, {"rettype": "fasta", "retmode": "text", "ext": "fasta"}, {"rettype": "rsr", "retmode": "text", "ext": "rsr"}, {"rettype": "ssexemplar", "retmode": "text", "ext": "ssexemplar"}, {"rettype": "chr", "retmode": "text", "ext": "chr"}, {"rettype": "docset", "retmode": "text", "ext": "docset"}, {"rettype": "uilist", "retmode": "text", "ext": "uilist"}, {"rettype": "uilist", "retmode": "xml", "ext": "uilist.xml"}, ], "sra": [{"rettype": "full", "retmode": "xml", "ext": "xml"}], "taxonomy": [ {"rettype": "null", "retmode": "xml", "ext": "xml"}, {"rettype": "uilist", "retmode": "text", "ext": "uilist"}, {"rettype": "uilist", "retmode": "xml", "ext": "uilist.xml"}, ], } @property def valid_extensions(self): extensions = set() for db, db_options in self.efetch_options.items(): for options in db_options: extensions |= set([options["ext"]]) return list(extensions) def dbs_for_options(self, file_ext, rettype=None, retmode=None): possible_dbs = set() for db, db_options in self.efetch_options.items(): for option_dict in db_options: if option_dict["ext"] == file_ext: if retmode and option_dict["retmode"] != retmode: continue if rettype and option_dict["rettype"] != rettype: continue possible_dbs |= set([db]) break return possible_dbs def options_for_db_and_extension(self, db, file_ext, rettype=None, retmode=None): possible_options = [] assert file_ext, "file_ext must be defined" if not self.is_valid_db(db): raise NCBIFileException( "DB specified is not valid. Options include: {dbs}".format( dbs=", ".join(self.valid_dbs) ) ) db_options = self.efetch_options[db] for opt in db_options: if file_ext == opt["ext"]: if retmode and opt["retmode"] != retmode: continue if rettype and opt["rettype"] != rettype: continue possible_options.append(opt) return possible_options def guess_db_options_for_extension( self, file_ext, db=None, rettype=None, retmode=None ): if db and rettype and retmode: if self.is_valid_db_request(db, rettype, retmode): request_options = {} request_options["db"] = db request_options["rettype"] = rettype request_options["retmode"] = retmode request_options["ext"] = file_ext return request_options possible_dbs = [db] if db else self.dbs_for_options(file_ext, rettype, retmode) if len(possible_dbs) > 1: raise NCBIFileException( 'Ambigious db for file extension specified: "{}"; possible databases include: {}'.format( file_ext, ", ".join(list(possible_dbs)) ) ) elif len(possible_dbs) == 1: likely_db = possible_dbs.pop() likely_options = self.options_for_db_and_extension( likely_db, file_ext, rettype, retmode ) if len(likely_options) == 1: request_options = {} request_options["db"] = likely_db request_options["rettype"] = likely_options[0]["rettype"] request_options["retmode"] = likely_options[0]["retmode"] request_options["ext"] = likely_options[0]["ext"] return request_options elif len(likely_options) > 1: raise NCBIFileException( "Please clarify the rettype and retmode. Multiple request types are possible for the file extension ({}) specified: {}".format( file_ext, likely_options ) ) else: raise NCBIFileException( "No request options found. Please check the file extension ({}), db ({}), rettype ({}), and retmode ({}) specified.".format( file_ext, db, rettype, retmode ) ) def is_valid_db_request(self, db, rettype, retmode): if not self.is_valid_db(db): raise NCBIFileException( "DB specified is not valid. Options include: {dbs}".format( dbs=", ".join(self.valid_dbs) ) ) db_options = self.efetch_options[db] for opt in db_options: if opt["rettype"] == rettype and opt["retmode"] == retmode: return True return False @property def valid_dbs(self): return self.efetch_options.keys() def is_valid_db(self, db): return db in self.valid_dbs def parse_accession_str(self, id_str): """ This tries to match an NCBI accession as defined here: http://www.ncbi.nlm.nih.gov/Sequin/acc.html """ m = re.search( r"(?P(?:[a-zA-Z]{1,6}|NW_|NC_|NM_|NR_)\d{1,10})(?:\.(?P\d+))?(?:\.(?P\S+))?.*", id_str, ) accession, version, file_ext = ("", "", "") if m: accession = m.group("accession") version = m.group("version") file_ext = m.group("file_ext") assert ( file_ext ), "file_ext must be defined: {}.{}.. Possible values include: {}".format( accession, version, ", ".join(list(self.valid_extensions)) ) assert version, "version must be defined: {}..{}".format( accession, file_ext ) return accession, version, file_ext @staticmethod def _seq_chunks(seq, n): # http://stackoverflow.com/a/312464/190597 (Ned Batchelder) """ Yield successive n-sized chunks from seq.""" for i in range(0, len(seq), n): yield seq[i : i + n] def _esummary_and_parse( self, accession, xpath_selector, db="nuccore", return_type=int, raise_on_failure=True, retmode="xml", **kwargs ): result = self.entrez.esummary(db=db, id=accession, **kwargs) root = ET.fromstring(result.read()) nodes = root.findall(xpath_selector) retval = 0 if len(nodes): retval = return_type(nodes[0].text) else: if raise_on_failure: raise NCBIFileException("The esummary query failed.") return retval def exists(self, accession, db="nuccore"): result = self.entrez.esearch(db=db, term=accession, rettype="count") root = ET.fromstring(result.read()) nodes = root.findall(".//Count") count = 0 if len(nodes): count = int(nodes[0].text) else: raise NCBIFileException("The esummary query failed.") if count == 1: return True else: logger.warning( 'The accession specified, "{acc}", could not be found in the database "{db}".\nConsider if you may need to specify a different database via "db=".'.format( acc=accession, db=db ) ) return False def size(self, accession, db="nuccore"): return self._esummary_and_parse(accession, ".//*[@Name='Length']", db=db) def mtime(self, accession, db="nuccore"): update_date = self._esummary_and_parse( accession, ".//Item[@Name='UpdateDate']", db=db, return_type=str ) pattern = "%Y/%m/%d" epoch_update_date = int(time.mktime(time.strptime(update_date, pattern))) return epoch_update_date def fetch_from_ncbi( self, accession_list, destination_dir, force_overwrite=False, rettype="fasta", retmode="text", file_ext=None, combined_file_prefix=None, remove_separate_files=False, chunk_size=1, db="nuccore", **kwargs ): """ This function downloads and saves files from NCBI. Adapted in part from the BSD-licensed code here: https://github.com/broadinstitute/viral-ngs/blob/master/util/genbank.py """ max_chunk_size = 500 # Conform to NCBI retreival guidelines by chunking into 500-accession chunks if # >500 accessions are specified and chunk_size is set to 1 # Also clamp chunk size to 500 if the user specified a larger value. if chunk_size > max_chunk_size or ( len(accession_list) > max_chunk_size and chunk_size == 1 ): chunk_size = max_chunk_size outEx = {"fasta": "fasta", "ft": "tbl", "gb": "gbk"} output_directory = os.path.abspath(os.path.expanduser(destination_dir)) if not os.path.exists(output_directory): os.makedirs(output_directory) output_extension = str(file_ext) # ensure the extension starts with a ".", also allowing for passed-in # extensions that already have it if output_extension[:1] != ".": output_extension = "." + output_extension logger.info( "Fetching {} entries from NCBI: {}\n".format( str(len(accession_list)), ", ".join(accession_list[:10]) ) ) output_files = [] for chunk_num, chunk in enumerate(self._seq_chunks(accession_list, chunk_size)): # sleep to throttle requests to 2 per second per NCBI guidelines: # https://www.ncbi.nlm.nih.gov/books/NBK25497/#chapter2.Usage_Guidelines_and_Requiremen time.sleep(0.5) acc_string = ",".join(chunk) # if the filename would be longer than Linux allows, simply say "chunk-chunk_num" if len(acc_string) + len(output_extension) <= 254: output_file_path = os.path.join( output_directory, acc_string + output_extension ) else: output_file_path = os.path.join( output_directory, "chunk-{}".format(chunk_num) + output_extension ) if not force_overwrite: logger.info("not overwriting, checking for existence") assert not os.path.exists(output_file_path), ( """File %s already exists. Consider removing this file or specifying a different output directory. The files for the accessions specified can be overwritten if you add force_overwrite flag. Processing aborted.""" % output_file_path ) try_count = 1 while True: try: logger.info( "Fetching file {}: {}, try #{}".format( chunk_num + 1, acc_string, try_count ) ) handle = self.entrez.efetch( db=db, rettype=rettype, retmode=retmode, id=acc_string, **kwargs ) with open(output_file_path, "w") as outf: for line in handle: outf.write(line) output_files.append(output_file_path) except IOError: logger.warning( "Error fetching file {}: {}, try #{} probably because NCBI is too busy.".format( chunk_num + 1, acc_string, try_count ) ) try_count += 1 if try_count > 4: logger.warning("Tried too many times. Aborting.") raise # if the fetch failed, wait a few seconds and try again. logger.info("Waiting and retrying...") time.sleep(2) continue break # assert that we are not trying to remove the intermediate files without writing a combined file if remove_separate_files: assert combined_file_prefix, """The intermediate files can only be removed if a combined file is written via combined_file_prefix""" # build a path to the combined genome file if combined_file_prefix: concatenated_genome_file_path = os.path.join( output_directory, combined_file_prefix + output_extension ) if not force_overwrite: assert not os.path.exists(concatenated_genome_file_path), ( """File %s already exists. Consider removing this file or specifying a different output directory. The files for the accessions specified can be overwritten if you add force_overwrite flag. Processing aborted.""" % output_file_path ) # concatenate the files together into one genome file with open(concatenated_genome_file_path, "w") as outfile: for file_path in output_files: with open(file_path) as infile: for line in infile: outfile.write(line) # if the option is specified, remove the intermediate fasta files if remove_separate_files: while len(output_files) > 0: os.unlink(output_files.pop()) # add the combined file to the list of files returned output_files.append(concatenated_genome_file_path) # return list of files return output_files def search(self, query, *args, db="nuccore", idtype="acc", **kwargs): # enforce JSON return mode kwargs["retmode"] = "json" # if the user specifies retmax, use it and limit there # otherwise page 200 at a time and return all if "retmax" not in kwargs or not kwargs["retmax"]: kwargs["retmax"] = 100000 return_all = True else: return_all = False kwargs["retstart"] = kwargs.get("retstart", 0) def esearch_json(term, *args, **kwargs): handle = self.entrez.esearch(term=term, *args, **kwargs) json_result = json.loads(handle.read()) return json_result def result_ids(json): if ( "esearchresult" in json_results and "idlist" in json_results["esearchresult"] ): return json_results["esearchresult"]["idlist"] else: raise NCBIFileException("ESearch error") has_more = True while has_more: json_results = esearch_json( term=query, *args, db=db, idtype=idtype, **kwargs ) for acc in result_ids(json_results): yield acc if return_all and ( "count" in json_results["esearchresult"] and int(json_results["esearchresult"]["count"]) > kwargs["retmax"] + kwargs["retstart"] ): kwargs["retstart"] += kwargs["retmax"] # sleep to throttle requests to <2 per second per NCBI guidelines: # https://www.ncbi.nlm.nih.gov/books/NBK25497/#chapter2.Usage_Guidelines_and_Requiremen time.sleep(0.5) else: has_more = False snakemake-5.10.0/snakemake/remote/S3.py000066400000000000000000000307061361131222100176230ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" # built-ins import os import re import math import functools import concurrent.futures # module-specific from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError, S3FileException try: # third-party modules import boto3 import botocore except ImportError as e: raise WorkflowError( "The Python 3 package 'boto3' " "needs to be installed to use S3 remote() file functionality. %s" % e.msg ) class RemoteProvider( AbstractRemoteProvider ): # class inherits from AbstractRemoteProvider supports_default = True # class variable def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) # in addition to methods provided by AbstractRemoteProvider, we add these in self._s3c = S3Helper(*args, **kwargs) # _private variable by convention def remote_interface(self): return self._s3c @property # decorator, so that this function can be access as an attribute, instead of a method def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "s3://" @property # decorator, so that this function can be access as an attribute, instead of a method def available_protocols(self): """List of valid protocols for this remote provider.""" return ["s3://"] class RemoteObject(AbstractRemoteObject): """ This is a class to interact with the AWS S3 object store. """ def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) if provider: self._s3c = provider.remote_interface() else: self._s3c = S3Helper(*args, **kwargs) # === Implementations of abstract class members === def exists(self): if self._matched_s3_path: return self._s3c.exists_in_bucket(self.s3_bucket, self.s3_key) else: raise S3FileException( "The file cannot be parsed as an s3 path in form 'bucket/key': %s" % self.local_file() ) def mtime(self): if self.exists(): return self._s3c.key_last_modified(self.s3_bucket, self.s3_key) else: raise S3FileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def size(self): if self.exists(): return self._s3c.key_size(self.s3_bucket, self.s3_key) else: return self._iofile.size_local def download(self): self._s3c.download_from_s3(self.s3_bucket, self.s3_key, self.local_file()) os.sync() # ensure flush to disk def upload(self): self._s3c.upload_to_s3( self.s3_bucket, self.local_file(), self.s3_key, extra_args=self.kwargs.get("ExtraArgs", None), config=self.kwargs.get("Config", None), ) @property def list(self): return self._s3c.list_keys(self.s3_bucket) # === Related methods === @property def _matched_s3_path(self): return re.search("(?P[^/]*)/(?P.*)", self.local_file()) @property def s3_bucket(self): if len(self._matched_s3_path.groups()) == 2: return self._matched_s3_path.group("bucket") return None @property def name(self): return self.s3_key @property def s3_key(self): if len(self._matched_s3_path.groups()) == 2: return self._matched_s3_path.group("key") def s3_create_stub(self): if self._matched_s3_path: if not self.exists: self._s3c.download_from_s3( self.s3_bucket, self.s3_key, self.file, create_stub_only=True ) else: raise S3FileException( "The file to be downloaded cannot be parsed as an s3 path in form 'bucket/key': %s" % self.local_file() ) class S3Helper(object): def __init__(self, *args, **kwargs): # as per boto, expects the environment variables to be set: # AWS_ACCESS_KEY_ID # AWS_SECRET_ACCESS_KEY # Otherwise these values need to be passed in as kwargs # allow key_id and secret to be specified with aws_, gs_, or no prefix. # Standardize to the aws_ prefix expected by boto. if "gs_access_key_id" in kwargs: kwargs["aws_access_key_id"] = kwargs.pop("gs_access_key_id") if "gs_secret_access_key" in kwargs: kwargs["aws_secret_access_key"] = kwargs.pop("gs_secret_access_key") if "access_key_id" in kwargs: kwargs["aws_access_key_id"] = kwargs.pop("access_key_id") if "secret_access_key" in kwargs: kwargs["aws_secret_access_key"] = kwargs.pop("secret_access_key") if "host" in kwargs: kwargs["endpoint_url"] = kwargs.pop("host") self.s3 = boto3.resource("s3", **kwargs) def bucket_exists(self, bucket_name): try: self.s3.meta.client.head_bucket(Bucket=bucket_name) return True except: return False def upload_to_s3( self, bucket_name, file_path, key=None, use_relative_path_for_key=True, relative_start_dir=None, extra_args=None, config=None, ): """ Upload a file to S3 This function uploads a file to an AWS S3 bucket. Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) file_path: The path to the file to upload. key: The key to set for the file on S3. If not specified, this will default to the name of the file. use_relative_path_for_key: If set to True (default), and key is None, the S3 key will include slashes representing the path of the file relative to the CWD. If False only the file basename will be used for the key. relative_start_dir: The start dir to use for use_relative_path_for_key. No effect if key is set. Returns: The key of the file on S3 if written, None otherwise """ file_path = os.path.realpath(os.path.expanduser(file_path)) assert bucket_name, "bucket_name must be specified" assert os.path.exists(file_path), ( "The file path specified does not exist: %s" % file_path ) assert os.path.isfile(file_path), ( "The file path specified does not appear to be a file: %s" % file_path ) if not self.bucket_exists(bucket_name): self.s3.create_bucket(Bucket=bucket_name) if not key: if use_relative_path_for_key: if relative_start_dir: path_key = os.path.relpath(file_path, relative_start_dir) else: path_key = os.path.relpath(file_path) else: path_key = os.path.basename(file_path) key = path_key k = self.s3.Object(bucket_name, key) try: k.upload_file(file_path, ExtraArgs=extra_args, Config=config) except: raise def download_from_s3( self, bucket_name, key, destination_path=None, expandKeyIntoDirs=True, make_dest_dirs=True, create_stub_only=False, ): """ Download a file from s3 This function downloads an object from a specified AWS S3 bucket. Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) destination_path: If specified, the file will be saved to this path, otherwise cwd. expandKeyIntoDirs: Since S3 keys can include slashes, if this is True (defult) then S3 keys with slashes are expanded into directories on the receiving end. If it is False, the key is passed to os.path.basename() to get the substring following the last slash. make_dest_dirs: If this is True (default) and the destination path includes directories that do not exist, they will be created. Returns: The destination path of the downloaded file on the receiving end, or None if the destination_path could not be downloaded """ assert bucket_name, "bucket_name must be specified" assert key, "Key must be specified" if destination_path: destination_path = os.path.realpath(os.path.expanduser(destination_path)) else: if expandKeyIntoDirs: destination_path = os.path.join(os.getcwd(), key) else: destination_path = os.path.join(os.getcwd(), os.path.basename(key)) # if the destination path does not exist if make_dest_dirs: os.makedirs(os.path.dirname(destination_path), exist_ok=True) k = self.s3.Object(bucket_name, key) try: if not create_stub_only: k.download_file(destination_path) else: # just create an empty file with the right timestamps with open(destination_path, "wb") as fp: os.utime( fp.name, (k.last_modified.timestamp(), k.last_modified.timestamp()), ) return destination_path except: return None def delete_from_bucket(self, bucket_name, key): """ Delete a file from s3 This function deletes an object from a specified AWS S3 bucket. Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) key: the key of the object to delete from the bucket Returns: The name of the object deleted """ assert bucket_name, "bucket_name must be specified" assert key, "Key must be specified" k = self.s3.Object(bucket_name, key) ret = k.delete() return ret.name def exists_in_bucket(self, bucket_name, key): """ Returns whether the key exists in the bucket Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) key: the key of the object to delete from the bucket Returns: True | False """ assert bucket_name, "bucket_name must be specified" assert key, "Key must be specified" try: self.s3.Object(bucket_name, key).load() except botocore.exceptions.ClientError as e: if e.response["Error"]["Code"] == "404": return False else: raise return True def key_size(self, bucket_name, key): """ Returns the size of a key based on a HEAD request Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) key: the key of the object to delete from the bucket Returns: Size in kb """ assert bucket_name, "bucket_name must be specified" assert key, "Key must be specified" k = self.s3.Object(bucket_name, key) return k.content_length // 1024 def key_last_modified(self, bucket_name, key): """ Returns a timestamp of a key based on a HEAD request Args: bucket_name: the name of the S3 bucket to use (bucket name only, not ARN) key: the key of the object to delete from the bucket Returns: timestamp """ assert bucket_name, "bucket_name must be specified" assert key, "Key must be specified" k = self.s3.Object(bucket_name, key) return k.last_modified.timestamp() def list_keys(self, bucket_name): b = self.s3.Bucket(bucket_name) return [o.key for o in b.objects.iterator()] snakemake-5.10.0/snakemake/remote/S3Mocked.py000066400000000000000000000105511361131222100207420ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" # built-ins import os, sys from contextlib import contextmanager import pickle import time import threading import functools # intra-module from snakemake.remote.S3 import ( RemoteObject as S3RemoteObject, RemoteProvider as S3RemoteProvider, ) from snakemake.remote.S3 import S3Helper from snakemake.decorators import dec_all_methods from snakemake.exceptions import WorkflowError try: # third-party import boto3 from moto import mock_s3 except ImportError as e: raise WorkflowError( "The Python 3 packages 'moto' and boto3' " + "need to be installed to use S3Mocked remote() file functionality. %s" % e.msg ) def noop(): pass def pickled_moto_wrapper(func): """ This is a class decorator that in turn decorates all methods within a class to mock out boto calls with moto-simulated ones. Since the moto backends are not presistent across calls by default, the wrapper also pickles the bucket state after each function call, and restores it before execution. This way uploaded files are available for follow-on tasks. Since snakemake may execute with multiple threads it also waits for the pickled bucket state file to be available before loading it in. This is a hackey alternative to using proper locks, but works ok in practice. """ def wrapper_func(self, *args, **kwargs): moto_context_file = "motoState.p" moto_context = mock_s3() moto_context.start() moto_context.backends["global"].reset = noop # load moto buckets from pickle if os.path.isfile(moto_context_file) and os.path.getsize(moto_context_file) > 0: with file_lock(moto_context_file): with open(moto_context_file, "rb") as f: moto_context.backends["global"].buckets = pickle.load(f) mocked_function = moto_context(func) retval = mocked_function(self, *args, **kwargs) with file_lock(moto_context_file): with open(moto_context_file, "wb") as f: pickle.dump(moto_context.backends["global"].buckets, f) moto_context.stop() return retval functools.update_wrapper(wrapper_func, func) wrapper_func.__wrapped__ = func return wrapper_func @dec_all_methods(pickled_moto_wrapper, prefix=None) class RemoteProvider(S3RemoteProvider): supports_default = True def __init__(self, *args, **kwargs): super(RemoteProvider, self).__init__(*args, **kwargs) @dec_all_methods(pickled_moto_wrapper, prefix=None) class RemoteObject(S3RemoteObject): """ This is a derivative of the S3 remote provider that mocks out boto-based S3 calls using the "moto" Python package. Only the initializer is different; it "uploads" the input test file to the moto-simulated bucket at the start. """ def __init__( self, *args, keep_local=False, stay_on_remote=False, provider=None, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, stay_on_remote=False, provider=provider, **kwargs ) bucket_name = "test-remote-bucket" test_file = "test.txt" s3 = boto3.resource("s3") s3.create_bucket(Bucket=bucket_name) # "Upload" files that should be in S3 before tests... s3c = S3Helper() if not s3c.exists_in_bucket(bucket_name, test_file): s3c.upload_to_s3(bucket_name, test_file) # ====== Helpers ===== def touch(fname, mode=0o666, dir_fd=None, **kwargs): # create lock file faster # https://stackoverflow.com/a/1160227 flags = os.O_CREAT | os.O_APPEND with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: os.utime( f.fileno() if os.utime in os.supports_fd else fname, dir_fd=None if os.supports_fd else dir_fd, **kwargs ) @contextmanager def file_lock(filepath): lock_file = filepath + ".lock" while os.path.isfile(lock_file): time.sleep(2) touch(lock_file) try: yield finally: try: os.remove(lock_file) except OSError: pass snakemake-5.10.0/snakemake/remote/SFTP.py000066400000000000000000000127021361131222100201060ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import os from contextlib import contextmanager # module-specific from snakemake.remote import AbstractRemoteProvider, DomainObject from snakemake.exceptions import SFTPFileException, WorkflowError try: # third-party modules import pysftp except ImportError as e: raise WorkflowError( "The Python 3 package 'pysftp' " + "must be installed to use SFTP remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): supports_default = True allows_directories = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "sftp://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["ssh://", "sftp://"] class RemoteObject(DomainObject): """ This is a class to interact with an SFTP server. """ def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) # === Implementations of abstract class members === @contextmanager # makes this a context manager. after 'yield' is __exit__() def sftpc(self): # if args have been provided to remote(), use them over those given to RemoteProvider() args_to_use = self.provider.args if len(self.args): args_to_use = self.args # use kwargs passed in to remote() to override those given to the RemoteProvider() # default to the host and port given as part of the file, falling back to one specified # as a kwarg to remote() or the RemoteProvider (overriding the latter with the former if both) kwargs_to_use = {} kwargs_to_use["host"] = self.host kwargs_to_use["port"] = int(self.port) if self.port else 22 for k, v in self.provider.kwargs.items(): kwargs_to_use[k] = v for k, v in self.kwargs.items(): kwargs_to_use[k] = v conn = pysftp.Connection(*args_to_use, **kwargs_to_use) yield conn conn.close() def exists(self): if self._matched_address: with self.sftpc() as sftpc: return sftpc.exists(self.remote_path) return False else: raise SFTPFileException( "The file cannot be parsed as an SFTP path in form 'host:port/path/to/file': %s" % self.local_file() ) def mtime(self): if self.exists(): with self.sftpc() as sftpc: # As per local operation, don't follow symlinks when reporting mtime attr = sftpc.lstat(self.remote_path) return int(attr.st_mtime) else: raise SFTPFileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def is_newer(self, time): """ Returns true if the file is newer than time, or if it is a symlink that points to a file newer than time. """ with self.sftpc() as sftpc: return ( sftpc.stat(self.remote_path).st_mtime > time or sftpc.lstat(self.remote_path).st_mtime > time ) def size(self): if self.exists(): with self.sftpc() as sftpc: attr = sftpc.stat(self.remote_path) return int(attr.st_size) else: return self._iofile.size_local def download(self, make_dest_dirs=True): with self.sftpc() as sftpc: if self.exists(): # if the destination path does not exist if make_dest_dirs: os.makedirs(os.path.dirname(self.local_path), exist_ok=True) sftpc.get( remotepath=self.remote_path, localpath=self.local_path, preserve_mtime=True, ) os.sync() # ensure flush to disk else: raise SFTPFileException( "The file does not seem to exist remotely: %s" % self.local_file() ) def upload(self): with self.sftpc() as sftpc: sftpc.put( localpath=self.local_path, remotepath=self.remote_path, confirm=True, preserve_mtime=True, ) @property def list(self): file_list = [] first_wildcard = self._iofile.constant_prefix() dirname = first_wildcard.replace(self.path_prefix, "") with self.sftpc() as sftpc: def _append_item(file_path): file_path = file_path.lstrip("/") file_list.append(file_path) sftpc.walktree( dirname, fcallback=_append_item, dcallback=_append_item, ucallback=_append_item, ) return file_list snakemake-5.10.0/snakemake/remote/XRootD.py000066400000000000000000000173721361131222100205210ustar00rootroot00000000000000__author__ = "Chris Burr" __copyright__ = "Copyright 2017, Chris Burr" __email__ = "christopher.burr@cern.ch" __license__ = "MIT" import os from os.path import abspath, join, normpath import re from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError, XRootDFileException try: from XRootD import client from XRootD.client.flags import DirListFlags, MkDirFlags, StatInfoFlags except ImportError as e: raise WorkflowError( "The Python 3 package 'XRootD' must be installed to use XRootD " "remote() file functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self._xrd = XRootDHelper() def remote_interface(self): return self._xrd @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "root://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["root://", "roots://", "rootk://"] class RemoteObject(AbstractRemoteObject): """ This is a class to interact with XRootD servers.""" def __init__( self, *args, keep_local=False, stay_on_remote=False, provider=None, **kwargs ): super(RemoteObject, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, provider=provider, **kwargs ) if provider: self._xrd = provider.remote_interface() else: self._xrd = XRootDHelper() # === Implementations of abstract class members === def exists(self): return self._xrd.exists(self.remote_file()) def mtime(self): if self.exists(): return self._xrd.file_last_modified(self.remote_file()) else: raise XRootDFileException( "The file does not seem to exist remotely: %s" % self.remote_file() ) def size(self): if self.exists(): return self._xrd.file_size(self.remote_file()) else: return self._iofile.size_local def download(self): assert not self.stay_on_remote self._xrd.copy(self.remote_file(), self.file()) def upload(self): assert not self.stay_on_remote self._xrd.copy(self.file(), self.remote_file()) @property def name(self): return self.local_file() @property def list(self): dirname = os.path.dirname(self._iofile.constant_prefix()) + "/" files = list(self._xrd.list_directory_recursive(dirname)) return [normpath(f) for f in files] def remove(self): self._xrd.remove(self.remote_file()) class XRootDHelper(object): def __init__(self): self._clients = {} def get_client(self, domain): try: return self._clients[domain] except KeyError: self._clients[domain] = client.FileSystem(domain) return self._clients[domain] def _parse_url(self, url): match = re.search( "(?P(?:[A-Za-z]+://)[A-Za-z0-9:\_\-\.]+\:?/)(?P/.+)", url ) if match is None: return None domain = match.group("domain") dirname, filename = os.path.split(match.group("path")) # We need a trailing / to keep XRootD happy dirname += "/" return domain, dirname, filename def exists(self, url): domain, dirname, filename = self._parse_url(url) status, dirlist = self.get_client(domain).dirlist(dirname) if not status.ok: if status.errno == 3011: return False else: raise XRootDFileException( "Error listing directory " + dirname + " on domain " + domain + "\n" + repr(status) + "\n" + repr(dirlist) ) return filename in [f.name for f in dirlist.dirlist] def _get_statinfo(self, url): domain, dirname, filename = self._parse_url(url) matches = [ f for f in self.list_directory(domain, dirname) if f.name == filename ] assert len(matches) == 1 return matches[0].statinfo def file_last_modified(self, filename): return self._get_statinfo(filename).modtime def file_size(self, filename): return self._get_statinfo(filename).size def copy(self, source, destination): # Prepare the source path for XRootD if not self._parse_url(source): source = abspath(source) # Prepare the destination path for XRootD assert os.path.basename(source) == os.path.basename(destination) if self._parse_url(destination): domain, dirname, filename = self._parse_url(destination) self.makedirs(domain, dirname) else: destination = abspath(destination) if not os.path.isdir(os.path.dirname(destination)): os.makedirs(os.path.dirname(destination)) # Perform the copy operation process = client.CopyProcess() process.add_job(source, destination) process.prepare() status, returns = process.run() if not status.ok or not returns[0]["status"].ok: raise XRootDFileException( "Error copying from " + source + " to " + destination, repr(status), repr(returns), ) def makedirs(self, domain, dirname): print("Making directories", domain, dirname) assert dirname.endswith("/") status, _ = self.get_client(domain).mkdir(dirname, MkDirFlags.MAKEPATH) if not status.ok: raise XRootDFileException( "Failed to create directory " + dirname, repr(status) ) def list_directory(self, domain, dirname): status, dirlist = self.get_client(domain).dirlist(dirname, DirListFlags.STAT) if not status.ok: raise XRootDFileException( "Error listing directory " + dirname + " on domain " + domain + "\n" + repr(status) + "\n" + repr(dirlist) ) return dirlist.dirlist def list_directory_recursive(self, start_url): assert start_url.endswith("/") domain, dirname, filename = self._parse_url(start_url) assert not filename filename = join(dirname, filename) for f in self.list_directory(domain, dirname): if f.statinfo.flags & StatInfoFlags.IS_DIR: for _f_name in self.list_directory_recursive( domain + dirname + f.name + "/" ): yield _f_name else: # Only yield files as directories don't have timestamps on XRootD yield domain + dirname + f.name def remove(self, url): domain, dirname, filename = self._parse_url(url) filename = join(dirname, filename) status, _ = self.get_client(domain).rm(filename) if not status.ok: raise XRootDFileException( "Failed to remove file " + filename + " from remote " + domain + "\n" + repr(status) ) snakemake-5.10.0/snakemake/remote/__init__.py000066400000000000000000000200161361131222100210660ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" # built-ins import os import sys import re from abc import ABCMeta, abstractmethod from wrapt import ObjectProxy import copy # module-specific import snakemake.io class StaticRemoteObjectProxy(ObjectProxy): """Proxy that implements static-ness for remote objects. The constructor takes a real RemoteObject and returns a proxy that behaves the same except for the exists() and mtime() methods. """ def exists(self): return True def mtime(self): return float("-inf") def is_newer(self, time): return False def __copy__(self): copied_wrapped = copy.copy(self.__wrapped__) return type(self)(copied_wrapped) def __deepcopy__(self): copied_wrapped = copy.deepcopy(self.__wrapped__) return type(self)(copied_wrapped) class AbstractRemoteProvider: """ This is an abstract class to be used to derive remote provider classes. These might be used to hold common credentials, and are then passed to RemoteObjects. """ __metaclass__ = ABCMeta supports_default = False allows_directories = False def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): self.args = args self.stay_on_remote = stay_on_remote self.keep_local = keep_local self.is_default = is_default self.kwargs = kwargs def remote( self, value, *args, keep_local=None, stay_on_remote=None, static=False, **kwargs ): if snakemake.io.is_flagged(value, "temp"): raise SyntaxError("Remote and temporary flags are mutually exclusive.") if snakemake.io.is_flagged(value, "protected"): raise SyntaxError("Remote and protected flags are mutually exclusive.") if keep_local is None: keep_local = self.keep_local if stay_on_remote is None: stay_on_remote = self.stay_on_remote def _set_protocol(value): """Adds the default protocol to `value` if it doesn't already have one""" protocol = self.default_protocol for p in self.available_protocols: if value.startswith(p): value = value[len(p) :] protocol = p break return protocol, value if isinstance(value, str): protocol, value = _set_protocol(value) value = protocol + value if stay_on_remote else value else: protocol, value = list(zip(*[_set_protocol(v) for v in value])) if len(set(protocol)) != 1: raise SyntaxError("A single protocol must be used per RemoteObject") protocol = set(protocol).pop() value = [protocol + v if stay_on_remote else v for v in value] if "protocol" not in kwargs: if "protocol" not in self.kwargs: kwargs["protocol"] = protocol else: kwargs["protocol"] = self.kwargs["protocol"] provider = sys.modules[self.__module__] # get module of derived class remote_object = provider.RemoteObject( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, provider=self, **kwargs ) if static: remote_object = StaticRemoteObjectProxy(remote_object) return snakemake.io.flag(value, "remote_object", remote_object) def glob_wildcards(self, pattern, *args, **kwargs): args = self.args if not args else args kwargs = self.kwargs if not kwargs else kwargs referenceObj = snakemake.io._IOFile(self.remote(pattern, *args, **kwargs)) remote_object = snakemake.io.get_flag_value(referenceObj, "remote_object") if not remote_object.stay_on_remote: pattern = "./" + remote_object.name pattern = os.path.normpath(pattern) key_list = [k for k in remote_object.list] return snakemake.io.glob_wildcards(pattern, files=key_list) @abstractmethod def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" pass @abstractmethod def available_protocols(self): """List of valid protocols for this remote provider.""" pass @abstractmethod def remote_interface(self): pass class AbstractRemoteObject: """ This is an abstract class to be used to derive remote object classes for different cloud storage providers. For example, there could be classes for interacting with Amazon AWS S3 and Google Cloud Storage, both derived from this common base class. """ __metaclass__ = ABCMeta def __init__( self, *args, protocol=None, keep_local=False, stay_on_remote=False, provider=None, **kwargs ): assert protocol is not None # self._iofile must be set before the remote object can be used, in io.py or elsewhere self._iofile = None self.args = args self.kwargs = kwargs self.keep_local = keep_local self.stay_on_remote = stay_on_remote self.provider = provider self.protocol = protocol @property def _file(self): if self._iofile is None: return None return self._iofile._file def file(self): return self._file def local_file(self): if self.stay_on_remote: return self._file[len(self.protocol) :] else: return self._file def remote_file(self): return self.protocol + self.local_file() @abstractmethod def close(self): pass @abstractmethod def exists(self): pass @abstractmethod def mtime(self): pass @abstractmethod def size(self): pass @abstractmethod def download(self, *args, **kwargs): pass @abstractmethod def upload(self, *args, **kwargs): pass @abstractmethod def list(self, *args, **kwargs): pass @abstractmethod def name(self, *args, **kwargs): pass @abstractmethod def remote(self, value, keep_local=False, stay_on_remote=False): pass @abstractmethod def remove(self): raise NotImplementedError("Removal of files is unavailable for this remote") def local_touch_or_create(self): self._iofile.touch_or_create() class DomainObject(AbstractRemoteObject): """This is a mixin related to parsing components out of a location path specified as (host|IP):port/remote/location """ def __init__(self, *args, **kwargs): super(DomainObject, self).__init__(*args, **kwargs) @property def _matched_address(self): return re.search( "^(?P[a-zA-Z]+\://)?(?P[A-Za-z0-9\-\.]+)(?:\:(?P[0-9]+))?(?P.*)$", self.local_file(), ) @property def name(self): return self.path_remainder # if we ever parse out the protocol directly # @property # def protocol(self): # if self._matched_address: # return self._matched_address.group("protocol") @property def host(self): if self._matched_address: return self._matched_address.group("host") @property def port(self): if self._matched_address: return self._matched_address.group("port") @property def path_prefix(self): # this is the domain and port, however specified before the path remainder return self._iofile._file[: self._iofile._file.index(self.path_remainder)] @property def path_remainder(self): if self._matched_address: return self._matched_address.group("path_remainder") @property def local_path(self): return self._iofile._file @property def remote_path(self): return self.path_remainder snakemake-5.10.0/snakemake/remote/dropbox.py000066400000000000000000000131701361131222100210070ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import os # module-specific from snakemake.remote import AbstractRemoteProvider, AbstractRemoteObject from snakemake.exceptions import DropboxFileException, WorkflowError try: # third-party modules import dropbox # The official Dropbox API library except ImportError as e: raise WorkflowError( "The Python 3 package 'dropbox' " "must be installed to use Dropbox remote() file " "functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self._dropboxc = dropbox.Dropbox(*args, **kwargs) try: self._dropboxc.users_get_current_account() except dropbox.exceptions.AuthError as err: DropboxFileException( "ERROR: Invalid Dropbox OAuth access token; try re-generating an access token from the app console on the web." ) def remote_interface(self): return self._dropboxc @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "dropbox://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["dropbox://"] class RemoteObject(AbstractRemoteObject): """ This is a class to interact with the Dropbox API. """ def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) if provider: self._dropboxc = provider.remote_interface() else: self._dropboxc = dropbox.Dropbox(*args, **kwargs) try: self._dropboxc.users_get_current_account() except dropbox.exceptions.AuthError as err: DropboxFileException( "ERROR: Invalid Dropbox OAuth access token; try re-generating an access token from the app console on the web." ) # === Implementations of abstract class members === def exists(self): try: metadata = self._dropboxc.files_get_metadata(self.dropbox_file()) return True except: return False def mtime(self): if self.exists(): metadata = self._dropboxc.files_get_metadata(self.dropbox_file()) epochTime = metadata.server_modified.timestamp() return epochTime else: raise DropboxFileException( "The file does not seem to exist remotely: %s" % self.dropbox_file() ) def size(self): if self.exists(): metadata = self._dropboxc.files_get_metadata(self.dropbox_file()) return int(metadata.size) else: return self._iofile.size_local def download(self, make_dest_dirs=True): if self.exists(): # if the destination path does not exist, make it if make_dest_dirs: os.makedirs(os.path.dirname(self.local_file()), exist_ok=True) self._dropboxc.files_download_to_file( self.local_file(), self.dropbox_file() ) os.sync() # ensure flush to disk else: raise DropboxFileException( "The file does not seem to exist remotely: %s" % self.dropbox_file() ) def upload(self, mode=dropbox.files.WriteMode("overwrite")): # Chunk file into 10MB slices because Dropbox does not accept more than 150MB chunks chunksize = 10000000 with open(self.local_file(), mode="rb") as f: data = f.read(chunksize) # Start upload session res = self._dropboxc.files_upload_session_start(data) offset = len(data) # Upload further chunks until file is complete while len(data) == chunksize: data = f.read(chunksize) self._dropboxc.files_upload_session_append(data, res.session_id, offset) offset += len(data) # Finish session and store in the desired path self._dropboxc.files_upload_session_finish( f.read(chunksize), dropbox.files.UploadSessionCursor(res.session_id, offset), dropbox.files.CommitInfo(path=self.dropbox_file(), mode=mode), ) def dropbox_file(self): return ( "/" + self.local_file() if not self.local_file().startswith("/") else self.local_file() ) @property def name(self): return self.local_file() @property def list(self): file_list = [] first_wildcard = self._iofile.constant_prefix() dirname = ( "/" + first_wildcard if not first_wildcard.startswith("/") else first_wildcard ) while "//" in dirname: dirname = dirname.replace("//", "/") dirname = dirname.rstrip("/") for item in self._dropboxc.files_list_folder(dirname, recursive=True).entries: file_list.append( os.path.join(os.path.dirname(item.path_lower), item.name).lstrip("/") ) return file_list snakemake-5.10.0/snakemake/remote/gfal.py000066400000000000000000000117121361131222100202430ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2017-2019, Johannes Köster" __email__ = "johannes.koester@tu-dortmund.de" __license__ = "MIT" import os import re import shutil import subprocess as sp from datetime import datetime import time from snakemake.remote import AbstractRemoteObject, AbstractRemoteProvider from snakemake.exceptions import WorkflowError from snakemake.common import lazy_property from snakemake.logging import logger if not shutil.which("gfal-copy"): raise WorkflowError( "The gfal-* commands need to be available for " "gfal remote support." ) class RemoteProvider(AbstractRemoteProvider): supports_default = True allows_directories = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, retry=5, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self.retry = retry @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "gsiftp://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" # TODO gfal provides more. Extend this list. return ["gsiftp://", "srm://"] class RemoteObject(AbstractRemoteObject): mtime_re = re.compile(r"^\s*Modify: (.+)$", flags=re.MULTILINE) size_re = re.compile(r"^\s*Size: ([0-9]+).*$", flags=re.MULTILINE) def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) def _gfal(self, cmd, *args, retry=None, raise_workflow_error=True): if retry is None: retry = self.provider.retry _cmd = ["gfal-" + cmd] + list(args) for i in range(retry + 1): try: logger.debug(_cmd) return sp.run( _cmd, check=True, stderr=sp.PIPE, stdout=sp.PIPE ).stdout.decode() except sp.CalledProcessError as e: if i == retry: if raise_workflow_error: raise WorkflowError( "Error calling gfal-{}:\n{}".format(cmd, e.stderr.decode()) ) else: raise e else: # try again after some seconds time.sleep(1) continue # === Implementations of abstract class members === def exists(self): try: self._gfal( "ls", "-a", self.remote_file(), retry=0, raise_workflow_error=False ) except sp.CalledProcessError as e: if e.returncode == 2: # exit code 2 means no such file or directory return False else: raise WorkflowError( "Error calling gfal-ls:\n{}".format(e.stderr.decode()) ) # exit code 0 means that the file is present return True def _stat(self): stat = self._gfal("stat", self.remote_file()) return stat def mtime(self): # assert self.exists() stat = self._stat() mtime = self.mtime_re.search(stat).group(1) date = datetime.strptime(mtime, "%Y-%m-%d %H:%M:%S.%f") return date.timestamp() def size(self): # assert self.exists() stat = self._stat() size = self.size_re.search(stat).group(1) return int(size) def download(self): if self.exists(): if self.size() == 0: # Globus erroneously thinks that a transfer is incomplete if a # file is empty. Hence we manually touch the local file. self.local_touch_or_create() return self.local_file() # Download file. Wait for staging. source = self.remote_file() target = "file://" + os.path.abspath(self.local_file()) # disable all timeouts (file transfers can take a long time) self._gfal( "copy", "-p", "-f", "-n", "4", "-t", "0", "-T", "0", source, target ) os.sync() return self.local_file() return None def upload(self): target = self.remote_file() source = "file://" + os.path.abspath(self.local_file()) # disable all timeouts (file transfers can take a long time) self._gfal("copy", "-p", "-f", "-n", "4", "-t", "0", "-T", "0", source, target) @property def list(self): # TODO implement listing of remote files with patterns raise NotImplementedError() def host(self): return self.local_file().split("/")[0] snakemake-5.10.0/snakemake/remote/gridftp.py000066400000000000000000000047671361131222100210050ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2017-2019, Johannes Köster" __email__ = "johannes.koester@tu-dortmund.de" __license__ = "MIT" import os import subprocess as sp import time import shutil from snakemake.exceptions import WorkflowError from snakemake.logging import logger if not shutil.which("globus-url-copy"): raise WorkflowError( "The globus-url-copy command has to be available for " "gridftp remote support." ) if not shutil.which("gfal-ls"): raise WorkflowError( "The gfal-* commands need to be available for " "gridftp remote support." ) from snakemake.remote import gfal class RemoteProvider(gfal.RemoteProvider): pass class RemoteObject(gfal.RemoteObject): def _globus(self, *args): retry = self.provider.retry cmd = ["globus-url-copy"] + list(args) for i in range(retry + 1): try: logger.debug(" ".join(cmd)) return sp.run( cmd, check=True, stderr=sp.PIPE, stdout=sp.PIPE ).stdout.decode() except sp.CalledProcessError as e: if i == retry: raise WorkflowError( "Error calling globus-url-copy:\n{}".format( cmd, e.stderr.decode() ) ) else: # try again after some seconds time.sleep(1) continue def download(self): if self.exists(): if self.size() == 0: # Globus erroneously thinks that a transfer is incomplete if a # file is empty. Hence we manually touch the local file. self.local_touch_or_create() return self.local_file() # Download file. Wait for staging. source = self.remote_file() target = "file://" + os.path.abspath(self.local_file()) self._globus( "-parallel", "4", "-create-dest", "-recurse", "-dp", source, target ) os.sync() return self.local_file() return None def upload(self): target = self.remote_file() source = "file://" + os.path.abspath(self.local_file()) if self.exists(): # first delete file, such that globus does not fail self._gfal("rm", target) self._globus( "-parallel", "4", "-create-dest", "-recurse", "-dp", source, target ) snakemake-5.10.0/snakemake/remote/iRODS.py000066400000000000000000000207501361131222100202540ustar00rootroot00000000000000__author__ = "Oliver Stolpe" __copyright__ = "Copyright 2017, BIH Core Unit Bioinformatics" __email__ = "oliver.stolpe@bihealth.org" __license__ = "MIT" import os import re from contextlib import contextmanager from datetime import datetime, timedelta from pytz import timezone # module-specific from snakemake.remote import AbstractRemoteProvider, AbstractRemoteObject from snakemake.exceptions import WorkflowError try: # third-party modules from irods.session import iRODSSession from irods.meta import iRODSMeta from irods.models import DataObject from irods.exception import CollectionDoesNotExist, DataObjectDoesNotExist import irods.keywords as kw except ImportError as e: raise WorkflowError( "The Python 3 package 'python-irodsclient' " + "must be installed to use iRODS remote() file functionality. %s" % e.msg ) utc = datetime.utcfromtimestamp(0) def _irods_session(*args, **kwargs): try: irods_env_file = os.environ["IRODS_ENVIRONMENT_FILE"] except KeyError: irods_env_file = kwargs.get( "irods_env_file", os.path.expanduser("~/.irods/irods_environment.json") ) if not os.path.isfile(irods_env_file): raise WorkflowError( "Expecting iRODS configuration file in %s, but none found." % kwargs["irods_env_file"] ) with iRODSSession(irods_env_file=irods_env_file) as session: try: session.collections.get( os.path.join(os.sep, session.zone, "home", session.username) ) except ConnectionError as e: raise e return session class RemoteProvider(AbstractRemoteProvider): supports_default = True def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) self._irods_session = _irods_session(*args, **kwargs) def remote_interface(self): return self._irods_session @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "irods://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["irods://"] class RemoteObject(AbstractRemoteObject): """ This is a class to interact with an iRODS server. """ def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__( *args, keep_local=keep_local, provider=provider, **kwargs ) if provider: self._irods_session = provider.remote_interface() self._timezone = provider.kwargs.get("timezone") else: self._irods_session = _irods_session(*args, **kwargs) def exists(self): try: self._irods_session.data_objects.get(self.remote_path) return True except (CollectionDoesNotExist, DataObjectDoesNotExist): return False def _convert_time(self, timestamp, tz=None): dt = timestamp.replace(tzinfo=timezone("UTC")) if tz: dt = dt.astimezone(timezone(tz)) return dt def mtime(self): if self.exists(): obj = self._irods_session.data_objects.get(self.remote_path) meta = self._irods_session.metadata.get(DataObject, self.remote_path) # if mtime was set in metadata during upload, take this information # otherwise fall back to iRODS timestamp (upload time!) and change # timezone accordingly (iRODS might ignore the servers local timezone) for m in meta: if m.name == "mtime": mtime = float(m.value) break else: dt = self._convert_time(obj.modify_time, self._timezone) utc2 = self._convert_time(utc) mtime = (dt - utc2).total_seconds() return int(mtime) else: raise WorkflowError( "The file does not seem to exist remotely: %s" % self.local_file() ) def atime(self): if self.exists(): obj = self._irods_session.data_objects.get(self.remote_path) meta = self._irods_session.metadata.get(DataObject, self.remote_path) # if mtime was set in metadata during upload, take this information # otherwise fall back to iRODS timestamp (upload time!) and change # timezone accordingly (iRODS might ignore the servers local timezone) for m in meta: if m.name == "atime": atime = float(m.value) break else: dt = self._convert_time(obj.modify_time, self._timezone) utc2 = self._convert_time(utc) atime = (dt - utc2).total_seconds() return int(atime) else: raise WorkflowError("File doesn't exist remotely: %s" % self.local_file()) def is_newer(self, time): """ Returns true of the file is newer than time, or if it is a symlink that points to a file newer than time. """ return self.mtime() > time def size(self): if self.exists(): obj = self._irods_session.data_objects.get(self.remote_path) return int(obj.size) else: return self._iofile.size_local def download(self, make_dest_dirs=True): if self.exists(): if make_dest_dirs: os.makedirs(os.path.dirname(self.local_path), exist_ok=True) # force irods to overwrite existing file if this option is set opt = {} if self.kwargs.get("overwrite"): opt[kw.FORCE_FLAG_KW] = "" # get object and change timestamp obj = self._irods_session.data_objects.get( self.remote_path, self.local_path, options=opt ) os.utime(self.local_path, (self.atime(), self.mtime())) os.sync() else: raise WorkflowError( "The file does not seem to exist remotely: %s" % self.local_file() ) def upload(self): # get current local timestamp stat = os.stat(self.local_path) # create folder structure on remote folders = os.path.dirname(self.remote_path).split(os.sep)[1:] collpath = os.sep for folder in folders: collpath = os.path.join(collpath, folder) try: self._irods_session.collections.get(collpath) except: self._irods_session.collections.create(collpath) # upload file and store local timestamp in metadata since irods sets the files modification time to # the upload time rather than retaining the local modification time self._irods_session.data_objects.put(self.local_path, self.remote_path) # erase meta data (if exists) before adding it. there is no update routine available in the API for m in self._irods_session.metadata.get(DataObject, self.remote_path): if m.name in ("mtime", "atime", "ctime"): self._irods_session.metadata.remove( DataObject, self.remote_path, iRODSMeta(m.name, m.value, m.units) ) self._irods_session.metadata.add( DataObject, self.remote_path, iRODSMeta("mtime", str(stat.st_mtime), "s") ) self._irods_session.metadata.add( DataObject, self.remote_path, iRODSMeta("atime", str(stat.st_atime), "s") ) self._irods_session.metadata.add( DataObject, self.remote_path, iRODSMeta("ctime", str(stat.st_ctime), "s") ) @property def remote_path(self): return os.path.join(os.sep, self._irods_session.zone, self.local_file()) @property def name(self): return self.local_file() @property def local_path(self): return self.local_file() @property def list(self): file_list = [] first_wildcard = self._iofile.constant_prefix() dirname = os.path.dirname(first_wildcard) collection = self._irods_session.collections.get(dirname) for _, _, objs in collection.walk(): for obj in objs: file_list.append(obj.path.lstrip("/")) return file_list snakemake-5.10.0/snakemake/remote/webdav.py000066400000000000000000000164141361131222100206060ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2017, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" import os, sys import email.utils from contextlib import contextmanager import functools # module-specific from snakemake.remote import AbstractRemoteProvider, AbstractRemoteObject, DomainObject from snakemake.exceptions import WebDAVFileException, WorkflowError try: # third-party modules import aioeasywebdav import asyncio except ImportError as e: raise WorkflowError( "The Python 3 packages 'aioeasywebdav' " " and 'asyncio' must be present to use WebDAV remote() file " "functionality. %s" % e.msg ) class RemoteProvider(AbstractRemoteProvider): def __init__( self, *args, keep_local=False, stay_on_remote=False, is_default=False, **kwargs ): # loop = asyncio.get_event_loop() super(RemoteProvider, self).__init__( *args, keep_local=keep_local, stay_on_remote=stay_on_remote, is_default=is_default, **kwargs ) @property def default_protocol(self): """The protocol that is prepended to the path when no protocol is specified.""" return "https://" @property def available_protocols(self): """List of valid protocols for this remote provider.""" return ["http://", "https://"] class RemoteObject(DomainObject): """ This is a class to interact with a WebDAV file store. """ def __init__(self, *args, keep_local=False, **kwargs): # self.loop = asyncio.get_event_loop() super(RemoteObject, self).__init__(*args, keep_local=keep_local, **kwargs) @contextmanager def webdavc(self): newloop = False if not hasattr(self, "loop"): try: self.loop = asyncio.get_event_loop() if self.loop.is_running(): raise NotImplementedError( "Cannot use aioutils in " "asynchroneous environment" ) except: newloop = True self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) self.loop = asyncio.new_event_loop() asyncio.set_event_loop(self.loop) if not hasattr(self, "conn") or ( hasattr(self, "conn") and not isinstance(self.conn, aioeasywebdav.Client) ): # if args have been provided to remote(), use them over those given to RemoteProvider() args_to_use = self.provider.args if len(self.args): args_to_use = self.args # use kwargs passed in to remote() to override those given to the RemoteProvider() # default to the host and port given as part of the file, falling back to one specified # as a kwarg to remote() or the RemoteProvider (overriding the latter with the former if both) kwargs_to_use = {} kwargs_to_use["host"] = self.host kwargs_to_use["protocol"] = self.protocol kwargs_to_use["port"] = int(self.port) if self.port != None else 443 for k, v in self.provider.kwargs.items(): kwargs_to_use[k] = v for k, v in self.kwargs.items(): kwargs_to_use[k] = v # easywebdav wants the protocol without "://" kwargs_to_use["protocol"] = kwargs_to_use["protocol"].replace("://", "") # monkey patch aioeasywebdav to noop _rate_calc() # since we don't care about download progress and # the parent (connection) object may be removed before the # sleep coroutine has a chance to be scheduled/finish, # and aioeasywebdav only calls close() on __del__() async def noop(_): pass aioeasywebdav.Client._rate_calc = noop self.conn = aioeasywebdav.connect(*args_to_use, **kwargs_to_use) yield # === Implementations of abstract class members === def exists(self): with self.webdavc() as webdavc: path_to_try = self.webdav_file return self.loop.run_until_complete(self.conn.exists(self.webdav_file)) def mtime(self): if self.exists(): with self.webdavc() as webdavc: metadata = self.loop.run_until_complete( self.conn.ls(remote_path=self.webdav_file) )[0] parsed_date = email.utils.parsedate_tz(metadata.mtime) epoch_time = email.utils.mktime_tz(parsed_date) return epoch_time else: raise EasyWebDAVFileException( "The file does not seem to exist remotely: %s" % self.webdav_file ) def size(self): if self.exists(): with self.webdavc() as webdavc: metadata = self.loop.run_until_complete( self.conn.ls(remote_path=self.webdav_file) )[0] return int(metadata.size) else: return self._iofile.size_local def download(self, make_dest_dirs=True): if self.exists(): # if the destination path does not exist, make it if make_dest_dirs: os.makedirs(os.path.dirname(self.local_file()), exist_ok=True) with self.webdavc() as webdavc: self.loop.run_until_complete( self.conn.download(self.webdav_file, self.local_file()) ) os.sync() # ensure flush to disk else: raise EasyWebDAVFileException( "The file does not seem to exist remotely: %s" % self.webdav_file ) def upload(self): # make containing folder with self.webdavc() as webdavc: self.loop.run_until_complete( self.conn.mkdirs(os.path.dirname(self.webdav_file)) ) self.loop.run_until_complete( self.conn.upload(self.local_file(), self.webdav_file) ) @property def webdav_file(self): filepath = ( self.local_file().replace(self.host, "").replace(":" + str(self.port), "") ) filepath = filepath if not filepath.startswith("/") else filepath[1:] return filepath @property def name(self): return self.local_file() @property def list(self): file_list = [] first_wildcard = ( self._iofile.constant_prefix() .replace(self.host, "") .replace(":" + str(self.port), "") ) dirname = ( first_wildcard[1:] if first_wildcard.startswith("/") else first_wildcard ) while "//" in dirname: dirname = dirname.replace("//", "/") dirname = dirname.rstrip("/") + "/" with self.webdavc() as webdavc: for item in self.loop.run_until_complete(self.conn.ls(dirname)): file_list.append( os.path.join(os.path.dirname(dirname), item.name.lstrip("/")) ) file_list.append( os.path.join( self._iofile.constant_prefix(), os.path.basename(item.name) ) ) return file_list snakemake-5.10.0/snakemake/report.css000066400000000000000000000045431361131222100175160ustar00rootroot00000000000000/** Credits for the colors and font selection go to the Twitter Bootstrap framework. */ body { color: rgb(51, 51, 51); font-size: 10pt; padding-top: 10px; font-family: "Helvetica Neue",Helvetica,Arial,sans-serif; } h1 { font-size: 150%; } h2 { font-size: 140%; } h3 { font-size: 130%; } h4 { font-size: 120%; } h5 { font-size: 110%; } h6 { font-size: 100%; } div#attachments { display: inline-block; color: gray; border-width: 1px; border-style: solid; border-color: white; border-radius: 4px 4px 4px 4px; padding: 0px; } div#attachments dt { margin-top: 2px; margin-bottom: 2px; } div#attachments dd p { margin-top: 2px; margin-bottom: 2px; } div#attachments :target dt { font-weight: bold; } div#attachments :target a { color: rgb(70, 136, 71); } h1.title { text-align: center; font-size: 180%; } div.document { position: relative; background: white; max-width: 800px; margin: auto; padding: 20px; border: 1px solid rgb(221, 221, 221); border-radius: 4px 4px 4px 4px; } div.document:after { content: "snakemake report"; position: absolute; top: -1px; right: -1px; padding: 3px 7px; background-color: #f5f5f5; border: 1px solid rgb(221, 221, 221); color: #9da0a4; font-weight: bold; font-size: 12pt; border-radius: 0 0 0 4px; } div.document p { text-align: justify; } div#metadata { text-align: right; } table.docutils { border: none; border-collapse: collapse; border-top: 2px solid gray; border-bottom: 2px solid gray; text-align: center; } table.docutils th { border: none; border-top: 2px solid gray; border-bottom: 2px solid gray; padding: 5px; } table.docutils td { border: none; padding: 5px; } table.docutils th:last-child, td:last-child { text-align: left; } table.docutils th:first-child, td:first-child { text-align: right; } table.docutils th:only-child, td:only-child { text-align: center; } table.docutils.footnote { border: none; text-align: left; } a { color: rgb(0, 136, 204); text-decoration: none; } a:hover { color: rgb(0, 85, 128); text-decoration: underline; } div.figure { margin-left: 2em; margin-right: 2em; } img { max-width: 100%; } p.caption { font-style: italic; } snakemake-5.10.0/snakemake/report/000077500000000000000000000000001361131222100167765ustar00rootroot00000000000000snakemake-5.10.0/snakemake/report/__init__.py000066400000000000000000000535251361131222100211210ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import sys import mimetypes import base64 import textwrap import datetime import io import uuid import json import time import shutil import subprocess as sp import itertools import csv from collections import namedtuple, defaultdict import requests from docutils.parsers.rst.directives.images import Image, Figure from docutils.parsers.rst import directives from docutils.core import publish_file, publish_parts from snakemake import script, wrapper from snakemake.utils import format from snakemake.logging import logger from snakemake.io import is_flagged, get_flag_value from snakemake.exceptions import WorkflowError from snakemake.script import Snakemake from snakemake import __version__ from snakemake.common import num_if_possible from snakemake import logging class EmbeddedMixin(object): """ Replaces the URI of a directive with a base64-encoded version. Useful for embedding images/figures in reports. """ def run(self): """ Image.run() handles most of the """ result = Image.run(self) reference = directives.uri(self.arguments[0]) self.options["uri"] = data_uri_from_file(reference) return result # Create (and register) new image:: and figure:: directives that use a base64 # data URI instead of pointing to a filename. class EmbeddedImage(Image, EmbeddedMixin): pass directives.register_directive("embeddedimage", EmbeddedImage) class EmbeddedFigure(Figure, EmbeddedMixin): pass directives.register_directive("embeddedfigure", EmbeddedFigure) def data_uri(data, filename, encoding="utf8", mime="text/plain"): """Craft a base64 data URI from file with proper encoding and mimetype.""" data = base64.b64encode(data) uri = "data:{mime};charset={charset};filename={filename};base64,{data}" "".format( filename=filename, mime=mime, charset=encoding, data=data.decode("utf-8") ) return uri def mime_from_file(file): mime, encoding = mimetypes.guess_type(file) if mime is None: mime = "text/plain" logger.info( "Could not detect mimetype for {}, assuming " "text/plain.".format(file) ) return mime, encoding def data_uri_from_file(file, defaultenc="utf8"): """Craft a base64 data URI from file with proper encoding and mimetype.""" mime, encoding = mime_from_file(file) if encoding is None: encoding = defaultenc with open(file, "rb") as f: return data_uri(f.read(), os.path.basename(file), encoding, mime) def report( text, path, stylesheet=None, defaultenc="utf8", template=None, metadata=None, **files ): if stylesheet is None: os.path.join(os.path.dirname(__file__), "report.css") outmime, _ = mimetypes.guess_type(path) if outmime != "text/html": raise ValueError("Path to report output has to be an HTML file.") definitions = textwrap.dedent( """ .. role:: raw-html(raw) :format: html """ ) metadata = textwrap.dedent( """ .. container:: :name: metadata {metadata}{date} """ ).format( metadata=metadata + " | " if metadata else "", date=datetime.date.today().isoformat(), ) text = format(textwrap.dedent(text), stepout=3) attachments = [] if files: attachments = [ textwrap.dedent( """ .. container:: :name: attachments """ ) ] for name, _files in sorted(files.items()): if not isinstance(_files, list): _files = [_files] links = [] for file in sorted(_files): data = data_uri_from_file(file) links.append( ':raw-html:`
{filename}`'.format( data=data, filename=os.path.basename(file) ) ) links = "\n\n ".join(links) attachments.append( """ .. container:: :name: {name} {name}: {links} """.format( name=name, links=links ) ) text = definitions + text + "\n\n" + "\n\n".join(attachments) + metadata overrides = dict() if template is not None: overrides["template"] = template if stylesheet is not None: overrides["stylesheet_path"] = stylesheet html = open(path, "w") publish_file( source=io.StringIO(text), destination=html, writer_name="html", settings_overrides=overrides, ) class Category: def __init__(self, name): if name is None: name = "Other" self.name = name self.id = "results-{name}".format(name=name.replace(" ", "_")) self.content_id = self.id + "-content" def __eq__(self, other): return self.name.__eq__(other.name) def __hash__(self): return self.name.__hash__() def __lt__(self, other): if self.name == "other": return False return self.name.__lt__(other.name) class RuleRecord: def __init__(self, job, job_rec): import yaml self.name = job_rec.rule self._rule = job.rule self.singularity_img_url = job_rec.singularity_img_url self.conda_env = None self._conda_env_raw = None if job_rec.conda_env: self._conda_env_raw = base64.b64decode(job_rec.conda_env).decode() self.conda_env = yaml.load(self._conda_env_raw, Loader=yaml.Loader) self.n_jobs = 1 self.output = list(job_rec.output) self.id = uuid.uuid4() def code(self): try: from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter from pygments import highlight import pygments.util except ImportError: raise WorkflowError( "Python package pygments must be installed to create reports." ) source, language = None, None if self._rule.shellcmd is not None: source = self._rule.shellcmd language = "bash" elif self._rule.script is not None: logger.info("Loading script code for rule {}".format(self.name)) _, source, language = script.get_source( self._rule.script, self._rule.basedir ) source = source.decode() elif self._rule.wrapper is not None: logger.info("Loading wrapper code for rule {}".format(self.name)) _, source, language = script.get_source( wrapper.get_script( self._rule.wrapper, prefix=self._rule.workflow.wrapper_prefix ) ) source = source.decode() try: lexer = get_lexer_by_name(language) return highlight( source, lexer, HtmlFormatter(linenos=True, cssclass="source", wrapcode=True), ) except pygments.util.ClassNotFound: return "
source
" def add(self, job_rec): self.n_jobs += 1 self.output.extend(job_rec.output) def __eq__(self, other): return ( self.name == other.name and self.conda_env == other.conda_env and self.singularity_img_url == other.singularity_img_url ) class ConfigfileRecord: def __init__(self, configfile): self.name = configfile def code(self): try: from pygments.lexers import get_lexer_by_name from pygments.formatters import HtmlFormatter from pygments import highlight except ImportError: raise WorkflowError( "Python package pygments must be installed to create reports." ) language = ( "yaml" if self.name.endswith(".yaml") or self.name.endswith(".yml") else "json" ) lexer = get_lexer_by_name(language) with open(self.name) as f: return highlight( f.read(), lexer, HtmlFormatter(linenos=True, cssclass="source", wrapcode=True), ) class JobRecord: def __init__(self): self.job = None self.rule = None self.starttime = sys.maxsize self.endtime = 0 self.output = [] self.conda_env_file = None self.singularity_img_url = None class FileRecord: def __init__(self, path, job, caption, env, category): self.path = path self.target = os.path.basename(path) self.size = os.path.getsize(self.path) logger.info("Adding {} ({:.2g} MB).".format(self.name, self.size / 1e6)) self.raw_caption = caption self.mime, _ = mime_from_file(self.path) self.id = uuid.uuid4() self.job = job self.wildcards = logging.format_wildcards(job.wildcards) self.params = logging.format_dict(job.params) self.png_uri = None self.category = category if self.is_img: convert = shutil.which("convert") if convert is not None: try: # 2048 aims at a reasonable balance between what displays # can show in a png-preview image and what renders quick # into a small enough png max_width = "2048" max_height = "2048" # '>' means only larger images scaled down to within max-dimensions max_spec = max_width + "x" + max_height + ">" png = sp.check_output( ["convert", "-resize", max_spec, self.path, "png:-"], stderr=sp.PIPE, ) uri = data_uri( png, os.path.basename(self.path) + ".png", mime="image/png" ) self.png_uri = uri except sp.CalledProcessError as e: logger.warning( "Failed to convert image to png with " "imagemagick convert: {}".format(e.stderr) ) else: logger.warning( "Command convert not in $PATH. Install " "imagemagick in order to have embedded " "images and pdfs in the report." ) if self.is_table: if self.size > 1e6: logger.warning( "Table {} >1MB. Rendering as generic file.".format(self.path) ) else: with open(self.path) as table: dialect = None for prefix in range(10, 17): try: table.seek(0) dialect = csv.Sniffer().sniff(table.read(prefix)) break except csv.Error: pass except UnicodeDecodeError: # table is not readable as UTF-8 break if dialect is None: logger.warning( "Failed to infer CSV/TSV dialect from table {}. " "Rendering as generic file.".format(self.path) ) else: table.seek(0) reader = csv.reader(table, dialect) columns = next(reader) table = map(lambda row: list(map(num_if_possible, row)), reader) template = env.get_template("table.html") html = template.render( columns=columns, table=table, name=self.name ).encode() self.mime = "text/html" self.path = os.path.basename(self.path) + ".html" self.data_uri = data_uri(html, self.path, mime=self.mime) return # fallback self.data_uri = data_uri_from_file(path) def render(self, env, rst_links, categories, files): if self.raw_caption is not None: try: from jinja2 import Template except ImportError as e: raise WorkflowError( "Python package jinja2 must be installed to create reports." ) job = self.job snakemake = Snakemake( job.input, job.output, job.params, job.wildcards, job.threads, job.resources, job.log, job.dag.workflow.config, job.rule.name, None, ) try: caption = open(self.raw_caption).read() + rst_links caption = env.from_string(caption).render( snakemake=snakemake, categories=categories, files=files ) self.caption = publish_parts(caption, writer_name="html")["body"] except Exception as e: raise WorkflowError( "Error loading caption file of output " "marked for report.", e ) @property def is_img(self): web_safe = { "image/gif", "image/jpeg", "image/png", "image/svg+xml", "application/pdf", } return self.mime in web_safe @property def is_text(self): return self.is_table or self.mime == "text/plain" @property def is_table(self): return self.mime in {"text/csv", "text/tab-separated-values"} @property def is_vega(self): return ( self.mime == "application/json" and self.path.endswith(".vl.json") or self.path.endswith(".vg.json") ) @property def icon(self): if self.is_img: return "image" elif self.is_text: return "file-text" else: return "file" @property def name(self): return os.path.basename(self.path) def rulegraph_d3_spec(dag): try: import networkx as nx from networkx.drawing.nx_agraph import graphviz_layout from networkx.readwrite import json_graph except ImportError as e: raise WorkflowError( "Python packages networkx and pygraphviz must be " "installed to create reports.", e, ) g = nx.DiGraph() g.add_nodes_from(sorted(job.rule.name for job in dag.jobs)) for job in dag.jobs: target = job.rule.name for dep in dag.dependencies[job]: source = dep.rule.name g.add_edge(source, target) pos = graphviz_layout(g, "dot", args="-Grankdir=BT") xmax = max(x for x, y in pos.values()) + 100 # add offset to account for labels ymax = max(y for x, y in pos.values()) def encode_node(node): x, y = pos[node] return {"rule": node, "fx": x, "fy": y} nodes = list(map(encode_node, g.nodes)) idx = {node: i for i, node in enumerate(g.nodes)} links = [{"target": idx[u], "source": idx[v], "value": 1} for u, v in g.edges] return {"nodes": nodes, "links": links}, xmax, ymax def get_resource_as_string(url): r = requests.get(url) if r.status_code == requests.codes.ok: return r.text raise WorkflowError( "Failed to download resource needed for " "report: {}".format(url) ) def auto_report(dag, path): try: from jinja2 import Template, Environment, PackageLoader except ImportError as e: raise WorkflowError( "Python package jinja2 must be installed to create reports." ) if not path.endswith(".html"): raise WorkflowError("Report file does not end with .html") logger.info("Creating report...") env = Environment( loader=PackageLoader("snakemake", "report"), trim_blocks=True, lstrip_blocks=True, ) env.filters["get_resource_as_string"] = get_resource_as_string persistence = dag.workflow.persistence results = defaultdict(list) records = defaultdict(JobRecord) recorded_files = set() for job in dag.jobs: for f in itertools.chain(job.expanded_output, job.input): if is_flagged(f, "report") and f not in recorded_files: if not f.exists: raise WorkflowError( "File {} marked for report but does " "not exist.".format(f) ) if os.path.isfile(f): report_obj = get_flag_value(f, "report") category = Category(report_obj.category) results[category].append( FileRecord(f, job, report_obj.caption, env, category) ) recorded_files.add(f) for f in job.expanded_output: meta = persistence.metadata(f) if not meta: logger.warning( "Missing metadata for file {}. Maybe metadata " "was deleted or it was created using an older " "version of Snakemake. This is a non critical " "warning.".format(f) ) continue try: job_hash = meta["job_hash"] rule = meta["rule"] rec = records[(job_hash, rule)] rec.rule = rule rec.job = job rec.starttime = min(rec.starttime, meta["starttime"]) rec.endtime = max(rec.endtime, meta["endtime"]) rec.conda_env_file = None rec.conda_env = meta["conda_env"] rec.singularity_img_url = meta["singularity_img_url"] rec.output.append(f) except KeyError as e: print(e) logger.warning( "Metadata for file {} was created with a too " "old Snakemake version.".format(f) ) for catresults in results.values(): catresults.sort(key=lambda res: res.name) # prepare runtimes runtimes = [ {"rule": rec.rule, "runtime": rec.endtime - rec.starttime} for rec in sorted(records.values(), key=lambda rec: rec.rule) ] # prepare end times timeline = [ { "rule": rec.rule, "starttime": datetime.datetime.fromtimestamp(rec.starttime).isoformat(), "endtime": datetime.datetime.fromtimestamp(rec.endtime).isoformat(), } for rec in sorted(records.values(), key=lambda rec: rec.rule) ] # prepare per-rule information rules = defaultdict(list) for rec in records.values(): rule = RuleRecord(rec.job, rec) if rec.rule not in rules: rules[rec.rule].append(rule) else: merged = False for other in rules[rec.rule]: if rule == other: other.add(rec) merged = True break if not merged: rules[rec.rule].append(rule) # rulegraph rulegraph, xmax, ymax = rulegraph_d3_spec(dag) # configfiles configfiles = [ConfigfileRecord(f) for f in dag.workflow.configfiles] seen = set() files = [ seen.add(res.target) or res for cat in results.values() for res in cat if res.target not in seen ] rst_links = textwrap.dedent( """ .. _Results: #results .. _Rules: #rules .. _Statistics: #stats {% for cat, catresults in categories|dictsort %} .. _{{ cat.name }}: #{{ cat.id }} {% for res in files %} .. _{{ res.target }}: #{{ res.id }} {% endfor %} {% endfor %} .. _ """ ) for cat, catresults in results.items(): for res in catresults: res.render(env, rst_links, results, files) # global description text = "" if dag.workflow.report_text: with open(dag.workflow.report_text) as f: class Snakemake: config = dag.workflow.config text = f.read() + rst_links text = publish_parts( env.from_string(text).render( snakemake=Snakemake, categories=results, files=files ), writer_name="html", )["body"] # record time now = "{} {}".format(datetime.datetime.now().ctime(), time.tzname[0]) results_size = sum(res.size for cat in results.values() for res in cat) try: from pygments.formatters import HtmlFormatter except ImportError: raise WorkflowError( "Python package pygments must be installed to create reports." ) # render HTML template = env.get_template("report.html") with open(path, "w", encoding="utf-8") as out: out.write( template.render( results=results, results_size=results_size, configfiles=configfiles, text=text, rulegraph_nodes=rulegraph["nodes"], rulegraph_links=rulegraph["links"], rulegraph_width=xmax + 20, rulegraph_height=ymax + 20, runtimes=runtimes, timeline=timeline, rules=[rec for recs in rules.values() for rec in recs], version=__version__, now=now, pygments_css=HtmlFormatter(style="trac").get_style_defs(".source"), ) ) logger.info("Report created.") snakemake-5.10.0/snakemake/report/report.html000066400000000000000000000606441361131222100212110ustar00rootroot00000000000000 Snakemake Report

Loading Snakemake Report...

Please enable Javascript in your browser to see this report.

Loading {{ results_size|filesizeformat }}. For large reports, this can take a while.

Workflow

{{ text }}

Detailed software versions can be found under Rules.

Results

{% for cat, catresults in results|dictsort %}
{% for res in catresults %} {% endfor %}
Workflow resultes
File Size Description Job properties
{{ res.name }} {{ res.size|filesizeformat }} {{ res.caption }} {% if res.wildcards %} {% endif %} {% if res.params %} {% endif %}
Job properties
Rule{{ res.job.rule }}
Wildcards{{ res.wildcards }}
Params{{ res.params }}
{% endfor %}

Statistics

If the workflow has been executed in cluster/cloud, runtimes include the waiting time in the queue.
{% if configfiles %}

Configuration

{% for configfile in configfiles %} {% endfor %}
Configuration files
File Code
{{ configfile.name }}
{{ configfile.code()|safe }}
{% endif %}

Rules

{% for rule in rules %} {% endfor %}
Workflow rules
Rule Jobs Output Singularity Conda environment Code
{{ rule.name }} {{ rule.n_jobs }}
    {% for f in rule.output %}
  • {{ f }}
  • {% endfor %}
{{ rule.singularity_img_url if rule.singularity_img_url is not none }} {% if rule.conda_env %}
    {% for dep in rule.conda_env["dependencies"] %}
  • {{ dep }}
  • {% endfor %}
{% endif %}
{% if rule.code is not none %}
{{ rule.code()|safe }}
{% endif %}
snakemake-5.10.0/snakemake/report/table.html000066400000000000000000000037321361131222100207600ustar00rootroot00000000000000 {{ name }}
{% for colname in columns %} {% endfor %}
Tabular results
{{ colname|escape }}
snakemake-5.10.0/snakemake/resources.py000066400000000000000000000032101361131222100200430ustar00rootroot00000000000000import re class DefaultResources: def __init__(self, args=None): self.args = args def fallback(val): def callable(wildcards, input, attempt, threads, rulename): value = eval( val, {"input": input, "attempt": attempt, "threads": threads} ) return value return callable self.parsed = dict(_cores=1, _nodes=1) if self.args is not None: self.parsed.update(parse_resources(args, fallback=fallback)) def parse_resources(resources_args, fallback=None): """Parse resources from args.""" resources = dict() if resources_args is not None: valid = re.compile("[a-zA-Z_]\w*$") for res in resources_args: try: res, val = res.split("=") except ValueError: raise ValueError("Resources have to be defined as name=value pairs.") if not valid.match(res): raise ValueError( "Resource definition must start with a valid identifier." ) try: val = int(val) except ValueError: if fallback is not None: val = fallback(val) else: raise ValueError( "Resource definiton must contain an integer after the identifier." ) if res == "_cores": raise ValueError( "Resource _cores is already defined internally. Use a different name." ) resources[res] = val return resources snakemake-5.10.0/snakemake/rules.py000066400000000000000000001164071361131222100172000ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import re import sys import inspect import sre_constants import collections from urllib.parse import urljoin from pathlib import Path from itertools import chain from snakemake.io import ( IOFile, _IOFile, protected, temp, dynamic, Namedlist, AnnotatedString, contains_wildcard_constraints, update_wildcard_constraints, ) from snakemake.io import ( expand, InputFiles, OutputFiles, Wildcards, Params, Log, Resources, strip_wildcard_constraints, ) from snakemake.io import ( apply_wildcards, is_flagged, not_iterable, is_callable, DYNAMIC_FILL, ReportObject, ) from snakemake.exceptions import ( RuleException, IOFileException, WildcardError, InputFunctionException, WorkflowError, IncompleteCheckpointException, ) from snakemake.logging import logger from snakemake.common import Mode, lazy_property, TBDInt class Rule: def __init__(self, *args, lineno=None, snakefile=None, restart_times=0): """ Create a rule Arguments name -- the name of the rule """ if len(args) == 2: name, workflow = args self.name = name self.workflow = workflow self.docstring = None self.message = None self._input = InputFiles() self._output = OutputFiles() self._params = Params() self._wildcard_constraints = dict() self.dependencies = dict() self.dynamic_output = set() self.dynamic_input = set() self.temp_output = set() self.protected_output = set() self.touch_output = set() self.subworkflow_input = dict() self.shadow_depth = None self.resources = None self.priority = 0 self._version = None self._log = Log() self._benchmark = None self._conda_env = None self._singularity_img = None self.env_modules = None self.group = None self._wildcard_names = None self.lineno = lineno self.snakefile = snakefile self.run_func = None self.shellcmd = None self.script = None self.notebook = None self.wrapper = None self.cwl = None self.norun = False self.is_branched = False self.is_checkpoint = False self.restart_times = 0 self.basedir = None elif len(args) == 1: other = args[0] self.name = other.name self.workflow = other.workflow self.docstring = other.docstring self.message = other.message self._input = InputFiles(other._input) self._output = OutputFiles(other._output) self._params = Params(other._params) self._wildcard_constraints = dict(other._wildcard_constraints) self.dependencies = dict(other.dependencies) self.dynamic_output = set(other.dynamic_output) self.dynamic_input = set(other.dynamic_input) self.temp_output = set(other.temp_output) self.protected_output = set(other.protected_output) self.touch_output = set(other.touch_output) self.subworkflow_input = dict(other.subworkflow_input) self.shadow_depth = other.shadow_depth self.resources = other.resources self.priority = other.priority self.version = other.version self._log = other._log self._benchmark = other._benchmark self._conda_env = other._conda_env self._singularity_img = other._singularity_img self.env_modules = other.env_modules self.group = other.group self._wildcard_names = ( set(other._wildcard_names) if other._wildcard_names is not None else None ) self.lineno = other.lineno self.snakefile = other.snakefile self.run_func = other.run_func self.shellcmd = other.shellcmd self.script = other.script self.notebook = other.notebook self.wrapper = other.wrapper self.cwl = other.cwl self.norun = other.norun self.is_branched = True self.is_checkpoint = other.is_checkpoint self.restart_times = other.restart_times self.basedir = other.basedir def dynamic_branch(self, wildcards, input=True): def get_io(rule): return ( (rule.input, rule.dynamic_input) if input else (rule.output, rule.dynamic_output) ) def partially_expand(f, wildcards): """Expand the wildcards in f from the ones present in wildcards This is done by replacing all wildcard delimiters by `{{` or `}}` that are not in `wildcards.keys()`. """ # perform the partial expansion from f's string representation s = str(f).replace("{", "{{").replace("}", "}}") for key in wildcards.keys(): s = s.replace("{{{{{}}}}}".format(key), "{{{}}}".format(key)) # build result anno_s = AnnotatedString(s) anno_s.flags = f.flags return IOFile(anno_s, f.rule) io, dynamic_io = get_io(self) branch = Rule(self) io_, dynamic_io_ = get_io(branch) expansion = collections.defaultdict(list) for i, f in enumerate(io): if f in dynamic_io: f = partially_expand(f, wildcards) try: for e in reversed(expand(str(f), zip, **wildcards)): # need to clone the flags so intermediate # dynamic remote file paths are expanded and # removed appropriately ioFile = IOFile(e, rule=branch) ioFile.clone_flags(f) expansion[i].append(ioFile) except KeyError: return None # replace the dynamic files with the expanded files replacements = [(i, io[i], e) for i, e in reversed(list(expansion.items()))] for i, old, exp in replacements: dynamic_io_.remove(old) io_._insert_items(i, exp) if not input: for i, old, exp in replacements: if old in branch.temp_output: branch.temp_output.discard(old) branch.temp_output.update(exp) if old in branch.protected_output: branch.protected_output.discard(old) branch.protected_output.update(exp) if old in branch.touch_output: branch.touch_output.discard(old) branch.touch_output.update(exp) branch.wildcard_names.clear() non_dynamic_wildcards = dict( (name, values[0]) for name, values in wildcards.items() if len(set(values)) == 1 ) # TODO have a look into how to concretize dependencies here branch._input, _, branch.dependencies = branch.expand_input( non_dynamic_wildcards ) branch._output, _ = branch.expand_output(non_dynamic_wildcards) resources = branch.expand_resources(non_dynamic_wildcards, branch._input, 1) branch._params = branch.expand_params( non_dynamic_wildcards, branch._input, branch._output, resources, omit_callable=True, ) branch.resources = dict(resources.items()) branch._log = branch.expand_log(non_dynamic_wildcards) branch._benchmark = branch.expand_benchmark(non_dynamic_wildcards) branch._conda_env = branch.expand_conda_env(non_dynamic_wildcards) return branch, non_dynamic_wildcards return branch def check_caching(self): if self.name in self.workflow.cache_rules: if len(self.output) == 0: raise RuleException( "Rules without output files cannot be cached.", rule=self ) if len(self.output) > 1: prefixes = set(out.multiext_prefix for out in self.output) if None in prefixes or len(prefixes) > 1: raise RuleException( "Rules with multiple output files must define them as a single multiext() " '(e.g. multiext("path/to/index", ".bwt", ".ann")). ' "The rationale is that multiple output files can only be unambiously resolved " "if they can be distinguished by a fixed set of extensions (i.e. mime types).", rule=self, ) if self.dynamic_output: raise RuleException( "Rules with dynamic output files may not be cached.", rule=self ) def has_wildcards(self): """ Return True if rule contains wildcards. """ return bool(self.wildcard_names) @property def version(self): return self._version @version.setter def version(self, version): if isinstance(version, str) and "\n" in version: raise WorkflowError( "Version string may not contain line breaks.", rule=self ) self._version = version @property def benchmark(self): return self._benchmark @benchmark.setter def benchmark(self, benchmark): if isinstance(benchmark, Path): benchmark = str(benchmark) if not callable(benchmark): benchmark = self.apply_default_remote(benchmark) benchmark = self._update_item_wildcard_constraints(benchmark) self._benchmark = IOFile(benchmark, rule=self) self.register_wildcards(self._benchmark.get_wildcard_names()) @property def conda_env(self): return self._conda_env @conda_env.setter def conda_env(self, conda_env): self._conda_env = IOFile(conda_env, rule=self) @property def singularity_img(self): return self._singularity_img @singularity_img.setter def singularity_img(self, singularity_img): self._singularity_img = singularity_img @property def input(self): return self._input def set_input(self, *input, **kwinput): """ Add a list of input files. Recursive lists are flattened. Arguments input -- the list of input files """ for item in input: self._set_inoutput_item(item) for name, item in kwinput.items(): self._set_inoutput_item(item, name=name) @property def output(self): return self._output @property def products(self): if self.benchmark: return chain(self.output, self.log, [self.benchmark]) else: return chain(self.output, self.log) def register_wildcards(self, wildcard_names): if self._wildcard_names is None: self._wildcard_names = wildcard_names else: if self.wildcard_names != wildcard_names: raise SyntaxError( "Not all output, log and benchmark files of " "rule {} contain the same wildcards. " "This is crucial though, in order to " "avoid that two or more jobs write to the " "same file.".format(self.name) ) @property def wildcard_names(self): if self._wildcard_names is None: return set() return self._wildcard_names def set_output(self, *output, **kwoutput): """ Add a list of output files. Recursive lists are flattened. After creating the output files, they are checked for duplicates. Arguments output -- the list of output files """ for item in output: self._set_inoutput_item(item, output=True) for name, item in kwoutput.items(): self._set_inoutput_item(item, output=True, name=name) for item in self.output: if self.dynamic_output and item not in self.dynamic_output: raise SyntaxError( "A rule with dynamic output may not define any " "non-dynamic output files." ) self.register_wildcards(item.get_wildcard_names()) # Check output file name list for duplicates self.check_output_duplicates() self.check_caching() def check_output_duplicates(self): """Check ``Namedlist`` for duplicate entries and raise a ``WorkflowError`` on problems. """ seen = dict() idx = None for name, value in self.output._allitems(): if name is None: if idx is None: idx = 0 else: idx += 1 if value in seen: raise WorkflowError( "Duplicate output file pattern in rule {}. First two " "duplicate for entries {} and {}".format( self.name, seen[value], name or idx ) ) seen[value] = name or idx def apply_default_remote(self, item): def is_annotated_callable(value): if isinstance(value, AnnotatedString): return bool(value.callable) def apply(value): if ( not is_flagged(value, "remote_object") and not is_flagged(value, "local") and not is_annotated_callable(value) and self.workflow.default_remote_provider is not None ): return self.workflow.apply_default_remote(value) else: return value assert not callable(item) if isinstance(item, dict): return {k: apply(v) for k, v in item.items()} elif isinstance(item, collections.abc.Iterable) and not isinstance(item, str): return [apply(e) for e in item] else: return apply(item) def update_wildcard_constraints(self): for i in range(len(self.output)): item = self.output[i] newitem = IOFile( self._update_item_wildcard_constraints(self.output[i]), rule=self ) # the updated item has to have the same flags newitem.clone_flags(item) self.output[i] = newitem def _update_item_wildcard_constraints(self, item): if not (self.wildcard_constraints or self.workflow._wildcard_constraints): return item try: return update_wildcard_constraints( item, self.wildcard_constraints, self.workflow._wildcard_constraints ) except ValueError as e: raise IOFileException(str(e), snakefile=self.snakefile, lineno=self.lineno) def _set_inoutput_item(self, item, output=False, name=None): """ Set an item to be input or output. Arguments item -- the item inoutput -- a Namedlist of either input or output items name -- an optional name for the item """ inoutput = self.output if output else self.input # Check to see if the item is a path, if so, just make it a string if isinstance(item, Path): item = str(item) if isinstance(item, str): item = self.apply_default_remote(item) # Check to see that all flags are valid # Note that "remote", "dynamic", and "expand" are valid for both inputs and outputs. if isinstance(item, AnnotatedString): for flag in item.flags: if not output and flag in [ "protected", "temp", "temporary", "directory", "touch", "pipe", ]: logger.warning( "The flag '{}' used in rule {} is only valid for outputs, not inputs.".format( flag, self ) ) if output and flag in ["ancient"]: logger.warning( "The flag '{}' used in rule {} is only valid for inputs, not outputs.".format( flag, self ) ) # add the rule to the dependencies if isinstance(item, _IOFile) and item.rule and item in item.rule.output: self.dependencies[item] = item.rule if output: item = self._update_item_wildcard_constraints(item) else: if ( contains_wildcard_constraints(item) and self.workflow.mode != Mode.subprocess ): logger.warning("Wildcard constraints in inputs are ignored.") # record rule if this is an output file output _item = IOFile(item, rule=self) if is_flagged(item, "temp"): if output: self.temp_output.add(_item) if is_flagged(item, "protected"): if output: self.protected_output.add(_item) if is_flagged(item, "touch"): if output: self.touch_output.add(_item) if is_flagged(item, "dynamic"): if output: self.dynamic_output.add(_item) else: self.dynamic_input.add(_item) if is_flagged(item, "report"): report_obj = item.flags["report"] if report_obj.caption is not None: r = ReportObject( os.path.join(self.workflow.current_basedir, report_obj.caption), report_obj.category, ) item.flags["report"] = r if is_flagged(item, "subworkflow"): if output: raise SyntaxError("Only input files may refer to a subworkflow") else: # record the workflow this item comes from sub = item.flags["subworkflow"] if _item in self.subworkflow_input: other = self.subworkflow_input[_item] if sub != other: raise WorkflowError( "The input file {} is ambiguously " "associated with two subworkflows " "{} and {}.".format(item, sub, other), rule=self, ) self.subworkflow_input[_item] = sub inoutput.append(_item) if name: inoutput._add_name(name) elif callable(item): if output: raise SyntaxError("Only input files can be specified as functions") inoutput.append(item) if name: inoutput._add_name(name) else: try: start = len(inoutput) for i in item: self._set_inoutput_item(i, output=output) if name: # if the list was named, make it accessible inoutput._set_name(name, start, end=len(inoutput)) except TypeError: raise SyntaxError( "Input and output files have to be specified as strings or lists of strings." ) @property def params(self): return self._params def set_params(self, *params, **kwparams): for item in params: self._set_params_item(item) for name, item in kwparams.items(): self._set_params_item(item, name=name) def _set_params_item(self, item, name=None): self.params.append(item) if name: self.params._add_name(name) @property def wildcard_constraints(self): return self._wildcard_constraints def set_wildcard_constraints(self, **kwwildcard_constraints): self._wildcard_constraints.update(kwwildcard_constraints) @property def log(self): return self._log def set_log(self, *logs, **kwlogs): for item in logs: self._set_log_item(item) for name, item in kwlogs.items(): self._set_log_item(item, name=name) for item in self.log: self.register_wildcards(item.get_wildcard_names()) def _set_log_item(self, item, name=None): # Pathlib compatibility if isinstance(item, Path): item = str(item) if isinstance(item, str) or callable(item): if not callable(item): item = self.apply_default_remote(item) item = self._update_item_wildcard_constraints(item) self.log.append(IOFile(item, rule=self) if isinstance(item, str) else item) if name: self.log._add_name(name) else: try: start = len(self.log) for i in item: self._set_log_item(i) if name: self.log._set_name(name, start, end=len(self.log)) except TypeError: raise SyntaxError("Log files have to be specified as strings.") def check_wildcards(self, wildcards): missing_wildcards = self.wildcard_names - set(wildcards.keys()) if missing_wildcards: raise RuleException( "Could not resolve wildcards in rule {}:\n{}".format( self.name, "\n".join(self.wildcard_names) ), lineno=self.lineno, snakefile=self.snakefile, ) def apply_input_function( self, func, wildcards, incomplete_checkpoint_func=lambda e: None, raw_exceptions=False, **aux_params ): incomplete = False if isinstance(func, _IOFile): func = func._file.callable elif isinstance(func, AnnotatedString): func = func.callable sig = inspect.signature(func) _aux_params = {k: v for k, v in aux_params.items() if k in sig.parameters} try: value = func(Wildcards(fromdict=wildcards), **_aux_params) except IncompleteCheckpointException as e: value = incomplete_checkpoint_func(e) incomplete = True except (Exception, BaseException) as e: if raw_exceptions: raise e else: raise InputFunctionException(e, rule=self, wildcards=wildcards) return value, incomplete def _apply_wildcards( self, newitems, olditems, wildcards, concretize=None, check_return_type=True, omit_callable=False, mapping=None, no_flattening=False, aux_params=None, apply_default_remote=True, incomplete_checkpoint_func=lambda e: None, allow_unpack=True, ): if aux_params is None: aux_params = dict() for name, item in olditems._allitems(): start = len(newitems) is_unpack = is_flagged(item, "unpack") _is_callable = is_callable(item) if _is_callable: if omit_callable: continue item, incomplete = self.apply_input_function( item, wildcards, incomplete_checkpoint_func=incomplete_checkpoint_func, is_unpack=is_unpack, **aux_params ) if apply_default_remote: item = self.apply_default_remote(item) if is_unpack and not incomplete: if not allow_unpack: raise WorkflowError( "unpack() is not allowed with params. " "Simply return a dictionary which can be directly ." "used, e.g. via {params[mykey]}." ) # Sanity checks before interpreting unpack() if not isinstance(item, (list, dict)): raise WorkflowError( "Can only use unpack() on list and dict", rule=self ) if name: raise WorkflowError( "Cannot combine named input file with unpack()", rule=self ) # Allow streamlined code with/without unpack if isinstance(item, list): pairs = zip([None] * len(item), item) else: assert isinstance(item, dict) pairs = item.items() else: pairs = [(name, item)] for name, item in pairs: is_iterable = True if not_iterable(item) or no_flattening: item = [item] is_iterable = False for item_ in item: if check_return_type and not isinstance(item_, str): raise WorkflowError( "Function did not return str or list " "of str.", rule=self ) concrete = concretize(item_, wildcards, _is_callable) newitems.append(concrete) if mapping is not None: mapping[concrete] = item_ if name: newitems._set_name( name, start, end=len(newitems) if is_iterable else None ) start = len(newitems) def expand_input(self, wildcards): def concretize_iofile(f, wildcards, is_from_callable): if is_from_callable: if isinstance(f, Path): f = str(Path) return IOFile(f, rule=self).apply_wildcards( wildcards, fill_missing=f in self.dynamic_input, fail_dynamic=self.dynamic_output, ) else: return f.apply_wildcards( wildcards, fill_missing=f in self.dynamic_input, fail_dynamic=self.dynamic_output, ) def handle_incomplete_checkpoint(exception): """If checkpoint is incomplete, target it such that it is completed before this rule gets executed.""" return exception.targetfile input = InputFiles() mapping = dict() try: self._apply_wildcards( input, self.input, wildcards, concretize=concretize_iofile, mapping=mapping, incomplete_checkpoint_func=handle_incomplete_checkpoint, ) except WildcardError as e: raise WildcardError( "Wildcards in input files cannot be " "determined from output files:", str(e), rule=self, ) if self.dependencies: dependencies = { f: self.dependencies[f_] for f, f_ in mapping.items() if f_ in self.dependencies } if None in self.dependencies: dependencies[None] = self.dependencies[None] else: dependencies = self.dependencies for f in input: f.check() return input, mapping, dependencies def expand_params(self, wildcards, input, output, resources, omit_callable=False): def concretize_param(p, wildcards, is_from_callable): if not is_from_callable: if isinstance(p, str): return apply_wildcards(p, wildcards) if isinstance(p, list): return [ (apply_wildcards(v, wildcards) if isinstance(v, str) else v) for v in p ] return p params = Params() try: # When applying wildcards to params, the return type need not be # a string, so the check is disabled. self._apply_wildcards( params, self.params, wildcards, concretize=concretize_param, check_return_type=False, omit_callable=omit_callable, allow_unpack=False, no_flattening=True, apply_default_remote=False, aux_params={ "input": input._plainstrings(), "resources": resources, "output": output._plainstrings(), "threads": resources._cores, }, incomplete_checkpoint_func=lambda e: "", ) except WildcardError as e: raise WildcardError( "Wildcards in params cannot be " "determined from output files. Note that you have " "to use a function to deactivate automatic wildcard expansion " "in params strings, e.g., `lambda wildcards: '{test}'`. Also " "see https://snakemake.readthedocs.io/en/stable/snakefiles/" "rules.html#non-file-parameters-for-rules:", str(e), rule=self, ) return params def expand_output(self, wildcards): output = OutputFiles(o.apply_wildcards(wildcards) for o in self.output) output._take_names(self.output._get_names()) mapping = {f: f_ for f, f_ in zip(output, self.output)} for f in output: f.check() # Note that we do not need to check for duplicate file names after # expansion as all output patterns have contain all wildcards anyway. return output, mapping def expand_log(self, wildcards): def concretize_logfile(f, wildcards, is_from_callable): if is_from_callable: return IOFile(f, rule=self) else: return f.apply_wildcards( wildcards, fill_missing=False, fail_dynamic=self.dynamic_output ) log = Log() try: self._apply_wildcards( log, self.log, wildcards, concretize=concretize_logfile ) except WildcardError as e: raise WildcardError( "Wildcards in log files cannot be " "determined from output files:", str(e), rule=self, ) for f in log: f.check() return log def expand_benchmark(self, wildcards): try: benchmark = ( self.benchmark.apply_wildcards(wildcards) if self.benchmark else None ) except WildcardError as e: raise WildcardError( "Wildcards in benchmark file cannot be " "determined from output files:", str(e), rule=self, ) if benchmark is not None: benchmark.check() return benchmark def expand_resources(self, wildcards, input, attempt): resources = dict() def apply(name, res, threads=None): if callable(res): aux = dict(rulename=self.name) if threads: aux["threads"] = threads try: try: res, _ = self.apply_input_function( res, wildcards, input=input, attempt=attempt, incomplete_checkpoint_func=lambda e: 0, raw_exceptions=True, **aux ) except FileNotFoundError as e: # Resources can depend on input files. Since expansion can happen during dryrun, # where input files are not yet present, we need to skip such resources and # mark them as [TBD]. if e.filename in input: # use zero for resource if it cannot yet be determined res = TBDInt(0) else: raise e except e: raise InputFunctionException(e, rule=self, wildcards=wildcards) if not isinstance(res, int): raise WorkflowError( "Resources function did not return int.", rule=self ) res = min(self.workflow.global_resources.get(name, res), res) return res threads = apply("_cores", self.resources["_cores"]) resources["_cores"] = threads for name, res in self.resources.items(): if name != "_cores": resources[name] = apply(name, res, threads=threads) resources = Resources(fromdict=resources) return resources def expand_group(self, wildcards): """Expand the group given wildcards.""" if callable(self.group): item, _ = self.apply_input_function(self.group, wildcards) return item elif isinstance(self.group, str): return apply_wildcards(self.group, wildcards, dynamic_fill=DYNAMIC_FILL) else: return self.group def expand_conda_env(self, wildcards): try: conda_env = ( self.conda_env.apply_wildcards(wildcards) if self.conda_env else None ) except WildcardError as e: raise WildcardError( "Wildcards in conda environment file cannot be " "determined from output files:", str(e), rule=self, ) if conda_env is not None: conda_env.check() return conda_env def is_producer(self, requested_output): """ Returns True if this rule is a producer of the requested output. """ try: for o in self.products: if o.match(requested_output): return True return False except sre_constants.error as ex: raise IOFileException( "{} in wildcard statement".format(ex), snakefile=self.snakefile, lineno=self.lineno, ) except ValueError as ex: raise IOFileException( "{}".format(ex), snakefile=self.snakefile, lineno=self.lineno ) def get_wildcards(self, requested_output): """ Return wildcard dictionary by matching regular expression output files to the requested concrete ones. Arguments requested_output -- a concrete filepath """ if requested_output is None: return dict() bestmatchlen = 0 bestmatch = None for o in self.products: match = o.match(requested_output) if match: l = self.get_wildcard_len(match.groupdict()) if not bestmatch or bestmatchlen > l: bestmatch = match.groupdict() bestmatchlen = l self.check_wildcards(bestmatch) return bestmatch @staticmethod def get_wildcard_len(wildcards): """ Return the length of the given wildcard values. Arguments wildcards -- a dict of wildcards """ return sum(map(len, wildcards.values())) def __lt__(self, rule): comp = self.workflow._ruleorder.compare(self, rule) return comp < 0 def __gt__(self, rule): comp = self.workflow._ruleorder.compare(self, rule) return comp > 0 def __str__(self): return self.name def __hash__(self): return self.name.__hash__() def __eq__(self, other): return self.name == other.name and self.output == other.output class Ruleorder: def __init__(self): self.order = list() def add(self, *rulenames): """ Records the order of given rules as rule1 > rule2 > rule3, ... """ self.order.append(list(rulenames)) def compare(self, rule1, rule2): """ Return whether rule2 has a higher priority than rule1. """ # if rules have the same name, they have been specialized by dynamic output # in that case, clauses are irrelevant and have to be skipped if rule1.name != rule2.name: # try the last clause first, # i.e. clauses added later overwrite those before. for clause in reversed(self.order): try: i = clause.index(rule1.name) j = clause.index(rule2.name) # rules with higher priority should have a smaller index comp = j - i if comp < 0: comp = -1 elif comp > 0: comp = 1 return comp except ValueError: pass # if no ruleorder given, prefer rule without wildcards wildcard_cmp = rule2.has_wildcards() - rule1.has_wildcards() if wildcard_cmp != 0: return wildcard_cmp return 0 def __iter__(self): return self.order.__iter__() class RuleProxy: def __init__(self, rule): self.rule = rule @lazy_property def output(self): return self._to_iofile(self.rule.output) @lazy_property def input(self): return self.rule.input._stripped_constraints() @lazy_property def params(self): return self.rule.params._clone() @property def benchmark(self): return IOFile(strip_wildcard_constraints(self.rule.benchmark), rule=self.rule) @lazy_property def log(self): return self._to_iofile(self.rule.log) def _to_iofile(self, files): def cleanup(f): prefix = self.rule.workflow.default_remote_prefix # remove constraints and turn this into a plain string cleaned = strip_wildcard_constraints(f) if ( self.rule.workflow.default_remote_provider is not None and f.startswith(prefix) and not is_flagged(f, "local") ): cleaned = f[len(prefix) + 1 :] cleaned = IOFile(cleaned, rule=self.rule) else: cleaned = IOFile(AnnotatedString(cleaned), rule=self.rule) cleaned.clone_remote_object(f) return cleaned files = Namedlist(files, custom_map=cleanup) return files snakemake-5.10.0/snakemake/scheduler.py000066400000000000000000000507671361131222100200320ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os, signal, sys import threading import operator from functools import partial from collections import defaultdict from itertools import chain, accumulate from contextlib import ContextDecorator import time from snakemake.executors import DryrunExecutor, TouchExecutor, CPUExecutor from snakemake.executors import ( GenericClusterExecutor, SynchronousClusterExecutor, DRMAAExecutor, KubernetesExecutor, TibannaExecutor, ) from snakemake.exceptions import RuleException, WorkflowError, print_exception from snakemake.shell import shell from snakemake.logging import logger from fractions import Fraction def cumsum(iterable, zero=[0]): return list(chain(zero, accumulate(iterable))) _ERROR_MSG_FINAL = ( "Exiting because a job execution failed. " "Look above for error message" ) class DummyRateLimiter(ContextDecorator): def __enter__(self): return self def __exit__(self, *args): return False class JobScheduler: def __init__( self, workflow, dag, cores, local_cores=1, dryrun=False, touch=False, cluster=None, cluster_status=None, cluster_config=None, cluster_sync=None, drmaa=None, drmaa_log_dir=None, kubernetes=None, kubernetes_envvars=None, container_image=None, tibanna=None, tibanna_sfn=None, precommand="", jobname=None, quiet=False, printreason=False, printshellcmds=False, keepgoing=False, max_jobs_per_second=None, max_status_checks_per_second=100, latency_wait=3, greediness=1.0, force_use_threads=False, assume_shared_fs=True, keepincomplete=False, ): """ Create a new instance of KnapsackJobScheduler. """ from ratelimiter import RateLimiter self.cluster = cluster self.cluster_config = cluster_config self.cluster_sync = cluster_sync self.dag = dag self.workflow = workflow self.dryrun = dryrun self.touch = touch self.quiet = quiet self.keepgoing = keepgoing self.running = set() self.failed = set() self.finished_jobs = 0 self.greediness = 1 self.max_jobs_per_second = max_jobs_per_second self.keepincomplete = keepincomplete self.resources = dict(self.workflow.global_resources) use_threads = ( force_use_threads or (os.name != "posix") or cluster or cluster_sync or drmaa ) self._open_jobs = threading.Semaphore(0) self._lock = threading.Lock() self._errors = False self._finished = False self._job_queue = None self._submit_callback = self._noop self._finish_callback = partial( self._proceed, update_dynamic=not self.dryrun, print_progress=not self.quiet and not self.dryrun, ) self._local_executor = None if dryrun: self._executor = DryrunExecutor( workflow, dag, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, ) elif touch: self._executor = TouchExecutor( workflow, dag, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, ) elif cluster or cluster_sync or (drmaa is not None): if not workflow.immediate_submit: # No local jobs when using immediate submit! # Otherwise, they will fail due to missing input self._local_executor = CPUExecutor( workflow, dag, local_cores, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cores=local_cores, keepincomplete=keepincomplete, ) if cluster or cluster_sync: if cluster_sync: constructor = SynchronousClusterExecutor else: constructor = partial( GenericClusterExecutor, statuscmd=cluster_status, max_status_checks_per_second=max_status_checks_per_second, ) self._executor = constructor( workflow, dag, None, submitcmd=(cluster or cluster_sync), cluster_config=cluster_config, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, assume_shared_fs=assume_shared_fs, keepincomplete=keepincomplete, ) if workflow.immediate_submit: self._submit_callback = partial( self._proceed, update_dynamic=False, print_progress=False, update_resources=False, handle_job_success=False, ) else: self._executor = DRMAAExecutor( workflow, dag, None, drmaa_args=drmaa, drmaa_log_dir=drmaa_log_dir, jobname=jobname, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, assume_shared_fs=assume_shared_fs, max_status_checks_per_second=max_status_checks_per_second, keepincomplete=keepincomplete, ) elif kubernetes: self._local_executor = CPUExecutor( workflow, dag, local_cores, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cores=local_cores, keepincomplete=keepincomplete, ) self._executor = KubernetesExecutor( workflow, dag, kubernetes, kubernetes_envvars, container_image=container_image, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, cluster_config=cluster_config, keepincomplete=keepincomplete, ) elif tibanna: self._local_executor = CPUExecutor( workflow, dag, local_cores, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, use_threads=use_threads, latency_wait=latency_wait, cores=local_cores, keepincomplete=keepincomplete, ) self._executor = TibannaExecutor( workflow, dag, cores, tibanna_sfn, precommand=precommand, container_image=container_image, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, latency_wait=latency_wait, keepincomplete=keepincomplete, ) else: self._executor = CPUExecutor( workflow, dag, cores, printreason=printreason, quiet=quiet, printshellcmds=printshellcmds, use_threads=use_threads, latency_wait=latency_wait, cores=cores, keepincomplete=keepincomplete, ) if self.max_jobs_per_second and not self.dryrun: max_jobs_frac = Fraction(self.max_jobs_per_second).limit_denominator() self.rate_limiter = RateLimiter( max_calls=max_jobs_frac.numerator, period=max_jobs_frac.denominator ) else: # essentially no rate limit self.rate_limiter = DummyRateLimiter() self._user_kill = None signal.signal(signal.SIGTERM, self.exit_gracefully) self._open_jobs.release() @property def stats(self): try: return self._executor.stats except AttributeError: raise TypeError("Executor does not support stats") def candidate(self, job): """ Return whether a job is a candidate to be executed. """ return ( job not in self.running and job not in self.failed and (self.dryrun or (not job.dynamic_input and not self.dag.dynamic(job))) ) @property def open_jobs(self): """ Return open jobs. """ return filter(self.candidate, list(job for job in self.dag.ready_jobs)) def schedule(self): """ Schedule jobs that are ready, maximizing cpu usage. """ try: while True: # work around so that the wait does not prevent keyboard interrupts # while not self._open_jobs.acquire(False): # time.sleep(1) self._open_jobs.acquire() # obtain needrun and running jobs in a thread-safe way with self._lock: needrun = list(self.open_jobs) running = list(self.running) errors = self._errors user_kill = self._user_kill # handle errors if user_kill or (not self.keepgoing and errors): if user_kill == "graceful": logger.info( "Will exit after finishing " "currently running jobs." ) if not running: logger.info("Shutting down, this might take some time.") self._executor.shutdown() if not user_kill: logger.error(_ERROR_MSG_FINAL) return False continue # normal shutdown because all jobs have been finished if not needrun and (not running or self.workflow.immediate_submit): self._executor.shutdown() if errors: logger.error(_ERROR_MSG_FINAL) return not errors # continue if no new job needs to be executed if not needrun: continue # select jobs by solving knapsack problem (omit with dryrun) if self.dryrun: run = needrun else: logger.debug( "Resources before job selection: {}".format(self.resources) ) logger.debug( "Ready jobs ({}):\n\t".format(len(needrun)) + "\n\t".join(map(str, needrun)) ) run = self.job_selector(needrun) logger.debug( "Selected jobs ({}):\n\t".format(len(run)) + "\n\t".join(map(str, run)) ) logger.debug( "Resources after job selection: {}".format(self.resources) ) # update running jobs with self._lock: self.running.update(run) # actually run jobs for job in run: with self.rate_limiter: self.run(job) except (KeyboardInterrupt, SystemExit): logger.info( "Terminating processes on user request, this might take some time." ) self._executor.cancel() return False def get_executor(self, job): if self._local_executor is None: return self._executor else: return self._local_executor if job.is_local else self._executor def run(self, job): self.get_executor(job).run( job, callback=self._finish_callback, submit_callback=self._submit_callback, error_callback=self._error, ) def _noop(self, job): pass def _free_resources(self, job): for name, value in job.resources.items(): if name in self.resources: value = self.calc_resource(name, value) self.resources[name] += value def _proceed( self, job, update_dynamic=True, print_progress=False, update_resources=True, handle_job_success=True, ): """ Do stuff after job is finished. """ with self._lock: if handle_job_success: # by calling this behind the lock, we avoid race conditions try: self.get_executor(job).handle_job_success(job) except (RuleException, WorkflowError) as e: # if an error occurs while processing job output, # we do the same as in case of errors during execution print_exception(e, self.workflow.linemaps) self._handle_error(job) return try: self.dag.finish(job, update_dynamic=update_dynamic) except (RuleException, WorkflowError) as e: # if an error occurs while processing job output, # we do the same as in case of errors during execution print_exception(e, self.workflow.linemaps) self._handle_error(job) return if update_resources: # normal jobs have len=1, group jobs have len>1 self.finished_jobs += len(job) self.running.remove(job) self._free_resources(job) if print_progress: if job.is_group(): for j in job: logger.job_finished(jobid=j.jobid) else: logger.job_finished(jobid=job.jobid) self.progress() if ( any(self.open_jobs) or not self.running or self.workflow.immediate_submit ): # go on scheduling if open jobs are ready or no job is running self._open_jobs.release() def _error(self, job): with self._lock: self._handle_error(job) def _handle_error(self, job): """Clear jobs and stop the workflow. If Snakemake is configured to restart jobs then the job might have "restart_times" left and we just decrement and let the scheduler try to run the job again. """ self.get_executor(job).handle_job_error(job) self.running.remove(job) self._free_resources(job) # attempt starts counting from 1, but the first attempt is not # a restart, hence we subtract 1. if job.restart_times > job.attempt - 1: logger.info("Trying to restart job {}.".format(self.dag.jobid(job))) job.attempt += 1 else: self._errors = True self.failed.add(job) if self.keepgoing: logger.info("Job failed, going on with independent jobs.") self._open_jobs.release() def exit_gracefully(self, *args): with self._lock: self._user_kill = "graceful" self._open_jobs.release() def job_selector(self, jobs): """ Using the greedy heuristic from "A Greedy Algorithm for the General Multidimensional Knapsack Problem", Akcay, Li, Xu, Annals of Operations Research, 2012 Args: jobs (list): list of jobs """ with self._lock: # each job is an item with one copy (0-1 MDKP) n = len(jobs) x = [0] * n # selected jobs E = set(range(n)) # jobs still free to select u = [1] * n a = list(map(self.job_weight, jobs)) # resource usage of jobs c = list(map(self.job_reward, jobs)) # job rewards def calc_reward(): return [c_j * y_j for c_j, y_j in zip(c, y)] b = [ self.resources[name] for name in self.workflow.global_resources ] # resource capacities while True: # Step 2: compute effective capacities y = [ ( min( (min(u[j], b_i // a_j_i) if a_j_i > 0 else u[j]) for b_i, a_j_i in zip(b, a[j]) if a_j_i ) if j in E else 0 ) for j in range(n) ] if not any(y): break y = [ (max(1, int(self.greediness * y_j)) if y_j > 0 else 0) for y_j in y ] # Step 3: compute rewards on cumulative sums reward = calc_reward() j_sel = max(E, key=reward.__getitem__) # argmax # Step 4: batch increment y_sel = y[j_sel] # Step 5: update information x[j_sel] += y_sel b = [b_i - (a_j_i * y_sel) for b_i, a_j_i in zip(b, a[j_sel])] u[j_sel] -= y_sel if not u[j_sel] or self.greediness == 1: E.remove(j_sel) if not E: break solution = [job for job, sel in zip(jobs, x) if sel] # update resources for name, b_i in zip(self.workflow.global_resources, b): self.resources[name] = b_i return solution def calc_resource(self, name, value): gres = self.workflow.global_resources[name] if value > gres: if name == "_cores": name = "threads" raise WorkflowError( "Job needs {name}={res} but only {name}={gres} " "are available. This is likely because two " "jobs are connected via a pipe and have to run " "simultaneously. Consider providing more " "resources (e.g. via --cores).".format(name=name, res=value, gres=gres) ) return value def rule_weight(self, rule): res = rule.resources return [ self.calc_resource(name, res.get(name, 0)) for name in self.workflow.global_resources ] def job_weight(self, job): res = job.resources return [ self.calc_resource(name, res.get(name, 0)) for name in self.workflow.global_resources ] def job_reward(self, job): if self.touch or self.dryrun or self.workflow.immediate_submit: temp_size = 0 input_size = 0 else: temp_size = self.dag.temp_size(job) input_size = job.inputsize # Usually, this should guide the scheduler to first schedule all jobs # that remove the largest temp file, then the second largest and so on. # Since the weight is summed up, it can in theory be that it sometimes # prefers a set of many jobs that all depend on smaller temp files though. # A real solution to the problem is therefore to use dummy jobs that # ensure selection of groups of jobs that together delete the same temp # file. return (job.priority, temp_size, input_size) def progress(self): """ Display the progress. """ logger.progress(done=self.finished_jobs, total=len(self.dag)) snakemake-5.10.0/snakemake/script.py000066400000000000000000000652611361131222100173530ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import inspect import itertools import os import tempfile import textwrap import sys import pickle import subprocess import collections import re from abc import ABC, abstractmethod from urllib.request import urlopen, pathname2url from urllib.error import URLError from snakemake.utils import format from snakemake.logging import logger from snakemake.exceptions import WorkflowError from snakemake.shell import shell from snakemake.common import MIN_PY_VERSION, SNAKEMAKE_SEARCHPATH from snakemake.io import git_content, split_git_path from snakemake.deployment import singularity PY_VER_RE = re.compile("Python (?P\d+\.\d+).*") # TODO use this to find the right place for inserting the preamble PY_PREAMBLE_RE = re.compile(r"from( )+__future__( )+import.*?(?P[;\n])") class Snakemake: def __init__( self, input_, output, params, wildcards, threads, resources, log, config, rulename, bench_iteration, scriptdir=None, ): # convert input and output to plain strings as some remote objects cannot # be pickled self.input = input_._plainstrings() self.output = output._plainstrings() self.params = params self.wildcards = wildcards self.threads = threads self.resources = resources self.log = log._plainstrings() self.config = config self.rule = rulename self.bench_iteration = bench_iteration self.scriptdir = scriptdir def log_fmt_shell(self, stdout=True, stderr=True, append=False): """ Return a shell redirection string to be used in `shell()` calls This function allows scripts and wrappers support optional `log` files specified in the calling rule. If no `log` was specified, then an empty string "" is returned, regardless of the values of `stdout`, `stderr`, and `append`. Parameters --------- stdout : bool Send stdout to log stderr : bool Send stderr to log append : bool Do not overwrite the log file. Useful for sending output of multiple commands to the same log. Note however that the log will not be truncated at the start. The following table describes the output: -------- -------- -------- ----- ------------- stdout stderr append log return value -------- -------- -------- ----- ------------ True True True fn >> fn 2>&1 True False True fn >> fn False True True fn 2>> fn True True False fn > fn 2>&1 True False False fn > fn False True False fn 2> fn any any any None "" -------- -------- -------- ----- ----------- """ if not self.log: return "" lookup = { (True, True, True): " >> {0} 2>&1", (True, False, True): " >> {0}", (False, True, True): " 2>> {0}", (True, True, False): " > {0} 2>&1", (True, False, False): " > {0}", (False, True, False): " 2> {0}", } return lookup[(stdout, stderr, append)].format(self.log) class REncoder: """Encoding Pyton data structures into R.""" @classmethod def encode_numeric(cls, value): if value is None: return "as.numeric(NA)" return str(value) @classmethod def encode_value(cls, value): if value is None: return "NULL" elif isinstance(value, str): return repr(value) elif isinstance(value, dict): return cls.encode_dict(value) elif isinstance(value, bool): return "TRUE" if value else "FALSE" elif isinstance(value, int) or isinstance(value, float): return str(value) elif isinstance(value, collections.abc.Iterable): # convert all iterables to vectors return cls.encode_list(value) else: # Try to convert from numpy if numpy is present try: import numpy as np if isinstance(value, np.number): return str(value) except ImportError: pass raise ValueError("Unsupported value for conversion into R: {}".format(value)) @classmethod def encode_list(cls, l): return "c({})".format(", ".join(map(cls.encode_value, l))) @classmethod def encode_items(cls, items): def encode_item(item): name, value = item return '"{}" = {}'.format(name, cls.encode_value(value)) return ", ".join(map(encode_item, items)) @classmethod def encode_dict(cls, d): d = "list({})".format(cls.encode_items(d.items())) return d @classmethod def encode_namedlist(cls, namedlist): positional = ", ".join(map(cls.encode_value, namedlist)) named = cls.encode_items(namedlist.items()) source = "list(" if positional: source += positional if named: source += ", " + named source += ")" return source class JuliaEncoder: """Encoding Pyton data structures into Julia.""" @classmethod def encode_value(cls, value): if value is None: return "nothing" elif isinstance(value, str): return repr(value) elif isinstance(value, dict): return cls.encode_dict(value) elif isinstance(value, bool): return "true" if value else "false" elif isinstance(value, int) or isinstance(value, float): return str(value) elif isinstance(value, collections.abc.Iterable): # convert all iterables to vectors return cls.encode_list(value) else: # Try to convert from numpy if numpy is present try: import numpy as np if isinstance(value, np.number): return str(value) except ImportError: pass raise ValueError( "Unsupported value for conversion into Julia: {}".format(value) ) @classmethod def encode_list(cls, l): return "[{}]".format(", ".join(map(cls.encode_value, l))) @classmethod def encode_items(cls, items): def encode_item(item): name, value = item return '"{}" => {}'.format(name, cls.encode_value(value)) return ", ".join(map(encode_item, items)) @classmethod def encode_positional_items(cls, namedlist): encoded = "" for index, value in enumerate(namedlist): encoded += "{} => {}, ".format(index + 1, cls.encode_value(value)) return encoded @classmethod def encode_dict(cls, d): d = "Dict({})".format(cls.encode_items(d.items())) return d @classmethod def encode_namedlist(cls, namedlist): positional = cls.encode_positional_items(namedlist) named = cls.encode_items(namedlist.items()) source = "Dict(" if positional: source += positional if named: source += named source += ")" return source class ScriptBase(ABC): def __init__( self, path, source, basedir, input_, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ): self.path = path self.source = source self.basedir = basedir self.input = input_ self.output = output self.params = params self.wildcards = wildcards self.threads = threads self.resources = resources self.log = log self.config = config self.rulename = rulename self.conda_env = conda_env self.singularity_img = singularity_img self.singularity_args = singularity_args self.env_modules = env_modules self.bench_record = bench_record self.jobid = jobid self.bench_iteration = bench_iteration self.cleanup_scripts = cleanup_scripts self.shadow_dir = shadow_dir def evaluate(self): fd = None try: # generate preamble preamble = self.get_preamble() # write script dir_ = ".snakemake/scripts" os.makedirs(dir_, exist_ok=True) with tempfile.NamedTemporaryFile( suffix="." + os.path.basename(self.path), dir=dir_, delete=False ) as fd: self.write_script(preamble, fd) # execute script self.execute_script(fd.name) except URLError as e: raise WorkflowError(e) finally: if fd and self.cleanup_scripts: os.remove(fd.name) else: if fd: logger.warning("Not cleaning up %s" % fd.name) else: # nothing to clean up (TODO: ??) pass @abstractmethod def get_preamble(self): ... @abstractmethod def write_script(self, preamble, fd): ... @abstractmethod def execute_script(self, fname): ... def _execute_cmd(self, cmd, **kwargs): shell( cmd, bench_record=self.bench_record, conda_env=self.conda_env, singularity_img=self.singularity_img, shadow_dir=self.shadow_dir, env_modules=self.env_modules, **kwargs ) class PythonScript(ScriptBase): @staticmethod def generate_preamble( path, source, basedir, input_, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, preamble_addendum="", ): wrapper_path = path[7:] if path.startswith("file://") else path snakemake = Snakemake( input_, output, params, wildcards, threads, resources, log, config, rulename, bench_iteration, os.path.dirname(wrapper_path), ) snakemake = pickle.dumps(snakemake) # Obtain search path for current snakemake module. # The module is needed for unpickling in the script. # We append it at the end (as a fallback). searchpath = SNAKEMAKE_SEARCHPATH if singularity_img is not None: searchpath = singularity.SNAKEMAKE_MOUNTPOINT searchpath = repr(searchpath) # For local scripts, add their location to the path in case they use path-based imports if path.startswith("file://"): searchpath += ", " + repr(os.path.dirname(path[7:])) return textwrap.dedent( """ ######## Snakemake header ######## import sys; sys.path.extend([{searchpath}]); import pickle; snakemake = pickle.loads({snakemake}); from snakemake.logging import logger; logger.printshellcmds = {printshellcmds}; {preamble_addendum} ######## Original script ######### """ ).format( searchpath=searchpath, snakemake=snakemake, printshellcmds=logger.printshellcmds, preamble_addendum=preamble_addendum, ) def get_preamble(self): wrapper_path = self.path[7:] if self.path.startswith("file://") else self.path preamble_addendum = "__real_file__ = __file__; __file__ = {file_override};".format( file_override=repr(os.path.realpath(wrapper_path)) ) return PythonScript.generate_preamble( self.path, self.source, self.basedir, self.input, self.output, self.params, self.wildcards, self.threads, self.resources, self.log, self.config, self.rulename, self.conda_env, self.singularity_img, self.singularity_args, self.env_modules, self.bench_record, self.jobid, self.bench_iteration, self.cleanup_scripts, self.shadow_dir, preamble_addendum=preamble_addendum, ) def write_script(self, preamble, fd): fd.write(preamble.encode()) fd.write(self.source) def execute_script(self, fname): py_exec = sys.executable if self.conda_env is not None: py = os.path.join(self.conda_env, "bin", "python") if os.path.exists(py): out = subprocess.check_output( [py, "--version"], stderr=subprocess.STDOUT, universal_newlines=True ) ver = tuple(map(int, PY_VER_RE.match(out).group("ver_min").split("."))) if ver >= MIN_PY_VERSION: # Python version is new enough, make use of environment # to execute script py_exec = "python" else: logger.warning( "Conda environment defines Python " "version < {0}.{1}. Using Python of the " "master process to execute " "script. Note that this cannot be avoided, " "because the script uses data structures from " "Snakemake which are Python >={0}.{1} " "only.".format(*MIN_PY_VERSION) ) if self.singularity_img is not None: # use python from image py_exec = "python" if self.env_modules is not None: # use python from environment module py_exec = "python" # use the same Python as the running process or the one from the environment self._execute_cmd("{py_exec} {fname:q}", py_exec=py_exec, fname=fname) class RScript(ScriptBase): @staticmethod def generate_preamble( path, source, basedir, input_, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, preamble_addendum="", ): return textwrap.dedent( """ ######## Snakemake header ######## library(methods) Snakemake <- setClass( "Snakemake", slots = c( input = "list", output = "list", params = "list", wildcards = "list", threads = "numeric", log = "list", resources = "list", config = "list", rule = "character", bench_iteration = "numeric", scriptdir = "character", source = "function" ) ) snakemake <- Snakemake( input = {}, output = {}, params = {}, wildcards = {}, threads = {}, log = {}, resources = {}, config = {}, rule = {}, bench_iteration = {}, scriptdir = {}, source = function(...){{ wd <- getwd() setwd(snakemake@scriptdir) source(...) setwd(wd) }} ) {preamble_addendum} ######## Original script ######### """ ).format( REncoder.encode_namedlist(input_), REncoder.encode_namedlist(output), REncoder.encode_namedlist(params), REncoder.encode_namedlist(wildcards), threads, REncoder.encode_namedlist(log), REncoder.encode_namedlist( { name: value for name, value in resources.items() if name != "_cores" and name != "_nodes" } ), REncoder.encode_dict(config), REncoder.encode_value(rulename), REncoder.encode_numeric(bench_iteration), REncoder.encode_value( os.path.dirname(path[7:]) if path.startswith("file://") else os.path.dirname(path) ), preamble_addendum=preamble_addendum, ) def get_preamble(self): return RScript.generate_preamble( self.path, self.source, self.basedir, self.input, self.output, self.params, self.wildcards, self.threads, self.resources, self.log, self.config, self.rulename, self.conda_env, self.singularity_img, self.singularity_args, self.env_modules, self.bench_record, self.jobid, self.bench_iteration, self.cleanup_scripts, self.shadow_dir, ) def write_script(self, preamble, fd): fd.write(preamble.encode()) fd.write(self.source) def execute_script(self, fname): if self.conda_env is not None and "R_LIBS" in os.environ: logger.warning( "R script job uses conda environment but " "R_LIBS environment variable is set. This " "is likely not intended, as R_LIBS can " "interfere with R packages deployed via " "conda. Consider running `unset R_LIBS` or " "remove it entirely before executing " "Snakemake." ) self._execute_cmd("Rscript --vanilla {fname:q}", fname=fname) class RMarkdown(ScriptBase): def get_preamble(self): return textwrap.dedent( """ ######## Snakemake header ######## library(methods) Snakemake <- setClass( "Snakemake", slots = c( input = "list", output = "list", params = "list", wildcards = "list", threads = "numeric", log = "list", resources = "list", config = "list", rule = "character", bench_iteration = "numeric", scriptdir = "character", source = "function" ) ) snakemake <- Snakemake( input = {}, output = {}, params = {}, wildcards = {}, threads = {}, log = {}, resources = {}, config = {}, rule = {}, bench_iteration = {}, scriptdir = {}, source = function(...){{ wd <- getwd() setwd(snakemake@scriptdir) source(...) setwd(wd) }} ) ######## Original script ######### """ ).format( REncoder.encode_namedlist(self.input), REncoder.encode_namedlist(self.output), REncoder.encode_namedlist(self.params), REncoder.encode_namedlist(self.wildcards), self.threads, REncoder.encode_namedlist(self.log), REncoder.encode_namedlist( { name: value for name, value in self.resources.items() if name != "_cores" and name != "_nodes" } ), REncoder.encode_dict(self.config), REncoder.encode_value(self.rulename), REncoder.encode_numeric(self.bench_iteration), REncoder.encode_value( os.path.dirname(self.path[7:]) if self.path.startswith("file://") else os.path.dirname(self.path) ), ) def write_script(self, preamble, fd): # Insert Snakemake object after the RMarkdown header code = self.source.decode() pos = next(itertools.islice(re.finditer(r"---\n", code), 1, 2)).start() + 3 fd.write(str.encode(code[:pos])) preamble = textwrap.dedent( """ ```{r, echo=FALSE, message=FALSE, warning=FALSE} %s ``` """ % preamble ) fd.write(preamble.encode()) fd.write(str.encode(code[pos:])) def execute_script(self, fname): if len(self.output) != 1: raise WorkflowError( "RMarkdown scripts (.Rmd) may only have a single output file." ) out = os.path.abspath(self.output[0]) self._execute_cmd( 'Rscript --vanilla -e \'rmarkdown::render("{fname}", output_file="{out}", quiet=TRUE, knit_root_dir = "{workdir}", params = list(rmd="{fname}"))\'', fname=fname, out=out, workdir=os.getcwd(), ) class JuliaScript(ScriptBase): def get_preamble(self): return textwrap.dedent( """ ######## Snakemake header ######## struct Snakemake input::Dict output::Dict params::Dict wildcards::Dict threads::Int64 log::Dict resources::Dict config::Dict rule::String bench_iteration scriptdir::String #source::Any end snakemake = Snakemake( {}, #input::Dict {}, #output::Dict {}, #params::Dict {}, #wildcards::Dict {}, #threads::Int64 {}, #log::Dict {}, #resources::Dict {}, #config::Dict {}, #rule::String {}, #bench_iteration::Int64 {}, #scriptdir::String #, #source::Any ) ######## Original script ######### """.format( JuliaEncoder.encode_namedlist(self.input), JuliaEncoder.encode_namedlist(self.output), JuliaEncoder.encode_namedlist(self.params), JuliaEncoder.encode_namedlist(self.wildcards), JuliaEncoder.encode_value(self.threads), JuliaEncoder.encode_namedlist(self.log), JuliaEncoder.encode_namedlist( { name: value for name, value in self.resources.items() if name != "_cores" and name != "_nodes" } ), JuliaEncoder.encode_dict(self.config), JuliaEncoder.encode_value(self.rulename), JuliaEncoder.encode_value(self.bench_iteration), JuliaEncoder.encode_value( os.path.dirname(self.path[7:]) if self.path.startswith("file://") else os.path.dirname(self.path) ), ).replace( "'", '"' ) ) def write_script(self, preamble, fd): fd.write(preamble.encode()) fd.write(self.source) def execute_script(self, fname): self._execute_cmd("julia {fname:q}", fname=fname) def get_source(path, basedir="."): import nbformat source = None if not path.startswith("http") and not path.startswith("git+file"): if path.startswith("file://"): path = path[7:] elif path.startswith("file:"): path = path[5:] if not os.path.isabs(path): path = os.path.abspath(os.path.join(basedir, path)) path = "file://" + path path = format(path, stepout=1) if path.startswith("file://"): sourceurl = "file:" + pathname2url(path[7:]) elif path.startswith("git+file"): source = git_content(path) (root_path, file_path, version) = split_git_path(path) path = path.rstrip("@" + version) else: sourceurl = path if source is None: with urlopen(sourceurl) as source: source = source.read() language = None if path.endswith(".py"): language = "python" elif path.endswith(".ipynb"): language = "jupyter" elif path.endswith(".R"): language = "r" elif path.endswith(".Rmd"): language = "rmarkdown" elif path.endswith(".jl"): language = "julia" # detect kernel language for Jupyter Notebooks if language == "jupyter": nb = nbformat.reads(source, as_version=nbformat.NO_CONVERT) kernel_language = nb["metadata"]["language_info"]["name"] language += "_" + kernel_language.lower() return path, source, language def script( path, basedir, input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ): """ Load a script from the given basedir + path and execute it. """ path, source, language = get_source(path, basedir) ExecClass = { "python": PythonScript, "r": RScript, "rmarkdown": RMarkdown, "julia": JuliaScript, }.get(language, None) if ExecClass is None: raise ValueError( "Unsupported script: Expecting either Python (.py), R (.R), RMarkdown (.Rmd) or Julia (.jl) script." ) executor = ExecClass( path, source, basedir, input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ) executor.evaluate() snakemake-5.10.0/snakemake/shell.py000066400000000000000000000130001361131222100171360ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import _io import sys import os import subprocess as sp import inspect import shutil import threading from snakemake.utils import format from snakemake.logging import logger from snakemake.deployment import singularity from snakemake.deployment.conda import Conda import snakemake __author__ = "Johannes Köster" STDOUT = sys.stdout if not isinstance(sys.stdout, _io.TextIOWrapper): # workaround for nosetest since it overwrites sys.stdout # in a strange way that does not work with Popen STDOUT = None class shell: _process_args = {} _process_prefix = "" _process_suffix = "" _lock = threading.Lock() _processes = {} @classmethod def get_executable(cls): return cls._process_args.get("executable", None) @classmethod def check_output(cls, cmd, **kwargs): return sp.check_output( cmd, shell=True, executable=cls.get_executable(), **kwargs ) @classmethod def executable(cls, cmd): if os.name == "posix" and not os.path.isabs(cmd): # always enforce absolute path cmd = shutil.which(cmd) if not cmd: raise WorkflowError( "Cannot set default shell {} because it " "is not available in your " "PATH.".format(cmd) ) if os.path.split(cmd)[-1] == "bash": cls._process_prefix = "set -euo pipefail; " cls._process_args["executable"] = cmd @classmethod def prefix(cls, prefix): cls._process_prefix = format(prefix, stepout=2) @classmethod def suffix(cls, suffix): cls._process_suffix = format(suffix, stepout=2) @classmethod def kill(cls, jobid): with cls._lock: if jobid in cls._processes: cls._processes[jobid].kill() del cls._processes[jobid] @classmethod def cleanup(cls): with cls._lock: cls._processes.clear() def __new__( cls, cmd, *args, iterable=False, read=False, bench_record=None, **kwargs ): if "stepout" in kwargs: raise KeyError("Argument stepout is not allowed in shell command.") cmd = format(cmd, *args, stepout=2, **kwargs) context = inspect.currentframe().f_back.f_locals # add kwargs to context (overwriting the locals of the caller) context.update(kwargs) stdout = sp.PIPE if iterable or read else STDOUT close_fds = sys.platform != "win32" jobid = context.get("jobid") if not context.get("is_shell"): logger.shellcmd(cmd) env_prefix = "" conda_env = context.get("conda_env", None) singularity_img = context.get("singularity_img", None) env_modules = context.get("env_modules", None) shadow_dir = context.get("shadow_dir", None) cmd = "{} {} {}".format( cls._process_prefix, cmd.strip(), cls._process_suffix ).strip() if env_modules: cmd = env_modules.shellcmd(cmd) logger.info("Activating environment modules: {}".format(env_modules)) if conda_env: cmd = Conda(singularity_img).shellcmd(conda_env, cmd) if singularity_img: args = context.get("singularity_args", "") cmd = singularity.shellcmd( singularity_img, cmd, args, shell_executable=cls._process_args["executable"], container_workdir=shadow_dir, ) logger.info("Activating singularity image {}".format(singularity_img)) if conda_env: logger.info("Activating conda environment: {}".format(conda_env)) proc = sp.Popen( cmd, bufsize=-1, shell=True, stdout=stdout, universal_newlines=iterable or None, close_fds=close_fds, **cls._process_args ) if jobid is not None: with cls._lock: cls._processes[jobid] = proc ret = None if iterable: return cls.iter_stdout(proc, cmd) if read: ret = proc.stdout.read() if bench_record is not None: from snakemake.benchmark import benchmarked with benchmarked(proc.pid, bench_record): retcode = proc.wait() else: retcode = proc.wait() if jobid is not None: with cls._lock: del cls._processes[jobid] if retcode: raise sp.CalledProcessError(retcode, cmd) return ret @staticmethod def iter_stdout(proc, cmd): for l in proc.stdout: yield l[:-1] retcode = proc.wait() if retcode: raise sp.CalledProcessError(retcode, cmd) # set bash as default shell on posix compatible OS if os.name == "posix": if not shutil.which("bash"): logger.warning( "Cannot set bash as default shell because it is not " "available in your PATH. Falling back to sh." ) if not shutil.which("sh"): logger.warning( "Cannot fall back to sh since it seems to be not " "available on this system. Using whatever is " "defined as default." ) else: shell.executable("sh") else: shell.executable("bash") snakemake-5.10.0/snakemake/stats.py000066400000000000000000000050351361131222100171760ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import time import csv import json from collections import defaultdict import snakemake.jobs fmt_time = time.ctime class Stats: def __init__(self): self.starttime = dict() self.endtime = dict() def report_job_start(self, job): if job.is_group(): for j in job: self.starttime[j] = time.time() else: self.starttime[job] = time.time() def report_job_end(self, job): if job.is_group(): for j in job: self.endtime[j] = time.time() else: self.endtime[job] = time.time() @property def rule_stats(self): runtimes = defaultdict(list) for job, t in self.starttime.items(): runtimes[job.rule].append(self.endtime[job] - t) for rule, runtimes in runtimes.items(): yield (rule, sum(runtimes) / len(runtimes), min(runtimes), max(runtimes)) @property def file_stats(self): for job, t in self.starttime.items(): for f in job.expanded_output: start, stop = t, self.endtime[job] yield f, fmt_time(start), fmt_time(stop), stop - start, job @property def overall_runtime(self): if self.starttime and self.endtime: return max(self.endtime.values()) - min(self.starttime.values()) else: return 0 def to_json(self, path): rule_stats = { rule.name: { "mean-runtime": mean_runtime, "min-runtime": min_runtime, "max-runtime": max_runtime, } for rule, mean_runtime, min_runtime, max_runtime in self.rule_stats } file_stats = { f: { "start-time": start, "stop-time": stop, "duration": duration, "priority": job.priority if job.priority != snakemake.jobs.Job.HIGHEST_PRIORITY else "highest", "resources": dict(job.resources.items()), } for f, start, stop, duration, job in self.file_stats } with open(path, "w") as f: json.dump( { "total_runtime": self.overall_runtime, "rules": rule_stats, "files": file_stats, }, f, indent=4, ) snakemake-5.10.0/snakemake/utils.py000066400000000000000000000442761361131222100172120ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import json import re import inspect import textwrap import platform from itertools import chain import collections import multiprocessing import string import shlex import sys from urllib.parse import urljoin from snakemake.io import regex, Namedlist, Wildcards, _load_configfile from snakemake.logging import logger from snakemake.exceptions import WorkflowError import snakemake def validate(data, schema, set_default=True): """Validate data with JSON schema at given path. Args: data (object): data to validate. Can be a config dict or a pandas data frame. schema (str): Path to JSON schema used for validation. The schema can also be in YAML format. If validating a pandas data frame, the schema has to describe a row record (i.e., a dict with column names as keys pointing to row values). See http://json-schema.org. The path is interpreted relative to the Snakefile when this function is called. set_default (bool): set default values defined in schema. See http://python-jsonschema.readthedocs.io/en/latest/faq/ for more information """ try: import jsonschema from jsonschema import validators, RefResolver except ImportError: raise WorkflowError( "The Python 3 package jsonschema must be installed " "in order to use the validate directive." ) if not os.path.isabs(schema): frame = inspect.currentframe().f_back # if workflow object is not available this has not been started from a workflow if "workflow" in frame.f_globals: workflow = frame.f_globals["workflow"] schema = os.path.join(workflow.current_basedir, schema) schemafile = schema schema = _load_configfile(schema, filetype="Schema") resolver = RefResolver( urljoin("file:", schemafile), schema, handlers={"file": lambda uri: _load_configfile(re.sub("^file://", "", uri))}, ) # Taken from http://python-jsonschema.readthedocs.io/en/latest/faq/ def extend_with_default(validator_class): validate_properties = validator_class.VALIDATORS["properties"] def set_defaults(validator, properties, instance, schema): for property, subschema in properties.items(): if "default" in subschema: instance.setdefault(property, subschema["default"]) for error in validate_properties(validator, properties, instance, schema): yield error return validators.extend(validator_class, {"properties": set_defaults}) Validator = validators.validator_for(schema) if Validator.META_SCHEMA["$schema"] != schema["$schema"]: logger.warning( "No validator found for JSON Schema version identifier '{}'".format( schema["$schema"] ) ) logger.warning( "Defaulting to validator for JSON Schema version '{}'".format( Validator.META_SCHEMA["$schema"] ) ) logger.warning("Note that schema file may not be validated correctly.") DefaultValidator = extend_with_default(Validator) if not isinstance(data, dict): try: import pandas as pd recordlist = [] if isinstance(data, pd.DataFrame): for i, record in enumerate(data.to_dict("records")): record = {k: v for k, v in record.items() if not pd.isnull(v)} try: if set_default: DefaultValidator(schema, resolver=resolver).validate(record) recordlist.append(record) else: jsonschema.validate(record, schema, resolver=resolver) except jsonschema.exceptions.ValidationError as e: raise WorkflowError( "Error validating row {} of data frame.".format(i), e ) if set_default: newdata = pd.DataFrame(recordlist, data.index) newcol = ~newdata.columns.isin(data.columns) n = len(data.columns) for col in newdata.loc[:, newcol].columns: data.insert(n, col, newdata.loc[:, col]) n = n + 1 return except ImportError: pass raise WorkflowError("Unsupported data type for validation.") else: try: if set_default: DefaultValidator(schema, resolver=resolver).validate(data) else: jsonschema.validate(data, schema, resolver=resolver) except jsonschema.exceptions.ValidationError as e: raise WorkflowError("Error validating config file.", e) def simplify_path(path): """Return a simplified version of the given path.""" relpath = os.path.relpath(path) if relpath.startswith("../../"): return path else: return relpath def linecount(filename): """Return the number of lines of given file. Args: filename (str): the path to the file """ with open(filename) as f: return sum(1 for l in f) def listfiles(pattern, restriction=None, omit_value=None): """Yield a tuple of existing filepaths for the given pattern. Wildcard values are yielded as the second tuple item. Args: pattern (str): a filepattern. Wildcards are specified in snakemake syntax, e.g. "{id}.txt" restriction (dict): restrict to wildcard values given in this dictionary omit_value (str): wildcard value to omit Yields: tuple: The next file matching the pattern, and the corresponding wildcards object """ pattern = os.path.normpath(pattern) first_wildcard = re.search("{[^{]", pattern) if first_wildcard: dirname = os.path.dirname(pattern[: first_wildcard.start()]) if not dirname: dirname = "." else: dirname = os.path.dirname(pattern) pattern = re.compile(regex(pattern)) for dirpath, dirnames, filenames in os.walk(dirname): for f in chain(filenames, dirnames): if dirpath != ".": f = os.path.normpath(os.path.join(dirpath, f)) match = re.match(pattern, f) if match: wildcards = Namedlist(fromdict=match.groupdict()) if restriction is not None: invalid = any( omit_value not in v and v != wildcards[k] for k, v in restriction.items() ) if not invalid: yield f, wildcards else: yield f, wildcards def makedirs(dirnames): """Recursively create the given directory or directories without reporting errors if they are present. """ if isinstance(dirnames, str): dirnames = [dirnames] for dirname in dirnames: os.makedirs(dirname, exist_ok=True) def report( text, path, stylesheet=None, defaultenc="utf8", template=None, metadata=None, **files ): """Create an HTML report using python docutils. This is deprecated in favor of the --report flag. Attention: This function needs Python docutils to be installed for the python installation you use with Snakemake. All keywords not listed below are intepreted as paths to files that shall be embedded into the document. They keywords will be available as link targets in the text. E.g. append a file as keyword arg via F1=input[0] and put a download link in the text like this: .. code:: python report(''' ============== Report for ... ============== Some text. A link to an embedded file: F1_. Further text. ''', outputpath, F1=input[0]) Instead of specifying each file as a keyword arg, you can also expand the input of your rule if it is completely named, e.g.: report(''' Some text... ''', outputpath, **input) Args: text (str): The "restructured text" as it is expected by python docutils. path (str): The path to the desired output file stylesheet (str): An optional path to a css file that defines the style of the document. This defaults to /report.css. Use the default to get a hint how to create your own. defaultenc (str): The encoding that is reported to the browser for embedded text files, defaults to utf8. template (str): An optional path to a docutils HTML template. metadata (str): E.g. an optional author name or email address. """ if stylesheet is None: os.path.join(os.path.dirname(__file__), "report.css") try: import snakemake.report except ImportError: raise WorkflowError( "Python 3 package docutils needs to be installed to use the report function." ) snakemake.report.report( text, path, stylesheet=stylesheet, defaultenc=defaultenc, template=template, metadata=metadata, **files ) def R(code): """Execute R code. This is deprecated in favor of the ``script`` directive. This function executes the R code given as a string. The function requires rpy2 to be installed. Args: code (str): R code to be executed """ try: import rpy2.robjects as robjects except ImportError: raise ValueError( "Python 3 package rpy2 needs to be installed to use the R function." ) robjects.r(format(textwrap.dedent(code), stepout=2)) class SequenceFormatter(string.Formatter): """string.Formatter subclass with special behavior for sequences. This class delegates formatting of individual elements to another formatter object. Non-list objects are formatted by calling the delegate formatter's "format_field" method. List-like objects (list, tuple, set, frozenset) are formatted by formatting each element of the list according to the specified format spec using the delegate formatter and then joining the resulting strings with a separator (space by default). """ def __init__( self, separator=" ", element_formatter=string.Formatter(), *args, **kwargs ): self.separator = separator self.element_formatter = element_formatter def format_element(self, elem, format_spec): """Format a single element For sequences, this is called once for each element in a sequence. For anything else, it is called on the entire object. It is intended to be overridden in subclases. """ return self.element_formatter.format_field(elem, format_spec) def format_field(self, value, format_spec): if isinstance(value, Wildcards): return ",".join( "{}={}".format(name, value) for name, value in sorted(value.items(), key=lambda item: item[0]) ) if isinstance(value, (list, tuple, set, frozenset)): return self.separator.join( self.format_element(v, format_spec) for v in value ) else: return self.format_element(value, format_spec) class QuotedFormatter(string.Formatter): """Subclass of string.Formatter that supports quoting. Using this formatter, any field can be quoted after formatting by appending "q" to its format string. By default, shell quoting is performed using "shlex.quote", but you can pass a different quote_func to the constructor. The quote_func simply has to take a string argument and return a new string representing the quoted form of the input string. Note that if an element after formatting is the empty string, it will not be quoted. """ def __init__(self, quote_func=None, *args, **kwargs): if quote_func is None: quote_func = shlex.quote if not ON_WINDOWS else argvquote self.quote_func = quote_func super().__init__(*args, **kwargs) def format_field(self, value, format_spec): do_quote = format_spec.endswith("q") if do_quote: format_spec = format_spec[:-1] formatted = super().format_field(value, format_spec) if do_quote and formatted != "": formatted = self.quote_func(formatted) return formatted class AlwaysQuotedFormatter(QuotedFormatter): """Subclass of QuotedFormatter that always quotes. Usage is identical to QuotedFormatter, except that it *always* acts like "q" was appended to the format spec. """ def format_field(self, value, format_spec): if not format_spec.endswith("q"): format_spec += "q" return super().format_field(value, format_spec) def format(_pattern, *args, stepout=1, _quote_all=False, **kwargs): """Format a pattern in Snakemake style. This means that keywords embedded in braces are replaced by any variable values that are available in the current namespace. """ frame = inspect.currentframe().f_back while stepout > 1: if not frame.f_back: break frame = frame.f_back stepout -= 1 variables = dict(frame.f_globals) # add local variables from calling rule/function variables.update(frame.f_locals) if "self" in variables and sys.version_info < (3, 5): # self is the first arg of fmt.format as well. Not removing it would # cause a multiple values error on Python <=3.4.2. del variables["self"] variables.update(kwargs) fmt = SequenceFormatter(separator=" ") if _quote_all: fmt.element_formatter = AlwaysQuotedFormatter() else: fmt.element_formatter = QuotedFormatter() try: return fmt.format(_pattern, *args, **variables) except KeyError as ex: raise NameError( "The name {} is unknown in this context. Please " "make sure that you defined that variable. " "Also note that braces not used for variable access " "have to be escaped by repeating them, " "i.e. {{{{print $1}}}}".format(str(ex)) ) class Unformattable: def __init__(self, errormsg="This cannot be used for formatting"): self.errormsg = errormsg def __str__(self): raise ValueError(self.errormsg) def read_job_properties( jobscript, prefix="# properties", pattern=re.compile("# properties = (.*)") ): """Read the job properties defined in a snakemake jobscript. This function is a helper for writing custom wrappers for the snakemake --cluster functionality. Applying this function to a jobscript will return a dict containing information about the job. """ with open(jobscript) as jobscript: for m in map(pattern.match, jobscript): if m: return json.loads(m.group(1)) def min_version(version): """Require minimum snakemake version, raise workflow error if not met.""" import pkg_resources if pkg_resources.parse_version(snakemake.__version__) < pkg_resources.parse_version( version ): raise WorkflowError("Expecting Snakemake version {} or higher.".format(version)) def update_config(config, overwrite_config): """Recursively update dictionary config with overwrite_config. See http://stackoverflow.com/questions/3232943/update-value-of-a-nested-dictionary-of-varying-depth for details. Args: config (dict): dictionary to update overwrite_config (dict): dictionary whose items will overwrite those in config """ def _update(d, u): for (key, value) in u.items(): if isinstance(value, collections.abc.Mapping): d[key] = _update(d.get(key, {}), value) else: d[key] = value return d _update(config, overwrite_config) def available_cpu_count(): """ Return the number of available virtual or physical CPUs on this system. The number of available CPUs can be smaller than the total number of CPUs when the cpuset(7) mechanism is in use, as is the case on some cluster systems. Adapted from http://stackoverflow.com/a/1006301/715090 """ try: with open("/proc/self/status") as f: status = f.read() m = re.search(r"(?m)^Cpus_allowed:\s*(.*)$", status) if m: res = bin(int(m.group(1).replace(",", ""), 16)).count("1") if res > 0: return min(res, multiprocessing.cpu_count()) except IOError: pass return multiprocessing.cpu_count() def argvquote(arg, force=True): """ Returns an argument quoted in such a way that that CommandLineToArgvW on Windows will return the argument string unchanged. This is the same thing Popen does when supplied with an list of arguments. Arguments in a command line should be separated by spaces; this function does not add these spaces. This implementation follows the suggestions outlined here: https://blogs.msdn.microsoft.com/twistylittlepassagesallalike/2011/04/23/everyone-quotes-command-line-arguments-the-wrong-way/ """ if not force and len(arg) != 0 and not any([c in arg for c in ' \t\n\v"']): return arg else: n_backslashes = 0 cmdline = '"' for c in arg: if c == "\\": # first count the number of current backslashes n_backslashes += 1 continue if c == '"': # Escape all backslashes and the following double quotation mark cmdline += (n_backslashes * 2 + 1) * "\\" else: # backslashes are not special here cmdline += n_backslashes * "\\" n_backslashes = 0 cmdline += c # Escape all backslashes, but let the terminating # double quotation mark we add below be interpreted # as a metacharacter cmdline += +n_backslashes * 2 * "\\" + '"' return cmdline ON_WINDOWS = platform.system() == "Windows" snakemake-5.10.0/snakemake/workflow.py000066400000000000000000001350511361131222100177140ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import re import os import sys import signal import json import urllib from collections import OrderedDict from itertools import filterfalse, chain from functools import partial from operator import attrgetter import copy import subprocess from pathlib import Path from snakemake.logging import logger, format_resources, format_resource_names from snakemake.rules import Rule, Ruleorder, RuleProxy from snakemake.exceptions import ( RuleException, CreateRuleException, UnknownRuleException, NoRulesException, print_exception, WorkflowError, ) from snakemake.shell import shell from snakemake.dag import DAG from snakemake.scheduler import JobScheduler from snakemake.parser import parse import snakemake.io from snakemake.io import ( protected, temp, temporary, ancient, directory, expand, dynamic, glob_wildcards, flag, not_iterable, touch, unpack, local, pipe, repeat, report, multiext, IOFile, ) from snakemake.persistence import Persistence from snakemake.utils import update_config from snakemake.script import script from snakemake.notebook import notebook from snakemake.wrapper import wrapper from snakemake.cwl import cwl import snakemake.wrapper from snakemake.common import Mode from snakemake.utils import simplify_path from snakemake.checkpoints import Checkpoint, Checkpoints from snakemake.resources import DefaultResources from snakemake.caching.local import OutputFileCache as LocalOutputFileCache from snakemake.caching.remote import OutputFileCache as RemoteOutputFileCache class Workflow: def __init__( self, snakefile=None, jobscript=None, overwrite_shellcmd=None, overwrite_config=dict(), overwrite_workdir=None, overwrite_configfiles=None, overwrite_clusterconfig=dict(), overwrite_threads=dict(), config_args=None, debug=False, verbose=False, use_conda=False, conda_prefix=None, use_singularity=False, use_env_modules=False, singularity_prefix=None, singularity_args="", shadow_prefix=None, mode=Mode.default, wrapper_prefix=None, printshellcmds=False, restart_times=None, attempt=1, default_remote_provider=None, default_remote_prefix="", run_local=True, default_resources=None, cache=None, nodes=1, cores=1, resources=None, ): """ Create the controller. """ self.global_resources = dict() if resources is None else resources self.global_resources["_cores"] = cores self.global_resources["_nodes"] = nodes self._rules = OrderedDict() self.first_rule = None self._workdir = None self.overwrite_workdir = overwrite_workdir self.workdir_init = os.path.abspath(os.curdir) self._ruleorder = Ruleorder() self._localrules = set() self.linemaps = dict() self.rule_count = 0 self.basedir = os.path.dirname(snakefile) self.snakefile = os.path.abspath(snakefile) self.included = [] self.included_stack = [] self.jobscript = jobscript self.persistence = None self.globals = globals() self._subworkflows = dict() self.overwrite_shellcmd = overwrite_shellcmd self.overwrite_config = overwrite_config self.overwrite_configfiles = overwrite_configfiles self.overwrite_clusterconfig = overwrite_clusterconfig self.overwrite_threads = overwrite_threads self.config_args = config_args self.immediate_submit = None self._onsuccess = lambda log: None self._onerror = lambda log: None self._onstart = lambda log: None self._wildcard_constraints = dict() self.debug = debug self.verbose = verbose self._rulecount = 0 self.use_conda = use_conda self.conda_prefix = conda_prefix self.use_singularity = use_singularity self.use_env_modules = use_env_modules self.singularity_prefix = singularity_prefix self.singularity_args = singularity_args self.shadow_prefix = shadow_prefix self.global_singularity_img = None self.mode = mode self.wrapper_prefix = wrapper_prefix self.printshellcmds = printshellcmds self.restart_times = restart_times self.attempt = attempt self.default_remote_provider = default_remote_provider self.default_remote_prefix = default_remote_prefix self.configfiles = [] self.run_local = run_local self.report_text = None if cache is not None: self.cache_rules = set(cache) if self.default_remote_provider is not None: self.output_file_cache = RemoteOutputFileCache( self.default_remote_provider ) else: self.output_file_cache = LocalOutputFileCache() else: self.output_file_cache = None self.cache_rules = set() if default_resources is not None: self.default_resources = default_resources else: # only _cores and _nodes self.default_resources = DefaultResources() self.iocache = snakemake.io.IOCache() global config config = copy.deepcopy(self.overwrite_config) global cluster_config cluster_config = copy.deepcopy(self.overwrite_clusterconfig) global rules rules = Rules() global checkpoints checkpoints = Checkpoints() def is_cached_rule(self, rule: Rule): return rule.name in self.cache_rules def get_sources(self): files = set() def norm_rule_relpath(f, rule): if not os.path.isabs(f): f = os.path.join(rule.basedir, f) return os.path.relpath(f) # get registered sources for f in self.included: files.add(os.path.relpath(f)) for rule in self.rules: script_path = rule.script or rule.notebook if script_path: script_path = norm_rule_relpath(script_path, rule) files.add(script_path) script_dir = os.path.dirname(script_path) files.update( os.path.join(dirpath, f) for dirpath, _, files in os.walk(script_dir) for f in files ) if rule.conda_env: env_path = norm_rule_relpath(rule.conda_env, rule) files.add(env_path) for f in self.configfiles: files.add(f) # get git-managed files # TODO allow a manifest file as alternative try: out = subprocess.check_output( ["git", "ls-files", "."], stderr=subprocess.PIPE ) for f in out.decode().split("\n"): if f: files.add(os.path.relpath(f)) except subprocess.CalledProcessError as e: if "fatal: not a git repository" in e.stderr.decode().lower(): logger.warning( "Unable to retrieve additional files from git. " "This is not a git repository." ) else: raise WorkflowError( "Error executing git:\n{}".format(e.stderr.decode()) ) return files @property def subworkflows(self): return self._subworkflows.values() @property def rules(self): return self._rules.values() @property def cores(self): return self.global_resources["_cores"] @property def nodes(self): return self.global_resources["_nodes"] @property def concrete_files(self): return ( file for rule in self.rules for file in chain(rule.input, rule.output) if not callable(file) and not file.contains_wildcard() ) def check(self): for clause in self._ruleorder: for rulename in clause: if not self.is_rule(rulename): raise UnknownRuleException( rulename, prefix="Error in ruleorder definition." ) def add_rule(self, name=None, lineno=None, snakefile=None, checkpoint=False): """ Add a rule. """ if name is None: name = str(len(self._rules) + 1) if self.is_rule(name): raise CreateRuleException( "The name {} is already used by another rule".format(name) ) rule = Rule(name, self, lineno=lineno, snakefile=snakefile) self._rules[rule.name] = rule self.rule_count += 1 if not self.first_rule: self.first_rule = rule.name return name def is_rule(self, name): """ Return True if name is the name of a rule. Arguments name -- a name """ return name in self._rules def get_rule(self, name): """ Get rule by name. Arguments name -- the name of the rule """ if not self._rules: raise NoRulesException() if not name in self._rules: raise UnknownRuleException(name) return self._rules[name] def list_rules(self, only_targets=False): rules = self.rules if only_targets: rules = filterfalse(Rule.has_wildcards, rules) for rule in rules: logger.rule_info(name=rule.name, docstring=rule.docstring) def list_resources(self): for resource in set( resource for rule in self.rules for resource in rule.resources ): if resource not in "_cores _nodes".split(): logger.info(resource) def is_local(self, rule): return rule.group is None and (rule.name in self._localrules or rule.norun) def check_localrules(self): undefined = self._localrules - set(rule.name for rule in self.rules) if undefined: logger.warning( "localrules directive specifies rules that are not " "present in the Snakefile:\n{}\n".format( "\n".join(map("\t{}".format, undefined)) ) ) def inputfile(self, path): """Mark file as being an input file of the workflow. This also means that eventual --default-remote-provider/prefix settings will be applied to this file. The file is returned as _IOFile object, such that it can e.g. be transparently opened with _IOFile.open(). """ if isinstance(path, Path): path = str(path) if self.default_remote_provider is not None: path = self.apply_default_remote(path) return IOFile(path) def apply_default_remote(self, path): """Apply the defined default remote provider to the given path and return the updated _IOFile. Asserts that default remote provider is defined. """ assert ( self.default_remote_provider is not None ), "No default remote provider is defined, calling this anyway is a bug" path = "{}/{}".format(self.default_remote_prefix, path) path = os.path.normpath(path) return self.default_remote_provider.remote(path) def execute( self, targets=None, dryrun=False, touch=False, local_cores=1, forcetargets=False, forceall=False, forcerun=None, until=[], omit_from=[], prioritytargets=None, quiet=False, keepgoing=False, printshellcmds=False, printreason=False, printdag=False, cluster=None, cluster_sync=None, jobname=None, immediate_submit=False, ignore_ambiguity=False, printrulegraph=False, printfilegraph=False, printd3dag=False, drmaa=None, drmaa_log_dir=None, kubernetes=None, kubernetes_envvars=None, tibanna=None, tibanna_sfn=None, precommand="", container_image=None, stats=None, force_incomplete=False, ignore_incomplete=False, list_version_changes=False, list_code_changes=False, list_input_changes=False, list_params_changes=False, list_untracked=False, list_conda_envs=False, summary=False, archive=None, delete_all_output=False, delete_temp_output=False, detailed_summary=False, latency_wait=3, wait_for_files=None, nolock=False, unlock=False, notemp=False, nodeps=False, cleanup_metadata=None, cleanup_conda=False, cleanup_shadow=False, cleanup_scripts=True, subsnakemake=None, updated_files=None, keep_target_files=False, keep_shadow=False, keep_remote_local=False, allowed_rules=None, max_jobs_per_second=None, max_status_checks_per_second=None, greediness=1.0, no_hooks=False, force_use_threads=False, create_envs_only=False, assume_shared_fs=True, cluster_status=None, report=None, export_cwl=False, batch=None, keepincomplete=False, ): self.check_localrules() self.immediate_submit = immediate_submit self.cleanup_scripts = cleanup_scripts def rules(items): return map(self._rules.__getitem__, filter(self.is_rule, items)) if keep_target_files: def files(items): return filterfalse(self.is_rule, items) else: def files(items): relpath = lambda f: f if os.path.isabs(f) else os.path.relpath(f) return map(relpath, filterfalse(self.is_rule, items)) if not targets: targets = [self.first_rule] if self.first_rule is not None else list() if prioritytargets is None: prioritytargets = list() if forcerun is None: forcerun = list() if until is None: until = list() if omit_from is None: omit_from = list() priorityrules = set(rules(prioritytargets)) priorityfiles = set(files(prioritytargets)) forcerules = set(rules(forcerun)) forcefiles = set(files(forcerun)) untilrules = set(rules(until)) untilfiles = set(files(until)) omitrules = set(rules(omit_from)) omitfiles = set(files(omit_from)) targetrules = set( chain( rules(targets), filterfalse(Rule.has_wildcards, priorityrules), filterfalse(Rule.has_wildcards, forcerules), filterfalse(Rule.has_wildcards, untilrules), ) ) targetfiles = set(chain(files(targets), priorityfiles, forcefiles, untilfiles)) if forcetargets: forcefiles.update(targetfiles) forcerules.update(targetrules) rules = self.rules if allowed_rules: rules = [rule for rule in rules if rule.name in set(allowed_rules)] if wait_for_files is not None: try: snakemake.io.wait_for_files(wait_for_files, latency_wait=latency_wait) except IOError as e: logger.error(str(e)) return False dag = DAG( self, rules, dryrun=dryrun, targetfiles=targetfiles, targetrules=targetrules, # when cleaning up conda, we should enforce all possible jobs # since their envs shall not be deleted forceall=forceall or cleanup_conda, forcefiles=forcefiles, forcerules=forcerules, priorityfiles=priorityfiles, priorityrules=priorityrules, untilfiles=untilfiles, untilrules=untilrules, omitfiles=omitfiles, omitrules=omitrules, ignore_ambiguity=ignore_ambiguity, force_incomplete=force_incomplete, ignore_incomplete=ignore_incomplete or printdag or printrulegraph or printfilegraph, notemp=notemp, keep_remote_local=keep_remote_local, batch=batch, ) self.persistence = Persistence( nolock=nolock, dag=dag, conda_prefix=self.conda_prefix, singularity_prefix=self.singularity_prefix, shadow_prefix=self.shadow_prefix, warn_only=dryrun or printrulegraph or printfilegraph or printdag or summary or archive or list_version_changes or list_code_changes or list_input_changes or list_params_changes or list_untracked or delete_all_output or delete_temp_output, ) if cleanup_metadata: for f in cleanup_metadata: self.persistence.cleanup_metadata(f) return True logger.info("Building DAG of jobs...") dag.init() dag.update_checkpoint_dependencies() # check incomplete has to run BEFORE any call to postprocess dag.check_incomplete() dag.check_dynamic() if unlock: try: self.persistence.cleanup_locks() logger.info("Unlocking working directory.") return True except IOError: logger.error( "Error: Unlocking the directory {} failed. Maybe " "you don't have the permissions?" ) return False try: self.persistence.lock() except IOError: logger.error( "Error: Directory cannot be locked. Please make " "sure that no other Snakemake process is trying to create " "the same files in the following directory:\n{}\n" "If you are sure that no other " "instances of snakemake are running on this directory, " "the remaining lock was likely caused by a kill signal or " "a power loss. It can be removed with " "the --unlock argument.".format(os.getcwd()) ) return False if cleanup_shadow: self.persistence.cleanup_shadow() return True if ( self.subworkflows and not printdag and not printrulegraph and not printfilegraph ): # backup globals globals_backup = dict(self.globals) # execute subworkflows for subworkflow in self.subworkflows: subworkflow_targets = subworkflow.targets(dag) logger.debug( "Files requested from subworkflow:\n {}".format( "\n ".join(subworkflow_targets) ) ) updated = list() if subworkflow_targets: logger.info("Executing subworkflow {}.".format(subworkflow.name)) if not subsnakemake( subworkflow.snakefile, workdir=subworkflow.workdir, targets=subworkflow_targets, configfiles=[subworkflow.configfile], updated_files=updated, ): return False dag.updated_subworkflow_files.update( subworkflow.target(f) for f in updated ) else: logger.info( "Subworkflow {}: Nothing to be done.".format(subworkflow.name) ) if self.subworkflows: logger.info("Executing main workflow.") # rescue globals self.globals.update(globals_backup) dag.postprocess() # deactivate IOCache such that from now on we always get updated # size, existence and mtime information # ATTENTION: this may never be removed without really good reason. # Otherwise weird things may happen. self.iocache.deactivate() # clear and deactivate persistence cache, from now on we want to see updates self.persistence.deactivate_cache() if nodeps: missing_input = [ f for job in dag.targetjobs for f in job.input if dag.needrun(job) and not os.path.exists(f) ] if missing_input: logger.error( "Dependency resolution disabled (--nodeps) " "but missing input " "files detected. If this happens on a cluster, please make sure " "that you handle the dependencies yourself or turn off " "--immediate-submit. Missing input files:\n{}".format( "\n".join(missing_input) ) ) return False updated_files.extend(f for job in dag.needrun_jobs for f in job.output) if export_cwl: from snakemake.cwl import dag_to_cwl import json with open(export_cwl, "w") as cwl: json.dump(dag_to_cwl(dag), cwl, indent=4) return True elif report: from snakemake.report import auto_report auto_report(dag, report) return True elif printd3dag: dag.d3dag() return True elif printdag: print(dag) return True elif printrulegraph: print(dag.rule_dot()) return True elif printfilegraph: print(dag.filegraph_dot()) return True elif summary: print("\n".join(dag.summary(detailed=False))) return True elif detailed_summary: print("\n".join(dag.summary(detailed=True))) return True elif archive: dag.archive(archive) return True elif delete_all_output: dag.clean(only_temp=False, dryrun=dryrun) return True elif delete_temp_output: dag.clean(only_temp=True, dryrun=dryrun) return True elif list_version_changes: items = list(chain(*map(self.persistence.version_changed, dag.jobs))) if items: print(*items, sep="\n") return True elif list_code_changes: items = list(chain(*map(self.persistence.code_changed, dag.jobs))) for j in dag.jobs: items.extend(list(j.outputs_older_than_script_or_notebook())) if items: print(*items, sep="\n") return True elif list_input_changes: items = list(chain(*map(self.persistence.input_changed, dag.jobs))) if items: print(*items, sep="\n") return True elif list_params_changes: items = list(chain(*map(self.persistence.params_changed, dag.jobs))) if items: print(*items, sep="\n") return True elif list_untracked: dag.list_untracked() return True if self.use_singularity: if assume_shared_fs: dag.pull_singularity_imgs( dryrun=dryrun or list_conda_envs, quiet=list_conda_envs ) if self.use_conda: if assume_shared_fs: dag.create_conda_envs( dryrun=dryrun or list_conda_envs or cleanup_conda, quiet=list_conda_envs, ) if create_envs_only: return True if list_conda_envs: print("environment", "container", "location", sep="\t") for env in set(job.conda_env for job in dag.jobs): if env: print( simplify_path(env.file), env.singularity_img_url or "", simplify_path(env.path), sep="\t", ) return True if cleanup_conda: self.persistence.cleanup_conda() return True scheduler = JobScheduler( self, dag, self.cores, local_cores=local_cores, dryrun=dryrun, touch=touch, cluster=cluster, cluster_status=cluster_status, cluster_config=cluster_config, cluster_sync=cluster_sync, jobname=jobname, max_jobs_per_second=max_jobs_per_second, max_status_checks_per_second=max_status_checks_per_second, quiet=quiet, keepgoing=keepgoing, drmaa=drmaa, drmaa_log_dir=drmaa_log_dir, kubernetes=kubernetes, kubernetes_envvars=kubernetes_envvars, tibanna=tibanna, tibanna_sfn=tibanna_sfn, precommand=precommand, container_image=container_image, printreason=printreason, printshellcmds=printshellcmds, latency_wait=latency_wait, greediness=greediness, force_use_threads=force_use_threads, assume_shared_fs=assume_shared_fs, keepincomplete=keepincomplete, ) if not dryrun: if len(dag): shell_exec = shell.get_executable() if shell_exec is not None: logger.info("Using shell: {}".format(shell_exec)) if cluster or cluster_sync or drmaa: logger.resources_info( "Provided cluster nodes: {}".format(self.nodes) ) else: warning = ( "" if self.cores > 1 else " (use --cores to define parallelism)" ) logger.resources_info( "Provided cores: {}{}".format(self.cores, warning) ) logger.resources_info( "Rules claiming more threads " "will be scaled down." ) provided_resources = format_resources(self.global_resources) if provided_resources: logger.resources_info("Provided resources: " + provided_resources) if self.run_local and any(rule.group for rule in self.rules): logger.info("Group jobs: inactive (local execution)") if not self.use_conda and any(rule.conda_env for rule in self.rules): logger.info("Conda environments: ignored") if not self.use_singularity and any( rule.singularity_img for rule in self.rules ): logger.info("Singularity containers: ignored") logger.run_info("\n".join(dag.stats())) else: logger.info("Nothing to be done.") else: # the dryrun case if len(dag): logger.run_info("\n".join(dag.stats())) else: logger.info("Nothing to be done.") return True if quiet: # in case of dryrun and quiet, just print above info and exit return True if not dryrun and not no_hooks: self._onstart(logger.get_logfile()) success = scheduler.schedule() if success: if dryrun: if len(dag): logger.run_info("\n".join(dag.stats())) logger.info( "This was a dry-run (flag -n). The order of jobs " "does not reflect the order of execution." ) logger.remove_logfile() else: if stats: scheduler.stats.to_json(stats) logger.logfile_hint() if not dryrun and not no_hooks: self._onsuccess(logger.get_logfile()) return True else: if not dryrun and not no_hooks: self._onerror(logger.get_logfile()) logger.logfile_hint() return False @property def current_basedir(self): """Basedir of currently parsed Snakefile.""" assert self.included_stack return os.path.abspath(os.path.dirname(self.included_stack[-1])) def include( self, snakefile, overwrite_first_rule=False, print_compilation=False, overwrite_shellcmd=None, ): """ Include a snakefile. """ # check if snakefile is a path to the filesystem if not urllib.parse.urlparse(snakefile).scheme: if not os.path.isabs(snakefile) and self.included_stack: snakefile = os.path.join(self.current_basedir, snakefile) # Could still be an url if relative import was used if not urllib.parse.urlparse(snakefile).scheme: snakefile = os.path.abspath(snakefile) # else it could be an url. # at least we don't want to modify the path for clarity. if snakefile in self.included: logger.info("Multiple include of {} ignored".format(snakefile)) return self.included.append(snakefile) self.included_stack.append(snakefile) global workflow workflow = self first_rule = self.first_rule code, linemap, rulecount = parse( snakefile, overwrite_shellcmd=self.overwrite_shellcmd, rulecount=self._rulecount, ) self._rulecount = rulecount if print_compilation: print(code) # insert the current directory into sys.path # this allows to import modules from the workflow directory sys.path.insert(0, os.path.dirname(snakefile)) self.linemaps[snakefile] = linemap exec(compile(code, snakefile, "exec"), self.globals) if not overwrite_first_rule: self.first_rule = first_rule self.included_stack.pop() def onstart(self, func): """Register onstart function.""" self._onstart = func def onsuccess(self, func): """Register onsuccess function.""" self._onsuccess = func def onerror(self, func): """Register onerror function.""" self._onerror = func def global_wildcard_constraints(self, **content): """Register global wildcard constraints.""" self._wildcard_constraints.update(content) # update all rules so far for rule in self.rules: rule.update_wildcard_constraints() def workdir(self, workdir): """Register workdir.""" if self.overwrite_workdir is None: os.makedirs(workdir, exist_ok=True) self._workdir = workdir os.chdir(workdir) def configfile(self, fp): """ Update the global config with data from the given file. """ global config self.configfiles.append(fp) c = snakemake.io.load_configfile(fp) update_config(config, c) update_config(config, self.overwrite_config) def report(self, path): """ Define a global report description in .rst format.""" self.report_text = os.path.join(self.current_basedir, path) @property def config(self): global config return config def ruleorder(self, *rulenames): self._ruleorder.add(*rulenames) def subworkflow(self, name, snakefile=None, workdir=None, configfile=None): # Take absolute path of config file, because it is relative to current # workdir, which could be changed for the subworkflow. if configfile: configfile = os.path.abspath(configfile) sw = Subworkflow(self, name, snakefile, workdir, configfile) self._subworkflows[name] = sw self.globals[name] = sw.target def localrules(self, *rulenames): self._localrules.update(rulenames) def rule(self, name=None, lineno=None, snakefile=None, checkpoint=False): name = self.add_rule(name, lineno, snakefile, checkpoint) rule = self.get_rule(name) rule.is_checkpoint = checkpoint def decorate(ruleinfo): if ruleinfo.wildcard_constraints: rule.set_wildcard_constraints( *ruleinfo.wildcard_constraints[0], **ruleinfo.wildcard_constraints[1] ) if ruleinfo.input: rule.set_input(*ruleinfo.input[0], **ruleinfo.input[1]) if ruleinfo.output: rule.set_output(*ruleinfo.output[0], **ruleinfo.output[1]) if ruleinfo.params: rule.set_params(*ruleinfo.params[0], **ruleinfo.params[1]) # handle default resources if self.default_resources is not None: rule.resources = copy.deepcopy(self.default_resources.parsed) if ruleinfo.threads is not None: if ( not isinstance(ruleinfo.threads, int) and not isinstance(ruleinfo.threads, float) and not callable(ruleinfo.threads) ): raise RuleException( "Threads value has to be an integer, float, or a callable.", rule=rule, ) if name in self.overwrite_threads: rule.resources["_cores"] = self.overwrite_threads[name] else: rule.resources["_cores"] = int(ruleinfo.threads) if ruleinfo.shadow_depth: if ruleinfo.shadow_depth not in (True, "shallow", "full", "minimal"): raise RuleException( "Shadow must either be 'minimal', 'shallow', 'full', " "or True (equivalent to 'full')", rule=rule, ) if ruleinfo.shadow_depth is True: rule.shadow_depth = "full" logger.warning( "Shadow is set to True in rule {} (equivalent to 'full'). It's encouraged to use the more explicit options 'minimal|shallow|full' instead.".format( rule ) ) else: rule.shadow_depth = ruleinfo.shadow_depth if ruleinfo.resources: args, resources = ruleinfo.resources if args: raise RuleException("Resources have to be named.") if not all( map(lambda r: isinstance(r, int) or callable(r), resources.values()) ): raise RuleException( "Resources values have to be integers or callables", rule=rule ) rule.resources.update(resources) if ruleinfo.priority: if not isinstance(ruleinfo.priority, int) and not isinstance( ruleinfo.priority, float ): raise RuleException( "Priority values have to be numeric.", rule=rule ) rule.priority = ruleinfo.priority if ruleinfo.version: rule.version = ruleinfo.version if ruleinfo.log: rule.set_log(*ruleinfo.log[0], **ruleinfo.log[1]) if ruleinfo.message: rule.message = ruleinfo.message if ruleinfo.benchmark: rule.benchmark = ruleinfo.benchmark if not self.run_local and ruleinfo.group is not None: rule.group = ruleinfo.group if ruleinfo.wrapper: if self.use_conda: rule.conda_env = snakemake.wrapper.get_conda_env( ruleinfo.wrapper, prefix=self.wrapper_prefix ) # TODO retrieve suitable singularity image if self.use_env_modules and ruleinfo.env_modules: # If using environment modules and they are defined for the rule, # ignore conda and singularity directive below. # The reason is that this is likely intended in order to use # a software stack specifically compiled for a particular # HPC cluster. invalid_rule = not ( ruleinfo.script or ruleinfo.wrapper or ruleinfo.shellcmd or ruleinfo.notebook ) if invalid_rule: raise RuleException( "Modules directive is only allowed with " "shell, script, notebook, or wrapper directives (not with run)", rule=rule, ) from snakemake.deployment.env_modules import EnvModules rule.env_modules = EnvModules(*ruleinfo.env_modules) else: if ruleinfo.conda_env and self.use_conda: if not ( ruleinfo.script or ruleinfo.wrapper or ruleinfo.shellcmd or ruleinfo.notebook ): raise RuleException( "Conda environments are only allowed " "with shell, script, notebook, or wrapper directives " "(not with run).", rule=rule, ) if not ( urllib.parse.urlparse(ruleinfo.conda_env).scheme or os.path.isabs(ruleinfo.conda_env) ): ruleinfo.conda_env = os.path.join( self.current_basedir, ruleinfo.conda_env ) rule.conda_env = ruleinfo.conda_env if self.use_singularity: invalid_rule = not ( ruleinfo.script or ruleinfo.wrapper or ruleinfo.shellcmd or ruleinfo.notebook ) if ruleinfo.singularity_img: if invalid_rule: raise RuleException( "Singularity directive is only allowed " "with shell, script, notebook or wrapper directives " "(not with run).", rule=rule, ) rule.singularity_img = ruleinfo.singularity_img elif self.global_singularity_img: if not invalid_rule: # skip rules with run directive rule.singularity_img = self.global_singularity_img rule.norun = ruleinfo.norun rule.docstring = ruleinfo.docstring rule.run_func = ruleinfo.func rule.shellcmd = ruleinfo.shellcmd rule.script = ruleinfo.script rule.notebook = ruleinfo.notebook rule.wrapper = ruleinfo.wrapper rule.cwl = ruleinfo.cwl rule.restart_times = self.restart_times rule.basedir = self.current_basedir ruleinfo.func.__name__ = "__{}".format(rule.name) self.globals[ruleinfo.func.__name__] = ruleinfo.func setattr(rules, rule.name, RuleProxy(rule)) if checkpoint: checkpoints.register(rule) return ruleinfo.func return decorate def docstring(self, string): def decorate(ruleinfo): ruleinfo.docstring = string return ruleinfo return decorate def input(self, *paths, **kwpaths): def decorate(ruleinfo): ruleinfo.input = (paths, kwpaths) return ruleinfo return decorate def output(self, *paths, **kwpaths): def decorate(ruleinfo): ruleinfo.output = (paths, kwpaths) return ruleinfo return decorate def params(self, *params, **kwparams): def decorate(ruleinfo): ruleinfo.params = (params, kwparams) return ruleinfo return decorate def wildcard_constraints(self, *wildcard_constraints, **kwwildcard_constraints): def decorate(ruleinfo): ruleinfo.wildcard_constraints = ( wildcard_constraints, kwwildcard_constraints, ) return ruleinfo return decorate def message(self, message): def decorate(ruleinfo): ruleinfo.message = message return ruleinfo return decorate def benchmark(self, benchmark): def decorate(ruleinfo): ruleinfo.benchmark = benchmark return ruleinfo return decorate def conda(self, conda_env): def decorate(ruleinfo): ruleinfo.conda_env = conda_env return ruleinfo return decorate def singularity(self, singularity_img): def decorate(ruleinfo): ruleinfo.singularity_img = singularity_img return ruleinfo return decorate def envmodules(self, *env_modules): def decorate(ruleinfo): ruleinfo.env_modules = env_modules return ruleinfo return decorate def global_singularity(self, singularity_img): self.global_singularity_img = singularity_img def threads(self, threads): def decorate(ruleinfo): ruleinfo.threads = threads return ruleinfo return decorate def shadow(self, shadow_depth): def decorate(ruleinfo): ruleinfo.shadow_depth = shadow_depth return ruleinfo return decorate def resources(self, *args, **resources): def decorate(ruleinfo): ruleinfo.resources = (args, resources) return ruleinfo return decorate def priority(self, priority): def decorate(ruleinfo): ruleinfo.priority = priority return ruleinfo return decorate def version(self, version): def decorate(ruleinfo): ruleinfo.version = version return ruleinfo return decorate def group(self, group): def decorate(ruleinfo): ruleinfo.group = group return ruleinfo return decorate def log(self, *logs, **kwlogs): def decorate(ruleinfo): ruleinfo.log = (logs, kwlogs) return ruleinfo return decorate def shellcmd(self, cmd): def decorate(ruleinfo): ruleinfo.shellcmd = cmd return ruleinfo return decorate def script(self, script): def decorate(ruleinfo): ruleinfo.script = script return ruleinfo return decorate def notebook(self, notebook): def decorate(ruleinfo): ruleinfo.notebook = notebook return ruleinfo return decorate def wrapper(self, wrapper): def decorate(ruleinfo): ruleinfo.wrapper = wrapper return ruleinfo return decorate def cwl(self, cwl): def decorate(ruleinfo): ruleinfo.cwl = cwl return ruleinfo return decorate def norun(self): def decorate(ruleinfo): ruleinfo.norun = True return ruleinfo return decorate def run(self, func): return RuleInfo(func) @staticmethod def _empty_decorator(f): return f class RuleInfo: def __init__(self, func): self.func = func self.shellcmd = None self.norun = False self.input = None self.output = None self.params = None self.message = None self.benchmark = None self.conda_env = None self.singularity_img = None self.env_modules = None self.wildcard_constraints = None self.threads = None self.shadow_depth = None self.resources = None self.priority = None self.version = None self.log = None self.docstring = None self.group = None self.script = None self.notebook = None self.wrapper = None self.cwl = None class Subworkflow: def __init__(self, workflow, name, snakefile, workdir, configfile): self.workflow = workflow self.name = name self._snakefile = snakefile self._workdir = workdir self.configfile = configfile @property def snakefile(self): if self._snakefile is None: return os.path.abspath(os.path.join(self.workdir, "Snakefile")) if not os.path.isabs(self._snakefile): return os.path.abspath(os.path.join(self.workflow.basedir, self._snakefile)) return self._snakefile @property def workdir(self): workdir = "." if self._workdir is None else self._workdir if not os.path.isabs(workdir): return os.path.abspath(os.path.join(self.workflow.basedir, workdir)) return workdir def target(self, paths): if not_iterable(paths): path = paths path = path if os.path.isabs(path) else os.path.join(self.workdir, path) return flag(path, "subworkflow", self) return [self.target(path) for path in paths] def targets(self, dag): def relpath(f): if f.startswith(self.workdir): return os.path.relpath(f, start=self.workdir) # do not adjust absolute targets outside of workdir return f return [ relpath(f) for job in dag.jobs for f in job.subworkflow_input if job.subworkflow_input[f] is self ] class Rules: """ A namespace for rules so that they can be accessed via dot notation. """ pass def srcdir(path): """Return the absolute path, relative to the source directory of the current Snakefile.""" if not workflow.included_stack: return None return os.path.join(os.path.dirname(workflow.included_stack[-1]), path) snakemake-5.10.0/snakemake/wrapper.py000066400000000000000000000063621361131222100175240ustar00rootroot00000000000000__author__ = "Johannes Köster" __copyright__ = "Copyright 2016-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import os import posixpath from urllib.error import URLError from urllib.request import urlopen from snakemake.exceptions import WorkflowError from snakemake.script import script def is_script(path): return ( path.endswith("wrapper.py") or path.endswith("wrapper.R") or path.endswith("wrapper.jl") ) def get_path(path, prefix=None): if not ( path.startswith("http") or path.startswith("file:") or path.startswith("git+file") ): if prefix is None: prefix = "https://github.com/snakemake/snakemake-wrappers/raw/" elif prefix.startswith("git+file"): parts = path.split("/") path = "/" + "/".join(parts[1:]) + "@" + parts[0] path = prefix + path return path def is_local(path): return path.startswith("file:") def is_git_path(path): return path.startswith("git+file:") def find_extension(path, extensions=[".py", ".R", ".Rmd", ".jl"]): for ext in extensions: if path.endswith("wrapper{}".format(ext)): return path for ext in extensions: script = "/wrapper{}".format(ext) if is_local(path): if path.startswith("file://"): p = path[7:] elif path.startswith("file:"): p = path[5:] if os.path.exists(p + script): return path + script else: try: urlopen(path + script) return path + script except URLError: continue if is_git_path(path): path, version = path.split("@") return os.path.join(path, "wrapper.py") + "@" + version else: return path + "/wrapper.py" # default case def get_script(path, prefix=None): path = get_path(path, prefix=prefix) return find_extension(path) def get_conda_env(path, prefix=None): path = get_path(path, prefix=prefix) if is_script(path): # URLs and posixpaths share the same separator. Hence use posixpath here. path = posixpath.dirname(path) if is_git_path(path): path, version = path.split("@") return os.path.join(path, "environment.yaml") + "@" + version return path + "/environment.yaml" def wrapper( path, input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, prefix, jobid, bench_iteration, cleanup_scripts, shadow_dir, ): """ Load a wrapper from https://github.com/snakemake/snakemake-wrappers under the given path + wrapper.(py|R|Rmd) and execute it. """ path = get_script(path, prefix=prefix) script( path, "", input, output, params, wildcards, threads, resources, log, config, rulename, conda_env, singularity_img, singularity_args, env_modules, bench_record, jobid, bench_iteration, cleanup_scripts, shadow_dir, ) snakemake-5.10.0/test-environment.yml000066400000000000000000000014261361131222100175730ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - python >=3.5 - datrie - boto3 - moto - httpretty - wrapt - pyyaml - pytest - pytest-cov - ftputil - pysftp - requests - dropbox - numpy - appdirs - pytools - docutils - pygments - pandoc <2.0 # pandoc has changed the CLI API so that it is no longer compatible with the version of r-markdown below - xorg-libxrender - xorg-libxext - xorg-libxau - xorg-libxdmcp - xorg-libsm - psutil - google-cloud-storage - azure-storage - azure-storage-common - ratelimiter - configargparse - appdirs - python-irodsclient - cwltool - jsonschema - pandas - networkx - pygraphviz - python-kubernetes - gitpython - tibanna - environment-modules - nbformat - toposort snakemake-5.10.0/tests/000077500000000000000000000000001361131222100146665ustar00rootroot00000000000000snakemake-5.10.0/tests/__init__.py000066400000000000000000000000001361131222100167650ustar00rootroot00000000000000snakemake-5.10.0/tests/knapsack/000077500000000000000000000000001361131222100164615ustar00rootroot00000000000000snakemake-5.10.0/tests/knapsack/1.txt000066400000000000000000000000001361131222100173500ustar00rootroot00000000000000snakemake-5.10.0/tests/knapsack/2.txt000066400000000000000000000000001361131222100173510ustar00rootroot00000000000000snakemake-5.10.0/tests/knapsack/3.txt000066400000000000000000000000001361131222100173520ustar00rootroot00000000000000snakemake-5.10.0/tests/knapsack/Snakefile000066400000000000000000000007431361131222100203110ustar00rootroot00000000000000# kate: syntax python; DATASETS = '1 2 3'.split() rule mapmature_all: input: '{ds}.bam'.format(ds=ds) for ds in DATASETS rule gzip: output: '{file}.gz' input: '{file}' shell: 'gzip < {input} > {output}' rule cutadapt: output: fastq=temp('{ds}.fastq') input: '{ds}.txt' shell: 'echo hello > {output.fastq}' rule bwa_mature: output: bam='{ds}.bam' input: reads='{ds}.fastq.gz' threads: 4 shell: 'echo starting with {threads} threads; sleep 10; touch {output.bam}' snakemake-5.10.0/tests/profile/000077500000000000000000000000001361131222100163265ustar00rootroot00000000000000snakemake-5.10.0/tests/profile/Snakefile000066400000000000000000000005461361131222100201570ustar00rootroot00000000000000 rule all: input: expand("{name}.d", name=range(1000)) rule: output: "{name}.a" shell: "touch {output}" rule: input: "{name}.a" output: "{name}.b" shell: "touch {output}" rule: input: "{name}.b" output: "{name}.c" shell: "touch {output}" rule: input: "{name}.c" output: "{name}.d" shell: "touch {output}" snakemake-5.10.0/tests/test (with parenthese's)/000077500000000000000000000000001361131222100212735ustar00rootroot00000000000000snakemake-5.10.0/tests/test (with parenthese's)/Snakefile000066400000000000000000000003321361131222100231150ustar00rootroot00000000000000 rule: input: "test.in" output: "test.out" run: test = shell("echo 42;", read=True) assert int(test) == 42 with open(output[0], "w") as f: for l in shell("cat {input}", iterable=True): print(l, file=f) snakemake-5.10.0/tests/test (with parenthese's)/expected-results/000077500000000000000000000000001361131222100245735ustar00rootroot00000000000000snakemake-5.10.0/tests/test (with parenthese's)/expected-results/test.out000066400000000000000000000000101361131222100262720ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test (with parenthese's)/test.in000066400000000000000000000000101361131222100225710ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test01/000077500000000000000000000000001361131222100160065ustar00rootroot00000000000000snakemake-5.10.0/tests/test01/Snakefile000066400000000000000000000023501361131222100176320ustar00rootroot00000000000000from snakemake.utils import min_version from snakemake import __version__ print(__version__, file=sys.stderr) shell.prefix("source ~/.bashrc; ") #min_version("2.1") TEST = "abc" onstart: print("Workflow starting") print("Log:") print(log) onsuccess: print("Workflow finished") print("Log:") print(log) onerror: print("Workflow failed") print("Log:") print(log) ruleorder: rule2 > rule4 def testin(wildcards): return "test.in" def version(): return "3.3" rule rule1: # This rule creates an intermediate file. input: 'test.inter' output: 'dir/test.out' log: a='log/logfile.log' version: version() threads: 3 shell: 'if [ {threads} -ne 3 ]; then echo "This test has to be run with -j3 in order to succeed!"; exit 1; fi; ' \ 'echo {TEST}; echo {version}; cp {input[0]} {output[0]}; ' # append a comment 'echo test > {log.a}' rule rule2: input: testin output: 'test.inter' message: 'Copying {input[0]} to {output[0]}' shell: ''' cp {input[0]} {output[0]} ''' rule rule4: input: "test.in" output: "test.inter" shell: "cp {input} {output}" # this should be ignored since test.in is present rule rule3: input: "dir/{a}.out" output: "{a}.in" shell: "cp {input} {output}" snakemake-5.10.0/tests/test01/expected-results/000077500000000000000000000000001361131222100213065ustar00rootroot00000000000000snakemake-5.10.0/tests/test01/expected-results/dir/000077500000000000000000000000001361131222100220645ustar00rootroot00000000000000snakemake-5.10.0/tests/test01/expected-results/dir/test.out000066400000000000000000000000101361131222100235630ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test01/expected-results/test.inter000066400000000000000000000000101361131222100233170ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test01/test.in000066400000000000000000000000101361131222100173040ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test02/000077500000000000000000000000001361131222100160075ustar00rootroot00000000000000snakemake-5.10.0/tests/test02/Snakefile000066400000000000000000000005471361131222100176410ustar00rootroot00000000000000rule all: input: 'test.out' rule rule1: input: '{name}.in' output: '{name}.inter' #message: 'Copying {input[0]} to {output[0]}' log: "logs/{name}.log" shell: 'echo test > {log}; cp {input[0]} {output[0]}' rule rule2: input: '{name}.inter' output: '{name}.out' #message: 'Copying {input[0]} to {output[0]}' shell: 'cp {input[0]} {output[0]}' snakemake-5.10.0/tests/test02/expected-results/000077500000000000000000000000001361131222100213075ustar00rootroot00000000000000snakemake-5.10.0/tests/test02/expected-results/test.out000066400000000000000000000000101361131222100230060ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test02/test.in000066400000000000000000000000101361131222100173050ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test03/000077500000000000000000000000001361131222100160105ustar00rootroot00000000000000snakemake-5.10.0/tests/test03/Snakefile000066400000000000000000000002121361131222100176270ustar00rootroot00000000000000rule rule1: input: '{name}.in' output: '{name}.out' message: 'Copying {input[0]} to {output[0]}' shell: 'cp {input[0]} {output[0]}' snakemake-5.10.0/tests/test03/expected-results/000077500000000000000000000000001361131222100213105ustar00rootroot00000000000000snakemake-5.10.0/tests/test03/expected-results/test.out000066400000000000000000000000101361131222100230070ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test03/params000066400000000000000000000000111361131222100172060ustar00rootroot00000000000000test.out snakemake-5.10.0/tests/test03/test.in000066400000000000000000000000101361131222100173060ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test04/000077500000000000000000000000001361131222100160115ustar00rootroot00000000000000snakemake-5.10.0/tests/test04/Snakefile000066400000000000000000000006701361131222100176400ustar00rootroot00000000000000rule rule1: input: '{name}.bogus.in' output: '{name}.out' message: 'Writing junk to {output[0]}' shell: 'echo "junk" > {output[0]}; cat {input}' rule rule2: input: '{name}.in' output: '{name}.out' message: 'Copying {input[0]} to {output[0]}' shell: 'cp {input[0]} {output[0]}' rule rule3: input: '{name}.more.bogus.in' output: '{name}.out' message: 'Writing junk to {output[0]}' shell: 'echo "junk" > {output[0]}; cat{input}' snakemake-5.10.0/tests/test04/expected-results/000077500000000000000000000000001361131222100213115ustar00rootroot00000000000000snakemake-5.10.0/tests/test04/expected-results/test.out000066400000000000000000000000101361131222100230100ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test04/params000066400000000000000000000000111361131222100172070ustar00rootroot00000000000000test.out snakemake-5.10.0/tests/test04/test.in000066400000000000000000000000101361131222100173070ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test05/000077500000000000000000000000001361131222100160125ustar00rootroot00000000000000snakemake-5.10.0/tests/test05/Snakefile000066400000000000000000000014621361131222100176410ustar00rootroot00000000000000from snakemake import shell chromosomes = [1,2,3] #shell('rm test.*.inter 2> /dev/null | true') rule all: input: 'test.predictions' rule compute1: input: '{name}.in' output: inter=expand('{{name}}.{chr}.inter', chr=chromosomes) resources: gpu=1 run: assert len(output.inter) > 0 print(output.inter) for out in output: shell('(cat {input[0]} && echo "Part {out}") > {out}') rule compute2: input: '{name}.{chromosome}.inter', 'other.txt' output: '{name}.{chromosome}.inter2' threads: 2 resources: io=1 shell: 'cp {input[0]} {output[0]}' rule gather: input: ['{name}.%s.inter2'%c for c in chromosomes] output: '{name}.predictions' run: shell('cat {} > {}'.format(' '.join(input), output[0])) rule other: output: 'other.txt' priority: 50 resources: gpu=1 shell: 'touch other.txt' snakemake-5.10.0/tests/test05/expected-results/000077500000000000000000000000001361131222100213125ustar00rootroot00000000000000snakemake-5.10.0/tests/test05/expected-results/test.1.inter000066400000000000000000000000321361131222100234660ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test05/expected-results/test.1.inter2000066400000000000000000000000321361131222100235500ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test05/expected-results/test.2.inter000066400000000000000000000000321361131222100234670ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test05/expected-results/test.2.inter2000066400000000000000000000000321361131222100235510ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test05/expected-results/test.3.inter000066400000000000000000000000321361131222100234700ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test05/expected-results/test.3.inter2000066400000000000000000000000321361131222100235520ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test05/expected-results/test.predictions000066400000000000000000000001161361131222100245340ustar00rootroot00000000000000testz0r Part test.1.inter testz0r Part test.2.inter testz0r Part test.3.inter snakemake-5.10.0/tests/test05/test.in000066400000000000000000000000101361131222100173100ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test06/000077500000000000000000000000001361131222100160135ustar00rootroot00000000000000snakemake-5.10.0/tests/test06/Snakefile000066400000000000000000000003061361131222100176360ustar00rootroot00000000000000rule all: input: 'test.bla.out' rule wildcards: input: 'test.in' output: 'test.{xyz}.out' message: 'Creating file {output[0]}, xyz={wildcards.xyz}' shell: 'echo {wildcards.xyz} > {output[0]}' snakemake-5.10.0/tests/test06/expected-results/000077500000000000000000000000001361131222100213135ustar00rootroot00000000000000snakemake-5.10.0/tests/test06/expected-results/test.bla.out000066400000000000000000000000041361131222100235520ustar00rootroot00000000000000bla snakemake-5.10.0/tests/test06/test.in000066400000000000000000000000101361131222100173110ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test07/000077500000000000000000000000001361131222100160145ustar00rootroot00000000000000snakemake-5.10.0/tests/test07/Snakefile000066400000000000000000000002501361131222100176350ustar00rootroot00000000000000 rule rule1: input: 'test.in' output: 'test.out' shell: 'cp {input} {output}' rule rule2: input: 'test.in' output: 'test2.out' shell: 'cp {input} {output}' snakemake-5.10.0/tests/test07/expected-results/000077500000000000000000000000001361131222100213145ustar00rootroot00000000000000snakemake-5.10.0/tests/test07/expected-results/test.out000066400000000000000000000000101361131222100230130ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test07/expected-results/test2.out000066400000000000000000000000101361131222100230750ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test07/test.in000066400000000000000000000000101361131222100173120ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test08/000077500000000000000000000000001361131222100160155ustar00rootroot00000000000000snakemake-5.10.0/tests/test08/Snakefile000066400000000000000000000002641361131222100176430ustar00rootroot00000000000000rule rule1: input: '{file}.in' output: '{file}.inter' shell: 'cp {input} {output}' rule rule2: input: '{file}.inter' output: '{file}.out' shell: 'cp {input} {output}' snakemake-5.10.0/tests/test08/expected-results/000077500000000000000000000000001361131222100213155ustar00rootroot00000000000000snakemake-5.10.0/tests/test08/expected-results/test.out000066400000000000000000000000101361131222100230140ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test08/expected-results/test2.out000066400000000000000000000000041361131222100231010ustar00rootroot00000000000000Hoi snakemake-5.10.0/tests/test08/test.in000066400000000000000000000000101361131222100173130ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test08/test2.in000066400000000000000000000000041361131222100174000ustar00rootroot00000000000000Hoi snakemake-5.10.0/tests/test09/000077500000000000000000000000001361131222100160165ustar00rootroot00000000000000snakemake-5.10.0/tests/test09/Snakefile000066400000000000000000000005541361131222100176460ustar00rootroot00000000000000from snakemake import shell def fail(input, output): shell("false && cp {input} {output}") def x(input, output): fail(input, output) rule rule2: input: 'test.inter' output: 'test.out' shell: 'cp {input} {output}' rule rule1: input: 'test.in' output: 'test.inter' log: "logs/test.log" shell: "touch {log} && false && cp {input} {output}" snakemake-5.10.0/tests/test09/expected-results/000077500000000000000000000000001361131222100213165ustar00rootroot00000000000000snakemake-5.10.0/tests/test09/expected-results/.gitignore000066400000000000000000000000001361131222100232740ustar00rootroot00000000000000snakemake-5.10.0/tests/test09/test.in000066400000000000000000000000101361131222100173140ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test10/000077500000000000000000000000001361131222100160065ustar00rootroot00000000000000snakemake-5.10.0/tests/test10/Snakefile000066400000000000000000000001271361131222100176320ustar00rootroot00000000000000rule rule1: input: 'test.in' output: 'test.out' run: shell('cp {input} {output}') snakemake-5.10.0/tests/test10/expected-results/000077500000000000000000000000001361131222100213065ustar00rootroot00000000000000snakemake-5.10.0/tests/test10/expected-results/test.out000066400000000000000000000000101361131222100230050ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test10/test.in000066400000000000000000000000101361131222100173040ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test11/000077500000000000000000000000001361131222100160075ustar00rootroot00000000000000snakemake-5.10.0/tests/test11/Snakefile000066400000000000000000000001551361131222100176340ustar00rootroot00000000000000include: "import.snakefile" rule: input: 'test.inter' output: 'test.out' shell: 'cp {input} {output}' snakemake-5.10.0/tests/test11/expected-results/000077500000000000000000000000001361131222100213075ustar00rootroot00000000000000snakemake-5.10.0/tests/test11/expected-results/test.inter000066400000000000000000000000101361131222100233200ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test11/expected-results/test.out000066400000000000000000000000101361131222100230060ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test11/import.snakefile000066400000000000000000000001171361131222100212030ustar00rootroot00000000000000rule: input: 'test.in' output: 'test.inter' shell: 'cp {input} {output}' snakemake-5.10.0/tests/test11/test.in000066400000000000000000000000101361131222100173050ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test12/000077500000000000000000000000001361131222100160105ustar00rootroot00000000000000snakemake-5.10.0/tests/test12/Snakefile000066400000000000000000000004341361131222100176350ustar00rootroot00000000000000 rule rule1: input: 'test.inter' output: 'test.out' #message: 'Copying {input[0]} to {output[0]}' shell: 'cp {input[0]} {output[0]}' rule rule2: input: 'test.in' output: temp('test.inter') #message: 'Copying {input[0]} to {output[0]}' shell: 'cp {input[0]} {output[0]}' snakemake-5.10.0/tests/test12/expected-results/000077500000000000000000000000001361131222100213105ustar00rootroot00000000000000snakemake-5.10.0/tests/test12/expected-results/test.out000066400000000000000000000000101361131222100230070ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test12/test.in000066400000000000000000000000101361131222100173060ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test13/000077500000000000000000000000001361131222100160115ustar00rootroot00000000000000snakemake-5.10.0/tests/test13/Snakefile000066400000000000000000000005411361131222100176350ustar00rootroot00000000000000 rule all: input: 'test.algo1-p7-improved.out' rule run_algo1: input: '{dataset}.in' output: '{dataset}.algo1-p{param,[0-9]+}.out' shell: 'echo "algo1 / {wildcards.param}" > {output}' rule postprocess: input: '{dataset}.{algorithm}.out' output: '{dataset}.{algorithm}-improved.out' shell: 'cp {input} {output} && (echo "IMPROVED" >> {output})' snakemake-5.10.0/tests/test13/expected-results/000077500000000000000000000000001361131222100213115ustar00rootroot00000000000000snakemake-5.10.0/tests/test13/expected-results/test.algo1-p7-improved.out000066400000000000000000000000231361131222100261650ustar00rootroot00000000000000algo1 / 7 IMPROVED snakemake-5.10.0/tests/test13/expected-results/test.algo1-p7.out000066400000000000000000000000121361131222100243400ustar00rootroot00000000000000algo1 / 7 snakemake-5.10.0/tests/test13/test.in000066400000000000000000000000101361131222100173070ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test14/000077500000000000000000000000001361131222100160125ustar00rootroot00000000000000snakemake-5.10.0/tests/test14/Snakefile.nonstandard000066400000000000000000000011531361131222100221500ustar00rootroot00000000000000from snakemake import shell chromosomes = [1,2,3,4,5] localrules: all rule all: input: 'test.predictions', 'test.2.inter2' rule compute1: input: '{name}.in' output: ['{name}.%s.inter'%c for c in chromosomes] run: for out in output: shell('(cat {input[0]} && echo "Part {out}") > {out}') rule compute2: input: '{name}.{chromosome}.inter' output: '{name}.{chromosome}.inter2' params: test="a=b" shell: 'echo copy; cp {input[0]} {output[0]}' rule gather: input: ['{name}.%s.inter2'%c for c in chromosomes] output: '{name}.predictions' run: shell('cat {} > {}'.format(' '.join(input), output[0])) snakemake-5.10.0/tests/test14/expected-results/000077500000000000000000000000001361131222100213125ustar00rootroot00000000000000snakemake-5.10.0/tests/test14/expected-results/test.1.inter000066400000000000000000000000321361131222100234660ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test14/expected-results/test.1.inter2000066400000000000000000000000321361131222100235500ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test14/expected-results/test.2.inter000066400000000000000000000000321361131222100234670ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test14/expected-results/test.2.inter2000066400000000000000000000000321361131222100235510ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test14/expected-results/test.3.inter000066400000000000000000000000321361131222100234700ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test14/expected-results/test.3.inter2000066400000000000000000000000321361131222100235520ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test14/expected-results/test.predictions000066400000000000000000000002021361131222100245300ustar00rootroot00000000000000testz0r Part test.1.inter testz0r Part test.2.inter testz0r Part test.3.inter testz0r Part test.4.inter testz0r Part test.5.inter snakemake-5.10.0/tests/test14/qsub000077500000000000000000000002231361131222100167070ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test14/qsub.py000077500000000000000000000004771361131222100173510ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import os import random from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) with open("qsub.log", "a") as log: print(job_properties, file=log) print(random.randint(1, 100)) os.system("sh {}".format(jobscript)) snakemake-5.10.0/tests/test14/test.in000066400000000000000000000000101361131222100173100ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test15/000077500000000000000000000000001361131222100160135ustar00rootroot00000000000000snakemake-5.10.0/tests/test15/Snakefile000066400000000000000000000002331361131222100176350ustar00rootroot00000000000000 rule a: input: test = lambda wildcards: "test2.in" if os.path.exists("test2.in") else "test.in" output: "test.out" shell: "cp {input.test} {output}" snakemake-5.10.0/tests/test15/expected-results/000077500000000000000000000000001361131222100213135ustar00rootroot00000000000000snakemake-5.10.0/tests/test15/expected-results/test.out000066400000000000000000000000101361131222100230120ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test15/test.in000066400000000000000000000000101361131222100173110ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/testHighWorkload/000077500000000000000000000000001361131222100201505ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/Snakefile000066400000000000000000000012121361131222100217700ustar00rootroot00000000000000import os.path import sys instances = [os.path.basename(s[:len(s)-4]) for s in os.listdir('mfa') if s.endswith('.mfa')] rule all: input: ['fisher/%s.pairs.gz'%s for s in instances] + ['ph/%s.ph'%s for s in instances] rule extract_mismatch_counts: input: 'mfa/{instance}.mfa' output: 'fisher/{instance}.pairs.gz' message: 'Extracting number mismatches for each pair of sequences from {input}' shell: 'sleep 2; touch {output}' rule create_tree: input: 'mfa/{instance}.mfa' output: 'ph/{instance}.ph', 'ph/{instance}.ph.log' message: 'Running CLUSTALW to compute NJ tree from {input}' shell: "sleep 2; touch {output[0]}; touch {output[1]}" snakemake-5.10.0/tests/testHighWorkload/mfa/000077500000000000000000000000001361131222100207135ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/00.mfa000066400000000000000000000000001361131222100216050ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/01.mfa000066400000000000000000000000001361131222100216060ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/02.mfa000066400000000000000000000000001361131222100216070ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/03.mfa000066400000000000000000000000001361131222100216100ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/04.mfa000066400000000000000000000000001361131222100216110ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/05.mfa000066400000000000000000000000001361131222100216120ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/06.mfa000066400000000000000000000000001361131222100216130ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/07.mfa000066400000000000000000000000001361131222100216140ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/08.mfa000066400000000000000000000000001361131222100216150ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/09.mfa000066400000000000000000000000001361131222100216160ustar00rootroot00000000000000snakemake-5.10.0/tests/testHighWorkload/mfa/10.mfa000066400000000000000000000000001361131222100216060ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ancient/000077500000000000000000000000001361131222100173465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ancient/Snakefile000066400000000000000000000022311361131222100211700ustar00rootroot00000000000000from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() shell("touch C && sleep 1 && touch B && sleep 1 && touch A && touch D && \ mkdir -p github.com/snakemake/snakemake/raw/master/tests/test_remote_http/expected-results && \ touch github.com/snakemake/snakemake/raw/master/tests/test_remote_http/expected-results/landsat-data.txt && \ touch -t 200101010101 old_file") #Will not be executed even though A is newer rule a: input: ancient("A") output: "B" shell: "echo \"B recreated\" > {output}" #Will be executed because B is newer rule b: input: "B" output: "C" shell: "echo \"C recreated\" > {output}" #Will be executed because C was updated in rule b rule c: input: ancient("C") output: "D" shell: "echo \"D recreated\" > {output}" # This should not run even though output is older than input rule remote_ancient: input: ancient(HTTP.remote("github.com/snakemake/snakemake/raw/master/tests/test_remote_http/expected-results/landsat-data.txt")) output: "old_file" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_ancient/expected-results/000077500000000000000000000000001361131222100226465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ancient/expected-results/A000066400000000000000000000000001361131222100227370ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ancient/expected-results/B000066400000000000000000000000001361131222100227400ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ancient/expected-results/C000066400000000000000000000000141361131222100227460ustar00rootroot00000000000000C recreated snakemake-5.10.0/tests/test_ancient/expected-results/D000066400000000000000000000000141361131222100227470ustar00rootroot00000000000000D recreated snakemake-5.10.0/tests/test_ancient/expected-results/old_file000066400000000000000000000000001361131222100243340ustar00rootroot00000000000000snakemake-5.10.0/tests/test_archive/000077500000000000000000000000001361131222100173465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_archive/Snakefile000066400000000000000000000001561361131222100211740ustar00rootroot00000000000000rule a: output: "test.out" conda: "test-env.yaml" shell: "touch {output}" snakemake-5.10.0/tests/test_archive/expected-results/000077500000000000000000000000001361131222100226465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_archive/expected-results/.gitignore000066400000000000000000000000001361131222100246240ustar00rootroot00000000000000snakemake-5.10.0/tests/test_archive/test-env.yaml000066400000000000000000000001051361131222100217730ustar00rootroot00000000000000channels: - bioconda - conda-forge dependencies: - bwa =0.7.17 snakemake-5.10.0/tests/test_bash/000077500000000000000000000000001361131222100166425ustar00rootroot00000000000000snakemake-5.10.0/tests/test_bash/Snakefile000066400000000000000000000001271361131222100204660ustar00rootroot00000000000000 rule a: output: "test.out" shell: "basename $BASH > {output}" snakemake-5.10.0/tests/test_bash/expected-results/000077500000000000000000000000001361131222100221425ustar00rootroot00000000000000snakemake-5.10.0/tests/test_bash/expected-results/test.out000066400000000000000000000000051361131222100236450ustar00rootroot00000000000000bash snakemake-5.10.0/tests/test_batch/000077500000000000000000000000001361131222100170065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch/Snakefile000066400000000000000000000006371361131222100206400ustar00rootroot00000000000000SAMPLES = ["a", "b", "c"] rule all: input: "foo.txt", "bar.txt" rule aggregate: input: expand("{sample}.inter.txt", sample=SAMPLES) output: "foo.txt" shell: "touch {output}" rule feed: output: temp("{sample}.inter.txt") shell: "touch {output}" rule independent: output: "bar.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_batch/expected-results/000077500000000000000000000000001361131222100223065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch/expected-results/a.inter.txt000066400000000000000000000000001361131222100243750ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch/expected-results/bar.txt000066400000000000000000000000001361131222100236010ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch_final/000077500000000000000000000000001361131222100201575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch_final/Snakefile000066400000000000000000000006371361131222100220110ustar00rootroot00000000000000SAMPLES = ["a", "b", "c"] rule all: input: "foo.txt", "bar.txt" rule aggregate: input: expand("{sample}.inter.txt", sample=SAMPLES) output: "foo.txt" shell: "touch {output}" rule feed: output: temp("{sample}.inter.txt") shell: "touch {output}" rule independent: output: "bar.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_batch_final/expected-results/000077500000000000000000000000001361131222100234575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch_final/expected-results/bar.txt000066400000000000000000000000001361131222100247520ustar00rootroot00000000000000snakemake-5.10.0/tests/test_batch_final/expected-results/foo.txt000066400000000000000000000000001361131222100247710ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/000077500000000000000000000000001361131222100176575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/Snakefile000066400000000000000000000011771361131222100215110ustar00rootroot00000000000000import time rule all: input: "test.benchmark_shell.txt", "test.benchmark_script.txt", "test.benchmark_run.txt", "test.benchmark_run_shell.txt", rule bench_shell: benchmark: repeat("test.benchmark_shell.txt", 2) threads: 2 shell: "stress --cpu {threads} --timeout 10" rule bench_script: benchmark: "test.benchmark_script.txt" script: 'script.py' rule bench_run: benchmark: "test.benchmark_run.txt" run: time.sleep(1) rule bench_run_shell: benchmark: "test.benchmark_run_shell.txt" run: shell("sleep 1") snakemake-5.10.0/tests/test_benchmark/expected-results/000077500000000000000000000000001361131222100231575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/expected-results/test.benchmark_run.txt000066400000000000000000000000001361131222100275020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/expected-results/test.benchmark_run_shell.txt000066400000000000000000000000001361131222100306710ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/expected-results/test.benchmark_script.txt000066400000000000000000000000001361131222100302020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/expected-results/test.benchmark_shell.txt000066400000000000000000000000001361131222100300050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_benchmark/script.py000077500000000000000000000001451361131222100215400ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import time print('Hello World!', file=sys.stderr) time.sleep(1) snakemake-5.10.0/tests/test_checkpoints/000077500000000000000000000000001361131222100202375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/Snakefile000066400000000000000000000017451361131222100220720ustar00rootroot00000000000000 rule all: input: "aggregated/a.txt", "aggregated/b.txt" checkpoint somestep: input: "samples/{sample}.txt" output: "somestep/{sample}.txt" shell: # simulate some output vale "echo {wildcards.sample} > somestep/{wildcards.sample}.txt" rule intermediate: input: "somestep/{sample}.txt" output: "post/{sample}.txt" shell: "touch {output}" rule alt_intermediate: input: "somestep/{sample}.txt" output: "alt/{sample}.txt" shell: "touch {output}" def aggregate_input(wildcards): # decision based on content of output file with checkpoints.somestep.get(**wildcards).output[0].open() as f: if f.read().strip() == "a": return "post/{sample}.txt" else: return "alt/{sample}.txt" rule aggregate: input: aggregate_input output: "aggregated/{sample}.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_checkpoints/expected-results/000077500000000000000000000000001361131222100235375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/expected-results/aggregated/000077500000000000000000000000001361131222100256315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/expected-results/aggregated/a.txt000066400000000000000000000000001361131222100266000ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/samples/000077500000000000000000000000001361131222100217035ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/samples/a.txt000066400000000000000000000000001361131222100226520ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints/samples/b.txt000066400000000000000000000000001361131222100226530ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/000077500000000000000000000000001361131222100210755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/Snakefile000066400000000000000000000015421361131222100227230ustar00rootroot00000000000000 rule all: input: "aggregated/a.txt", "aggregated/b.txt" checkpoint clustering: input: "samples/{sample}.txt" output: clusters=directory("clustering/{sample}") shell: "mkdir clustering/{wildcards.sample}; " "for i in 1 2 3; do echo $i > clustering/{wildcards.sample}/$i.txt; done" rule intermediate: input: "clustering/{sample}/{i}.txt" output: "post/{sample}/{i}.txt" shell: "cp {input} {output}" def aggregate_input(wildcards): return expand("post/{sample}/{i}.txt", sample=wildcards.sample, i=glob_wildcards(os.path.join(checkpoints.clustering.get(**wildcards).output[0], "{i}.txt")).i) rule aggregate: input: aggregate_input output: "aggregated/{sample}.txt" shell: "cat {input} > {output}" snakemake-5.10.0/tests/test_checkpoints_dir/expected-results/000077500000000000000000000000001361131222100243755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/expected-results/aggregated/000077500000000000000000000000001361131222100264675ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/expected-results/aggregated/a.txt000066400000000000000000000000061361131222100274440ustar00rootroot000000000000002 1 3 snakemake-5.10.0/tests/test_checkpoints_dir/expected-results/aggregated/b.txt000066400000000000000000000000061361131222100274450ustar00rootroot000000000000002 1 3 snakemake-5.10.0/tests/test_checkpoints_dir/samples/000077500000000000000000000000001361131222100225415ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/samples/a.txt000066400000000000000000000000001361131222100235100ustar00rootroot00000000000000snakemake-5.10.0/tests/test_checkpoints_dir/samples/b.txt000066400000000000000000000000001361131222100235110ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conda/000077500000000000000000000000001361131222100170115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conda/Snakefile000066400000000000000000000005311361131222100206340ustar00rootroot00000000000000rule all: input: expand("test{i}.out2", i=range(3)) rule a: output: "test{i}.out" conda: "test-env.yaml" shell: "bwa 2> {output} || true" rule b: input: "test{i}.out" output: "test{i}.out2" conda: "test-env.yaml" shell: "bwa 2> {output} || true" snakemake-5.10.0/tests/test_conda/expected-results/000077500000000000000000000000001361131222100223115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conda/expected-results/test0.out000066400000000000000000000022341361131222100241020ustar00rootroot00000000000000 Program: bwa (alignment via Burrows-Wheeler transformation) Version: 0.7.17-r1188 Contact: Heng Li Usage: bwa [options] Command: index index sequences in the FASTA format mem BWA-MEM algorithm fastmap identify super-maximal exact matches pemerge merge overlapping paired ends (EXPERIMENTAL) aln gapped/ungapped alignment samse generate alignment (single ended) sampe generate alignment (paired ended) bwasw BWA-SW for long queries shm manage indices in shared memory fa2pac convert FASTA to PAC format pac2bwt generate BWT from PAC pac2bwtgen alternative algorithm for generating BWT bwtupdate update .bwt to the new format bwt2sa generate SA from BWT and Occ Note: To use BWA, you need to first index the genome with `bwa index'. There are three alignment algorithms in BWA: `mem', `bwasw', and `aln/samse/sampe'. If you are not sure which to use, try `bwa mem' first. Please `man ./bwa.1' for the manual. snakemake-5.10.0/tests/test_conda/expected-results/test1.out000066400000000000000000000022341361131222100241030ustar00rootroot00000000000000 Program: bwa (alignment via Burrows-Wheeler transformation) Version: 0.7.17-r1188 Contact: Heng Li Usage: bwa [options] Command: index index sequences in the FASTA format mem BWA-MEM algorithm fastmap identify super-maximal exact matches pemerge merge overlapping paired ends (EXPERIMENTAL) aln gapped/ungapped alignment samse generate alignment (single ended) sampe generate alignment (paired ended) bwasw BWA-SW for long queries shm manage indices in shared memory fa2pac convert FASTA to PAC format pac2bwt generate BWT from PAC pac2bwtgen alternative algorithm for generating BWT bwtupdate update .bwt to the new format bwt2sa generate SA from BWT and Occ Note: To use BWA, you need to first index the genome with `bwa index'. There are three alignment algorithms in BWA: `mem', `bwasw', and `aln/samse/sampe'. If you are not sure which to use, try `bwa mem' first. Please `man ./bwa.1' for the manual. snakemake-5.10.0/tests/test_conda/expected-results/test2.out000066400000000000000000000022341361131222100241040ustar00rootroot00000000000000 Program: bwa (alignment via Burrows-Wheeler transformation) Version: 0.7.17-r1188 Contact: Heng Li Usage: bwa [options] Command: index index sequences in the FASTA format mem BWA-MEM algorithm fastmap identify super-maximal exact matches pemerge merge overlapping paired ends (EXPERIMENTAL) aln gapped/ungapped alignment samse generate alignment (single ended) sampe generate alignment (paired ended) bwasw BWA-SW for long queries shm manage indices in shared memory fa2pac convert FASTA to PAC format pac2bwt generate BWT from PAC pac2bwtgen alternative algorithm for generating BWT bwtupdate update .bwt to the new format bwt2sa generate SA from BWT and Occ Note: To use BWA, you need to first index the genome with `bwa index'. There are three alignment algorithms in BWA: `mem', `bwasw', and `aln/samse/sampe'. If you are not sure which to use, try `bwa mem' first. Please `man ./bwa.1' for the manual. snakemake-5.10.0/tests/test_conda/test-env.yaml000066400000000000000000000001061361131222100214370ustar00rootroot00000000000000channels: - bioconda - conda-forge dependencies: - bwa ==0.7.17 snakemake-5.10.0/tests/test_conda_custom_prefix/000077500000000000000000000000001361131222100217605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conda_custom_prefix/Snakefile000066400000000000000000000003561361131222100236100ustar00rootroot00000000000000rule all: input: expand("test{i}.out", i=range(3)) rule a: output: "test{i}.out" conda: "test-env.yaml" shell: "test -e custom/*/bin/snakemake && " "snakemake --version > {output}" snakemake-5.10.0/tests/test_conda_custom_prefix/expected-results/000077500000000000000000000000001361131222100252605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conda_custom_prefix/expected-results/test0.out000066400000000000000000000000061361131222100270440ustar00rootroot000000000000005.7.1 snakemake-5.10.0/tests/test_conda_custom_prefix/expected-results/test1.out000066400000000000000000000000061361131222100270450ustar00rootroot000000000000005.7.1 snakemake-5.10.0/tests/test_conda_custom_prefix/expected-results/test2.out000066400000000000000000000000061361131222100270460ustar00rootroot000000000000005.7.1 snakemake-5.10.0/tests/test_conda_custom_prefix/test-env.yaml000066400000000000000000000001231361131222100244050ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - snakemake-minimal ==5.7.1 snakemake-5.10.0/tests/test_conditional/000077500000000000000000000000001361131222100202305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conditional/Snakefile000066400000000000000000000006621361131222100220600ustar00rootroot00000000000000 CONDITION = False if CONDITION: rule: output: "test.out" shell: "echo bla; exit 1" else: rule: output: "test.out" shell: "touch {output}" # the following is bad style and just for testing. You should rather write a rule that collects test{i}.out as input files and have ONE rule that generates any of the files with a wildcard. for i in range(3): rule: output: "test.{i}.out".format(i=i) shell: "touch {output}" snakemake-5.10.0/tests/test_conditional/expected-results/000077500000000000000000000000001361131222100235305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conditional/expected-results/test.0.out000066400000000000000000000000001361131222100253640ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conditional/expected-results/test.1.out000066400000000000000000000000001361131222100253650ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conditional/expected-results/test.2.out000066400000000000000000000000001361131222100253660ustar00rootroot00000000000000snakemake-5.10.0/tests/test_conditional/expected-results/test.out000066400000000000000000000000001361131222100252260ustar00rootroot00000000000000snakemake-5.10.0/tests/test_config/000077500000000000000000000000001361131222100171725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_config/Snakefile000066400000000000000000000002041361131222100210120ustar00rootroot00000000000000 include: "test.rules" configfile: "test.json" rule: output: config["outfile"] shell: "touch {output}" snakemake-5.10.0/tests/test_config/expected-results/000077500000000000000000000000001361131222100224725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_config/expected-results/test.out000066400000000000000000000000001361131222100241700ustar00rootroot00000000000000snakemake-5.10.0/tests/test_config/test.json000066400000000000000000000000361361131222100210430ustar00rootroot00000000000000{ "outfile": "test.out" } snakemake-5.10.0/tests/test_config/test.rules000066400000000000000000000000311361131222100212170ustar00rootroot00000000000000configfile: "test2.json" snakemake-5.10.0/tests/test_config/test2.json000066400000000000000000000000371361131222100211260ustar00rootroot00000000000000{ "outfile": "test2.out" } snakemake-5.10.0/tests/test_config/test3.json000066400000000000000000000000371361131222100211270ustar00rootroot00000000000000{ "outfile": "test3.out" } snakemake-5.10.0/tests/test_convert_to_cwl/000077500000000000000000000000001361131222100207545ustar00rootroot00000000000000snakemake-5.10.0/tests/test_convert_to_cwl/Snakefile000066400000000000000000000017371361131222100226100ustar00rootroot00000000000000import os print(os.listdir(".")) print(os.getcwd()) TEST = "abc" onstart: print("Workflow starting") print("Log:") print(log) onsuccess: print("Workflow finished") print("Log:") print(log) onerror: print("Workflow failed") print("Log:") print(log) ruleorder: rule2 > rule4 def testin(wildcards): return "test.in" def version(): return "3.3" rule rule1: # This rule creates an intermediate file. input: 'dir/a/test.inter' output: 'dir/test.out' log: a='log/logfile.log' version: version() shell: 'echo {TEST}; echo {version}; cp {input[0]} {output[0]}; ' # append a comment 'echo test > {log.a}' rule rule2: input: testin output: 'dir/a/test.inter' shell: ''' cp {input[0]} {output[0]} ''' rule rule4: input: "test.in" output: "dir/a/test.inter" shell: "cp {input} {output}" # this should be ignored since test.in is present rule rule3: input: "dir/{a}.out" output: "{a}.in" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_convert_to_cwl/test.in000066400000000000000000000000101361131222100222520ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test_core_dependent_threads/000077500000000000000000000000001361131222100224155ustar00rootroot00000000000000snakemake-5.10.0/tests/test_core_dependent_threads/Snakefile000066400000000000000000000001711361131222100242400ustar00rootroot00000000000000rule a: output: "test.out" threads: workflow.cores * 0.75 shell: "echo {threads} > {output}" snakemake-5.10.0/tests/test_core_dependent_threads/expected-results/000077500000000000000000000000001361131222100257155ustar00rootroot00000000000000snakemake-5.10.0/tests/test_core_dependent_threads/expected-results/test.out000066400000000000000000000000021361131222100274150ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_cwl/000077500000000000000000000000001361131222100165125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_cwl/Snakefile000066400000000000000000000003351361131222100203370ustar00rootroot00000000000000rule a: input: input=["test.txt"] output: output="sorted.txt" params: key=["2"] cwl: "https://github.com/common-workflow-language/workflows/raw/master/tools/linux-sort.cwl" snakemake-5.10.0/tests/test_cwl/expected-results/000077500000000000000000000000001361131222100220125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_cwl/expected-results/sorted.txt000066400000000000000000000000141361131222100240460ustar00rootroot00000000000000b 1 a 2 c 3 snakemake-5.10.0/tests/test_cwl/test.txt000066400000000000000000000000141361131222100202250ustar00rootroot00000000000000c 3 a 2 b 1 snakemake-5.10.0/tests/test_default_remote/000077500000000000000000000000001361131222100207245ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_remote/Snakefile000066400000000000000000000032401361131222100225470ustar00rootroot00000000000000rule all: input: "test.3.txt", 'test.4.txt', 'test.5.txt', 'test.6.txt' def check_remote(f): if not f.startswith("test-remote-bucket/"): raise ValueError("Input and output are not remote files.") def list_function(wildcards): return ["test.2.txt", "test.txt"] def dict_function(wildcards): return {'a': "test.2.txt", 'b': "test.txt"} rule a: input: "test.txt" output: "{sample}.2.txt" run: check_remote(input[0]) check_remote(output[0]) shell("cp {input} {output}") rule b: input: list_function output: "test.3.txt" run: for e in input: check_remote(e) check_remote(output[0]) shell("cp {input[0]} {output}") rule c: input: **dict_function(None) output: "test.4.txt" run: for e in input: check_remote(e) check_remote(output[0]) shell("cp {input.a} {output}") rule d: input: unpack(dict_function) output: "test.5.txt" run: for e in input: check_remote(e) check_remote(output[0]) shell("cp {input.a} {output}") rule e: input: rules.a.output[0] output: "{sample}.6.txt" run: for e in input: check_remote(e) check_remote(output[0]) shell("cp {input} {output}") # after we finish, we need to remove the pickle storing # the local moto "buckets" so we are starting fresh # next time this test is run. This file is created by # the moto wrapper defined in S3Mocked.py onsuccess: shell("rm ./motoState.p") onerror: shell("rm ./motoState.p") snakemake-5.10.0/tests/test_default_remote/expected-results/000077500000000000000000000000001361131222100242245ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_remote/expected-results/.gitignore000066400000000000000000000000001361131222100262020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_remote/test.txt000066400000000000000000000000051361131222100224370ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_default_resources/000077500000000000000000000000001361131222100214435ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_resources/Snakefile000066400000000000000000000007061361131222100232720ustar00rootroot00000000000000from snakemake.remote.S3Mocked import RemoteProvider as S3RemoteProvider s3 = S3RemoteProvider() rule a: input: s3.remote("test-remote-bucket/test.in") output: "test.out" group: "test" shell: "[[ '{resources.mem_mb}' == '1000' ]] && touch {output}" rule b: output: s3.remote("test-remote-bucket/test.in") group: "test" shell: "[[ '{resources.mem_mb}' == '1000' ]] && touch {output}" snakemake-5.10.0/tests/test_default_resources/expected-results/000077500000000000000000000000001361131222100247435ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_resources/expected-results/test.out000066400000000000000000000000001361131222100264410ustar00rootroot00000000000000snakemake-5.10.0/tests/test_default_resources/test.txt000066400000000000000000000000051361131222100231560ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_deferred_func_eval/000077500000000000000000000000001361131222100215275ustar00rootroot00000000000000snakemake-5.10.0/tests/test_deferred_func_eval/Snakefile000066400000000000000000000006561361131222100233620ustar00rootroot00000000000000import os def get_mem_gb(wildcards, input): size = 0 if os.path.exists(input[0]): size = int(os.path.getsize(input[0]) / (1024 ** 3)) return max(size, 1) rule a: input: "test1.in", "test2.in" output: "test.out" params: a=lambda wildcards, input, resources: "+".join(input) resources: mem_gb=get_mem_gb shell: "echo {params.a} > {output}" snakemake-5.10.0/tests/test_deferred_func_eval/expected-results/000077500000000000000000000000001361131222100250275ustar00rootroot00000000000000snakemake-5.10.0/tests/test_deferred_func_eval/expected-results/test.out000066400000000000000000000000221361131222100265310ustar00rootroot00000000000000test1.in+test2.in snakemake-5.10.0/tests/test_deferred_func_eval/test1.in000066400000000000000000000000001361131222100231050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_deferred_func_eval/test2.in000066400000000000000000000000001361131222100231060ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_all_output/000077500000000000000000000000001361131222100214375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_all_output/Snakefile000066400000000000000000000016221361131222100232640ustar00rootroot00000000000000rule all: input: "delete_all_output", "delete_temp_output" output: touch("all_ok") rule delete_all_output: output: touch("delete_all_output") shell: """ echo $PATH ls python -m snakemake -s Snakefile_inner all && \ rm nothere && \ python -m snakemake -s Snakefile_inner --delete-all-output all && \ test -f infile && test ! -f intermediate && test ! -f somedir/final && \ test ! -d empty_dir && test ! -L dangling && test ! -d full_dir """ rule delete_temp_output: output: touch("delete_temp_output") shell: """ python -m snakemake -s Snakefile_inner --notemp temp && \ python -m snakemake -s Snakefile_inner --delete-temp-output temp && \ test -f infile && test ! -f temp_intermediate && \ test ! -d temp_empty_dir && test ! -d temp_full_dir && test -f temp_keep """ snakemake-5.10.0/tests/test_delete_all_output/Snakefile_inner000066400000000000000000000017061361131222100244620ustar00rootroot00000000000000rule all: input: "some_dir/final", "empty_dir", "full_dir" rule a: input: "infile" output: "intermediate", "dangling", touch(protected("protected")) shell: "ln -s {input} {output[0]} && touch nothere && ln -s nothere {output[1]}" rule b: input: "intermediate" output: touch("some_dir/final") rule c: output: directory("empty_dir") shell: "mkdir empty_dir" rule d: output: directory("full_dir") shell: "mkdir full_dir && touch full_dir/somefile" rule e: input: "infile" output: temp("temp_intermediate") shell: "touch {output}" rule f: input: "temp_intermediate" output: directory(temp("temp_empty_dir")) shell: "mkdir temp_empty_dir" rule g: input: "temp_intermediate" output: directory(temp("temp_full_dir")) shell: "mkdir temp_full_dir && touch temp_full_dir/somefile" rule temp: input: "temp_empty_dir", "temp_full_dir" output: touch("temp_keep") snakemake-5.10.0/tests/test_delete_all_output/expected-results/000077500000000000000000000000001361131222100247375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_all_output/expected-results/all_ok000066400000000000000000000000001361131222100261110ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_all_output/infile000066400000000000000000000000001361131222100226160ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/000077500000000000000000000000001361131222100206075ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/Snakefile000066400000000000000000000040341361131222100224340ustar00rootroot00000000000000# See bug #300. This tests that output files really are cleaned up # before running a rule, and touched afterwards. # # The output should be deleted before the job starts. # (The output should be deleted on the the head node for cluster jobs.) # The path to the output should be created # The output should be touch'd on the head node to always be new. # # Additionally this should work for directories, symlinks and symlinks # to directories. # # TODO - consider adding a cluster-based test for point 2 above. # Setup - touch a mock input file and an out-of-date output file. shell("touch -t 201604010000 output.file") shell("touch input.file") # An empty directory shell("mkdir -p output.dir ; touch -ch -t 201604010000 output.dir") # A dangling symlink shell("ln -fs nosuchfile output.link ; touch -ch -t 201604010000 output.link") # A symlink to an empty directory shell("mkdir -p an_empty_dir; ln -fs an_empty_dir output.dirlink ; touch -ch -t 201604010000 an_empty_dir output.dirlink") rule main: input: "output.file", "output.dir", "output.link", "output.dirlink" rule make_the_file: output: "output.file", "foo/output.foo.file" input: "input.file" # Rule fails if any output.file is already present run: shell("test ! -e output.file") shell("test -d foo") shell("test ! -e foo/*") shell("touch -t 201604010000 output.file") shell("touch foo/output.foo.file") rule make_the_dir: output: directory("output.dir") input: "input.file" #mkdir fails if the dir is already present run: shell("mkdir output.dir") shell("touch output.dir/foo") rule make_the_links: output: "output.link", directory("output.dirlink") input: "input.file" # Both links should be gone, but an_empty_dir should not have been removed # as it's not a direct target of the rule. run: shell("touch arealfile") shell("ln -s arealfile output.link") shell("test -d an_empty_dir") shell("mkdir empty_dir2") shell("ln -s empty_dir2 output.dirlink") snakemake-5.10.0/tests/test_delete_output/expected-results/000077500000000000000000000000001361131222100241075ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/expected-results/foo/000077500000000000000000000000001361131222100246725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/expected-results/foo/output.foo.file000066400000000000000000000000001361131222100276430ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/expected-results/output.file000066400000000000000000000000001361131222100262760ustar00rootroot00000000000000snakemake-5.10.0/tests/test_delete_output/nosuchfile000066400000000000000000000000001361131222100226570ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/000077500000000000000000000000001361131222100177315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/Snakefile000066400000000000000000000024711361131222100215610ustar00rootroot00000000000000shell("mkdir -p input_dir && touch input_dir/child") rule downstream: input: "some/dir" output: touch(directory("some/other_dir")) rule normal: input: "input_dir" output: directory("some/dir") shell: "mkdir -p {output} && touch some/dir/some_file" rule symlinked_output: output: directory("symlinked_dir") shell: "mkdir dir_to_link_to && ln -s dir_to_link_to symlinked_dir" rule symlinked_input: input: "symlinked_dir" # Check that there is an error if the output isn't a directory rule file_expecting_dir: output: directory("not_a_dir") shell: "touch not_a_dir" # Check that there is an error if an output directory isn't flagged with directory() rule dir_expecting_file: output: "is_a_dir" shell: "mkdir is_a_dir" # Should fail due to being a child to some/dir rule child_to_other: input: "some/other_dir" output: touch("some/dir/child") # Shouldn't fail, even though some/dir is a shared prefix rule not_child_to_other: input: "some/other_dir" output: touch("some/dir-child") # Shouldn't fail since children to inputs are currently allowed. rule child_to_input: input: "input_dir/child" output: touch("child_to_input") rule shadow: input: "some/dir" output: directory("some/shadow") shadow: "shallow" shell: "mkdir -p {output}" snakemake-5.10.0/tests/test_directory/expected-results/000077500000000000000000000000001361131222100232315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/child_to_input000066400000000000000000000000001361131222100261460ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/dir_to_link_to/000077500000000000000000000000001361131222100262305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/dir_to_link_to/.snakemake_timestamp000066400000000000000000000000001361131222100322410ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some/000077500000000000000000000000001361131222100241745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some/dir-child000066400000000000000000000000001361131222100257440ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some/shadow/000077500000000000000000000000001361131222100254615ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some/shadow/.snakemake_timestamp000066400000000000000000000000001361131222100314720ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some_other_dir/000077500000000000000000000000001361131222100262335ustar00rootroot00000000000000snakemake-5.10.0/tests/test_directory/expected-results/some_other_dir/.snakemake_timestamp000066400000000000000000000000001361131222100322440ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dup_out_patterns/000077500000000000000000000000001361131222100213245ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dup_out_patterns/Snakefile000066400000000000000000000002551361131222100231520ustar00rootroot00000000000000rule default: input: "out.txt" rule my_rule: output: "{name}.txt", "{name}.txt", shell: r""" touch {output} """ snakemake-5.10.0/tests/test_dup_out_patterns/expected-results/000077500000000000000000000000001361131222100246245ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dup_out_patterns/expected-results/.gitkeep000066400000000000000000000000001361131222100262430ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/000077500000000000000000000000001361131222100173515ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/Snakefile000066400000000000000000000010511361131222100211720ustar00rootroot00000000000000# snakemake rule all: input: a=dynamic("test.{n}.{bla}.out"), b=dynamic("test.{n}.{bla}.csv") run: print(input.b) rule inter: input: "test.{n}.{bla}.inter" output: "test.{n}.{bla}.out" shell: "cp {input} {output}" rule dynoutput: input: "test.xy.in" output: dynamic("test.{n}.{bla}.inter") shell: "for i in {{0..2}}; do touch test.0$i.xy.inter; done" rule: input: "test.{n}.{bla}.inter" output: "test.{n}.{bla}.txt" shell: "touch {output}" rule: input: "test.{n}.{bla}.txt" output: "test.{n}.{bla}.csv" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_dynamic/expected-results/000077500000000000000000000000001361131222100226515ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.00.xy.csv000066400000000000000000000000001361131222100252100ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.00.xy.out000066400000000000000000000000001361131222100252240ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.01.xy.csv000066400000000000000000000000001361131222100252110ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.01.xy.out000066400000000000000000000000001361131222100252250ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.02.xy.csv000066400000000000000000000000001361131222100252120ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/expected-results/test.02.xy.out000066400000000000000000000000001361131222100252260ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic/test.xy.in000066400000000000000000000000001361131222100213050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic_temp/000077500000000000000000000000001361131222100203765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic_temp/Snakefile000066400000000000000000000014021361131222100222170ustar00rootroot00000000000000# Snakefile # each input is 8 lines SCRATCH = 'test' rule all: input: 'out.txt' run: if os.listdir('test'): raise ValueError('not all temp files have been deleted') rule gather: input: dynamic('{}/split_1.{{id}}'.format(SCRATCH)), dynamic('{}/split_2.{{id}}'.format(SCRATCH)) output: 'out.txt' shell: 'touch {output}' rule scatter: input: 'test1.txt', 'test2.txt' output: temp(dynamic('{}/split_1.{{id}}'.format(SCRATCH))), temp(dynamic('{}/split_2.{{id}}'.format(SCRATCH))) shell: ('cat {{input[0]}} | head -8 | split -a 4 -l 4 - {}/split_1.; ' 'cat {{input[1]}} | head -8 | split -a 4 -l 4 - {}/split_2.' .format(SCRATCH, SCRATCH)) snakemake-5.10.0/tests/test_dynamic_temp/expected-results/000077500000000000000000000000001361131222100236765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic_temp/expected-results/out.txt000066400000000000000000000000001361131222100252340ustar00rootroot00000000000000snakemake-5.10.0/tests/test_dynamic_temp/test1.txt000066400000000000000000000000201361131222100221670ustar00rootroot000000000000001 2 3 4 5 6 7 8 snakemake-5.10.0/tests/test_dynamic_temp/test2.txt000066400000000000000000000000201361131222100221700ustar00rootroot000000000000001 2 3 4 5 6 7 8 snakemake-5.10.0/tests/test_empty_include/000077500000000000000000000000001361131222100205665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_empty_include/Snakefile000066400000000000000000000000311361131222100224040ustar00rootroot00000000000000include: "include.rules" snakemake-5.10.0/tests/test_empty_include/expected-results/000077500000000000000000000000001361131222100240665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_empty_include/expected-results/.gitignore000066400000000000000000000000001361131222100260440ustar00rootroot00000000000000snakemake-5.10.0/tests/test_empty_include/include.rules000066400000000000000000000000001361131222100232530ustar00rootroot00000000000000snakemake-5.10.0/tests/test_env_modules/000077500000000000000000000000001361131222100202455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_env_modules/Snakefile000066400000000000000000000001671361131222100220750ustar00rootroot00000000000000rule a: output: "test.out" envmodules: "dot" shell: "test-env-modules.sh {output}" snakemake-5.10.0/tests/test_env_modules/expected-results/000077500000000000000000000000001361131222100235455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_env_modules/expected-results/test.out000066400000000000000000000000051361131222100252500ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_env_modules/test-env-modules.sh000077500000000000000000000000171361131222100240150ustar00rootroot00000000000000echo test > $1 snakemake-5.10.0/tests/test_expand.py000066400000000000000000000043471361131222100175660ustar00rootroot00000000000000from snakemake.io import expand from snakemake.exceptions import WildcardError import pytest def test_simple_expand(): # single filepattern assert expand("{a}.out", a="test") == ["test.out"] # multiple filepatterns assert expand(["{a}.out", "{b}.out"], a="a", b="b") == ["a.out", "b.out"] # multiple wildcards assert expand("{a}.out", a=["1", "2", "3"]) == ["1.out", "2.out", "3.out"] # multiple wildcards and patterns assert expand(["{a}_{b}.ab", "{b}.b"], a="1 2".split(), b="3 4".split()) == [ "1_3.ab", "1_4.ab", "2_3.ab", "2_4.ab", "3.b", "4.b", ] # replace product assert expand(["{a}_{b}.ab", "{b}.b"], zip, a="1 2".split(), b="3 4".split()) == [ "1_3.ab", "2_4.ab", "3.b", "4.b", ] def test_allow_missing(): # single filepattern assert expand("{a}_{b}.out", allow_missing=True) == ["{a}_{b}.out"] assert expand("{a}_{b}.out", a="test", allow_missing=True) == ["test_{b}.out"] # none missing assert expand("{a}.out", a="test", allow_missing=True) == ["test.out"] # wildcard is allow_missing assert expand("{allow_missing}.out", allow_missing=True) == ["True.out"] # allow_missing not True assert expand("{a}.out", a="test", allow_missing="test2") == ["test.out"] with pytest.raises(WildcardError) as e: expand("{a}.out", allow_missing="test2") assert str(e.value) == "No values given for wildcard 'a'." # multiple filepatterns assert expand(["{a}.out", "{b}.out"], allow_missing=True) == ["{a}.out", "{b}.out"] # multiple wildcards assert expand("{a}_{b}.out", a=["1", "2", "3"], allow_missing=True) == [ "1_{b}.out", "2_{b}.out", "3_{b}.out", ] # multiple wildcards and patterns assert expand( ["{a}_{b}_{C}.ab", "{b}_{c}.b"], a="1 2".split(), b="3 4".split(), allow_missing=True, ) == ["1_3_{C}.ab", "1_4_{C}.ab", "2_3_{C}.ab", "2_4_{C}.ab", "3_{c}.b", "4_{c}.b"] # replace product assert expand( ["{a}_{b}_{C}.ab", "{b}_{c}.b"], zip, a="1 2".split(), b="3 4".split(), allow_missing=True, ) == ["1_3_{C}.ab", "2_4_{C}.ab", "3_{c}.b", "4_{c}.b"] snakemake-5.10.0/tests/test_expand_flag/000077500000000000000000000000001361131222100201755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_expand_flag/Snakefile000066400000000000000000000001601361131222100220160ustar00rootroot00000000000000rule a: output: expand(directory("{sample}/dir"), sample=["a"]) shell: "mkdir {output}" snakemake-5.10.0/tests/test_expand_flag/expected-results/000077500000000000000000000000001361131222100234755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_expand_flag/expected-results/.gitignore000066400000000000000000000000001361131222100254530ustar00rootroot00000000000000snakemake-5.10.0/tests/test_filegraph/000077500000000000000000000000001361131222100176665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_filegraph/Snakefile000066400000000000000000000007061361131222100215150ustar00rootroot00000000000000import os rule all: input: expand("folder/file_{v1}_{v2}.dat", v1=[1, 2], v2=["a", "b"]), expand("visualization_of_1_a.pdf", v1=[1, 2]) rule data: output: "folder/file_{v1}_{v2}.dat" shell: "touch {output}" rule visualization: input: "folder/file_1_a.dat", lambda x: os.path.join("scripts", "vis.py"), output: "visualization_of_1_a.pdf" shell: "touch {output}" snakemake-5.10.0/tests/test_filegraph/scripts/000077500000000000000000000000001361131222100213555ustar00rootroot00000000000000snakemake-5.10.0/tests/test_filegraph/scripts/vis.py000066400000000000000000000000001361131222100225160ustar00rootroot00000000000000snakemake-5.10.0/tests/test_format_params/000077500000000000000000000000001361131222100205605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_format_params/Snakefile000066400000000000000000000001461361131222100224050ustar00rootroot00000000000000rule a: output: "test.out" params: [1, 2, 3] shell: "echo {params[0]} > {output}" snakemake-5.10.0/tests/test_format_params/expected-results/000077500000000000000000000000001361131222100240605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_format_params/expected-results/test.out000066400000000000000000000000061361131222100255640ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_format_wildcards/000077500000000000000000000000001361131222100212515ustar00rootroot00000000000000snakemake-5.10.0/tests/test_format_wildcards/Snakefile000066400000000000000000000002111361131222100230670ustar00rootroot00000000000000rule all: input: "foo.txt" rule a: output: "{test}.txt" shell: "echo {rule} {wildcards} > {output}" snakemake-5.10.0/tests/test_format_wildcards/expected-results/000077500000000000000000000000001361131222100245515ustar00rootroot00000000000000snakemake-5.10.0/tests/test_format_wildcards/expected-results/foo.txt000066400000000000000000000000131361131222100260670ustar00rootroot00000000000000a test=foo snakemake-5.10.0/tests/test_ftp_immediate_close/000077500000000000000000000000001361131222100217215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ftp_immediate_close/Snakefile000066400000000000000000000004401361131222100235430ustar00rootroot00000000000000from snakemake.remote.FTP import RemoteProvider FTP = RemoteProvider() rule a: input: FTP.remote("ftp://ftp.sra.ebi.ac.uk/vol1/ERA651/ERA651425/fastq/RAG16_R1.fastq.gz", immediate_close=True) output: "size.txt" run: shell("du -h {input} > {output}") snakemake-5.10.0/tests/test_ftp_immediate_close/expected-results/000077500000000000000000000000001361131222100252215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ftp_immediate_close/expected-results/size.txt000066400000000000000000000001051361131222100267300ustar00rootroot000000000000009.9M ftp.sra.ebi.ac.uk/vol1/ERA651/ERA651425/fastq/RAG16_R1.fastq.gz snakemake-5.10.0/tests/test_get_log_both/000077500000000000000000000000001361131222100203615ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_both/Snakefile000066400000000000000000000001571361131222100222100ustar00rootroot00000000000000rule: input: "test.in" output: "test.out" log: "test.log" wrapper: 'file://wrapper.py' snakemake-5.10.0/tests/test_get_log_both/expected-results/000077500000000000000000000000001361131222100236615ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_both/expected-results/test.log000066400000000000000000000000421361131222100253370ustar00rootroot00000000000000a stderr message a stdout message snakemake-5.10.0/tests/test_get_log_both/expected-results/test.out000066400000000000000000000000101361131222100253600ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_both/test.in000066400000000000000000000000101361131222100216570ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_both/wrapper.py000066400000000000000000000003431361131222100224130ustar00rootroot00000000000000from snakemake.shell import shell log = snakemake.log_fmt_shell(append=True) shell(''' cat {snakemake.input} > {snakemake.output} (>&2 echo "a stderr message") {log} (echo "a stdout message") {log} ''') snakemake-5.10.0/tests/test_get_log_complex/000077500000000000000000000000001361131222100210745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_complex/Snakefile000066400000000000000000000001571361131222100227230ustar00rootroot00000000000000rule: input: "test.in" output: "test.out" log: "test.log" wrapper: 'file://wrapper.py' snakemake-5.10.0/tests/test_get_log_complex/expected-results/000077500000000000000000000000001361131222100243745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_complex/expected-results/test.log000066400000000000000000000000551361131222100260560ustar00rootroot00000000000000first line a stderr message a stdout message snakemake-5.10.0/tests/test_get_log_complex/expected-results/test.out000066400000000000000000000000101361131222100260730ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_complex/test.in000066400000000000000000000000101361131222100223720ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_complex/wrapper.py000066400000000000000000000007331361131222100231310ustar00rootroot00000000000000from snakemake.shell import shell initial_log = snakemake.log_fmt_shell() stdout_log = snakemake.log_fmt_shell(stderr=False, append=True) stderr_log = snakemake.log_fmt_shell(stdout=False, append=True) shell(''' cat {snakemake.input} > {snakemake.output} echo "should not appear since next line truncates" {initial_log} echo "first line" {initial_log} (>&2 echo "a stderr message") {stderr_log} (echo "a stdout message") {stdout_log} ''') snakemake-5.10.0/tests/test_get_log_none/000077500000000000000000000000001361131222100203645ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_none/Snakefile000066400000000000000000000001311361131222100222030ustar00rootroot00000000000000rule: input: "test.in" output: "test.out" wrapper: 'file:wrapper.py' snakemake-5.10.0/tests/test_get_log_none/expected-results/000077500000000000000000000000001361131222100236645ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_none/expected-results/test.out000066400000000000000000000000101361131222100253630ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_none/test.in000066400000000000000000000000101361131222100216620ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_none/wrapper.py000066400000000000000000000003441361131222100224170ustar00rootroot00000000000000from snakemake.shell import shell log = snakemake.log_fmt_shell(stdout=False) shell(''' cat {snakemake.input} > {snakemake.output} (>&2 echo "a stderr message") {log} (echo "a stdout message") {log} ''') snakemake-5.10.0/tests/test_get_log_stderr/000077500000000000000000000000001361131222100207305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_stderr/Snakefile000066400000000000000000000001571361131222100225570ustar00rootroot00000000000000rule: input: "test.in" output: "test.out" log: "test.log" wrapper: 'file://wrapper.py' snakemake-5.10.0/tests/test_get_log_stderr/expected-results/000077500000000000000000000000001361131222100242305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_stderr/expected-results/test.log000066400000000000000000000000211361131222100257030ustar00rootroot00000000000000a stderr message snakemake-5.10.0/tests/test_get_log_stderr/expected-results/test.out000066400000000000000000000000101361131222100257270ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_stderr/test.in000066400000000000000000000000101361131222100222260ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_stderr/wrapper.py000066400000000000000000000003611361131222100227620ustar00rootroot00000000000000from snakemake.shell import shell log = snakemake.log_fmt_shell(stdout=False, append=True) shell(''' cat {snakemake.input} > {snakemake.output} (>&2 echo "a stderr message") {log} (echo "a stdout message") {log} ''') snakemake-5.10.0/tests/test_get_log_stdout/000077500000000000000000000000001361131222100207475ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_stdout/Snakefile000066400000000000000000000001571361131222100225760ustar00rootroot00000000000000rule: input: "test.in" output: "test.out" log: "test.log" wrapper: 'file://wrapper.py' snakemake-5.10.0/tests/test_get_log_stdout/expected-results/000077500000000000000000000000001361131222100242475ustar00rootroot00000000000000snakemake-5.10.0/tests/test_get_log_stdout/expected-results/test.log000066400000000000000000000000211361131222100257220ustar00rootroot00000000000000a stdout message snakemake-5.10.0/tests/test_get_log_stdout/expected-results/test.out000066400000000000000000000000101361131222100257460ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_stdout/test.in000066400000000000000000000000101361131222100222450ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_get_log_stdout/wrapper.py000066400000000000000000000003611361131222100230010ustar00rootroot00000000000000from snakemake.shell import shell log = snakemake.log_fmt_shell(stderr=False, append=True) shell(''' cat {snakemake.input} > {snakemake.output} (>&2 echo "a stderr message") {log} (echo "a stdout message") {log} ''') snakemake-5.10.0/tests/test_github_issue105/000077500000000000000000000000001361131222100206455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue105/Snakefile000066400000000000000000000005611361131222100224730ustar00rootroot00000000000000rule all: input: "b.txt" checkpoint a: output: "a.txt" shell: "touch {output}" def aggregate(wildcards): # files = {'inp1': "Snakefile"} files = {'inp1': checkpoints.a.get(**wildcards).output[0]} return files rule b: input: unpack(aggregate) output: "b.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_github_issue105/expected-results/000077500000000000000000000000001361131222100241455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue105/expected-results/a.txt000066400000000000000000000000001361131222100251140ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue105/expected-results/b.txt000066400000000000000000000000001361131222100251150ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue52/000077500000000000000000000000001361131222100205665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue52/B000066400000000000000000000000001361131222100206600ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue52/Snakefile000066400000000000000000000003751361131222100224170ustar00rootroot00000000000000rule All: input: "C" rule A: output: "outputA/A1", "outputA/A2", directory("outputA") shell: "mkdir -p outputA; touch outputA/A1; touch outputA/A2" rule C: input: test="outputA/A1", testb="B" output: "C" shell: "cp {input.test} C" snakemake-5.10.0/tests/test_github_issue52/expected-results/000077500000000000000000000000001361131222100240665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue52/expected-results/.gitkeep000066400000000000000000000000001361131222100255050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue52/other.smk000066400000000000000000000005001361131222100224160ustar00rootroot00000000000000rule All: input: "C" rule A: output: "outputA/A1", "outputA/A2", directory("outputA") shell: "mkdir -p outputA; touch outputA/A1; touch outputA/A2" rule B: input: "outputA" output: "B" shell: "touch B" rule C: input: test="outputA/A1", testb="B" output: "C" shell: "cp {input.test} C" snakemake-5.10.0/tests/test_github_issue78/000077500000000000000000000000001361131222100205765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue78/Snakefile000066400000000000000000000015111361131222100224200ustar00rootroot00000000000000rule all: input: "test.out" rule singularity_ok: input: "test_a.out", "test_b.out", "test_c.out" output: touch("test.out") shell: """ test ! -f junk_a.out test ! -f junk_b.out test ! -f junk_c.out """ rule a: output: "test_a.out" singularity: "docker://bash" shadow: "minimal" shell: 'echo 1 > junk_a.out; echo "test" > {output}' rule b: output: "test_b.out" shadow: "minimal" shell: 'echo 1 > junk_b.out; echo "test" > {output}' rule c: output: "test_c.out" shadow: "minimal" run: with open(output[0], 'w') as fp: print("test", file=fp) with open('junk_c.out', 'w') as fp: print("junk", file=fp) snakemake-5.10.0/tests/test_github_issue78/expected-results/000077500000000000000000000000001361131222100240765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue78/expected-results/test.out000066400000000000000000000000001361131222100255740ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue78/expected-results/test_a.out000066400000000000000000000000051361131222100261010ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_github_issue78/expected-results/test_b.out000066400000000000000000000000051361131222100261020ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_github_issue78/expected-results/test_c.out000066400000000000000000000000051361131222100261030ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_github_issue_14/000077500000000000000000000000001361131222100207235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue_14/Snakefile000066400000000000000000000001631361131222100225470ustar00rootroot00000000000000rule all: input: "pythonTest" rule test_github_issue_14: output: "pythonTest" script: "pythonTest.py" snakemake-5.10.0/tests/test_github_issue_14/expected-results/000077500000000000000000000000001361131222100242235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_github_issue_14/expected-results/pythonTest000066400000000000000000000000451361131222100263260ustar00rootroot00000000000000local_script.contents blah blah blah snakemake-5.10.0/tests/test_github_issue_14/local_script.py000066400000000000000000000000341361131222100237500ustar00rootroot00000000000000contents = "blah blah blah" snakemake-5.10.0/tests/test_github_issue_14/pythonTest.py000066400000000000000000000007111361131222100234550ustar00rootroot00000000000000#!/usr/bin/env python import sys import os import local_script # Ensure that the __real_file__ path ends in .snakemake dname = os.path.dirname(__real_file__) print(dname) if not dname.endswith(os.path.join(".snakemake", "scripts")): sys.exit("We're not being written in the output directory!\n") # Write out script to indicte success. of = open(snakemake.output[0], "w") of.write("local_script.contents {}\n".format(local_script.contents)) of.close() snakemake-5.10.0/tests/test_globwildcards/000077500000000000000000000000001361131222100205455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/Snakefile000066400000000000000000000002551361131222100223730ustar00rootroot00000000000000 IDS, = glob_wildcards("test.{id}.txt") rule all: input: expand("test.{id}.out", id=IDS) rule: input: "test.{id}.txt" output: "test.{id}.out" shell: "touch {output}" snakemake-5.10.0/tests/test_globwildcards/expected-results/000077500000000000000000000000001361131222100240455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/expected-results/test.0.out000066400000000000000000000000001361131222100257010ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/expected-results/test.1.out000066400000000000000000000000001361131222100257020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/expected-results/test.2.out000066400000000000000000000000001361131222100257030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/test.0.txt000066400000000000000000000000001361131222100224110ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/test.1.txt000066400000000000000000000000001361131222100224120ustar00rootroot00000000000000snakemake-5.10.0/tests/test_globwildcards/test.2.txt000066400000000000000000000000001361131222100224130ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_job_fail/000077500000000000000000000000001361131222100207065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_job_fail/Snakefile000066400000000000000000000007021361131222100225310ustar00rootroot00000000000000samples = [1,2,3,4,5] rule all: input: "test.out" rule a: output: "a/{sample}.out" group: 0 shell: "touch {output}" rule b: input: "a/{sample}.out" output: "b/{sample}.out" group: 0 shell: "touch {output}; exit 1" rule c: input: expand("b/{sample}.out", sample=samples) output: "test.out" group: 1 shell: "touch {output}" snakemake-5.10.0/tests/test_group_job_fail/expected-results/000077500000000000000000000000001361131222100242065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_job_fail/expected-results/.gitignore000066400000000000000000000000001361131222100261640ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_jobs/000077500000000000000000000000001361131222100200765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_jobs/Snakefile000066400000000000000000000006721361131222100217270ustar00rootroot00000000000000samples = [1,2,3,4,5] rule all: input: "test.out" rule a: output: "a/{sample}.out" group: 0 shell: "touch {output}" rule b: input: "a/{sample}.out" output: "b/{sample}.out" group: 0 shell: "touch {output}" rule c: input: expand("b/{sample}.out", sample=samples) output: "test.out" group: 1 shell: "touch {output}" snakemake-5.10.0/tests/test_group_jobs/expected-results/000077500000000000000000000000001361131222100233765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_jobs/expected-results/test.out000066400000000000000000000000001361131222100250740ustar00rootroot00000000000000snakemake-5.10.0/tests/test_group_jobs/qsub000077500000000000000000000002001361131222100207660ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM sh $1 snakemake-5.10.0/tests/test_gs_requester_pays/000077500000000000000000000000001361131222100214715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_gs_requester_pays/Snakefile000066400000000000000000000007151361131222100233200ustar00rootroot00000000000000from snakemake.remote import GS import google.auth try: GS = GS.RemoteProvider() rule copy: input: GS.remote(config["url"], user_project=config["project"]) output: "landsat-data.txt" shell: "cp {input} {output}" except google.auth.exceptions.DefaultCredentialsError: # ignore the test if not authenticated print("skipping test_remote_gs because we are not authenticated with gcloud") snakemake-5.10.0/tests/test_gs_requester_pays/expected-results/000077500000000000000000000000001361131222100247715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_gs_requester_pays/expected-results/landsat-data.txt000066400000000000000000000203761361131222100300770ustar00rootroot00000000000000GROUP = L1_METADATA_FILE GROUP = METADATA_FILE_INFO ORIGIN = "Image courtesy of the U.S. Geological Survey" REQUEST_ID = "0501705013406_00001" LANDSAT_SCENE_ID = "LC80010032017120LGN00" LANDSAT_PRODUCT_ID = "LC08_L1GT_001003_20170430_20170501_01_RT" COLLECTION_NUMBER = 01 FILE_DATE = 2017-05-01T16:00:24Z STATION_ID = "LGN" PROCESSING_SOFTWARE_VERSION = "LPGS_2.7.0" END_GROUP = METADATA_FILE_INFO GROUP = PRODUCT_METADATA DATA_TYPE = "L1GT" COLLECTION_CATEGORY = "RT" ELEVATION_SOURCE = "GLS2000" OUTPUT_FORMAT = "GEOTIFF" SPACECRAFT_ID = "LANDSAT_8" SENSOR_ID = "OLI_TIRS" WRS_PATH = 1 WRS_ROW = 3 NADIR_OFFNADIR = "NADIR" TARGET_WRS_PATH = 1 TARGET_WRS_ROW = 3 DATE_ACQUIRED = 2017-04-30 SCENE_CENTER_TIME = "14:07:16.5180850Z" CORNER_UL_LAT_PRODUCT = 80.21528 CORNER_UL_LON_PRODUCT = -17.96312 CORNER_UR_LAT_PRODUCT = 80.28798 CORNER_UR_LON_PRODUCT = -3.45901 CORNER_LL_LAT_PRODUCT = 77.79053 CORNER_LL_LON_PRODUCT = -16.19251 CORNER_LR_LAT_PRODUCT = 77.84841 CORNER_LR_LON_PRODUCT = -4.56164 CORNER_UL_PROJECTION_X_PRODUCT = 330600.000 CORNER_UL_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_UR_PROJECTION_X_PRODUCT = 604200.000 CORNER_UR_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_LL_PROJECTION_X_PRODUCT = 330600.000 CORNER_LL_PROJECTION_Y_PRODUCT = 8645400.000 CORNER_LR_PROJECTION_X_PRODUCT = 604200.000 CORNER_LR_PROJECTION_Y_PRODUCT = 8645400.000 PANCHROMATIC_LINES = 18221 PANCHROMATIC_SAMPLES = 18241 REFLECTIVE_LINES = 9111 REFLECTIVE_SAMPLES = 9121 THERMAL_LINES = 9111 THERMAL_SAMPLES = 9121 FILE_NAME_BAND_1 = "LC08_L1GT_001003_20170430_20170501_01_RT_B1.TIF" FILE_NAME_BAND_2 = "LC08_L1GT_001003_20170430_20170501_01_RT_B2.TIF" FILE_NAME_BAND_3 = "LC08_L1GT_001003_20170430_20170501_01_RT_B3.TIF" FILE_NAME_BAND_4 = "LC08_L1GT_001003_20170430_20170501_01_RT_B4.TIF" FILE_NAME_BAND_5 = "LC08_L1GT_001003_20170430_20170501_01_RT_B5.TIF" FILE_NAME_BAND_6 = "LC08_L1GT_001003_20170430_20170501_01_RT_B6.TIF" FILE_NAME_BAND_7 = "LC08_L1GT_001003_20170430_20170501_01_RT_B7.TIF" FILE_NAME_BAND_8 = "LC08_L1GT_001003_20170430_20170501_01_RT_B8.TIF" FILE_NAME_BAND_9 = "LC08_L1GT_001003_20170430_20170501_01_RT_B9.TIF" FILE_NAME_BAND_10 = "LC08_L1GT_001003_20170430_20170501_01_RT_B10.TIF" FILE_NAME_BAND_11 = "LC08_L1GT_001003_20170430_20170501_01_RT_B11.TIF" FILE_NAME_BAND_QUALITY = "LC08_L1GT_001003_20170430_20170501_01_RT_BQA.TIF" ANGLE_COEFFICIENT_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_ANG.txt" METADATA_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt" CPF_NAME = "LC08CPF_20170401_20170630_01.02" BPF_NAME_OLI = "LO8BPF20170430140614_20170430144404.01" BPF_NAME_TIRS = "LT8BPF20170426235522_20170427000359.01" RLUT_FILE_NAME = "LC08RLUT_20150303_20431231_01_12.h5" END_GROUP = PRODUCT_METADATA GROUP = IMAGE_ATTRIBUTES CLOUD_COVER = 24.32 CLOUD_COVER_LAND = -1 IMAGE_QUALITY_OLI = 9 IMAGE_QUALITY_TIRS = 7 TIRS_SSM_MODEL = "PRELIMINARY" TIRS_SSM_POSITION_STATUS = "ESTIMATED" TIRS_STRAY_LIGHT_CORRECTION_SOURCE = "TIRS" ROLL_ANGLE = -0.001 SUN_AZIMUTH = -156.47580997 SUN_ELEVATION = 24.99099132 EARTH_SUN_DISTANCE = 1.0074392 SATURATION_BAND_1 = "N" SATURATION_BAND_2 = "N" SATURATION_BAND_3 = "N" SATURATION_BAND_4 = "N" SATURATION_BAND_5 = "N" SATURATION_BAND_6 = "N" SATURATION_BAND_7 = "N" SATURATION_BAND_8 = "N" SATURATION_BAND_9 = "N" TRUNCATION_OLI = "UPPER" END_GROUP = IMAGE_ATTRIBUTES GROUP = MIN_MAX_RADIANCE RADIANCE_MAXIMUM_BAND_1 = 748.87909 RADIANCE_MINIMUM_BAND_1 = -61.84268 RADIANCE_MAXIMUM_BAND_2 = 766.86133 RADIANCE_MINIMUM_BAND_2 = -63.32766 RADIANCE_MAXIMUM_BAND_3 = 706.65613 RADIANCE_MINIMUM_BAND_3 = -58.35589 RADIANCE_MAXIMUM_BAND_4 = 595.89227 RADIANCE_MINIMUM_BAND_4 = -49.20898 RADIANCE_MAXIMUM_BAND_5 = 364.65634 RADIANCE_MINIMUM_BAND_5 = -30.11344 RADIANCE_MAXIMUM_BAND_6 = 90.68671 RADIANCE_MINIMUM_BAND_6 = -7.48894 RADIANCE_MAXIMUM_BAND_7 = 30.56628 RADIANCE_MINIMUM_BAND_7 = -2.52417 RADIANCE_MAXIMUM_BAND_8 = 674.38605 RADIANCE_MINIMUM_BAND_8 = -55.69102 RADIANCE_MAXIMUM_BAND_9 = 142.51598 RADIANCE_MINIMUM_BAND_9 = -11.76902 RADIANCE_MAXIMUM_BAND_10 = 22.00180 RADIANCE_MINIMUM_BAND_10 = 0.10033 RADIANCE_MAXIMUM_BAND_11 = 22.00180 RADIANCE_MINIMUM_BAND_11 = 0.10033 END_GROUP = MIN_MAX_RADIANCE GROUP = MIN_MAX_REFLECTANCE REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 REFLECTANCE_MINIMUM_BAND_1 = -0.099980 REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 REFLECTANCE_MINIMUM_BAND_2 = -0.099980 REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 REFLECTANCE_MINIMUM_BAND_3 = -0.099980 REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 REFLECTANCE_MINIMUM_BAND_4 = -0.099980 REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 REFLECTANCE_MINIMUM_BAND_5 = -0.099980 REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 REFLECTANCE_MINIMUM_BAND_6 = -0.099980 REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 REFLECTANCE_MINIMUM_BAND_7 = -0.099980 REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 REFLECTANCE_MINIMUM_BAND_8 = -0.099980 REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 REFLECTANCE_MINIMUM_BAND_9 = -0.099980 END_GROUP = MIN_MAX_REFLECTANCE GROUP = MIN_MAX_PIXEL_VALUE QUANTIZE_CAL_MAX_BAND_1 = 65535 QUANTIZE_CAL_MIN_BAND_1 = 1 QUANTIZE_CAL_MAX_BAND_2 = 65535 QUANTIZE_CAL_MIN_BAND_2 = 1 QUANTIZE_CAL_MAX_BAND_3 = 65535 QUANTIZE_CAL_MIN_BAND_3 = 1 QUANTIZE_CAL_MAX_BAND_4 = 65535 QUANTIZE_CAL_MIN_BAND_4 = 1 QUANTIZE_CAL_MAX_BAND_5 = 65535 QUANTIZE_CAL_MIN_BAND_5 = 1 QUANTIZE_CAL_MAX_BAND_6 = 65535 QUANTIZE_CAL_MIN_BAND_6 = 1 QUANTIZE_CAL_MAX_BAND_7 = 65535 QUANTIZE_CAL_MIN_BAND_7 = 1 QUANTIZE_CAL_MAX_BAND_8 = 65535 QUANTIZE_CAL_MIN_BAND_8 = 1 QUANTIZE_CAL_MAX_BAND_9 = 65535 QUANTIZE_CAL_MIN_BAND_9 = 1 QUANTIZE_CAL_MAX_BAND_10 = 65535 QUANTIZE_CAL_MIN_BAND_10 = 1 QUANTIZE_CAL_MAX_BAND_11 = 65535 QUANTIZE_CAL_MIN_BAND_11 = 1 END_GROUP = MIN_MAX_PIXEL_VALUE GROUP = RADIOMETRIC_RESCALING RADIANCE_MULT_BAND_1 = 1.2371E-02 RADIANCE_MULT_BAND_2 = 1.2668E-02 RADIANCE_MULT_BAND_3 = 1.1674E-02 RADIANCE_MULT_BAND_4 = 9.8438E-03 RADIANCE_MULT_BAND_5 = 6.0239E-03 RADIANCE_MULT_BAND_6 = 1.4981E-03 RADIANCE_MULT_BAND_7 = 5.0494E-04 RADIANCE_MULT_BAND_8 = 1.1140E-02 RADIANCE_MULT_BAND_9 = 2.3543E-03 RADIANCE_MULT_BAND_10 = 3.3420E-04 RADIANCE_MULT_BAND_11 = 3.3420E-04 RADIANCE_ADD_BAND_1 = -61.85505 RADIANCE_ADD_BAND_2 = -63.34032 RADIANCE_ADD_BAND_3 = -58.36757 RADIANCE_ADD_BAND_4 = -49.21882 RADIANCE_ADD_BAND_5 = -30.11946 RADIANCE_ADD_BAND_6 = -7.49044 RADIANCE_ADD_BAND_7 = -2.52468 RADIANCE_ADD_BAND_8 = -55.70216 RADIANCE_ADD_BAND_9 = -11.77137 RADIANCE_ADD_BAND_10 = 0.10000 RADIANCE_ADD_BAND_11 = 0.10000 REFLECTANCE_MULT_BAND_1 = 2.0000E-05 REFLECTANCE_MULT_BAND_2 = 2.0000E-05 REFLECTANCE_MULT_BAND_3 = 2.0000E-05 REFLECTANCE_MULT_BAND_4 = 2.0000E-05 REFLECTANCE_MULT_BAND_5 = 2.0000E-05 REFLECTANCE_MULT_BAND_6 = 2.0000E-05 REFLECTANCE_MULT_BAND_7 = 2.0000E-05 REFLECTANCE_MULT_BAND_8 = 2.0000E-05 REFLECTANCE_MULT_BAND_9 = 2.0000E-05 REFLECTANCE_ADD_BAND_1 = -0.100000 REFLECTANCE_ADD_BAND_2 = -0.100000 REFLECTANCE_ADD_BAND_3 = -0.100000 REFLECTANCE_ADD_BAND_4 = -0.100000 REFLECTANCE_ADD_BAND_5 = -0.100000 REFLECTANCE_ADD_BAND_6 = -0.100000 REFLECTANCE_ADD_BAND_7 = -0.100000 REFLECTANCE_ADD_BAND_8 = -0.100000 REFLECTANCE_ADD_BAND_9 = -0.100000 END_GROUP = RADIOMETRIC_RESCALING GROUP = TIRS_THERMAL_CONSTANTS K1_CONSTANT_BAND_10 = 774.8853 K2_CONSTANT_BAND_10 = 1321.0789 K1_CONSTANT_BAND_11 = 480.8883 K2_CONSTANT_BAND_11 = 1201.1442 END_GROUP = TIRS_THERMAL_CONSTANTS GROUP = PROJECTION_PARAMETERS MAP_PROJECTION = "UTM" DATUM = "WGS84" ELLIPSOID = "WGS84" UTM_ZONE = 29 GRID_CELL_SIZE_PANCHROMATIC = 15.00 GRID_CELL_SIZE_REFLECTIVE = 30.00 GRID_CELL_SIZE_THERMAL = 30.00 ORIENTATION = "NORTH_UP" RESAMPLING_OPTION = "CUBIC_CONVOLUTION" END_GROUP = PROJECTION_PARAMETERS END_GROUP = L1_METADATA_FILE END snakemake-5.10.0/tests/test_inoutput_is_path/000077500000000000000000000000001361131222100213235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_inoutput_is_path/Snakefile000066400000000000000000000005111361131222100231440ustar00rootroot00000000000000from pathlib import Path rule: input: Path("test.in").resolve() # absolute, must exist output: Path("test.out") # relative, does not have to exist (yet) run: test = shell("echo 42;", read=True) assert int(test) == 42 with open(output[0], "w") as f: for l in shell("cat {input}", iterable=True): print(l, file=f) snakemake-5.10.0/tests/test_inoutput_is_path/expected-results/000077500000000000000000000000001361131222100246235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_inoutput_is_path/expected-results/test.out000066400000000000000000000000101361131222100263220ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_inoutput_is_path/test.in000066400000000000000000000000101361131222100226210ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_inoutput_is_path/test.out000066400000000000000000000000101361131222100230220ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_input_generator/000077500000000000000000000000001361131222100211325ustar00rootroot00000000000000snakemake-5.10.0/tests/test_input_generator/Snakefile000066400000000000000000000014631361131222100227620ustar00rootroot00000000000000# What if an input function returns a generator rather than a list? # Pythonically, generators are preferred to lists. #This always worked fine: def input_func1(wildcards): return [ "%s.%d.in" % (wildcards.base, n) for n in range(1,4) ] #This fails: def input_func2(wildcards): return ( "%s.%d.in" % (wildcards.base, n) for n in range(1,4) ) #As does this def input_func3(wildcards): for n in range(1,4): yield "%s.%d.in" % (wildcards.base, n) rule main: input: "foo.out1", "foo.out2", "foo.out3" rule test1: output: "{base}.out1" input: input_func1 shell: "cat {input} > {output}" rule test2: output: "{base}.out2" input: input_func2 shell: "cat {input} > {output}" rule test3: output: "{base}.out3" input: input_func3 shell: "cat {input} > {output}" snakemake-5.10.0/tests/test_input_generator/expected-results/000077500000000000000000000000001361131222100244325ustar00rootroot00000000000000snakemake-5.10.0/tests/test_input_generator/expected-results/foo.out1000066400000000000000000000000061361131222100260230ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_input_generator/expected-results/foo.out2000066400000000000000000000000061361131222100260240ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_input_generator/expected-results/foo.out3000066400000000000000000000000061361131222100260250ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_input_generator/foo.1.in000066400000000000000000000000021361131222100223740ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_input_generator/foo.2.in000066400000000000000000000000021361131222100223750ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_input_generator/foo.3.in000066400000000000000000000000021361131222100223760ustar00rootroot000000000000003 snakemake-5.10.0/tests/test_io.py000066400000000000000000000065251361131222100167160ustar00rootroot00000000000000from snakemake.io import _wildcard_regex, expand from snakemake.exceptions import WildcardError def test_wildcard_regex(): def matches(text): return [ (match.group("name"), match.group("constraint")) for match in _wildcard_regex.finditer(text) ] # without constraints assert matches("") == [] assert matches("{") == [] assert matches("}") == [] assert matches("{}") == [] assert matches("{0}") == [("0", None)] assert matches("{abc}") == [("abc", None)] assert matches("abc{def}{ghi}") == [("def", None), ("ghi", None)] # with constraints assert matches("{w,constraint}") == [("w", "constraint")] assert matches("{w , constraint}") == [("w", "constraint")] # fails because constraint is detected as 'constraint ' # assert matches('{w,constraint }') == [('w', 'constraint')] assert matches("abc { w , constraint} def") == [("w", "constraint")] # multiple wildcards assert matches("{a,1} {b,2} {c,3}") == [("a", "1"), ("b", "2"), ("c", "3")] # more complicated constraints assert matches(r"{w,([a-z]+|pat\|t*ern)}") == [("w", r"([a-z]+|pat\|t*ern)")] assert matches(r"{w,([a-z]+|pat\|te{1,3}rn){5,7}}") == [ ("w", r"([a-z]+|pat\|te{1,3}rn){5,7}") ] # This used to be very slow with an older version of the regex assert matches("{w, long constraint without closing brace") == [] def test_expand(): wildcards = {"a": [1, 2], "b": [3, 4], "c": [5]} # each provided wildcard is used in the filepattern assert sorted(expand("{a}{b}{c}", **wildcards)) == sorted( ["135", "145", "235", "245"] ) # redundant wildcards are provided assert sorted(expand("{a}{c}", **wildcards)) == sorted(["15", "25"]) # missing wildcards (should fail) try: expand("{a}{d}", **wildcards) assert False except WildcardError: pass # do not expand on strings and non iterables assert expand("{x}{y}", **{"x": "Hello, ", "y": "world!"}) == ["Hello, world!"] assert expand("{x}{y}", **{"x": 4, "y": 2}) == ["42"] # format-minilang: field names assert sorted( expand("first letter of sample: {samples[0]}", samples=["A123", "B456", "C789"]) ) == sorted( [ "first letter of sample: A", "first letter of sample: B", "first letter of sample: C", ] ) assert expand("{str.__class__}", str="") == [""] # format-minilang: conversions class ConvTest: def __str__(self): return "string" def __repr__(self): return "representation" assert expand("{test!r}", test=ConvTest()) == ["representation"] assert expand("{test!s}", test=ConvTest()) == ["string"] # format-minilang: format specifications assert sorted( expand( "The answer to life, the universe, and everything: {answer:f}", answer=range(41, 43), ) ) == sorted( [ "The answer to life, the universe, and everything: 41.000000", "The answer to life, the universe, and everything: 42.000000", ] ) # multiple filepatterns with different wildcards assert sorted( expand(["a: {a} + b: {b}", "c: {c}"], a="aa", b=["b", "bb"], c=["c", "cc"]) ) == sorted(["a: aa + b: b", "a: aa + b: bb", "c: c", "c: cc"]) snakemake-5.10.0/tests/test_issue1037/000077500000000000000000000000001361131222100173705ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1037/Foo_A.start000066400000000000000000000000001361131222100214200ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1037/Snakefile000066400000000000000000000006501361131222100212150ustar00rootroot00000000000000ruleorder: done2 > done1 rule step1: input: '{sample}.start' output: '{sample}.step1' group: 'mygroup' shell: 'touch {output}' rule done1: input: '{sample}.step1' output: '{sample}.done' group: 'mygroup' shell: 'touch {output}' rule done2: input: '{sample}.step1' output: '{sample}.done' wildcard_constraints: sample = 'Foo_.+' group: 'mygroup' shell: 'touch {output}' snakemake-5.10.0/tests/test_issue1037/expected-results/000077500000000000000000000000001361131222100226705ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1037/expected-results/.gitignore000066400000000000000000000000001361131222100246460ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1037/qsub000077500000000000000000000002231361131222100202650ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test_issue1041/000077500000000000000000000000001361131222100173635ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1041/Snakefile000066400000000000000000000007471361131222100212170ustar00rootroot00000000000000CHUNKS = range(3) rule all: input: expand('raw.split_{chunk}.txt', chunk=CHUNKS) rule start: group: 'group1' output: 'raw.txt' shell: 'echo "hi" > {output}' rule split: input: txt='raw.txt' output: expand('raw.split_{chunk}.txt', chunk=CHUNKS) group: 'group1' shell: ''' sleep 3 echo 0 > {output[0]} echo 1 > {output[1]} echo 2 > {output[2]} ''' snakemake-5.10.0/tests/test_issue1041/expected-results/000077500000000000000000000000001361131222100226635ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1041/expected-results/raw.split_0.txt000066400000000000000000000000021361131222100255560ustar00rootroot000000000000000 snakemake-5.10.0/tests/test_issue1041/expected-results/raw.split_1.txt000066400000000000000000000000021361131222100255570ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_issue1041/expected-results/raw.split_2.txt000066400000000000000000000000021361131222100255600ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_issue1046/000077500000000000000000000000001361131222100173705ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1046/Snakefile000066400000000000000000000004311361131222100212120ustar00rootroot00000000000000rule all: input: "test_report.html" rule test: input: "{name}.csv" output: "{name}_report.html" params: # DOES NOT WORK! title = lambda wildcards, input: input[0] # WORKS!! # title = lambda wildcards: wildcards.name wrapper: "file:my_wrapper" snakemake-5.10.0/tests/test_issue1046/expected-results/000077500000000000000000000000001361131222100226705ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1046/expected-results/test_report.html000066400000000000000000000000111361131222100261200ustar00rootroot00000000000000test.csv snakemake-5.10.0/tests/test_issue1046/my_wrapper/000077500000000000000000000000001361131222100215555ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1046/my_wrapper/wrapper.py000066400000000000000000000001521361131222100236050ustar00rootroot00000000000000from snakemake import shell shell("echo {} > {}".format(snakemake.params['title'], snakemake.output[0])) snakemake-5.10.0/tests/test_issue1046/test.csv000066400000000000000000000000001361131222100210520ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1083/000077500000000000000000000000001361131222100173715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1083/Snakefile000066400000000000000000000003421361131222100212140ustar00rootroot00000000000000rule: output: "foo.txt" input: "bar.txt" singularity: "docker://bash" shadow: "minimal" shell: "mount; " "pwd; " "ls -l {input}; " "cat {input} > {output}" snakemake-5.10.0/tests/test_issue1083/bar.txt000066400000000000000000000000001361131222100206640ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1083/expected-results/000077500000000000000000000000001361131222100226715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1083/expected-results/foo.txt000066400000000000000000000000001361131222100242030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1085/000077500000000000000000000000001361131222100173735ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1085/Snakefile000066400000000000000000000002421361131222100212150ustar00rootroot00000000000000def get_params(wildcards): return {'test': 'oo', 'norm': 'oo'} rule test: output: "aa" params: unpack(get_params) shell: "echo {params}" snakemake-5.10.0/tests/test_issue1085/expected-results/000077500000000000000000000000001361131222100226735ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1085/expected-results/.gitignore000066400000000000000000000000001361131222100246510ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1092/000077500000000000000000000000001361131222100173715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1092/Snakefile000066400000000000000000000017341361131222100212220ustar00rootroot00000000000000# a target rule to define the desired final output rule all: input: "aggregated.txt", # the checkpoint that shall trigger re-evaluation of the DAG checkpoint clustering: output: clusters=directory("clustering") shell: "mkdir clustering; " "for i in 1 2 3; do echo $i > clustering/$i.txt; done" def aggregate_input(wildcards): checkpoint_output = checkpoints.clustering.get(**wildcards).output[0] print("beyond") return sorted( expand("post/{i}.txt", i=glob_wildcards(os.path.join(checkpoint_output, "{i}.txt")).i) ) # an aggregation over all produced clusters rule aggregate: input: aggregate_input output: "aggregated.txt" #shell: # "cat {input} > {output}" run: shell("cat {input} > {output}") # an intermediate rule rule intermediate: input: "clustering/{i}.txt" output: "post/{i}.txt" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_issue1092/expected-results/000077500000000000000000000000001361131222100226715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1092/expected-results/aggregated.txt000066400000000000000000000000061361131222100255200ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_issue1093/000077500000000000000000000000001361131222100173725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1093/Snakefile000066400000000000000000000017001361131222100212140ustar00rootroot00000000000000# a target rule to define the desired final output rule all: input: "aggregated.txt", # the checkpoint that shall trigger re-evaluation of the DAG checkpoint clustering: output: clusters=directory("clustering") shell: "mkdir clustering; " "for i in 1 2 3; do echo $i > clustering/$i.txt; done" def aggregate_input(wildcards): checkpoint_output = checkpoints.clustering.get(**wildcards).output[0] return expand("post/{i}.txt", i=sorted(glob_wildcards(os.path.join(checkpoint_output, "{i}.txt")).i)) # an aggregation over all produced clusters rule aggregate: input: aggregate_input output: "aggregated.txt" shell: "cat {input} > {output}" # an intermediate rule rule intermediate: input: "clustering/{i}.txt" output: "post/{i}.txt" conda: "condaenv.yaml" shell: "which bwa; " "cp {input} {output}" snakemake-5.10.0/tests/test_issue1093/condaenv.yaml000066400000000000000000000001031361131222100220450ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - bwa snakemake-5.10.0/tests/test_issue1093/expected-results/000077500000000000000000000000001361131222100226725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1093/expected-results/aggregated.txt000066400000000000000000000000061361131222100255210ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_issue1281/000077500000000000000000000000001361131222100173715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1281/Snakefile000066400000000000000000000010461361131222100212160ustar00rootroot00000000000000# using dictionary expansion mydictionary={ 'apple': 'crunchy fruit', 'banana': 'mushy and yellow' } rule all: input: expand('{key}.sh', key=mydictionary.keys()) rule test: output: temp('{f}.txt') params: keyval=lambda wildcards: mydictionary[wildcards.f] shell: """ echo {params.keyval} > {output} cat {output} """ rule test2: input: rules.test.output output: '{f}.sh' shell: "touch {output}" rule checkme: input: expand(rules.test2.output, f=mydictionary.keys()) snakemake-5.10.0/tests/test_issue1281/expected-results/000077500000000000000000000000001361131222100226715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1281/expected-results/apple.sh000066400000000000000000000000001361131222100243140ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1281/expected-results/banana.sh000066400000000000000000000000001361131222100244330ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1284/000077500000000000000000000000001361131222100173745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1284/Snakefile000066400000000000000000000014531361131222100212230ustar00rootroot00000000000000rule all: input: 'result.r1', 'result.c1', # targeting checkpoint output overrides input 'result.c2' def rule1_input(wc): checkpoints.check1.get(**wc).output[0] return "result.r2" rule r1: input: rule1_input output: 'result.r1', shell: 'echo {input} > {output}' rule r2: output: 'result.r2' shell: 'echo r2 > {output}' def check1_input(wc): checkpoints.check2.get(**wc).output[0] return 'result.r3' checkpoint check1: input: check1_input output: 'result.c1' shell: 'echo c1 > {output}' checkpoint check2: output: 'result.c2' shell: 'echo c2 > {output}' rule r3: output: 'result.r3' shell: 'echo r3 > {output}' snakemake-5.10.0/tests/test_issue1284/expected-results/000077500000000000000000000000001361131222100226745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue1284/expected-results/result.c1000066400000000000000000000000031361131222100244300ustar00rootroot00000000000000c1 snakemake-5.10.0/tests/test_issue1284/expected-results/result.c2000066400000000000000000000000031361131222100244310ustar00rootroot00000000000000c2 snakemake-5.10.0/tests/test_issue1284/expected-results/result.r1000066400000000000000000000000121361131222100244470ustar00rootroot00000000000000result.r2 snakemake-5.10.0/tests/test_issue1284/expected-results/result.r2000066400000000000000000000000031361131222100244500ustar00rootroot00000000000000r2 snakemake-5.10.0/tests/test_issue1284/expected-results/result.r3000066400000000000000000000000031361131222100244510ustar00rootroot00000000000000r3 snakemake-5.10.0/tests/test_issue260/000077500000000000000000000000001361131222100173055ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue260/Snakefile000066400000000000000000000010311361131222100211240ustar00rootroot00000000000000rule all: input: 'output/result.n3', output: 'output/done.txt', shell: 'echo all >> {output}' rule n3: input: dynamic("output/{id2}.n2"), output: "output/result.n3", shell: 'echo n3 > {output}' rule n2: input: dyn=dynamic("output/{id1}.n1"), output: dynamic("output/{id2}.n2"), shell: 'echo n2 > output/result.n2' rule n1: output: dyn=dynamic("output/{id1}.n1"), shell: 'echo n1 > output/result.n1' snakemake-5.10.0/tests/test_issue260/expected-results/000077500000000000000000000000001361131222100226055ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue260/expected-results/output/000077500000000000000000000000001361131222100241455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue260/expected-results/output/done.txt000066400000000000000000000000041361131222100256250ustar00rootroot00000000000000all snakemake-5.10.0/tests/test_issue260/expected-results/output/result.n1000066400000000000000000000000031361131222100257140ustar00rootroot00000000000000n1 snakemake-5.10.0/tests/test_issue260/expected-results/output/result.n2000066400000000000000000000000031361131222100257150ustar00rootroot00000000000000n2 snakemake-5.10.0/tests/test_issue260/expected-results/output/result.n3000066400000000000000000000000031361131222100257160ustar00rootroot00000000000000n3 snakemake-5.10.0/tests/test_issue328/000077500000000000000000000000001361131222100173125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue328/Snakefile000066400000000000000000000007711361131222100211430ustar00rootroot00000000000000from pytools.persistent_dict import PersistentDict storage = PersistentDict("storage") rule merge: input: dynamic("in_{sample}.txt") output: "out.txt" shell: "touch {output}" storage["split_counter"] = 0 rule split: input: "in.txt" output: dynamic("in_{sample}.txt") run: if storage["split_counter"] > 0: raise Exception("Rule split was executed multiple times.") storage["split_counter"] += 1 shell("touch in_1.txt in_2.txt") snakemake-5.10.0/tests/test_issue328/expected-results/000077500000000000000000000000001361131222100226125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue328/expected-results/out.txt000066400000000000000000000000001361131222100241500ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue328/in.txt000066400000000000000000000000001361131222100204470ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue381/000077500000000000000000000000001361131222100173115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue381/Snakefile000066400000000000000000000003641361131222100211400ustar00rootroot00000000000000 rule all: input: "b.out" rule a: input: "a.in" output: "a.out" shell: "touch {output}" rule b: input: rules.a.input output: "b.out" shell: "touch {output}" snakemake-5.10.0/tests/test_issue381/a.in000066400000000000000000000000001361131222100200470ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue381/expected-results/000077500000000000000000000000001361131222100226115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue381/expected-results/b.out000066400000000000000000000000001361131222100235510ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue471/000077500000000000000000000000001361131222100173115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue471/Snakefile000066400000000000000000000004611361131222100211360ustar00rootroot00000000000000rule all: input: "test1.2.out" def process_params(wildcards) : e,a = wildcards.ea.split(".") return { 'epsilon' : '0' + e, 'alpha' : '0.' + a } rule a: output: "test{ea}.out" params: p=process_params shell: "echo {params.p[epsilon]} > {output}" snakemake-5.10.0/tests/test_issue471/expected-results/000077500000000000000000000000001361131222100226115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue471/expected-results/test1.2.out000066400000000000000000000000031361131222100245330ustar00rootroot0000000000000001 snakemake-5.10.0/tests/test_issue584/000077500000000000000000000000001361131222100173165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue584/Snakefile000066400000000000000000000002421361131222100211400ustar00rootroot00000000000000rule all: input: "out1" rule generate: output: "out1" params: test=lambda wildcards: "{test}" shell: "echo "" > out1" snakemake-5.10.0/tests/test_issue584/expected-results/000077500000000000000000000000001361131222100226165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue584/expected-results/out1000066400000000000000000000000011361131222100234200ustar00rootroot00000000000000 snakemake-5.10.0/tests/test_issue612/000077500000000000000000000000001361131222100173065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue612/Snakefile000066400000000000000000000001351361131222100211310ustar00rootroot00000000000000rule A: run: print("A") rule B: run: print("B") rule C: run: print("C") snakemake-5.10.0/tests/test_issue612/expected-results/000077500000000000000000000000001361131222100226065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue612/expected-results/.gitignore000066400000000000000000000000001361131222100245640ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue635/000077500000000000000000000000001361131222100173135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue635/Snakefile000066400000000000000000000003021361131222100211320ustar00rootroot00000000000000 rule all: input: "report.html" rule rmd: input: "input.txt" output: "report.html" conda: "envs/rmarkdown.yaml" script: "scripts/report.Rmd" snakemake-5.10.0/tests/test_issue635/envs/000077500000000000000000000000001361131222100202665ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue635/envs/rmarkdown.yaml000066400000000000000000000001201361131222100231470ustar00rootroot00000000000000channels: - conda-forge dependencies: - r-base =3.6.1 - r-rmarkdown =1.17 snakemake-5.10.0/tests/test_issue635/expected-results/000077500000000000000000000000001361131222100226135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue635/expected-results/report.html000066400000000000000000025615521361131222100250340ustar00rootroot00000000000000 Test Report

R Markdown

This is an R Markdown document.

Test include from snakemake input.txt.

# This works, but indicates that the working directory is NOT the workdir
data <- read.table("datadir/input.txt")
data
##   V1 V2 V3
## 1  1  2  3
getwd()
## [1] "/home/johannes/scms/snakemake/tests/test_issue365"
# This fails
#data <- read.table("snakemake@input[[1]]")
#data
snakemake-5.10.0/tests/test_issue635/input.txt000066400000000000000000000000061361131222100212070ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_issue635/scripts/000077500000000000000000000000001361131222100210025ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue635/scripts/report.Rmd000066400000000000000000000012351361131222100227620ustar00rootroot00000000000000--- title: "Test Report" author: - "Your Name" date: "`r format(Sys.time(), '%d %B, %Y')`" params: rmd: "report.Rmd" output: html_document: highlight: tango number_sections: no theme: default toc: yes toc_depth: 3 toc_float: collapsed: no smooth_scroll: yes --- ## R Markdown This is an R Markdown document. Test include from snakemake ``r snakemake@input``. ```{r} print(getwd()) ``` ```{r} # This fails data <- read.table(snakemake@input[[1]]) data ``` ## Source R Markdown source file (to produce this document) snakemake-5.10.0/tests/test_issue805/000077500000000000000000000000001361131222100173125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue805/Snakefile000066400000000000000000000003331361131222100211350ustar00rootroot00000000000000rule a: output: "test.out" shell: "echo {params.b} > {output}" params: b=1 rule b: input: "test.out" output: "test2.out" shell: "touch {output}" snakemake-5.10.0/tests/test_issue805/expected-results/000077500000000000000000000000001361131222100226125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue805/expected-results/test.out000066400000000000000000000000021361131222100243120ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_issue850/000077500000000000000000000000001361131222100173125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue850/Snakefile000066400000000000000000000007261361131222100211430ustar00rootroot00000000000000 lists = [["1.bam", "2.bam"], ["3.bam", "4.bam"]] rule done: input: ["-".join(l) for l in lists], "2.qc2" rule a: output: touch("{sample}.bam") group: "cell" rule b: input: "{sample}.bam" output: touch("{sample}.qc") group: "cell" rule c: input: "{sample}.qc" output: touch("{sample}.qc2") group: "cell" for l in lists: rule: input: l output: temp(touch(str("-".join(l)))) group: "cell" snakemake-5.10.0/tests/test_issue850/expected-results/000077500000000000000000000000001361131222100226125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue850/expected-results/2.qc2000066400000000000000000000000001361131222100233500ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue850/qsub000077500000000000000000000002231361131222100202070ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test_issue854/000077500000000000000000000000001361131222100173165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue854/Snakefile000066400000000000000000000002001361131222100211320ustar00rootroot00000000000000 rule a: output: "test.{x}.txt" benchmark: "benchmarks/test.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_issue854/expected-results/000077500000000000000000000000001361131222100226165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue854/expected-results/.gitignore000066400000000000000000000000001361131222100245740ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/000077500000000000000000000000001361131222100173135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/Snakefile000066400000000000000000000010541361131222100211370ustar00rootroot00000000000000rule a: output: "{sample}.bam" group: "cell" shell: "touch {output}" rule b: input: "{sample}.bam" output: "{sample}.qc" group: "cell" shadow: "minimal" shell: "touch {output}" rule c: input: "{sample}.qc" output: "{sample}.qc2" group: "cell" shell: "touch {output}" lists = [["1.bam", "2.bam"], ["3.bam", "4.bam"]] for l in lists: rule: input: l output: temp(touch(str("-".join(l)))) group: "cell" rule done: input: [str("-".join(l)) for l in lists], "2.qc2" snakemake-5.10.0/tests/test_issue860/expected-results/000077500000000000000000000000001361131222100226135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/1.bam000066400000000000000000000000001361131222100234220ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/2.bam000066400000000000000000000000001361131222100234230ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/2.qc000066400000000000000000000000001361131222100232670ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/2.qc2000066400000000000000000000000001361131222100233510ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/3.bam000066400000000000000000000000001361131222100234240ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/expected-results/4.bam000066400000000000000000000000001361131222100234250ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue860/qsub000077500000000000000000000002231361131222100202100ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test_issue894/000077500000000000000000000000001361131222100173225ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue894/Snakefile000066400000000000000000000001511361131222100211430ustar00rootroot00000000000000onsuccess: assert not os.path.exists("b") rule a: output: "a", temp("b") shell: "touch a b" snakemake-5.10.0/tests/test_issue894/expected-results/000077500000000000000000000000001361131222100226225ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue894/expected-results/a000066400000000000000000000000001361131222100227530ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/000077500000000000000000000000001361131222100173115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/Snakefile000066400000000000000000000014351361131222100211400ustar00rootroot00000000000000# Create the directory fake/test and the file fake/test/sample.Mutect2.snpSift.vcf.gz # Run on cluster, example: # snakemake --cluster 'bash ' -j1 -p rule all: input: expand( "fake/test/{t}.Mutect2.snpSift.hardfilter.vcf.gz", t=['sample']), rule rule1: input: vcf = ancient('fake/{y}/{x}.vcf.gz'), output: indel = temp('fake/{y}/{x}.indels.raw.vcf.gz'), log: 'fake/logs/rule1.{y}.{x}.log' params: jobname = 'rule1.{x}' shell: 'touch {output.indel}' rule rule2: input: indel = 'fake/{y}/{x}.indels.raw.vcf.gz', output: 'fake/{y}/{x}.hardfilter.vcf.gz' log: 'fake/logs/rule2.{y}.{x}.log' params: jobname = 'rule2', shell: 'touch {output}' snakemake-5.10.0/tests/test_issue912/expected-results/000077500000000000000000000000001361131222100226115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/expected-results/fake/000077500000000000000000000000001361131222100235175ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/expected-results/fake/logs/000077500000000000000000000000001361131222100244635ustar00rootroot00000000000000rule1.test.sample.Mutect2.snpSift.log000066400000000000000000000000001361131222100333320ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/expected-results/fake/logsrule2.test.sample.Mutect2.snpSift.log000066400000000000000000000000001361131222100333330ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/expected-results/fake/logssnakemake-5.10.0/tests/test_issue912/expected-results/fake/test/000077500000000000000000000000001361131222100244765ustar00rootroot00000000000000sample.Mutect2.snpSift.hardfilter.vcf.gz000066400000000000000000000000001361131222100340770ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/expected-results/fake/testsnakemake-5.10.0/tests/test_issue912/fake/000077500000000000000000000000001361131222100202175ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/fake/test/000077500000000000000000000000001361131222100211765ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/fake/test/sample.Mutect2.snpSift.vcf.gz000066400000000000000000000000001361131222100265330ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue912/qsub000077500000000000000000000002231361131222100202060ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test_issue916/000077500000000000000000000000001361131222100173155ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue916/Snakefile000066400000000000000000000001531361131222100211400ustar00rootroot00000000000000rule all: input: "pythonTest" rule testIssue916: output: "pythonTest" script: "pythonTest.py" snakemake-5.10.0/tests/test_issue916/expected-results/000077500000000000000000000000001361131222100226155ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue916/expected-results/pythonTest000066400000000000000000000000451361131222100247200ustar00rootroot00000000000000local_script.contents blah blah blah snakemake-5.10.0/tests/test_issue916/local_script.py000066400000000000000000000000341361131222100223420ustar00rootroot00000000000000contents = "blah blah blah" snakemake-5.10.0/tests/test_issue916/pythonTest.py000066400000000000000000000012151361131222100220470ustar00rootroot00000000000000#!/usr/bin/env python import sys import os import local_script # Ensure that the __real_file__ path ends in .snakemake dname = os.path.dirname(__real_file__) print(dname) if not dname.endswith(os.path.join(".snakemake", "scripts")): sys.exit("We're not being written in the output directory!\n") # Ensure that the __file__ path ends in test_issue916 dname = os.path.dirname(__file__) print(dname) if not dname.endswith("test_issue916"): sys.exit("We're not faking __file__ properly!\n") # Write out script to indicte success. of = open(snakemake.output[0], "w") of.write("local_script.contents {}\n".format(local_script.contents)) of.close() snakemake-5.10.0/tests/test_issue930/000077500000000000000000000000001361131222100173115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue930/Snakefile000066400000000000000000000012061361131222100211340ustar00rootroot00000000000000samples = ["0","1"] rule all: input: "test.out" rule build_index: output: "large_reference_index" shell: "touch {output}" rule a: output: "a/{sample}.out" group: "sample_group" shell: "touch {output}" rule b: input: rules.a.output, rules.build_index.output output: "b/{sample}.out" group: "sample_group" shell: "touch {output}" rule c: input: expand("a/{sample}.out", sample=samples), expand("b/{sample}.out", sample=samples) output: "test.out" shell: "touch {output}" snakemake-5.10.0/tests/test_issue930/expected-results/000077500000000000000000000000001361131222100226115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue930/expected-results/test.out000066400000000000000000000000001361131222100243070ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue930/qsub000077500000000000000000000002231361131222100202060ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM cat $1 >> qsub.log sh $1 snakemake-5.10.0/tests/test_issue956/000077500000000000000000000000001361131222100173215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue956/Snakefile000066400000000000000000000016011361131222100211430ustar00rootroot00000000000000rule all: input: expand( "f_{result_type}.txt", result_type=["A", "B"]), rule make_A: output: result_A = "A_{result_type}.txt", shell: """ echo "A" > {output.result_A} """ rule make_B: output: result_B = "B_{result_type}.txt", shell: """ echo "B" > {output.result_B} """ def source_result_type(wildcards): if wildcards.result_type == "A": return rules.make_A.output.result_A elif wildcards.result_type == "B": return rules.make_B.output.result_B else: raise NotImplementedError(f"{wildcards.result_type} not possible.\n") rule generate_final_results: input: file_in = source_result_type, output: file_out = "f_{result_type}.txt", shell: """ cat {input.file_in} > {output.file_out} """ snakemake-5.10.0/tests/test_issue956/expected-results/000077500000000000000000000000001361131222100226215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue956/expected-results/f_A.txt000066400000000000000000000000021361131222100240370ustar00rootroot00000000000000A snakemake-5.10.0/tests/test_issue956/expected-results/f_B.txt000066400000000000000000000000021361131222100240400ustar00rootroot00000000000000B snakemake-5.10.0/tests/test_issue958/000077500000000000000000000000001361131222100173235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue958/Snakefile000066400000000000000000000002541361131222100211500ustar00rootroot00000000000000rule all: input: 'b.txt' rule g_touch_a: output: touch('a.txt') group: 'g' rule g_make_b_from_a: input: 'a.txt' output: touch('b.txt') group: 'g' snakemake-5.10.0/tests/test_issue958/a.txt000066400000000000000000000000001361131222100202720ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue958/expected-results/000077500000000000000000000000001361131222100226235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_issue958/expected-results/.gitignore000066400000000000000000000000001361131222100246010ustar00rootroot00000000000000snakemake-5.10.0/tests/test_job_properties/000077500000000000000000000000001361131222100207535ustar00rootroot00000000000000snakemake-5.10.0/tests/test_job_properties/Snakefile000066400000000000000000000005571361131222100226060ustar00rootroot00000000000000 rule all: input: "test2.out" rule a: output: "test1.in" shell: "touch {output}" rule b: input: "test1.in" output: "test1.out" group: "x" shell: "touch {output}" rule c: input: "test1.out" output: "test2.out" group: "x" shell: "touch {output}" snakemake-5.10.0/tests/test_job_properties/expected-results/000077500000000000000000000000001361131222100242535ustar00rootroot00000000000000snakemake-5.10.0/tests/test_job_properties/expected-results/test2.out000066400000000000000000000000001361131222100260330ustar00rootroot00000000000000snakemake-5.10.0/tests/test_job_properties/qsub.py000077500000000000000000000004771361131222100223120ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import os import random from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) with open("qsub.log", "a") as log: print(job_properties, file=log) print(random.randint(1, 100)) os.system("sh {}".format(jobscript)) snakemake-5.10.0/tests/test_jupyter_notebook/000077500000000000000000000000001361131222100213275ustar00rootroot00000000000000snakemake-5.10.0/tests/test_jupyter_notebook/Notebook.ipynb000066400000000000000000000017421361131222100241560ustar00rootroot00000000000000{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open(snakemake.input.infile) as fd:\n", " data = fd.read()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "new_results = data + '!!!'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "with open(snakemake.output.outfile, 'w') as fd:\n", " fd.write(new_results)" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.4" } }, "nbformat": 4, "nbformat_minor": 2 } snakemake-5.10.0/tests/test_jupyter_notebook/Snakefile000066400000000000000000000011521361131222100231520ustar00rootroot00000000000000rule all: input: 'result_final.txt' rule foo: output: fname = 'data.txt' run: with open(output.fname, 'w') as fd: fd.write('result of serious computation') rule bar: input: infile = 'data.txt' output: outfile = 'result_intermediate.txt' conda: 'env.yaml' notebook: 'Notebook.ipynb' rule baz: input: infile = 'result_intermediate.txt' output: outfile = 'result_final.txt' log: notebook = 'Notebook_Processed.ipynb' conda: 'env.yaml' notebook: 'Notebook.ipynb' snakemake-5.10.0/tests/test_jupyter_notebook/env.yaml000066400000000000000000000001221361131222100227760ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - python >=3.5 - jupyter snakemake-5.10.0/tests/test_keyword_list/000077500000000000000000000000001361131222100204445ustar00rootroot00000000000000snakemake-5.10.0/tests/test_keyword_list/Snakefile000066400000000000000000000002221361131222100222640ustar00rootroot00000000000000rule: input: bla="test.in1 test.in2".split() output: "test.out" run: print(input.bla) assert len(input.bla) == 2 shell("touch {output}") snakemake-5.10.0/tests/test_keyword_list/expected-results/000077500000000000000000000000001361131222100237445ustar00rootroot00000000000000snakemake-5.10.0/tests/test_keyword_list/expected-results/test.out000066400000000000000000000000001361131222100254420ustar00rootroot00000000000000snakemake-5.10.0/tests/test_keyword_list/test.in1000066400000000000000000000000001361131222100220220ustar00rootroot00000000000000snakemake-5.10.0/tests/test_keyword_list/test.in2000066400000000000000000000000001361131222100220230ustar00rootroot00000000000000snakemake-5.10.0/tests/test_kubernetes/000077500000000000000000000000001361131222100200745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_kubernetes/README.md000066400000000000000000000011071361131222100213520ustar00rootroot00000000000000# Executing this test case To run this test, you need a running kubernetes setup. For google cloud, see [here](https://snakemake.readthedocs.io/en/stable/executable.html#google-cloud-engine). With this, you can execute in case of google cloud: snakemake --kubernetes --use-conda --default-remote-provider GS --default-remote-prefix my-bucket while replacing ``my-bucket`` with your storage bucket. The same test should also work on amazon (given that kubernetes is setup): snakemake --kubernetes --use-conda --default-remote-provider S3 --default-remote-prefix my-bucket snakemake-5.10.0/tests/test_kubernetes/Snakefile000066400000000000000000000017551361131222100217300ustar00rootroot00000000000000import os from snakemake.remote.GS import RemoteProvider as GSRemoteProvider GS = GSRemoteProvider() rule all: input: "landsat-data.txt.bz2" rule copy: input: GS.remote("gcp-public-data-landsat/LC08/01/001/003/LC08_L1GT_001003_20170430_20170501_01_RT/LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt") output: "landsat-data.txt" resources: mem_mb=100 run: # we could test volume size like this but it is currently unclear what f1-micro instances provide as boot disk size #stats = os.statvfs('.') #volume_gib = stats.f_bsize * stats.f_blocks / 1.074e9 #assert volume_gib > 90 shell("cp {input} {output}") rule pack: input: "landsat-data.txt" output: "landsat-data.txt.bz2" conda: "envs/gzip.yaml" singularity: "docker://continuumio/miniconda3:4.4.10" log: "logs/pack.log" shell: "bzip2 -c {input} > {output}; echo successful > {log}" snakemake-5.10.0/tests/test_kubernetes/envs/000077500000000000000000000000001361131222100210475ustar00rootroot00000000000000snakemake-5.10.0/tests/test_kubernetes/envs/gzip.yaml000066400000000000000000000000621361131222100227020ustar00rootroot00000000000000channels: - conda-forge dependencies: - bzip2 snakemake-5.10.0/tests/test_kubernetes/expected-results/000077500000000000000000000000001361131222100233745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_kubernetes/expected-results/.gitignore000066400000000000000000000000001361131222100253520ustar00rootroot00000000000000snakemake-5.10.0/tests/test_list_untracked/000077500000000000000000000000001361131222100207405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_list_untracked/Snakefile000066400000000000000000000001761361131222100225700ustar00rootroot00000000000000rule run_test: output: "leftover_files" shell: "python -m snakemake -s Snakefile_inner --list-untracked 2> {output}" snakemake-5.10.0/tests/test_list_untracked/Snakefile_inner000066400000000000000000000007111361131222100237560ustar00rootroot00000000000000shell("mkdir -p some_workdir/some_subdir && \ touch some_workdir/used_input && \ touch some_workdir/some_subdir/used_output && \ touch some_workdir/some_subdir/not_used && \ touch some_workdir/log && \ touch some_workdir/.hiddenfile && \ mkdir -p some_workdir/.hiddendir && \ touch some_workdir/.hiddendir/not_used2") workdir: "some_workdir" rule a: input: "used_input" output: "some_subdir/used_output" log: "log" snakemake-5.10.0/tests/test_list_untracked/expected-results/000077500000000000000000000000001361131222100242405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_list_untracked/expected-results/leftover_files000066400000000000000000000000551361131222100271730ustar00rootroot00000000000000Building DAG of jobs... some_subdir/not_used snakemake-5.10.0/tests/test_local_import/000077500000000000000000000000001361131222100204115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_local_import/Snakefile000066400000000000000000000001121361131222100222270ustar00rootroot00000000000000import bar import foo rule: output: "test.out" shell: "touch {output}" snakemake-5.10.0/tests/test_local_import/bar.py000066400000000000000000000000001361131222100215150ustar00rootroot00000000000000snakemake-5.10.0/tests/test_local_import/expected-results/000077500000000000000000000000001361131222100237115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_local_import/expected-results/test.out000066400000000000000000000000001361131222100254070ustar00rootroot00000000000000snakemake-5.10.0/tests/test_local_import/foo/000077500000000000000000000000001361131222100211745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_local_import/foo/__init__.py000066400000000000000000000000001361131222100232730ustar00rootroot00000000000000snakemake-5.10.0/tests/test_log_input/000077500000000000000000000000001361131222100177255ustar00rootroot00000000000000snakemake-5.10.0/tests/test_log_input/Snakefile000066400000000000000000000005131361131222100215500ustar00rootroot00000000000000 rule all: input: "test.a.txt" rule a: output: "unused.{sample}.txt" log: "logs/{sample}.txt" shell: "echo {wildcards.sample} > {log}; touch {output}" rule b: input: "logs/{sample}.txt" output: "test.{sample}.txt" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_log_input/expected-results/000077500000000000000000000000001361131222100232255ustar00rootroot00000000000000snakemake-5.10.0/tests/test_log_input/expected-results/test.a.txt000066400000000000000000000000021361131222100251540ustar00rootroot00000000000000a snakemake-5.10.0/tests/test_many_jobs/000077500000000000000000000000001361131222100177065ustar00rootroot00000000000000snakemake-5.10.0/tests/test_many_jobs/Snakefile000066400000000000000000000005541361131222100215360ustar00rootroot00000000000000 rule: input: expand("{sample}.out", sample=range(50000)) rule: input: "{sample}.inter2" output: "{sample}.out" shell: "touch {output}" rule: input: "{sample}.inter1" output: "{sample}.inter2" shell: "touch {output}" rule: input: "{sample}.in" output: "{sample}.inter1" shell: "touch {output}" rule: output: "{sample}.in" shell: "touch {output}" snakemake-5.10.0/tests/test_multiext/000077500000000000000000000000001361131222100176005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiext/Snakefile000066400000000000000000000001561361131222100214260ustar00rootroot00000000000000rule a: output: multiext("ref/genome", ".ann", ".bwt", ".sa") shell: "touch {output}" snakemake-5.10.0/tests/test_multiext/expected-results/000077500000000000000000000000001361131222100231005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiext/expected-results/ref/000077500000000000000000000000001361131222100236545ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiext/expected-results/ref/genome.ann000066400000000000000000000000001361131222100256120ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiext/expected-results/ref/genome.bwt000066400000000000000000000000001361131222100256320ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiext/expected-results/ref/genome.sa000066400000000000000000000000001361131222100254410ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiple_includes/000077500000000000000000000000001361131222100214465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiple_includes/Snakefile000066400000000000000000000001541361131222100232720ustar00rootroot00000000000000include: 'test_rule.smk' include: 'test_second_rule.smk' rule all: input: rules.test_second_rule.outputsnakemake-5.10.0/tests/test_multiple_includes/expected-results/000077500000000000000000000000001361131222100247465ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiple_includes/expected-results/test1.txt000066400000000000000000000000001361131222100265350ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiple_includes/expected-results/test2.txt000066400000000000000000000000001361131222100265360ustar00rootroot00000000000000snakemake-5.10.0/tests/test_multiple_includes/test_rule.smk000066400000000000000000000001041361131222100241630ustar00rootroot00000000000000rule test_rule: output: 'test1.txt' shell: 'touch {output}'snakemake-5.10.0/tests/test_multiple_includes/test_second_rule.smk000066400000000000000000000001551361131222100255240ustar00rootroot00000000000000rule test_second_rule: input: rules.test_rule.output output: 'test2.txt' shell: 'touch {output}'snakemake-5.10.0/tests/test_nonstr_params/000077500000000000000000000000001361131222100206135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_nonstr_params/Snakefile000066400000000000000000000002171361131222100224370ustar00rootroot00000000000000rule: output: "test.out" params: test=True run: assert params.test is True shell("touch {output}") snakemake-5.10.0/tests/test_nonstr_params/expected-results/000077500000000000000000000000001361131222100241135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_nonstr_params/expected-results/test.out000066400000000000000000000000001361131222100256110ustar00rootroot00000000000000snakemake-5.10.0/tests/test_omitfrom/000077500000000000000000000000001361131222100175615ustar00rootroot00000000000000snakemake-5.10.0/tests/test_omitfrom/Snakefile000066400000000000000000000023201361131222100214020ustar00rootroot00000000000000 rule all: input: "levelthree.txt", "independent.txt", expand("test{num}.final", num=[1, 2]) rule levelone: output: "levelone.txt" shell: "touch {output}" rule leveltwo_first: input: rules.levelone.output output: "leveltwo_first.txt" shell: "cp -f {input} {output}" rule leveltwo_second: input: rules.levelone.output output: "leveltwo_second.txt" shell: "cp -f {input} {output}" rule levelthree: # should not be created input: rules.leveltwo_first.output, rules.leveltwo_second.output output: "levelthree.txt" shell: "cat {input} > {output}" rule independent: # should be created in --omit-from but not --until output: "independent.txt" shell: "touch {output}" ###### Wildcard Rules ####### rule zeroth_wildcard: output: "test{num}.txt" shell: "touch {output}" rule first_wildcard: input: 'test{num}.txt' output: 'test{num}.first' shell: 'cp -f {input} {output}' rule second_wildcard: input: 'test{num}.first' output: 'test{num}.second' shell: 'cp -f {input} {output}' rule final_wildcard: input: 'test{num}.second' output: 'test{num}.final' shell: 'cp -f {input} {output}' snakemake-5.10.0/tests/test_omitfrom/expected-results/000077500000000000000000000000001361131222100230615ustar00rootroot00000000000000snakemake-5.10.0/tests/test_omitfrom/expected-results/independent.txt000066400000000000000000000000001361131222100261050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_omitfrom/expected-results/levelone.txt000066400000000000000000000000001361131222100254210ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/000077500000000000000000000000001361131222100214075ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/Snakefile000066400000000000000000000011431361131222100232320ustar00rootroot00000000000000rule all: input: "test3.x.out", "test3.y.out" rule a: output: "test.out" shell: "echo test > {output}" rule b: input: "test.out" output: multiext("test2", ".out", ".out2") shell: "echo test2 > {output[0]}; echo test22 > {output[1]}" rule c: input: "test2.out", "test.out" output: "test3.{w}.out" params: a=1.5 shell: "echo test3 {params.a} > {output}" rule invalid_multi: output: "invalid1.txt", "invalid2.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_output_file_cache/cache/000077500000000000000000000000001361131222100224525ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/cache/.gitkeep000066400000000000000000000000001361131222100240710ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/expected-results/000077500000000000000000000000001361131222100247075ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/expected-results/cache/000077500000000000000000000000001361131222100257525ustar00rootroot0000000000000013b6128773a87e3d14cac7fab4949705be33620855bc102f07cffdae63664ea1000066400000000000000000000000121361131222100364010ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/expected-results/cachetest3 1.5 c547ff53a86ac70fecf68cf675b774bd158027def062bb65892b2f4f1662bc0e.out000066400000000000000000000000061361131222100374640ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/expected-results/cachetest2 fa466047205bfd2dc7fe1fcbd5ec3591e3c1c5f73e8c7112493092191af2bee8000066400000000000000000000000051361131222100366310ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache/expected-results/cachetest snakemake-5.10.0/tests/test_output_file_cache/expected-results/test.out000066400000000000000000000000051361131222100264120ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_output_file_cache/expected-results/test2.out000066400000000000000000000000061361131222100264750ustar00rootroot00000000000000test2 snakemake-5.10.0/tests/test_output_file_cache/expected-results/test3.x.out000066400000000000000000000000121361131222100267410ustar00rootroot00000000000000test3 1.5 snakemake-5.10.0/tests/test_output_file_cache/expected-results/test3.y.out000066400000000000000000000000121361131222100267420ustar00rootroot00000000000000test3 1.5 snakemake-5.10.0/tests/test_output_file_cache_remote/000077500000000000000000000000001361131222100227625ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache_remote/Snakefile000066400000000000000000000006701361131222100246110ustar00rootroot00000000000000rule all: input: "test3.x.out", "test3.y.out" rule a: output: "test.out" shell: "echo test > {output}" rule b: input: "test.out" output: "test2.out" shell: "echo test2 > {output}" rule c: input: "test2.out", "test.out" output: "test3.{w}.out" params: a=1.5 shell: "echo test3 {params.a} > {output}" snakemake-5.10.0/tests/test_output_file_cache_remote/expected-results/000077500000000000000000000000001361131222100262625ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache_remote/expected-results/cache/000077500000000000000000000000001361131222100273255ustar00rootroot0000000000000013b6128773a87e3d14cac7fab4949705be33620855bc102f07cffdae63664ea1000066400000000000000000000000121361131222100377540ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache_remote/expected-results/cachetest3 1.5 c547ff53a86ac70fecf68cf675b774bd158027def062bb65892b2f4f1662bc0e000066400000000000000000000000061361131222100402310ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache_remote/expected-results/cachetest2 fa466047205bfd2dc7fe1fcbd5ec3591e3c1c5f73e8c7112493092191af2bee8000066400000000000000000000000051361131222100402040ustar00rootroot00000000000000snakemake-5.10.0/tests/test_output_file_cache_remote/expected-results/cachetest snakemake-5.10.0/tests/test_output_file_cache_remote/test.txt000066400000000000000000000000001361131222100244700ustar00rootroot00000000000000snakemake-5.10.0/tests/test_params/000077500000000000000000000000001361131222100172105ustar00rootroot00000000000000snakemake-5.10.0/tests/test_params/Snakefile000066400000000000000000000003571361131222100210410ustar00rootroot00000000000000 """ This is a test for the params syntax. """ rule: input: "somedir/test.out" rule: params: lambda wildcards: "-f", dir="{dir}" output: "{dir}/test.out" shell: "rm -r {params.dir}; mkdir -p {params.dir}; touch {params[0]} {output}" snakemake-5.10.0/tests/test_params/expected-results/000077500000000000000000000000001361131222100225105ustar00rootroot00000000000000snakemake-5.10.0/tests/test_params/expected-results/somedir/000077500000000000000000000000001361131222100241525ustar00rootroot00000000000000snakemake-5.10.0/tests/test_params/expected-results/somedir/test.out000066400000000000000000000000001361131222100256500ustar00rootroot00000000000000snakemake-5.10.0/tests/test_parser/000077500000000000000000000000001361131222100172215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_parser/Snakefile000066400000000000000000000001721361131222100210450ustar00rootroot00000000000000 class Test: def __init__(self): self.include = "test.out" rule: output: Test().include shell: "touch {output}" snakemake-5.10.0/tests/test_parser/expected-results/000077500000000000000000000000001361131222100225215ustar00rootroot00000000000000snakemake-5.10.0/tests/test_parser/expected-results/test.out000066400000000000000000000000001361131222100242170ustar00rootroot00000000000000snakemake-5.10.0/tests/test_parser/test.out000066400000000000000000000000001361131222100207170ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib/000077500000000000000000000000001361131222100173505ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib/Snakefile000066400000000000000000000031041361131222100211720ustar00rootroot00000000000000from pathlib import Path # Untested: # ancient # subworkflows # directory OUTDIR = Path("outdir") existing_file = Path("existing_file.txt") rule all: input: OUTDIR / "output_existing", #OUTDIR / "output_temp", OUTDIR / "output_protected", OUTDIR / "output_other_rule_output_existing", OUTDIR / "function_unpacking/output_function_unpacking", rule input_Path: input: Path("{sample}_file.txt") output: OUTDIR / "output_{sample}" log: OUTDIR / "log_{sample}" shell: """ cat {input} > {output} echo log {wildcards.sample} > {log} """ rule input_Path_output_temp: input: existing_file output: temp(OUTDIR / "output_temp") log: OUTDIR / "log_temp" shell: """ cat {input} > {output} echo log temp > {log} """ rule input_Path_output_protected: input: existing_file output: protected(OUTDIR / "output_protected") log: OUTDIR / "log_protected" shell: """ cat {input} > {output} echo log protected > {log} """ rule input_other_rule_output: input: rules.input_Path.output output: OUTDIR / "output_other_rule_output_{sample}" log: OUTDIR / "log_other_rule_output_{sample}" shell: """ cat {input} > {output} echo log protected > {log} """ def myfunc1(): return [existing_file] def myfunc2(): return {'existing_file2': existing_file} rule input_function_unpacking: input: *myfunc1(), **myfunc2(), output: OUTDIR / "function_unpacking/output_function_unpacking" log: OUTDIR / "log_function_unpacking" shell: """ echo {input[0]} > {output} echo {input.existing_file2} >> {output} echo log function unpacking > {log} """ snakemake-5.10.0/tests/test_pathlib/existing_file.txt000066400000000000000000000000111361131222100227320ustar00rootroot00000000000000I exist! snakemake-5.10.0/tests/test_pathlib/expected-results/000077500000000000000000000000001361131222100226505ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/000077500000000000000000000000001361131222100241565ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/function_unpacking/000077500000000000000000000000001361131222100300425ustar00rootroot00000000000000output_function_unpacking000066400000000000000000000000441361131222100352100ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/function_unpackingexisting_file.txt existing_file.txt snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/log_dynamic000066400000000000000000000000141361131222100263610ustar00rootroot00000000000000log dynamic snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/log_existing000066400000000000000000000000151361131222100265700ustar00rootroot00000000000000log existing snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/log_function_unpacking000066400000000000000000000000271361131222100306250ustar00rootroot00000000000000log function unpacking snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/log_other_rule_output000066400000000000000000000000161361131222100305270ustar00rootroot00000000000000log protected snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/log_protected000066400000000000000000000000161361131222100267300ustar00rootroot00000000000000log protected snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/output_existing000066400000000000000000000000111361131222100273430ustar00rootroot00000000000000I exist! snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/output_other_rule_output_existing000066400000000000000000000000111361131222100332130ustar00rootroot00000000000000I exist! snakemake-5.10.0/tests/test_pathlib/expected-results/outdir/output_protected000066400000000000000000000000111361131222100275020ustar00rootroot00000000000000I exist! snakemake-5.10.0/tests/test_pathlib_missing_file/000077500000000000000000000000001361131222100221005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib_missing_file/Snakefile000066400000000000000000000004541361131222100237270ustar00rootroot00000000000000from pathlib import Path # Things to test: # ancient # subworkflows output = Path("output") nonexistent_file = Path("nonexistent_file.txt") rule all: input: output rule input_nonexistent_Path: input: nonexistent_file output: output shell: """ echo "This should fail" > {output} """ snakemake-5.10.0/tests/test_pathlib_missing_file/expected-results/000077500000000000000000000000001361131222100254005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pathlib_missing_file/expected-results/.gitkeep000066400000000000000000000000001361131222100270170ustar00rootroot00000000000000snakemake-5.10.0/tests/test_persistent_dict/000077500000000000000000000000001361131222100211305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_persistent_dict/Snakefile000066400000000000000000000010171361131222100227530ustar00rootroot00000000000000from pytools.persistent_dict import PersistentDict storage = PersistentDict("mystorage") storage.store("var1", 100) rule all: input: expand("test.{i}.out", i=range(3)) rule: input: "test.in" output: "test.{i}.out" run: assert storage.fetch("var1") == 100 with open(output[0], "w") as out: v = storage.fetch("var2") assert v == 1 print(v, file=out) rule: output: temp("test.in") # mark output as temp, since var1 has to be stored in each run run: storage.store("var2", 1) shell("touch {output}") snakemake-5.10.0/tests/test_persistent_dict/expected-results/000077500000000000000000000000001361131222100244305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_persistent_dict/expected-results/.gitignore000066400000000000000000000000001361131222100264060ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes/000077500000000000000000000000001361131222100170455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes/Snakefile000066400000000000000000000005201361131222100206660ustar00rootroot00000000000000rule all: input: expand("test.{i}.out", i=range(2)) rule a: output: pipe("test.{i}.txt") shell: "for i in {{0..2}}; do echo {wildcards.i} >> {output}; done" rule b: input: "test.{i}.txt" output: "test.{i}.out" shell: "grep {wildcards.i} < {input} > {output}" snakemake-5.10.0/tests/test_pipes/expected-results/000077500000000000000000000000001361131222100223455ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes/expected-results/test.0.out000066400000000000000000000000061361131222100242070ustar00rootroot000000000000000 0 0 snakemake-5.10.0/tests/test_pipes/expected-results/test.1.out000066400000000000000000000000061361131222100242100ustar00rootroot000000000000001 1 1 snakemake-5.10.0/tests/test_pipes2/000077500000000000000000000000001361131222100171275ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes2/Snakefile000066400000000000000000000004511361131222100207530ustar00rootroot00000000000000rule all: input: "foo.txt" rule one: output: pipe("pipe.txt") shell: "echo test > {output}" rule two: input: pipe("pipe.txt") output: "foo.txt" shell: """ cat {input} > /dev/null sleep 10 touch {output} """ snakemake-5.10.0/tests/test_pipes2/expected-results/000077500000000000000000000000001361131222100224275ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes2/expected-results/foo.txt000066400000000000000000000000001361131222100237410ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes_fail/000077500000000000000000000000001361131222100200405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes_fail/Snakefile000066400000000000000000000006041361131222100216640ustar00rootroot00000000000000rule all: input: expand("test.{i}.out", i=range(2)) rule a: output: pipe("test.{i}.txt") shell: # this job fails because of a syntax error "for i in {{0..2}}; ddo echo {wildcards.i} >> {output}; done" rule b: input: "test.{i}.txt" output: "test.{i}.out" shell: "grep {wildcards.i} < {input} > {output}" snakemake-5.10.0/tests/test_pipes_fail/expected-results/000077500000000000000000000000001361131222100233405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_pipes_fail/expected-results/.gitignore000066400000000000000000000000001361131222100253160ustar00rootroot00000000000000snakemake-5.10.0/tests/test_profile/000077500000000000000000000000001361131222100173655ustar00rootroot00000000000000snakemake-5.10.0/tests/test_profile/Snakefile000066400000000000000000000001211361131222100212030ustar00rootroot00000000000000rule: shell: "python -m snakemake --profile . -s Snakefile.internal" snakemake-5.10.0/tests/test_profile/Snakefile.internal000066400000000000000000000001161361131222100230220ustar00rootroot00000000000000rule a: output: config["out"] shell: "touch {output}" snakemake-5.10.0/tests/test_profile/config.yaml000066400000000000000000000000561361131222100215170ustar00rootroot00000000000000configfile: "workflow-config.yaml" cores: all snakemake-5.10.0/tests/test_profile/expected-results/000077500000000000000000000000001361131222100226655ustar00rootroot00000000000000snakemake-5.10.0/tests/test_profile/expected-results/test.out000066400000000000000000000000001361131222100243630ustar00rootroot00000000000000snakemake-5.10.0/tests/test_profile/workflow-config.yaml000066400000000000000000000000201361131222100233560ustar00rootroot00000000000000out: "test.out" snakemake-5.10.0/tests/test_protected_symlink_output/000077500000000000000000000000001361131222100231045ustar00rootroot00000000000000snakemake-5.10.0/tests/test_protected_symlink_output/Snakefile000066400000000000000000000020501361131222100247250ustar00rootroot00000000000000# vim: ft=python """Snakemake will not delete write-protected files. But a symlink to a write-protected file is another matter. We should be able to overwrite those. """ """Description of the test: protected1 and protected2 are non-writeable files outlink is a symlink to protected1 after the main rule runs, outlink should point to protected2, and outfile should contain "contents2". """ import os import time # Set up the files just once if not os.path.exists("outlink"): shell("echo contents1 > protected1") shell("echo contents2 > protected2") shell("ln -s protected1 outlink") shell("chmod a-w protected1 protected2") # Print the result for debugging shell("ls -lR > /dev/stderr") rule main: output: "outfile", "outlink" run: # outlink should be gone but not protected1 or protected2 shell("test ! -e outlink") shell("test -e protected1 && test -e protected2") # Re-point the symlink shell("ln -s protected2 outlink") shell("cat outlink > outfile") snakemake-5.10.0/tests/test_protected_symlink_output/expected-results/000077500000000000000000000000001361131222100264045ustar00rootroot00000000000000snakemake-5.10.0/tests/test_protected_symlink_output/expected-results/outfile000066400000000000000000000000121361131222100277670ustar00rootroot00000000000000contents2 snakemake-5.10.0/tests/test_protected_symlink_output/expected-results/outlink000066400000000000000000000000121361131222100300050ustar00rootroot00000000000000contents2 snakemake-5.10.0/tests/test_r_wrapper/000077500000000000000000000000001361131222100177265ustar00rootroot00000000000000snakemake-5.10.0/tests/test_r_wrapper/Snakefile000066400000000000000000000001421361131222100215470ustar00rootroot00000000000000rule r_wrapper: output: "deleteme" wrapper: "file://tests/test_r_wrapper" snakemake-5.10.0/tests/test_r_wrapper/wrapper.R000066400000000000000000000001401361131222100215240ustar00rootroot00000000000000outfile = snakemake@output[[1]] x <- data.frame() write.table(x, file=outfile, col.names=FALSE) snakemake-5.10.0/tests/test_remote/000077500000000000000000000000001361131222100172205ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote/Snakefile000066400000000000000000000062331361131222100210500ustar00rootroot00000000000000#import re, os, sys from snakemake.remote.S3Mocked import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider() # remote dynamic file test # This makes use of a special provider that mocks up S3 using the moto # library so that boto calls hit local "buckets" rule all: input: # only keeping the file so we can copy it out to the cwd S3.remote("test-remote-bucket/out.txt", keep_local=True) run: shell("mv test-remote-bucket/out.txt ./") rule split: input: S3.remote('test-remote-bucket/test.txt') output: S3.remote(dynamic('test-remote-bucket/prefix{split_id}.txt')) run: shell('split -l 2 {input} test-remote-bucket/prefix') for f in os.listdir(os.getcwd()+"/test-remote-bucket"): if re.search('prefix[a-z][a-z]', f): os.rename("test-remote-bucket/"+f, "test-remote-bucket/"+f + '.txt') rule cut: input: S3.remote('test-remote-bucket/prefix{split_id,[a-z][a-z]}.txt') output: S3.remote('test-remote-bucket/{split_id}_cut.txt') shell: 'cut -f 1,2 {input} > {output}' rule merge: input: S3.remote(dynamic('test-remote-bucket/{split_id}_cut.txt')) output: S3.remote('test-remote-bucket/out.txt'), run: shell('echo {input}; cat {input} > {output}') # after we finish, we need to remove the pickle storing # the local moto "buckets" so we are starting fresh # next time this test is run. This file is created by # the moto wrapper defined in S3Mocked.py onsuccess: shell("rm ./motoState.p") onerror: shell("rm ./motoState.p") # or if you prefer to not instantiate a RemoteProvider object, and rely on the module # import S3Mocked as S3Mocked # # remote dynamic file test # # This makes use of a special provider that mocks up S3 using the moto # # library so that boto calls hit local "buckets" # rule all: # input: # # only keeping the file so we can copy it out to the cwd # remote("test-remote-bucket/out.txt", keep_local=True, provider=S3Mocked, additional_kwargs={}) # run: # shell("mv test-remote-bucket/out.txt ./") # rule split: # input: remote('test-remote-bucket/test.txt', keep_local=False, provider=S3Mocked, additional_kwargs={}) # output: remote(dynamic('test-remote-bucket/prefix{split_id}.txt'), provider=S3Mocked, additional_kwargs={}) # run: # shell('split -l 2 {input} test-remote-bucket/prefix') # for f in os.listdir(os.getcwd()+"/test-remote-bucket"): # if re.search('prefix[a-z][a-z]', f): # os.rename("test-remote-bucket/"+f, "test-remote-bucket/"+f + '.txt') # rule cut: # input: remote('test-remote-bucket/prefix{split_id,[a-z][a-z]}.txt', provider=S3Mocked, additional_kwargs={}) # output: # remote('test-remote-bucket/{split_id}_cut.txt', provider=S3Mocked, additional_kwargs={}) # shell: 'cut -f 1,2 {input} > {output}' # rule merge: # input: # remote(dynamic('test-remote-bucket/{split_id}_cut.txt'), provider=S3Mocked, additional_kwargs={}) # output: # remote('test-remote-bucket/out.txt', provider=S3Mocked, additional_kwargs={}), # run: # shell('echo {input}; cat {input} > {output}') snakemake-5.10.0/tests/test_remote/expected-results/000077500000000000000000000000001361131222100225205ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote/expected-results/out.txt000066400000000000000000000001101361131222100240600ustar00rootroot000000000000000 1 2 0 1 2 0 1 2 0 1 2 snakemake-5.10.0/tests/test_remote/test.txt000066400000000000000000000001101361131222100207300ustar00rootroot000000000000000 1 2 0 1 2 0 1 2 0 1 2 snakemake-5.10.0/tests/test_remote_azure/000077500000000000000000000000001361131222100204265ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_azure/.gitignore000066400000000000000000000000321361131222100224110ustar00rootroot00000000000000test.txt.gz globbing.done snakemake-5.10.0/tests/test_remote_azure/README.md000066400000000000000000000006701361131222100217100ustar00rootroot00000000000000# Instruction for testing of Azure Storage integration * in order to perform this test, an Azure Storage Account is required * Both the storage account and associated key need to be passed to snakemake at runtime * currently this is solved by setting and exporting environment variables called ** $AZURE_ACCOUNT ** $AZURE_KEY * furthermore, in the storage account, a container "snakemake-test" needs to be created prio to running the test snakemake-5.10.0/tests/test_remote_azure/Snakefile000066400000000000000000000023171361131222100222550ustar00rootroot00000000000000import os import fnmatch import snakemake from snakemake.exceptions import MissingInputException from snakemake.remote.AzureStorage import RemoteProvider as AzureRemoteProvider # setup Azure Storage for remote access # for testing these variable can be added to CircleCI account_key=os.environ['AZURE_KEY'] account_name=os.environ['AZURE_ACCOUNT'] AS = AzureRemoteProvider(account_name=account_name, account_key=account_key) rule upload_to_azure_storage: input: "data/test.txt.gz" output: AS.remote("snakemake-test/data_upload/test.txt.gz") run: shell("cp {input} {output}") rule download_from_azure_storage: input: AS.remote("snakemake-test/data_upload/test.txt.gz") output: "test.txt.gz" run: shell("cp {input} {output}") rule test_globbing: input: AS.remote("snakemake-test/data_upload/test.txt.gz") output: touch("globbing.done") run: basenames, = AS.glob_wildcards("snakemake-test/data_upload/{base}.txt.gz") assert "test" in basenames rule test_download: input: "globbing.done", "test.txt.gz" rule test_upload: input: AS.remote("snakemake-test/data_upload/test.txt.gz") snakemake-5.10.0/tests/test_remote_gs/000077500000000000000000000000001361131222100177115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_gs/Snakefile000066400000000000000000000010431361131222100215330ustar00rootroot00000000000000from snakemake.remote import GS import google.auth try: GS = GS.RemoteProvider() rule copy: input: GS.remote("gcp-public-data-landsat/LC08/01/001/003/LC08_L1GT_001003_20170430_20170501_01_RT/LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt") output: "landsat-data.txt" shell: "cp {input} {output}" except google.auth.exceptions.DefaultCredentialsError: # ignore the test if not authenticated print("skipping test_remote_gs because we are not authenticated with gcloud") snakemake-5.10.0/tests/test_remote_gs/expected-results/000077500000000000000000000000001361131222100232115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_gs/expected-results/landsat-data.txt000066400000000000000000000203761361131222100263170ustar00rootroot00000000000000GROUP = L1_METADATA_FILE GROUP = METADATA_FILE_INFO ORIGIN = "Image courtesy of the U.S. Geological Survey" REQUEST_ID = "0501705013406_00001" LANDSAT_SCENE_ID = "LC80010032017120LGN00" LANDSAT_PRODUCT_ID = "LC08_L1GT_001003_20170430_20170501_01_RT" COLLECTION_NUMBER = 01 FILE_DATE = 2017-05-01T16:00:24Z STATION_ID = "LGN" PROCESSING_SOFTWARE_VERSION = "LPGS_2.7.0" END_GROUP = METADATA_FILE_INFO GROUP = PRODUCT_METADATA DATA_TYPE = "L1GT" COLLECTION_CATEGORY = "RT" ELEVATION_SOURCE = "GLS2000" OUTPUT_FORMAT = "GEOTIFF" SPACECRAFT_ID = "LANDSAT_8" SENSOR_ID = "OLI_TIRS" WRS_PATH = 1 WRS_ROW = 3 NADIR_OFFNADIR = "NADIR" TARGET_WRS_PATH = 1 TARGET_WRS_ROW = 3 DATE_ACQUIRED = 2017-04-30 SCENE_CENTER_TIME = "14:07:16.5180850Z" CORNER_UL_LAT_PRODUCT = 80.21528 CORNER_UL_LON_PRODUCT = -17.96312 CORNER_UR_LAT_PRODUCT = 80.28798 CORNER_UR_LON_PRODUCT = -3.45901 CORNER_LL_LAT_PRODUCT = 77.79053 CORNER_LL_LON_PRODUCT = -16.19251 CORNER_LR_LAT_PRODUCT = 77.84841 CORNER_LR_LON_PRODUCT = -4.56164 CORNER_UL_PROJECTION_X_PRODUCT = 330600.000 CORNER_UL_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_UR_PROJECTION_X_PRODUCT = 604200.000 CORNER_UR_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_LL_PROJECTION_X_PRODUCT = 330600.000 CORNER_LL_PROJECTION_Y_PRODUCT = 8645400.000 CORNER_LR_PROJECTION_X_PRODUCT = 604200.000 CORNER_LR_PROJECTION_Y_PRODUCT = 8645400.000 PANCHROMATIC_LINES = 18221 PANCHROMATIC_SAMPLES = 18241 REFLECTIVE_LINES = 9111 REFLECTIVE_SAMPLES = 9121 THERMAL_LINES = 9111 THERMAL_SAMPLES = 9121 FILE_NAME_BAND_1 = "LC08_L1GT_001003_20170430_20170501_01_RT_B1.TIF" FILE_NAME_BAND_2 = "LC08_L1GT_001003_20170430_20170501_01_RT_B2.TIF" FILE_NAME_BAND_3 = "LC08_L1GT_001003_20170430_20170501_01_RT_B3.TIF" FILE_NAME_BAND_4 = "LC08_L1GT_001003_20170430_20170501_01_RT_B4.TIF" FILE_NAME_BAND_5 = "LC08_L1GT_001003_20170430_20170501_01_RT_B5.TIF" FILE_NAME_BAND_6 = "LC08_L1GT_001003_20170430_20170501_01_RT_B6.TIF" FILE_NAME_BAND_7 = "LC08_L1GT_001003_20170430_20170501_01_RT_B7.TIF" FILE_NAME_BAND_8 = "LC08_L1GT_001003_20170430_20170501_01_RT_B8.TIF" FILE_NAME_BAND_9 = "LC08_L1GT_001003_20170430_20170501_01_RT_B9.TIF" FILE_NAME_BAND_10 = "LC08_L1GT_001003_20170430_20170501_01_RT_B10.TIF" FILE_NAME_BAND_11 = "LC08_L1GT_001003_20170430_20170501_01_RT_B11.TIF" FILE_NAME_BAND_QUALITY = "LC08_L1GT_001003_20170430_20170501_01_RT_BQA.TIF" ANGLE_COEFFICIENT_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_ANG.txt" METADATA_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt" CPF_NAME = "LC08CPF_20170401_20170630_01.02" BPF_NAME_OLI = "LO8BPF20170430140614_20170430144404.01" BPF_NAME_TIRS = "LT8BPF20170426235522_20170427000359.01" RLUT_FILE_NAME = "LC08RLUT_20150303_20431231_01_12.h5" END_GROUP = PRODUCT_METADATA GROUP = IMAGE_ATTRIBUTES CLOUD_COVER = 24.32 CLOUD_COVER_LAND = -1 IMAGE_QUALITY_OLI = 9 IMAGE_QUALITY_TIRS = 7 TIRS_SSM_MODEL = "PRELIMINARY" TIRS_SSM_POSITION_STATUS = "ESTIMATED" TIRS_STRAY_LIGHT_CORRECTION_SOURCE = "TIRS" ROLL_ANGLE = -0.001 SUN_AZIMUTH = -156.47580997 SUN_ELEVATION = 24.99099132 EARTH_SUN_DISTANCE = 1.0074392 SATURATION_BAND_1 = "N" SATURATION_BAND_2 = "N" SATURATION_BAND_3 = "N" SATURATION_BAND_4 = "N" SATURATION_BAND_5 = "N" SATURATION_BAND_6 = "N" SATURATION_BAND_7 = "N" SATURATION_BAND_8 = "N" SATURATION_BAND_9 = "N" TRUNCATION_OLI = "UPPER" END_GROUP = IMAGE_ATTRIBUTES GROUP = MIN_MAX_RADIANCE RADIANCE_MAXIMUM_BAND_1 = 748.87909 RADIANCE_MINIMUM_BAND_1 = -61.84268 RADIANCE_MAXIMUM_BAND_2 = 766.86133 RADIANCE_MINIMUM_BAND_2 = -63.32766 RADIANCE_MAXIMUM_BAND_3 = 706.65613 RADIANCE_MINIMUM_BAND_3 = -58.35589 RADIANCE_MAXIMUM_BAND_4 = 595.89227 RADIANCE_MINIMUM_BAND_4 = -49.20898 RADIANCE_MAXIMUM_BAND_5 = 364.65634 RADIANCE_MINIMUM_BAND_5 = -30.11344 RADIANCE_MAXIMUM_BAND_6 = 90.68671 RADIANCE_MINIMUM_BAND_6 = -7.48894 RADIANCE_MAXIMUM_BAND_7 = 30.56628 RADIANCE_MINIMUM_BAND_7 = -2.52417 RADIANCE_MAXIMUM_BAND_8 = 674.38605 RADIANCE_MINIMUM_BAND_8 = -55.69102 RADIANCE_MAXIMUM_BAND_9 = 142.51598 RADIANCE_MINIMUM_BAND_9 = -11.76902 RADIANCE_MAXIMUM_BAND_10 = 22.00180 RADIANCE_MINIMUM_BAND_10 = 0.10033 RADIANCE_MAXIMUM_BAND_11 = 22.00180 RADIANCE_MINIMUM_BAND_11 = 0.10033 END_GROUP = MIN_MAX_RADIANCE GROUP = MIN_MAX_REFLECTANCE REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 REFLECTANCE_MINIMUM_BAND_1 = -0.099980 REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 REFLECTANCE_MINIMUM_BAND_2 = -0.099980 REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 REFLECTANCE_MINIMUM_BAND_3 = -0.099980 REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 REFLECTANCE_MINIMUM_BAND_4 = -0.099980 REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 REFLECTANCE_MINIMUM_BAND_5 = -0.099980 REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 REFLECTANCE_MINIMUM_BAND_6 = -0.099980 REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 REFLECTANCE_MINIMUM_BAND_7 = -0.099980 REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 REFLECTANCE_MINIMUM_BAND_8 = -0.099980 REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 REFLECTANCE_MINIMUM_BAND_9 = -0.099980 END_GROUP = MIN_MAX_REFLECTANCE GROUP = MIN_MAX_PIXEL_VALUE QUANTIZE_CAL_MAX_BAND_1 = 65535 QUANTIZE_CAL_MIN_BAND_1 = 1 QUANTIZE_CAL_MAX_BAND_2 = 65535 QUANTIZE_CAL_MIN_BAND_2 = 1 QUANTIZE_CAL_MAX_BAND_3 = 65535 QUANTIZE_CAL_MIN_BAND_3 = 1 QUANTIZE_CAL_MAX_BAND_4 = 65535 QUANTIZE_CAL_MIN_BAND_4 = 1 QUANTIZE_CAL_MAX_BAND_5 = 65535 QUANTIZE_CAL_MIN_BAND_5 = 1 QUANTIZE_CAL_MAX_BAND_6 = 65535 QUANTIZE_CAL_MIN_BAND_6 = 1 QUANTIZE_CAL_MAX_BAND_7 = 65535 QUANTIZE_CAL_MIN_BAND_7 = 1 QUANTIZE_CAL_MAX_BAND_8 = 65535 QUANTIZE_CAL_MIN_BAND_8 = 1 QUANTIZE_CAL_MAX_BAND_9 = 65535 QUANTIZE_CAL_MIN_BAND_9 = 1 QUANTIZE_CAL_MAX_BAND_10 = 65535 QUANTIZE_CAL_MIN_BAND_10 = 1 QUANTIZE_CAL_MAX_BAND_11 = 65535 QUANTIZE_CAL_MIN_BAND_11 = 1 END_GROUP = MIN_MAX_PIXEL_VALUE GROUP = RADIOMETRIC_RESCALING RADIANCE_MULT_BAND_1 = 1.2371E-02 RADIANCE_MULT_BAND_2 = 1.2668E-02 RADIANCE_MULT_BAND_3 = 1.1674E-02 RADIANCE_MULT_BAND_4 = 9.8438E-03 RADIANCE_MULT_BAND_5 = 6.0239E-03 RADIANCE_MULT_BAND_6 = 1.4981E-03 RADIANCE_MULT_BAND_7 = 5.0494E-04 RADIANCE_MULT_BAND_8 = 1.1140E-02 RADIANCE_MULT_BAND_9 = 2.3543E-03 RADIANCE_MULT_BAND_10 = 3.3420E-04 RADIANCE_MULT_BAND_11 = 3.3420E-04 RADIANCE_ADD_BAND_1 = -61.85505 RADIANCE_ADD_BAND_2 = -63.34032 RADIANCE_ADD_BAND_3 = -58.36757 RADIANCE_ADD_BAND_4 = -49.21882 RADIANCE_ADD_BAND_5 = -30.11946 RADIANCE_ADD_BAND_6 = -7.49044 RADIANCE_ADD_BAND_7 = -2.52468 RADIANCE_ADD_BAND_8 = -55.70216 RADIANCE_ADD_BAND_9 = -11.77137 RADIANCE_ADD_BAND_10 = 0.10000 RADIANCE_ADD_BAND_11 = 0.10000 REFLECTANCE_MULT_BAND_1 = 2.0000E-05 REFLECTANCE_MULT_BAND_2 = 2.0000E-05 REFLECTANCE_MULT_BAND_3 = 2.0000E-05 REFLECTANCE_MULT_BAND_4 = 2.0000E-05 REFLECTANCE_MULT_BAND_5 = 2.0000E-05 REFLECTANCE_MULT_BAND_6 = 2.0000E-05 REFLECTANCE_MULT_BAND_7 = 2.0000E-05 REFLECTANCE_MULT_BAND_8 = 2.0000E-05 REFLECTANCE_MULT_BAND_9 = 2.0000E-05 REFLECTANCE_ADD_BAND_1 = -0.100000 REFLECTANCE_ADD_BAND_2 = -0.100000 REFLECTANCE_ADD_BAND_3 = -0.100000 REFLECTANCE_ADD_BAND_4 = -0.100000 REFLECTANCE_ADD_BAND_5 = -0.100000 REFLECTANCE_ADD_BAND_6 = -0.100000 REFLECTANCE_ADD_BAND_7 = -0.100000 REFLECTANCE_ADD_BAND_8 = -0.100000 REFLECTANCE_ADD_BAND_9 = -0.100000 END_GROUP = RADIOMETRIC_RESCALING GROUP = TIRS_THERMAL_CONSTANTS K1_CONSTANT_BAND_10 = 774.8853 K2_CONSTANT_BAND_10 = 1321.0789 K1_CONSTANT_BAND_11 = 480.8883 K2_CONSTANT_BAND_11 = 1201.1442 END_GROUP = TIRS_THERMAL_CONSTANTS GROUP = PROJECTION_PARAMETERS MAP_PROJECTION = "UTM" DATUM = "WGS84" ELLIPSOID = "WGS84" UTM_ZONE = 29 GRID_CELL_SIZE_PANCHROMATIC = 15.00 GRID_CELL_SIZE_REFLECTIVE = 30.00 GRID_CELL_SIZE_THERMAL = 30.00 ORIENTATION = "NORTH_UP" RESAMPLING_OPTION = "CUBIC_CONVOLUTION" END_GROUP = PROJECTION_PARAMETERS END_GROUP = L1_METADATA_FILE END snakemake-5.10.0/tests/test_remote_gs/landsat-data.txt000066400000000000000000000203761361131222100230170ustar00rootroot00000000000000GROUP = L1_METADATA_FILE GROUP = METADATA_FILE_INFO ORIGIN = "Image courtesy of the U.S. Geological Survey" REQUEST_ID = "0501705013406_00001" LANDSAT_SCENE_ID = "LC80010032017120LGN00" LANDSAT_PRODUCT_ID = "LC08_L1GT_001003_20170430_20170501_01_RT" COLLECTION_NUMBER = 01 FILE_DATE = 2017-05-01T16:00:24Z STATION_ID = "LGN" PROCESSING_SOFTWARE_VERSION = "LPGS_2.7.0" END_GROUP = METADATA_FILE_INFO GROUP = PRODUCT_METADATA DATA_TYPE = "L1GT" COLLECTION_CATEGORY = "RT" ELEVATION_SOURCE = "GLS2000" OUTPUT_FORMAT = "GEOTIFF" SPACECRAFT_ID = "LANDSAT_8" SENSOR_ID = "OLI_TIRS" WRS_PATH = 1 WRS_ROW = 3 NADIR_OFFNADIR = "NADIR" TARGET_WRS_PATH = 1 TARGET_WRS_ROW = 3 DATE_ACQUIRED = 2017-04-30 SCENE_CENTER_TIME = "14:07:16.5180850Z" CORNER_UL_LAT_PRODUCT = 80.21528 CORNER_UL_LON_PRODUCT = -17.96312 CORNER_UR_LAT_PRODUCT = 80.28798 CORNER_UR_LON_PRODUCT = -3.45901 CORNER_LL_LAT_PRODUCT = 77.79053 CORNER_LL_LON_PRODUCT = -16.19251 CORNER_LR_LAT_PRODUCT = 77.84841 CORNER_LR_LON_PRODUCT = -4.56164 CORNER_UL_PROJECTION_X_PRODUCT = 330600.000 CORNER_UL_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_UR_PROJECTION_X_PRODUCT = 604200.000 CORNER_UR_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_LL_PROJECTION_X_PRODUCT = 330600.000 CORNER_LL_PROJECTION_Y_PRODUCT = 8645400.000 CORNER_LR_PROJECTION_X_PRODUCT = 604200.000 CORNER_LR_PROJECTION_Y_PRODUCT = 8645400.000 PANCHROMATIC_LINES = 18221 PANCHROMATIC_SAMPLES = 18241 REFLECTIVE_LINES = 9111 REFLECTIVE_SAMPLES = 9121 THERMAL_LINES = 9111 THERMAL_SAMPLES = 9121 FILE_NAME_BAND_1 = "LC08_L1GT_001003_20170430_20170501_01_RT_B1.TIF" FILE_NAME_BAND_2 = "LC08_L1GT_001003_20170430_20170501_01_RT_B2.TIF" FILE_NAME_BAND_3 = "LC08_L1GT_001003_20170430_20170501_01_RT_B3.TIF" FILE_NAME_BAND_4 = "LC08_L1GT_001003_20170430_20170501_01_RT_B4.TIF" FILE_NAME_BAND_5 = "LC08_L1GT_001003_20170430_20170501_01_RT_B5.TIF" FILE_NAME_BAND_6 = "LC08_L1GT_001003_20170430_20170501_01_RT_B6.TIF" FILE_NAME_BAND_7 = "LC08_L1GT_001003_20170430_20170501_01_RT_B7.TIF" FILE_NAME_BAND_8 = "LC08_L1GT_001003_20170430_20170501_01_RT_B8.TIF" FILE_NAME_BAND_9 = "LC08_L1GT_001003_20170430_20170501_01_RT_B9.TIF" FILE_NAME_BAND_10 = "LC08_L1GT_001003_20170430_20170501_01_RT_B10.TIF" FILE_NAME_BAND_11 = "LC08_L1GT_001003_20170430_20170501_01_RT_B11.TIF" FILE_NAME_BAND_QUALITY = "LC08_L1GT_001003_20170430_20170501_01_RT_BQA.TIF" ANGLE_COEFFICIENT_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_ANG.txt" METADATA_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt" CPF_NAME = "LC08CPF_20170401_20170630_01.02" BPF_NAME_OLI = "LO8BPF20170430140614_20170430144404.01" BPF_NAME_TIRS = "LT8BPF20170426235522_20170427000359.01" RLUT_FILE_NAME = "LC08RLUT_20150303_20431231_01_12.h5" END_GROUP = PRODUCT_METADATA GROUP = IMAGE_ATTRIBUTES CLOUD_COVER = 24.32 CLOUD_COVER_LAND = -1 IMAGE_QUALITY_OLI = 9 IMAGE_QUALITY_TIRS = 7 TIRS_SSM_MODEL = "PRELIMINARY" TIRS_SSM_POSITION_STATUS = "ESTIMATED" TIRS_STRAY_LIGHT_CORRECTION_SOURCE = "TIRS" ROLL_ANGLE = -0.001 SUN_AZIMUTH = -156.47580997 SUN_ELEVATION = 24.99099132 EARTH_SUN_DISTANCE = 1.0074392 SATURATION_BAND_1 = "N" SATURATION_BAND_2 = "N" SATURATION_BAND_3 = "N" SATURATION_BAND_4 = "N" SATURATION_BAND_5 = "N" SATURATION_BAND_6 = "N" SATURATION_BAND_7 = "N" SATURATION_BAND_8 = "N" SATURATION_BAND_9 = "N" TRUNCATION_OLI = "UPPER" END_GROUP = IMAGE_ATTRIBUTES GROUP = MIN_MAX_RADIANCE RADIANCE_MAXIMUM_BAND_1 = 748.87909 RADIANCE_MINIMUM_BAND_1 = -61.84268 RADIANCE_MAXIMUM_BAND_2 = 766.86133 RADIANCE_MINIMUM_BAND_2 = -63.32766 RADIANCE_MAXIMUM_BAND_3 = 706.65613 RADIANCE_MINIMUM_BAND_3 = -58.35589 RADIANCE_MAXIMUM_BAND_4 = 595.89227 RADIANCE_MINIMUM_BAND_4 = -49.20898 RADIANCE_MAXIMUM_BAND_5 = 364.65634 RADIANCE_MINIMUM_BAND_5 = -30.11344 RADIANCE_MAXIMUM_BAND_6 = 90.68671 RADIANCE_MINIMUM_BAND_6 = -7.48894 RADIANCE_MAXIMUM_BAND_7 = 30.56628 RADIANCE_MINIMUM_BAND_7 = -2.52417 RADIANCE_MAXIMUM_BAND_8 = 674.38605 RADIANCE_MINIMUM_BAND_8 = -55.69102 RADIANCE_MAXIMUM_BAND_9 = 142.51598 RADIANCE_MINIMUM_BAND_9 = -11.76902 RADIANCE_MAXIMUM_BAND_10 = 22.00180 RADIANCE_MINIMUM_BAND_10 = 0.10033 RADIANCE_MAXIMUM_BAND_11 = 22.00180 RADIANCE_MINIMUM_BAND_11 = 0.10033 END_GROUP = MIN_MAX_RADIANCE GROUP = MIN_MAX_REFLECTANCE REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 REFLECTANCE_MINIMUM_BAND_1 = -0.099980 REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 REFLECTANCE_MINIMUM_BAND_2 = -0.099980 REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 REFLECTANCE_MINIMUM_BAND_3 = -0.099980 REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 REFLECTANCE_MINIMUM_BAND_4 = -0.099980 REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 REFLECTANCE_MINIMUM_BAND_5 = -0.099980 REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 REFLECTANCE_MINIMUM_BAND_6 = -0.099980 REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 REFLECTANCE_MINIMUM_BAND_7 = -0.099980 REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 REFLECTANCE_MINIMUM_BAND_8 = -0.099980 REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 REFLECTANCE_MINIMUM_BAND_9 = -0.099980 END_GROUP = MIN_MAX_REFLECTANCE GROUP = MIN_MAX_PIXEL_VALUE QUANTIZE_CAL_MAX_BAND_1 = 65535 QUANTIZE_CAL_MIN_BAND_1 = 1 QUANTIZE_CAL_MAX_BAND_2 = 65535 QUANTIZE_CAL_MIN_BAND_2 = 1 QUANTIZE_CAL_MAX_BAND_3 = 65535 QUANTIZE_CAL_MIN_BAND_3 = 1 QUANTIZE_CAL_MAX_BAND_4 = 65535 QUANTIZE_CAL_MIN_BAND_4 = 1 QUANTIZE_CAL_MAX_BAND_5 = 65535 QUANTIZE_CAL_MIN_BAND_5 = 1 QUANTIZE_CAL_MAX_BAND_6 = 65535 QUANTIZE_CAL_MIN_BAND_6 = 1 QUANTIZE_CAL_MAX_BAND_7 = 65535 QUANTIZE_CAL_MIN_BAND_7 = 1 QUANTIZE_CAL_MAX_BAND_8 = 65535 QUANTIZE_CAL_MIN_BAND_8 = 1 QUANTIZE_CAL_MAX_BAND_9 = 65535 QUANTIZE_CAL_MIN_BAND_9 = 1 QUANTIZE_CAL_MAX_BAND_10 = 65535 QUANTIZE_CAL_MIN_BAND_10 = 1 QUANTIZE_CAL_MAX_BAND_11 = 65535 QUANTIZE_CAL_MIN_BAND_11 = 1 END_GROUP = MIN_MAX_PIXEL_VALUE GROUP = RADIOMETRIC_RESCALING RADIANCE_MULT_BAND_1 = 1.2371E-02 RADIANCE_MULT_BAND_2 = 1.2668E-02 RADIANCE_MULT_BAND_3 = 1.1674E-02 RADIANCE_MULT_BAND_4 = 9.8438E-03 RADIANCE_MULT_BAND_5 = 6.0239E-03 RADIANCE_MULT_BAND_6 = 1.4981E-03 RADIANCE_MULT_BAND_7 = 5.0494E-04 RADIANCE_MULT_BAND_8 = 1.1140E-02 RADIANCE_MULT_BAND_9 = 2.3543E-03 RADIANCE_MULT_BAND_10 = 3.3420E-04 RADIANCE_MULT_BAND_11 = 3.3420E-04 RADIANCE_ADD_BAND_1 = -61.85505 RADIANCE_ADD_BAND_2 = -63.34032 RADIANCE_ADD_BAND_3 = -58.36757 RADIANCE_ADD_BAND_4 = -49.21882 RADIANCE_ADD_BAND_5 = -30.11946 RADIANCE_ADD_BAND_6 = -7.49044 RADIANCE_ADD_BAND_7 = -2.52468 RADIANCE_ADD_BAND_8 = -55.70216 RADIANCE_ADD_BAND_9 = -11.77137 RADIANCE_ADD_BAND_10 = 0.10000 RADIANCE_ADD_BAND_11 = 0.10000 REFLECTANCE_MULT_BAND_1 = 2.0000E-05 REFLECTANCE_MULT_BAND_2 = 2.0000E-05 REFLECTANCE_MULT_BAND_3 = 2.0000E-05 REFLECTANCE_MULT_BAND_4 = 2.0000E-05 REFLECTANCE_MULT_BAND_5 = 2.0000E-05 REFLECTANCE_MULT_BAND_6 = 2.0000E-05 REFLECTANCE_MULT_BAND_7 = 2.0000E-05 REFLECTANCE_MULT_BAND_8 = 2.0000E-05 REFLECTANCE_MULT_BAND_9 = 2.0000E-05 REFLECTANCE_ADD_BAND_1 = -0.100000 REFLECTANCE_ADD_BAND_2 = -0.100000 REFLECTANCE_ADD_BAND_3 = -0.100000 REFLECTANCE_ADD_BAND_4 = -0.100000 REFLECTANCE_ADD_BAND_5 = -0.100000 REFLECTANCE_ADD_BAND_6 = -0.100000 REFLECTANCE_ADD_BAND_7 = -0.100000 REFLECTANCE_ADD_BAND_8 = -0.100000 REFLECTANCE_ADD_BAND_9 = -0.100000 END_GROUP = RADIOMETRIC_RESCALING GROUP = TIRS_THERMAL_CONSTANTS K1_CONSTANT_BAND_10 = 774.8853 K2_CONSTANT_BAND_10 = 1321.0789 K1_CONSTANT_BAND_11 = 480.8883 K2_CONSTANT_BAND_11 = 1201.1442 END_GROUP = TIRS_THERMAL_CONSTANTS GROUP = PROJECTION_PARAMETERS MAP_PROJECTION = "UTM" DATUM = "WGS84" ELLIPSOID = "WGS84" UTM_ZONE = 29 GRID_CELL_SIZE_PANCHROMATIC = 15.00 GRID_CELL_SIZE_REFLECTIVE = 30.00 GRID_CELL_SIZE_THERMAL = 30.00 ORIENTATION = "NORTH_UP" RESAMPLING_OPTION = "CUBIC_CONVOLUTION" END_GROUP = PROJECTION_PARAMETERS END_GROUP = L1_METADATA_FILE END snakemake-5.10.0/tests/test_remote_http/000077500000000000000000000000001361131222100202575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_http/Snakefile000066400000000000000000000023071361131222100221050ustar00rootroot00000000000000from snakemake.remote.HTTP import RemoteProvider as HTTPRemoteProvider HTTP = HTTPRemoteProvider() # The plain text version and the unzipped .gz version should have the same content. # The diff command returns exit code 0 for identical files, 1 for differing files and 2 for errors. rule compare: input: "landsat-data.txt", "landsat-data-unzipped.txt" shell: "diff {input[0]} {input[1]}" rule copy: input: HTTP.remote("bitbucket.org/snakemake/snakemake/raw/http-gzip-autodecode/tests/test_remote_http/expected-results/landsat-data.txt") output: "landsat-data.txt" shell: "cp {input} {output}" rule copy_gz: input: HTTP.remote("bitbucket.org/snakemake/snakemake/raw/http-gzip-autodecode/tests/test_remote_http/expected-results/landsat-data.txt.gz") output: "landsat-data.txt.gz" shell: "cp {input} {output}" # This only works if the input file is a gzipped file. # Hence, it will fail, if the .txt.gz file is automagically decompressed by the requests module. rule unizp: input: "landsat-data.txt.gz" output: "landsat-data-unzipped.txt" shell: "gunzip -c {input} > {output}" snakemake-5.10.0/tests/test_remote_http/expected-results/000077500000000000000000000000001361131222100235575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_http/expected-results/landsat-data.txt000066400000000000000000000203761361131222100266650ustar00rootroot00000000000000GROUP = L1_METADATA_FILE GROUP = METADATA_FILE_INFO ORIGIN = "Image courtesy of the U.S. Geological Survey" REQUEST_ID = "0501705013406_00001" LANDSAT_SCENE_ID = "LC80010032017120LGN00" LANDSAT_PRODUCT_ID = "LC08_L1GT_001003_20170430_20170501_01_RT" COLLECTION_NUMBER = 01 FILE_DATE = 2017-05-01T16:00:24Z STATION_ID = "LGN" PROCESSING_SOFTWARE_VERSION = "LPGS_2.7.0" END_GROUP = METADATA_FILE_INFO GROUP = PRODUCT_METADATA DATA_TYPE = "L1GT" COLLECTION_CATEGORY = "RT" ELEVATION_SOURCE = "GLS2000" OUTPUT_FORMAT = "GEOTIFF" SPACECRAFT_ID = "LANDSAT_8" SENSOR_ID = "OLI_TIRS" WRS_PATH = 1 WRS_ROW = 3 NADIR_OFFNADIR = "NADIR" TARGET_WRS_PATH = 1 TARGET_WRS_ROW = 3 DATE_ACQUIRED = 2017-04-30 SCENE_CENTER_TIME = "14:07:16.5180850Z" CORNER_UL_LAT_PRODUCT = 80.21528 CORNER_UL_LON_PRODUCT = -17.96312 CORNER_UR_LAT_PRODUCT = 80.28798 CORNER_UR_LON_PRODUCT = -3.45901 CORNER_LL_LAT_PRODUCT = 77.79053 CORNER_LL_LON_PRODUCT = -16.19251 CORNER_LR_LAT_PRODUCT = 77.84841 CORNER_LR_LON_PRODUCT = -4.56164 CORNER_UL_PROJECTION_X_PRODUCT = 330600.000 CORNER_UL_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_UR_PROJECTION_X_PRODUCT = 604200.000 CORNER_UR_PROJECTION_Y_PRODUCT = 8918700.000 CORNER_LL_PROJECTION_X_PRODUCT = 330600.000 CORNER_LL_PROJECTION_Y_PRODUCT = 8645400.000 CORNER_LR_PROJECTION_X_PRODUCT = 604200.000 CORNER_LR_PROJECTION_Y_PRODUCT = 8645400.000 PANCHROMATIC_LINES = 18221 PANCHROMATIC_SAMPLES = 18241 REFLECTIVE_LINES = 9111 REFLECTIVE_SAMPLES = 9121 THERMAL_LINES = 9111 THERMAL_SAMPLES = 9121 FILE_NAME_BAND_1 = "LC08_L1GT_001003_20170430_20170501_01_RT_B1.TIF" FILE_NAME_BAND_2 = "LC08_L1GT_001003_20170430_20170501_01_RT_B2.TIF" FILE_NAME_BAND_3 = "LC08_L1GT_001003_20170430_20170501_01_RT_B3.TIF" FILE_NAME_BAND_4 = "LC08_L1GT_001003_20170430_20170501_01_RT_B4.TIF" FILE_NAME_BAND_5 = "LC08_L1GT_001003_20170430_20170501_01_RT_B5.TIF" FILE_NAME_BAND_6 = "LC08_L1GT_001003_20170430_20170501_01_RT_B6.TIF" FILE_NAME_BAND_7 = "LC08_L1GT_001003_20170430_20170501_01_RT_B7.TIF" FILE_NAME_BAND_8 = "LC08_L1GT_001003_20170430_20170501_01_RT_B8.TIF" FILE_NAME_BAND_9 = "LC08_L1GT_001003_20170430_20170501_01_RT_B9.TIF" FILE_NAME_BAND_10 = "LC08_L1GT_001003_20170430_20170501_01_RT_B10.TIF" FILE_NAME_BAND_11 = "LC08_L1GT_001003_20170430_20170501_01_RT_B11.TIF" FILE_NAME_BAND_QUALITY = "LC08_L1GT_001003_20170430_20170501_01_RT_BQA.TIF" ANGLE_COEFFICIENT_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_ANG.txt" METADATA_FILE_NAME = "LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt" CPF_NAME = "LC08CPF_20170401_20170630_01.02" BPF_NAME_OLI = "LO8BPF20170430140614_20170430144404.01" BPF_NAME_TIRS = "LT8BPF20170426235522_20170427000359.01" RLUT_FILE_NAME = "LC08RLUT_20150303_20431231_01_12.h5" END_GROUP = PRODUCT_METADATA GROUP = IMAGE_ATTRIBUTES CLOUD_COVER = 24.32 CLOUD_COVER_LAND = -1 IMAGE_QUALITY_OLI = 9 IMAGE_QUALITY_TIRS = 7 TIRS_SSM_MODEL = "PRELIMINARY" TIRS_SSM_POSITION_STATUS = "ESTIMATED" TIRS_STRAY_LIGHT_CORRECTION_SOURCE = "TIRS" ROLL_ANGLE = -0.001 SUN_AZIMUTH = -156.47580997 SUN_ELEVATION = 24.99099132 EARTH_SUN_DISTANCE = 1.0074392 SATURATION_BAND_1 = "N" SATURATION_BAND_2 = "N" SATURATION_BAND_3 = "N" SATURATION_BAND_4 = "N" SATURATION_BAND_5 = "N" SATURATION_BAND_6 = "N" SATURATION_BAND_7 = "N" SATURATION_BAND_8 = "N" SATURATION_BAND_9 = "N" TRUNCATION_OLI = "UPPER" END_GROUP = IMAGE_ATTRIBUTES GROUP = MIN_MAX_RADIANCE RADIANCE_MAXIMUM_BAND_1 = 748.87909 RADIANCE_MINIMUM_BAND_1 = -61.84268 RADIANCE_MAXIMUM_BAND_2 = 766.86133 RADIANCE_MINIMUM_BAND_2 = -63.32766 RADIANCE_MAXIMUM_BAND_3 = 706.65613 RADIANCE_MINIMUM_BAND_3 = -58.35589 RADIANCE_MAXIMUM_BAND_4 = 595.89227 RADIANCE_MINIMUM_BAND_4 = -49.20898 RADIANCE_MAXIMUM_BAND_5 = 364.65634 RADIANCE_MINIMUM_BAND_5 = -30.11344 RADIANCE_MAXIMUM_BAND_6 = 90.68671 RADIANCE_MINIMUM_BAND_6 = -7.48894 RADIANCE_MAXIMUM_BAND_7 = 30.56628 RADIANCE_MINIMUM_BAND_7 = -2.52417 RADIANCE_MAXIMUM_BAND_8 = 674.38605 RADIANCE_MINIMUM_BAND_8 = -55.69102 RADIANCE_MAXIMUM_BAND_9 = 142.51598 RADIANCE_MINIMUM_BAND_9 = -11.76902 RADIANCE_MAXIMUM_BAND_10 = 22.00180 RADIANCE_MINIMUM_BAND_10 = 0.10033 RADIANCE_MAXIMUM_BAND_11 = 22.00180 RADIANCE_MINIMUM_BAND_11 = 0.10033 END_GROUP = MIN_MAX_RADIANCE GROUP = MIN_MAX_REFLECTANCE REFLECTANCE_MAXIMUM_BAND_1 = 1.210700 REFLECTANCE_MINIMUM_BAND_1 = -0.099980 REFLECTANCE_MAXIMUM_BAND_2 = 1.210700 REFLECTANCE_MINIMUM_BAND_2 = -0.099980 REFLECTANCE_MAXIMUM_BAND_3 = 1.210700 REFLECTANCE_MINIMUM_BAND_3 = -0.099980 REFLECTANCE_MAXIMUM_BAND_4 = 1.210700 REFLECTANCE_MINIMUM_BAND_4 = -0.099980 REFLECTANCE_MAXIMUM_BAND_5 = 1.210700 REFLECTANCE_MINIMUM_BAND_5 = -0.099980 REFLECTANCE_MAXIMUM_BAND_6 = 1.210700 REFLECTANCE_MINIMUM_BAND_6 = -0.099980 REFLECTANCE_MAXIMUM_BAND_7 = 1.210700 REFLECTANCE_MINIMUM_BAND_7 = -0.099980 REFLECTANCE_MAXIMUM_BAND_8 = 1.210700 REFLECTANCE_MINIMUM_BAND_8 = -0.099980 REFLECTANCE_MAXIMUM_BAND_9 = 1.210700 REFLECTANCE_MINIMUM_BAND_9 = -0.099980 END_GROUP = MIN_MAX_REFLECTANCE GROUP = MIN_MAX_PIXEL_VALUE QUANTIZE_CAL_MAX_BAND_1 = 65535 QUANTIZE_CAL_MIN_BAND_1 = 1 QUANTIZE_CAL_MAX_BAND_2 = 65535 QUANTIZE_CAL_MIN_BAND_2 = 1 QUANTIZE_CAL_MAX_BAND_3 = 65535 QUANTIZE_CAL_MIN_BAND_3 = 1 QUANTIZE_CAL_MAX_BAND_4 = 65535 QUANTIZE_CAL_MIN_BAND_4 = 1 QUANTIZE_CAL_MAX_BAND_5 = 65535 QUANTIZE_CAL_MIN_BAND_5 = 1 QUANTIZE_CAL_MAX_BAND_6 = 65535 QUANTIZE_CAL_MIN_BAND_6 = 1 QUANTIZE_CAL_MAX_BAND_7 = 65535 QUANTIZE_CAL_MIN_BAND_7 = 1 QUANTIZE_CAL_MAX_BAND_8 = 65535 QUANTIZE_CAL_MIN_BAND_8 = 1 QUANTIZE_CAL_MAX_BAND_9 = 65535 QUANTIZE_CAL_MIN_BAND_9 = 1 QUANTIZE_CAL_MAX_BAND_10 = 65535 QUANTIZE_CAL_MIN_BAND_10 = 1 QUANTIZE_CAL_MAX_BAND_11 = 65535 QUANTIZE_CAL_MIN_BAND_11 = 1 END_GROUP = MIN_MAX_PIXEL_VALUE GROUP = RADIOMETRIC_RESCALING RADIANCE_MULT_BAND_1 = 1.2371E-02 RADIANCE_MULT_BAND_2 = 1.2668E-02 RADIANCE_MULT_BAND_3 = 1.1674E-02 RADIANCE_MULT_BAND_4 = 9.8438E-03 RADIANCE_MULT_BAND_5 = 6.0239E-03 RADIANCE_MULT_BAND_6 = 1.4981E-03 RADIANCE_MULT_BAND_7 = 5.0494E-04 RADIANCE_MULT_BAND_8 = 1.1140E-02 RADIANCE_MULT_BAND_9 = 2.3543E-03 RADIANCE_MULT_BAND_10 = 3.3420E-04 RADIANCE_MULT_BAND_11 = 3.3420E-04 RADIANCE_ADD_BAND_1 = -61.85505 RADIANCE_ADD_BAND_2 = -63.34032 RADIANCE_ADD_BAND_3 = -58.36757 RADIANCE_ADD_BAND_4 = -49.21882 RADIANCE_ADD_BAND_5 = -30.11946 RADIANCE_ADD_BAND_6 = -7.49044 RADIANCE_ADD_BAND_7 = -2.52468 RADIANCE_ADD_BAND_8 = -55.70216 RADIANCE_ADD_BAND_9 = -11.77137 RADIANCE_ADD_BAND_10 = 0.10000 RADIANCE_ADD_BAND_11 = 0.10000 REFLECTANCE_MULT_BAND_1 = 2.0000E-05 REFLECTANCE_MULT_BAND_2 = 2.0000E-05 REFLECTANCE_MULT_BAND_3 = 2.0000E-05 REFLECTANCE_MULT_BAND_4 = 2.0000E-05 REFLECTANCE_MULT_BAND_5 = 2.0000E-05 REFLECTANCE_MULT_BAND_6 = 2.0000E-05 REFLECTANCE_MULT_BAND_7 = 2.0000E-05 REFLECTANCE_MULT_BAND_8 = 2.0000E-05 REFLECTANCE_MULT_BAND_9 = 2.0000E-05 REFLECTANCE_ADD_BAND_1 = -0.100000 REFLECTANCE_ADD_BAND_2 = -0.100000 REFLECTANCE_ADD_BAND_3 = -0.100000 REFLECTANCE_ADD_BAND_4 = -0.100000 REFLECTANCE_ADD_BAND_5 = -0.100000 REFLECTANCE_ADD_BAND_6 = -0.100000 REFLECTANCE_ADD_BAND_7 = -0.100000 REFLECTANCE_ADD_BAND_8 = -0.100000 REFLECTANCE_ADD_BAND_9 = -0.100000 END_GROUP = RADIOMETRIC_RESCALING GROUP = TIRS_THERMAL_CONSTANTS K1_CONSTANT_BAND_10 = 774.8853 K2_CONSTANT_BAND_10 = 1321.0789 K1_CONSTANT_BAND_11 = 480.8883 K2_CONSTANT_BAND_11 = 1201.1442 END_GROUP = TIRS_THERMAL_CONSTANTS GROUP = PROJECTION_PARAMETERS MAP_PROJECTION = "UTM" DATUM = "WGS84" ELLIPSOID = "WGS84" UTM_ZONE = 29 GRID_CELL_SIZE_PANCHROMATIC = 15.00 GRID_CELL_SIZE_REFLECTIVE = 30.00 GRID_CELL_SIZE_THERMAL = 30.00 ORIENTATION = "NORTH_UP" RESAMPLING_OPTION = "CUBIC_CONVOLUTION" END_GROUP = PROJECTION_PARAMETERS END_GROUP = L1_METADATA_FILE END snakemake-5.10.0/tests/test_remote_http/expected-results/landsat-data.txt.gz000066400000000000000000000037041361131222100273000ustar00rootroot00000000000000DZlandsat-data.txt[SD;H@,Ƙ~VދG#=:O\$g4j5Կ~ve>骭w? lϙII1/vSoݷyyU:?%؟o?Ǯhw?w'﫳m?eHXp'+$6iVfH%0AũDh.uuhS3: i |21_Y iUYje{~4 &> D=O}"݋xSaKi4yyMu4_L7 Xő$+vs Myy3K!Sߜ`;+׮M;apԭJYe㱳6ufNs!{{V6mU[3}э<LqKm}ʌLOYnl~nsyd{nD&ų>Iއ%Dȥ"8Rb2S(+Q4b\9ڋe8t)ZtJDX>G-;($Q, O¯5H Aߦ㢰o8b>Ã,2}nq-y DVA1]@lUx $VAcx$Uz VA=hhrg6)rsr% iy)h't^<:J;G_{ 5w$Pobf/! jl.D9!3QFg]7FA@}DewL99A%cΟ[5U{{e+a%f'N?Hꆥ۸6ٞCVj9/4Uwe%ƭF(nTjtG@?OK-z5g Ýp](M[=? c."sGXvãXq?HY͋uC-}I(fTu(h[Uɗs6a6a6a7lræ&62lZj:˔L^sjv_8 21H-ty9s I)ϣn@s{M$9ݞ cs"$1_Z uBsl0)00 `; `w<0DCaC0 `; M2tCZ/1XSSXQ 8luގBnCհr sizes.txt" snakemake-5.10.0/tests/test_remote_ncbi/expected-results/000077500000000000000000000000001361131222100235135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_ncbi/expected-results/sizes.txt000066400000000000000000000001161361131222100254070ustar00rootroot000000000000005801 KY785484.1.fasta 5255 KY785481.1.fasta 5318 KY785480.1.fasta 16374 total snakemake-5.10.0/tests/test_remote_ncbi_simple/000077500000000000000000000000001361131222100215645ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_ncbi_simple/Snakefile000066400000000000000000000006211361131222100234070ustar00rootroot00000000000000from snakemake.remote.NCBI import RemoteProvider as NCBIRemoteProvider NCBI = NCBIRemoteProvider(email="someone@example.com") # email required by NCBI to prevent abuse rule all: input: "sizes.txt" rule download_and_count: input: NCBI.remote("KY785484.1.fasta", db="nuccore") output: "sizes.txt" shell: r"wc -c {input} | sed 's/^[ \t]*//' > sizes.txt" snakemake-5.10.0/tests/test_remote_ncbi_simple/expected-results/000077500000000000000000000000001361131222100250645ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_ncbi_simple/expected-results/sizes.txt000066400000000000000000000000271361131222100267610ustar00rootroot0000000000000010861 KY785484.1.fasta snakemake-5.10.0/tests/test_remote_sftp/000077500000000000000000000000001361131222100202545ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_sftp/Snakefile000066400000000000000000000003741361131222100221040ustar00rootroot00000000000000from snakemake.remote.SFTP import RemoteProvider SFTP = RemoteProvider(username="demo", password="password") rule a: input: SFTP.remote("test.rebex.net/readme.txt") output: "readme.txt" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_remote_sftp/expected-results/000077500000000000000000000000001361131222100235545ustar00rootroot00000000000000snakemake-5.10.0/tests/test_remote_sftp/expected-results/readme.txt000066400000000000000000000006231361131222100255530ustar00rootroot00000000000000Welcome, you are connected to an FTP or SFTP server used for testing purposes by Rebex FTP/SSL or Rebex SFTP sample code. Only read access is allowed and the FTP download speed is limited to 16KBps. For infomation about Rebex FTP/SSL, Rebex SFTP and other Rebex .NET components, please visit our website at http://www.rebex.net/ For feedback and support, contact support@rebex.net Thanks! snakemake-5.10.0/tests/test_report/000077500000000000000000000000001361131222100172405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/.snakemake/000077500000000000000000000000001361131222100212555ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/.snakemake/metadata/000077500000000000000000000000001361131222100230355ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC40Lm91dA==000066400000000000000000000015301361131222100253460ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.4.out", "incomplete": false, "starttime": 1526474374.13434, "endtime": 1526474376.911341, "job_hash": -4193666763668718067, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC41Lm91dA==000066400000000000000000000015321361131222100253510ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.5.out", "incomplete": false, "starttime": 1526474375.8873405, "endtime": 1526474377.318341, "job_hash": -2933524654530661864, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC42Lm91dA==000066400000000000000000000015321361131222100253520ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.6.out", "incomplete": false, "starttime": 1526474374.1203399, "endtime": 1526474375.9023407, "job_hash": 8643010067298761385, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC43Lm91dA==000066400000000000000000000015311361131222100253520ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.7.out", "incomplete": false, "starttime": 1526474374.12534, "endtime": 1526474377.9363413, "job_hash": -2922925998717494899, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC44Lm91dA==000066400000000000000000000015301361131222100253520ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.8.out", "incomplete": false, "starttime": 1526474374.11534, "endtime": 1526474377.9283412, "job_hash": 2309792234881268873, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC45Lm91dA==000066400000000000000000000015301361131222100253530ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.9.out", "incomplete": false, "starttime": 1526474374.10834, "endtime": 1526474375.8943405, "job_hash": 3703979124286100515, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC4wLm91dA==000066400000000000000000000015311361131222100254560ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.0.out", "incomplete": false, "starttime": 1526474375.8993406, "endtime": 1526474379.3473418, "job_hash": 835220792718358845, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC4xLm91dA==000066400000000000000000000015331361131222100254610ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.1.out", "incomplete": false, "starttime": 1526474374.1403399, "endtime": 1526474376.8773408, "job_hash": -6430540820858145098, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC4yLm91dA==000066400000000000000000000015301361131222100254570ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.2.out", "incomplete": false, "starttime": 1526474374.11134, "endtime": 1526474375.8843405, "job_hash": 7606400900657219655, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/.snakemake/metadata/dGVzdC4zLm91dA==000066400000000000000000000015321361131222100254620ustar00rootroot00000000000000{"version": null, "code": "gAMoQxB0AGQBfA1kAo0CAQBkAFMAcQAoWAUAAABpbnB1dHEBWAYAAABvdXRwdXRxAlgGAAAAcGFyYW1zcQNYCQAAAHdpbGRjYXJkc3EEWAcAAAB0aHJlYWRzcQVYCQAAAHJlc291cmNlc3EGWAMAAABsb2dxB1gHAAAAdmVyc2lvbnEIWAQAAABydWxlcQlYCQAAAGNvbmRhX2VudnEKWA8AAABzaW5ndWxhcml0eV9pbWdxC1gQAAAAc2luZ3VsYXJpdHlfYXJnc3EMWA8AAAB1c2Vfc2luZ3VsYXJpdHlxDVgMAAAAYmVuY2hfcmVjb3JkcQ5YBQAAAGpvYmlkcQ90cRBdcREoTlgoAAAAc2xlZXAgYHNodWYgLWkgMS0zIC1uIDFgOyB0b3VjaCB7b3V0cHV0fXESaA6FcRNlWAUAAABzaGVsbHEUhXEVdHEWLg==", "rule": "c", "input": [], "log": [], "params": [], "shellcmd": "sleep `shuf -i 1-3 -n 1`; touch test.3.out", "incomplete": false, "starttime": 1526474374.1303399, "endtime": 1526474377.8753412, "job_hash": 7995860855952686702, "conda_env": "Y2hhbm5lbHM6CiAgLSBjb25kYS1mb3JnZQpkZXBlbmRlbmNpZXM6CiAgLSBtYWtlCg==", "singularity_img_url": "docker://continuumio/miniconda3:4.4.10"}snakemake-5.10.0/tests/test_report/Snakefile000066400000000000000000000016541361131222100210720ustar00rootroot00000000000000 report: "report/workflow.rst" rule all: input: ["fig1.svg", "fig2.png", "test.csv"] rule c: output: "test.{i}.out" singularity: "docker://continuumio/miniconda3:4.4.10" conda: "envs/test.yaml" shell: "sleep `shuf -i 1-3 -n 1`; touch {output}" rule a: input: expand("test.{i}.out", i=range(10)) output: report("fig1.svg", caption="report/fig1.rst", category="Step 1") shell: "sleep `shuf -i 1-3 -n 1`; cp data/fig1.svg {output}" rule b: input: expand("test.{i}.out", i=range(10)) output: report("fig2.png", caption="report/fig2.rst", category="Step 2") shell: "sleep `shuf -i 1-3 -n 1`; cp data/fig2.png {output}" rule d: output: report("test.csv", caption="report/table.rst") shell: "curl http://samplecsvs.s3.amazonaws.com/Sacramentorealestatetransactions.csv > {output}" snakemake-5.10.0/tests/test_report/data/000077500000000000000000000000001361131222100201515ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/data/fig1.svg000066400000000000000000000672611361131222100215340ustar00rootroot00000000000000 snakemake-5.10.0/tests/test_report/data/fig2.png000066400000000000000000000216011361131222100215060ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxmy{ fY F$dC BoElCHgRbq1c6m44`CiG v&ц l /EY(JP^dw9{k8㜳[U*J!@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@ʬ#// QS~>}zL4).\ z*~GBTJRCG/b\tEq饗Ɗ+N ނU&{쉻+""^~x %@dժU1x7n\z 2$0^y啢B2ټys6l#"?x{vJu։^ e'VZ;w /qQWWw=k׮w|;fϟ-*z33sάwۛ1cFlݺ3Ʒ? vg~ٳS)Yfw7/?r㬳ΊiӦw1qĄ)C:rfzΙs{sN Ikd e2a„'>ַqeţ>+V??#F="Z[[cձe˖khhތ)%Kmmmrʸ bѢEq-=ꢽ=,Xk׮۷ѣcz̷; eTSS . = Kuuu]w]y(JERQFū/r 4ϟCv%ZZZFx˗/˗wwނ?H~ǀ-'&OwI&4Q-XeuԵCŇ>&by-oyKL:56l/ w}w@:RFcٲeq=ċ/Ç3fwoۋ 27o^̛713 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ 뮨= B$ٹsg|_FUUU@!jRq455g-z(W@G?|0-ZR+ T,Rfyŧ>hll,zz+Ν3f(z3`ْ%KbǎQ={DSSSlٲQs= . Fmmm v8H~1lذ7o^ѣKk׮-z~[do|#-Z;w~z*? RWW1gΜ3fLG)zz.ڎ|f FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiH=1shhhFmmm455Ųeˊ QSَ;⥗^9sD}}}߿?VXGc۶m UUT*=D%ycҤI}nuttĤIb1qĂ&xy+Yuuu5*:묢Gtނ`x⡇x(X`A]6<5551yhmm@$g>""&޸ <MMMe˖n7lWvP+V?>7K.-z,ێöl ,HӉW@7.ƍgώ+2ϟf͊<v>=@&@ |$_5|ɘ0aQϟ??ZKKKdk˗˻]ۻwoA/"u,-m݀3VM͉f. /3 euԵ^{-(ɓ'q7 UF7xc۷/.첨ݻwDzebӦMkV\kkk^Dohht!@~f|k_{.SLŋ?(hoos@8&RF\ꢭ18 FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiH=q7Gccc 4(FͱyGB=@/})c̙qGggg,^8&N=X466=" e~6.䒨1777)p:'@hԩG];vl]70g@JxgbذaEBꊹsFccc7.cܹUh@ He˖Ů] _BѣPaMMMe˖n7lW+h:Rx$ƍ㦛n?^8Tn8l˖-`J$@޽;8bŊQUUUHTkqS[ qWNj/W#FϏCv---~}z*cݮݻiRfӧSO=VwozϢEbĉ QIPi1iҤ&:ͱf͚xbʔ)EDl|;ٳ JW>J#@?iTUU??pǪꢽ=,Xk׮FMMML<9Z[[} ^ )Gyhkk+z FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiHqwUW]o}[:.]ZXPRF]]]qwƓO>&L2ݻw֭[_rk˗//z33sάw[9qnd etg#"T*zk\x\sM<#qƂ 6*Mׯc&~z{ny1f̘n׆ Rq wyg=:&L>J޵ak_җ=fΜ_|qtvvŋcĉcEccq]˹Vicǎx饗bΜ9Q__+VG?ضm[|?k}9*m~Ν;_b 8P+%j͚5ҟvرcKMMM'KUUU|ȵyW|:˹Jׯ/W^y3ϔJRiݺuҥKOJ޵[ڏk͛K{ni'w/Vv,*M0tykrf\ҴiJϷo=-XebŊoȵs9'>OF{{{<'wĈq 76lذ5kV?CkeH}9JR۷/:TQOg}v ><"{w/vXԩS cǎw]q[ɻ֗s;vXcԨQqYgykrvX%ڏ~xcѢEGs雱o=#@'?I\tE1hРn/䒈;qģ_r%شiө4җs;/!C믏z,k]{+""&n]ݹU-Y$v?{t}Rf/s9{9p@=fΜ3g<뮻.ʸ⮻}k~3\%Z_ص7lܸ1nhjj'|][ڭf͊]vŲeoZέw瞋 … Gڷ e6`xW~#3ݩŔ)SNWJ޵Svmqy+VxJ۵7܎vmܸq1nܸ={v\y15kqwƮ܎RvoaÆżyz|})#GBQ___{tG?|g*yʡRv^:^|1běczwnS)> /O>qc׎v2v<}6o7b޼ysضm[l۶-8jl߾_}Rf~cӦMo߾n׬Y&L8&L3Ϛ5kbqEO}9uuudw*a8ӧOz*1w}k=㩄];og>c*}׎dx=-oۏZvmlڴ)ƌwyqo=#@lƌqС#x'|{xoq8x`{yw{ڳ><@L>ޙ/u?韢#kc~СCk֬x Lrٵrnk˂ 7IDAT_{ȇz;;;ӗs]?~|\2rhllѣG'?T*sҚcʕq뭷FCCC,]4֭[o^ziDSضm[OռKsܑsx /,*ޞۅ^'NI&Ő!C#ox,^8v%K 7pբ[n%l׎V6{c>zٳ#¿~U_έRwp۷/.첨ݻwDzebӦM"®[L6-{#)+ρJJ#G,{)S~t{Μ9sJե۷wϗ4lذK_~ytޞ^z]:th.]pngϞ/!\P*UUUKG3kGUM6#ϳk*uۿۥ#F:Rmmmk)ZZw}9Jݵ6mZiݮٷ g@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4ܬSSIENDB`snakemake-5.10.0/tests/test_report/envs/000077500000000000000000000000001361131222100202135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/envs/test.yaml000066400000000000000000000000611361131222100220530ustar00rootroot00000000000000channels: - conda-forge dependencies: - make snakemake-5.10.0/tests/test_report/expected-results/000077500000000000000000000000001361131222100225405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/expected-results/report.html000066400000000000000000116166561361131222100247670ustar00rootroot00000000000000 Snakemake Report

Loading Snakemake Report...

Please enable Javascript in your browser to see this report.

Loading 150.6 kB. For large reports, this can take a while.

Workflow

This is the workflow description. Test reference fig1.svg.

Detailed software versions can be found under Rules.

Results

Workflow resultes
File Size Description Job properties
test.csv.html 113.2 kB

An example table.

Job properties
Ruled
Workflow resultes
File Size Description Job properties
fig1.svg 28.3 kB

This is a caption with a link.

Job properties
Rulea
Workflow resultes
File Size Description Job properties
fig2.png 9.1 kB

Some math ii2.

Job properties
Ruleb

Statistics

If the workflow has been executed in cluster/cloud, runtimes include the waiting time in the queue.

Rules

Workflow rules
Rule Jobs Output Singularity Conda environment Code
a 1
  • fig1.svg
1
sleep `shuf -i 1-3 -n 1`; cp data/fig1.svg {output}
b 1
  • fig2.png
1
sleep `shuf -i 1-3 -n 1`; cp data/fig2.png {output}
d 1
  • test.csv
1
curl http://samplecsvs.s3.amazonaws.com/Sacramentorealestatetransactions.csv > {output}
c 10
  • test.0.out
  • test.1.out
  • test.2.out
  • test.3.out
  • test.4.out
  • test.5.out
  • test.6.out
  • test.7.out
  • test.8.out
  • test.9.out
docker://continuumio/miniconda3:4.4.10
  • make
1
sleep `shuf -i 1-3 -n 1`; touch {output}
snakemake-5.10.0/tests/test_report/fig1.svg000066400000000000000000000672611361131222100206230ustar00rootroot00000000000000 snakemake-5.10.0/tests/test_report/fig2.png000066400000000000000000000216011361131222100205750ustar00rootroot00000000000000PNG  IHDR XvpsBIT|d pHYsaa?i IDATxmy{ fY F$dC BoElCHgRbq1c6m44`CiG v&ц l /EY(JP^dw9{k8㜳[U*J!@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@ʬ#// QS~>}zL4).\ z*~GBTJRCG/b\tEq饗Ɗ+N ނU&{쉻+""^~x %@dժU1x7n\z 2$0^y啢B2ټys6l#"?x{vJu։^ e'VZ;w /qQWWw=k׮w|;fϟ-*z33sάwۛ1cFlݺ3Ʒ? vg~ٳS)Yfw7/?r㬳ΊiӦw1qĄ)C:rfzΙs{sN Ikd e2a„'>ַqeţ>+V??#F="Z[[cձe˖khhތ)%Kmmmrʸ bѢEq-=ꢽ=,Xk׮۷ѣcz̷; eTSS . = Kuuu]w]y(JERQFū/r 4ϟCv%ZZZFx˗/˗wwނ?H~ǀ-'&OwI&4Q-XeuԵCŇ>&by-oyKL:56l/ w}w@:RFcٲeq=ċ/Ç3fwoۋ 27o^̛713 @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ @ 뮨= B$ٹsg|_FUUU@!jRq455g-z(W@G?|0-ZR+ T,Rfyŧ>hll,zz+Ν3f(z3`ْ%KbǎQ={DSSSlٲQs= . Fmmm v8H~1lذ7o^ѣKk׮-z~[do|#-Z;w~z*? RWW1gΜ3fLG)zz.ڎ|f FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiH=1shhhFmmm455Ųeˊ QSَ;⥗^9sD}}}߿?VXGc۶m UUT*=D%ycҤI}nuttĤIb1qĂ&xy+Yuuu5*:묢Gtނ`x⡇x(X`A]6<5551yhmm@$g>""&޸ <MMMe˖n7lWvP+V?>7K.-z,ێöl ,HӉW@7.ƍgώ+2ϟf͊<v>=@&@ |$_5|ɘ0aQϟ??ZKKKdk˗˻]ۻwoA/"u,-m݀3VM͉f. /3 euԵ^{-(ɓ'q7 UF7xc۷/.첨ݻwDzebӦMkV\kkk^Dohht!@~f|k_{.SLŋ?(hoos@8&RF\ꢭ18 FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiH=q7Gccc 4(FͱyGB=@/})c̙qGggg,^8&N=X466=" e~6.䒨1777)p:'@hԩG];vl]70g@JxgbذaEBꊹsFccc7.cܹUh@ He˖Ů] _BѣPaMMMe˖n7lW+h:Rx$ƍ㦛n?^8Tn8l˖-`J$@޽;8bŊQUUUHTkqS[ qWNj/W#FϏCv---~}z*cݮݻiRfӧSO=VwozϢEbĉ QIPi1iҤ&:ͱf͚xbʔ)EDl|;ٳ JW>J#@?iTUU??pǪꢽ=,Xk׮FMMML<9Z[[} ^ )Gyhkk+z FiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiFiHqwUW]o}[:.]ZXPRF]]]qwƓO>&L2ݻw֭[_rk˗//z33sάw[9qnd etg#"T*zk\x\sM<#qƂ 6*Mׯc&~z{ny1f̘n׆ Rq wyg=:&L>J޵ak_җ=fΜ_|qtvvŋcĉcEccq]˹Vicǎx饗bΜ9Q__+VG?ضm[|?k}9*m~Ν;_b 8P+%j͚5ҟvرcKMMM'KUUU|ȵyW|:˹Jׯ/W^y3ϔJRiݺuҥKOJ޵[ڏk͛K{ni'w/Vv,*M0tykrf\ҴiJϷo=-XebŊoȵs9'>OF{{{<'wĈq 76lذ5kV?CkeH}9JR۷/:TQOg}v ><"{w/vXԩS cǎw]q[ɻ֗s;vXcԨQqYgykrvX%ڏ~xcѢEGs雱o=#@'?I\tE1hРn/䒈;qģ_r%شiө4җs;/!C믏z,k]{+""&n]ݹU-Y$v?{t}Rf/s9{9p@=fΜ3g<뮻.ʸ⮻}k~3\%Z_ص7lܸ1nhjj'|][ڭf͊]vŲeoZέw瞋 … Gڷ e6`xW~#3ݩŔ)SNWJ޵Svmqy+VxJ۵7܎vmܸq1nܸ={v\y15kqwƮ܎RvoaÆżyz|})#GBQ___{tG?|g*yʡRv^:^|1běczwnS)> /O>qc׎v2v<}6o7b޼ysضm[l۶-8jl߾_}Rf~cӦMo߾n׬Y&L8&L3Ϛ5kbqEO}9uuudw*a8ӧOz*1w}k=㩄];og>c*}׎dx=-oۏZvmlڴ)ƌwyqo=#@lƌqС#x'|{xoq8x`{yw{ڳ><@L>ޙ/u?韢#kc~СCk֬x Lrٵrnk˂ 7IDAT_{ȇz;;;ӗs]?~|\2rhllѣG'?T*sҚcʕq뭷FCCC,]4֭[o^ziDSضm[OռKsܑsx /,*ޞۅ^'NI&Ő!C#ox,^8v%K 7pբ[n%l׎V6{c>zٳ#¿~U_έRwp۷/.첨ݻwDzebӦM"®[L6-{#)+ρJJ#G,{)S~t{Μ9sJե۷wϗ4lذK_~ytޞ^z]:th.]pngϞ/!\P*UUUKG3kGUM6#ϳk*uۿۥ#F:Rmmmk)ZZw}9Jݵ6mZiݮٷ g@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4H#@4ܬSSIENDB`snakemake-5.10.0/tests/test_report/report/000077500000000000000000000000001361131222100205535ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/report/fig1.rst000066400000000000000000000000731361131222100221330ustar00rootroot00000000000000This is a caption with a `link `_. snakemake-5.10.0/tests/test_report/report/fig2.rst000066400000000000000000000000361361131222100221330ustar00rootroot00000000000000Some math :math:`\sum_i i^2`. snakemake-5.10.0/tests/test_report/report/table.rst000066400000000000000000000000221361131222100223660ustar00rootroot00000000000000An example table. snakemake-5.10.0/tests/test_report/report/workflow.rst000066400000000000000000000000741361131222100231600ustar00rootroot00000000000000This is the workflow description. Test reference fig1.svg_. snakemake-5.10.0/tests/test_report/test.0.out000066400000000000000000000000001361131222100210740ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.1.out000066400000000000000000000000001361131222100210750ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.2.out000066400000000000000000000000001361131222100210760ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.3.out000066400000000000000000000000001361131222100210770ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.4.out000066400000000000000000000000001361131222100211000ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.5.out000066400000000000000000000000001361131222100211010ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.6.out000066400000000000000000000000001361131222100211020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.7.out000066400000000000000000000000001361131222100211030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.8.out000066400000000000000000000000001361131222100211040ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.9.out000066400000000000000000000000001361131222100211050ustar00rootroot00000000000000snakemake-5.10.0/tests/test_report/test.csv000066400000000000000000003350371361131222100207470ustar00rootroot00000000000000street,city,zip,state,beds,baths,sq__ft,type,sale_date,price,latitude,longitude 3526 HIGH ST,SACRAMENTO,95838,CA,2,1,836,Residential,Wed May 21 00:00:00 EDT 2008,59222,38.631913,-121.434879 51 OMAHA CT,SACRAMENTO,95823,CA,3,1,1167,Residential,Wed May 21 00:00:00 EDT 2008,68212,38.478902,-121.431028 2796 BRANCH ST,SACRAMENTO,95815,CA,2,1,796,Residential,Wed May 21 00:00:00 EDT 2008,68880,38.618305,-121.443839 2805 JANETTE WAY,SACRAMENTO,95815,CA,2,1,852,Residential,Wed May 21 00:00:00 EDT 2008,69307,38.616835,-121.439146 6001 MCMAHON DR,SACRAMENTO,95824,CA,2,1,797,Residential,Wed May 21 00:00:00 EDT 2008,81900,38.51947,-121.435768 5828 PEPPERMILL CT,SACRAMENTO,95841,CA,3,1,1122,Condo,Wed May 21 00:00:00 EDT 2008,89921,38.662595,-121.327813 6048 OGDEN NASH WAY,SACRAMENTO,95842,CA,3,2,1104,Residential,Wed May 21 00:00:00 EDT 2008,90895,38.681659,-121.351705 2561 19TH AVE,SACRAMENTO,95820,CA,3,1,1177,Residential,Wed May 21 00:00:00 EDT 2008,91002,38.535092,-121.481367 11150 TRINITY RIVER DR Unit 114,RANCHO CORDOVA,95670,CA,2,2,941,Condo,Wed May 21 00:00:00 EDT 2008,94905,38.621188,-121.270555 7325 10TH ST,RIO LINDA,95673,CA,3,2,1146,Residential,Wed May 21 00:00:00 EDT 2008,98937,38.700909,-121.442979 645 MORRISON AVE,SACRAMENTO,95838,CA,3,2,909,Residential,Wed May 21 00:00:00 EDT 2008,100309,38.637663,-121.45152 4085 FAWN CIR,SACRAMENTO,95823,CA,3,2,1289,Residential,Wed May 21 00:00:00 EDT 2008,106250,38.470746,-121.458918 2930 LA ROSA RD,SACRAMENTO,95815,CA,1,1,871,Residential,Wed May 21 00:00:00 EDT 2008,106852,38.618698,-121.435833 2113 KIRK WAY,SACRAMENTO,95822,CA,3,1,1020,Residential,Wed May 21 00:00:00 EDT 2008,107502,38.482215,-121.492603 4533 LOCH HAVEN WAY,SACRAMENTO,95842,CA,2,2,1022,Residential,Wed May 21 00:00:00 EDT 2008,108750,38.672914,-121.35934 7340 HAMDEN PL,SACRAMENTO,95842,CA,2,2,1134,Condo,Wed May 21 00:00:00 EDT 2008,110700,38.700051,-121.351278 6715 6TH ST,RIO LINDA,95673,CA,2,1,844,Residential,Wed May 21 00:00:00 EDT 2008,113263,38.689591,-121.452239 6236 LONGFORD DR Unit 1,CITRUS HEIGHTS,95621,CA,2,1,795,Condo,Wed May 21 00:00:00 EDT 2008,116250,38.679776,-121.314089 250 PERALTA AVE,SACRAMENTO,95833,CA,2,1,588,Residential,Wed May 21 00:00:00 EDT 2008,120000,38.612099,-121.469095 113 LEEWILL AVE,RIO LINDA,95673,CA,3,2,1356,Residential,Wed May 21 00:00:00 EDT 2008,121630,38.689999,-121.46322 6118 STONEHAND AVE,CITRUS HEIGHTS,95621,CA,3,2,1118,Residential,Wed May 21 00:00:00 EDT 2008,122000,38.707851,-121.320707 4882 BANDALIN WAY,SACRAMENTO,95823,CA,4,2,1329,Residential,Wed May 21 00:00:00 EDT 2008,122682,38.468173,-121.444071 7511 OAKVALE CT,NORTH HIGHLANDS,95660,CA,4,2,1240,Residential,Wed May 21 00:00:00 EDT 2008,123000,38.702792,-121.38221 9 PASTURE CT,SACRAMENTO,95834,CA,3,2,1601,Residential,Wed May 21 00:00:00 EDT 2008,124100,38.628631,-121.488097 3729 BAINBRIDGE DR,NORTH HIGHLANDS,95660,CA,3,2,901,Residential,Wed May 21 00:00:00 EDT 2008,125000,38.701499,-121.37622 3828 BLACKFOOT WAY,ANTELOPE,95843,CA,3,2,1088,Residential,Wed May 21 00:00:00 EDT 2008,126640,38.70974,-121.37377 4108 NORTON WAY,SACRAMENTO,95820,CA,3,1,963,Residential,Wed May 21 00:00:00 EDT 2008,127281,38.537526,-121.478315 1469 JANRICK AVE,SACRAMENTO,95832,CA,3,2,1119,Residential,Wed May 21 00:00:00 EDT 2008,129000,38.476472,-121.501711 9861 CULP WAY,SACRAMENTO,95827,CA,4,2,1380,Residential,Wed May 21 00:00:00 EDT 2008,131200,38.558423,-121.327948 7825 CREEK VALLEY CIR,SACRAMENTO,95828,CA,3,2,1248,Residential,Wed May 21 00:00:00 EDT 2008,132000,38.472122,-121.404199 5201 LAGUNA OAKS DR Unit 140,ELK GROVE,95758,CA,2,2,1039,Condo,Wed May 21 00:00:00 EDT 2008,133000,38.423251,-121.444489 6768 MEDORA DR,NORTH HIGHLANDS,95660,CA,3,2,1152,Residential,Wed May 21 00:00:00 EDT 2008,134555,38.691161,-121.37192 3100 EXPLORER DR,SACRAMENTO,95827,CA,3,2,1380,Residential,Wed May 21 00:00:00 EDT 2008,136500,38.566663,-121.332644 7944 DOMINION WAY,ELVERTA,95626,CA,3,2,1116,Residential,Wed May 21 00:00:00 EDT 2008,138750,38.713182,-121.411227 5201 LAGUNA OAKS DR Unit 162,ELK GROVE,95758,CA,2,2,1039,Condo,Wed May 21 00:00:00 EDT 2008,141000,38.423251,-121.444489 3920 SHINING STAR DR,SACRAMENTO,95823,CA,3,2,1418,Residential,Wed May 21 00:00:00 EDT 2008,146250,38.48742,-121.462459 5031 CORVAIR ST,NORTH HIGHLANDS,95660,CA,3,2,1082,Residential,Wed May 21 00:00:00 EDT 2008,147308,38.658246,-121.375469 7661 NIXOS WAY,SACRAMENTO,95823,CA,4,2,1472,Residential,Wed May 21 00:00:00 EDT 2008,148750,38.479553,-121.463317 7044 CARTHY WAY,SACRAMENTO,95828,CA,4,2,1146,Residential,Wed May 21 00:00:00 EDT 2008,149593,38.49857,-121.420925 2442 LARKSPUR LN,SACRAMENTO,95825,CA,1,1,760,Condo,Wed May 21 00:00:00 EDT 2008,150000,38.58514,-121.403736 4800 WESTLAKE PKWY Unit 2109,SACRAMENTO,95835,CA,2,2,1304,Condo,Wed May 21 00:00:00 EDT 2008,152000,38.658812,-121.542345 2178 63RD AVE,SACRAMENTO,95822,CA,3,2,1207,Residential,Wed May 21 00:00:00 EDT 2008,154000,38.493955,-121.48966 8718 ELK WAY,ELK GROVE,95624,CA,3,2,1056,Residential,Wed May 21 00:00:00 EDT 2008,156896,38.41653,-121.379653 5708 RIDGEPOINT DR,ANTELOPE,95843,CA,2,2,1043,Residential,Wed May 21 00:00:00 EDT 2008,161250,38.72027,-121.331555 7315 KOALA CT,NORTH HIGHLANDS,95660,CA,4,2,1587,Residential,Wed May 21 00:00:00 EDT 2008,161500,38.699251,-121.371414 2622 ERIN DR,SACRAMENTO,95833,CA,4,1,1120,Residential,Wed May 21 00:00:00 EDT 2008,164000,38.613765,-121.488694 8421 SUNBLAZE WAY,SACRAMENTO,95823,CA,4,2,1580,Residential,Wed May 21 00:00:00 EDT 2008,165000,38.450543,-121.432538 7420 ALIX PKWY,SACRAMENTO,95823,CA,4,1,1955,Residential,Wed May 21 00:00:00 EDT 2008,166357,38.489405,-121.452811 3820 NATOMA WAY,SACRAMENTO,95838,CA,4,2,1656,Residential,Wed May 21 00:00:00 EDT 2008,166357,38.636748,-121.422159 4431 GREEN TREE DR,SACRAMENTO,95823,CA,3,2,1477,Residential,Wed May 21 00:00:00 EDT 2008,168000,38.499954,-121.454469 9417 SARA ST,ELK GROVE,95624,CA,3,2,1188,Residential,Wed May 21 00:00:00 EDT 2008,170000,38.415518,-121.370527 8299 HALBRITE WAY,SACRAMENTO,95828,CA,4,2,1590,Residential,Wed May 21 00:00:00 EDT 2008,173000,38.473814,-121.4 7223 KALLIE KAY LN,SACRAMENTO,95823,CA,3,2,1463,Residential,Wed May 21 00:00:00 EDT 2008,174250,38.477553,-121.419463 8156 STEINBECK WAY,SACRAMENTO,95828,CA,4,2,1714,Residential,Wed May 21 00:00:00 EDT 2008,174313,38.474853,-121.406326 7957 VALLEY GREEN DR,SACRAMENTO,95823,CA,3,2,1185,Residential,Wed May 21 00:00:00 EDT 2008,178480,38.465184,-121.434925 1122 WILD POPPY CT,GALT,95632,CA,3,2,1406,Residential,Wed May 21 00:00:00 EDT 2008,178760,38.287789,-121.294715 4520 BOMARK WAY,SACRAMENTO,95842,CA,4,2,1943,Multi-Family,Wed May 21 00:00:00 EDT 2008,179580,38.665724,-121.358576 9012 KIEFER BLVD,SACRAMENTO,95826,CA,3,2,1172,Residential,Wed May 21 00:00:00 EDT 2008,181000,38.547011,-121.366217 5332 SANDSTONE ST,CARMICHAEL,95608,CA,3,1,1152,Residential,Wed May 21 00:00:00 EDT 2008,181872,38.662105,-121.313945 5993 SAWYER CIR,SACRAMENTO,95823,CA,4,3,1851,Residential,Wed May 21 00:00:00 EDT 2008,182587,38.4473,-121.435218 4844 CLYDEBANK WAY,ANTELOPE,95843,CA,3,2,1215,Residential,Wed May 21 00:00:00 EDT 2008,182716,38.714609,-121.347887 306 CAMELLIA WAY,GALT,95632,CA,3,2,1130,Residential,Wed May 21 00:00:00 EDT 2008,182750,38.260443,-121.297864 9021 MADISON AVE,ORANGEVALE,95662,CA,4,2,1603,Residential,Wed May 21 00:00:00 EDT 2008,183200,38.664186,-121.217511 404 6TH ST,GALT,95632,CA,3,1,1479,Residential,Wed May 21 00:00:00 EDT 2008,188741,38.251808,-121.302493 8317 SUNNY CREEK WAY,SACRAMENTO,95823,CA,3,2,1420,Residential,Wed May 21 00:00:00 EDT 2008,189000,38.459041,-121.424644 2617 BASS CT,SACRAMENTO,95826,CA,3,2,1280,Residential,Wed May 21 00:00:00 EDT 2008,192067,38.560767,-121.377471 7005 TIANT WAY,ELK GROVE,95758,CA,3,2,1586,Residential,Wed May 21 00:00:00 EDT 2008,194000,38.422811,-121.423285 7895 CABER WAY,ANTELOPE,95843,CA,3,2,1362,Residential,Wed May 21 00:00:00 EDT 2008,194818,38.711279,-121.393449 7624 BOGEY CT,SACRAMENTO,95828,CA,4,4,2162,Multi-Family,Wed May 21 00:00:00 EDT 2008,195000,38.48009,-121.415102 6930 HAMPTON COVE WAY,SACRAMENTO,95823,CA,3,2,1266,Residential,Wed May 21 00:00:00 EDT 2008,198000,38.44004,-121.421012 8708 MESA BROOK WAY,ELK GROVE,95624,CA,4,2,1715,Residential,Wed May 21 00:00:00 EDT 2008,199500,38.44076,-121.385792 120 GRANT LN,FOLSOM,95630,CA,3,2,1820,Residential,Wed May 21 00:00:00 EDT 2008,200000,38.687742,-121.17104 5907 ELLERSLEE DR,CARMICHAEL,95608,CA,3,1,936,Residential,Wed May 21 00:00:00 EDT 2008,200000,38.664468,-121.32683 17 SERASPI CT,SACRAMENTO,95834,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,206000,38.631481,-121.50188 170 PENHOW CIR,SACRAMENTO,95834,CA,3,2,1511,Residential,Wed May 21 00:00:00 EDT 2008,208000,38.653439,-121.535169 8345 STAR THISTLE WAY,SACRAMENTO,95823,CA,4,2,1590,Residential,Wed May 21 00:00:00 EDT 2008,212864,38.454349,-121.439239 9080 FRESCA WAY,ELK GROVE,95758,CA,4,2,1596,Residential,Wed May 21 00:00:00 EDT 2008,221000,38.427818,-121.424026 391 NATALINO CIR,SACRAMENTO,95835,CA,2,2,1341,Residential,Wed May 21 00:00:00 EDT 2008,221000,38.67307,-121.506373 8373 BLACKMAN WAY,ELK GROVE,95624,CA,5,3,2136,Residential,Wed May 21 00:00:00 EDT 2008,223058,38.435436,-121.394536 9837 CORTE DORADO CT,ELK GROVE,95624,CA,4,2,1616,Residential,Wed May 21 00:00:00 EDT 2008,227887,38.400676,-121.38101 5037 J PKWY,SACRAMENTO,95823,CA,3,2,1478,Residential,Wed May 21 00:00:00 EDT 2008,231477,38.491399,-121.443547 10245 LOS PALOS DR,RANCHO CORDOVA,95670,CA,3,2,1287,Residential,Wed May 21 00:00:00 EDT 2008,234697,38.593699,-121.31089 6613 NAVION DR,CITRUS HEIGHTS,95621,CA,4,2,1277,Residential,Wed May 21 00:00:00 EDT 2008,235000,38.702855,-121.31308 2887 AZEVEDO DR,SACRAMENTO,95833,CA,4,2,1448,Residential,Wed May 21 00:00:00 EDT 2008,236000,38.618457,-121.509439 9186 KINBRACE CT,SACRAMENTO,95829,CA,4,3,2235,Residential,Wed May 21 00:00:00 EDT 2008,236685,38.463355,-121.358936 4243 MIDDLEBURY WAY,MATHER,95655,CA,3,2,2093,Residential,Wed May 21 00:00:00 EDT 2008,237800,38.547991,-121.280483 1028 FALLON PLACE CT,RIO LINDA,95673,CA,3,2,1193,Residential,Wed May 21 00:00:00 EDT 2008,240122,38.693818,-121.441153 4804 NORIKER DR,ELK GROVE,95757,CA,3,2,2163,Residential,Wed May 21 00:00:00 EDT 2008,242638,38.400974,-121.448424 7713 HARVEST WOODS DR,SACRAMENTO,95828,CA,3,2,1269,Residential,Wed May 21 00:00:00 EDT 2008,244000,38.478198,-121.412911 2866 KARITSA AVE,SACRAMENTO,95833,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,244500,38.626671,-121.52597 6913 RICHEVE WAY,SACRAMENTO,95828,CA,3,1,958,Residential,Wed May 21 00:00:00 EDT 2008,244960,38.502519,-121.420769 8636 TEGEA WAY,ELK GROVE,95624,CA,5,3,2508,Residential,Wed May 21 00:00:00 EDT 2008,245918,38.443832,-121.382087 5448 MAIDSTONE WAY,CITRUS HEIGHTS,95621,CA,3,2,1305,Residential,Wed May 21 00:00:00 EDT 2008,250000,38.665395,-121.293288 18 OLLIE CT,ELK GROVE,95758,CA,4,2,1591,Residential,Wed May 21 00:00:00 EDT 2008,250000,38.444909,-121.412345 4010 ALEX LN,CARMICHAEL,95608,CA,2,2,1326,Condo,Wed May 21 00:00:00 EDT 2008,250134,38.637028,-121.312963 4901 MILLNER WAY,ELK GROVE,95757,CA,3,2,1843,Residential,Wed May 21 00:00:00 EDT 2008,254200,38.38692,-121.447349 4818 BRITTNEY LEE CT,SACRAMENTO,95841,CA,4,2,1921,Residential,Wed May 21 00:00:00 EDT 2008,254200,38.653917,-121.34218 5529 LAGUNA PARK DR,ELK GROVE,95758,CA,5,3,2790,Residential,Wed May 21 00:00:00 EDT 2008,258000,38.42568,-121.438062 230 CANDELA CIR,SACRAMENTO,95835,CA,3,2,1541,Residential,Wed May 21 00:00:00 EDT 2008,260000,38.656251,-121.547572 4900 71ST ST,SACRAMENTO,95820,CA,3,1,1018,Residential,Wed May 21 00:00:00 EDT 2008,260014,38.53151,-121.421089 12209 CONSERVANCY WAY,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,263500,38.553867,-121.219141 4236 NATOMAS CENTRAL DR,SACRAMENTO,95834,CA,3,2,1672,Condo,Wed May 21 00:00:00 EDT 2008,265000,38.648879,-121.544023 5615 LUPIN LN,POLLOCK PINES,95726,CA,3,2,1380,Residential,Wed May 21 00:00:00 EDT 2008,265000,38.708315,-120.603872 5625 JAMES WAY,SACRAMENTO,95822,CA,3,1,975,Residential,Wed May 21 00:00:00 EDT 2008,271742,38.523947,-121.484946 7842 LAHONTAN CT,SACRAMENTO,95829,CA,4,3,2372,Residential,Wed May 21 00:00:00 EDT 2008,273750,38.472976,-121.318633 6850 21ST ST,SACRAMENTO,95822,CA,3,2,1446,Residential,Wed May 21 00:00:00 EDT 2008,275086,38.502194,-121.490795 2900 BLAIR RD,POLLOCK PINES,95726,CA,2,2,1284,Residential,Wed May 21 00:00:00 EDT 2008,280908,38.75485,-120.60476 2064 EXPEDITION WAY,SACRAMENTO,95832,CA,4,3,3009,Residential,Wed May 21 00:00:00 EDT 2008,280987,38.474099,-121.490711 2912 NORCADE CIR,SACRAMENTO,95826,CA,8,4,3612,Multi-Family,Wed May 21 00:00:00 EDT 2008,282400,38.559505,-121.364839 9507 SEA CLIFF WAY,ELK GROVE,95758,CA,4,2,2056,Residential,Wed May 21 00:00:00 EDT 2008,285000,38.410992,-121.479043 8882 AUTUMN GOLD CT,ELK GROVE,95624,CA,4,2,1993,Residential,Wed May 21 00:00:00 EDT 2008,287417,38.4439,-121.37255 5322 WHITE LOTUS WAY,ELK GROVE,95757,CA,3,2,1857,Residential,Wed May 21 00:00:00 EDT 2008,291000,38.391538,-121.442596 1838 CASTRO WAY,SACRAMENTO,95818,CA,2,1,1126,Residential,Wed May 21 00:00:00 EDT 2008,292024,38.556098,-121.490787 10158 CRAWFORD WAY,SACRAMENTO,95827,CA,4,4,2213,Multi-Family,Wed May 21 00:00:00 EDT 2008,297000,38.5703,-121.315735 7731 MASTERS ST,ELK GROVE,95758,CA,5,3,2494,Residential,Wed May 21 00:00:00 EDT 2008,297000,38.442031,-121.410873 4925 PERCHERON DR,ELK GROVE,95757,CA,3,2,1843,Residential,Wed May 21 00:00:00 EDT 2008,298000,38.40154,-121.447649 2010 PROMONTORY POINT LN,GOLD RIVER,95670,CA,2,2,1520,Residential,Wed May 21 00:00:00 EDT 2008,299000,38.62869,-121.261669 4727 SAVOIE WAY,SACRAMENTO,95835,CA,5,3,2800,Residential,Wed May 21 00:00:00 EDT 2008,304037,38.658182,-121.549521 8664 MAGNOLIA HILL WAY,ELK GROVE,95624,CA,4,2,2309,Residential,Wed May 21 00:00:00 EDT 2008,311000,38.442352,-121.389675 9570 HARVEST ROSE WAY,SACRAMENTO,95827,CA,5,3,2367,Residential,Wed May 21 00:00:00 EDT 2008,315537,38.555993,-121.340352 4359 CREGAN CT,RANCHO CORDOVA,95742,CA,5,4,3516,Residential,Wed May 21 00:00:00 EDT 2008,320000,38.545128,-121.224922 5337 DUSTY ROSE WAY,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,320000,38.528575,-121.2286 8929 SUTTERS GOLD DR,SACRAMENTO,95826,CA,4,3,1914,Residential,Wed May 21 00:00:00 EDT 2008,328360,38.550848,-121.370224 8025 PEERLESS AVE,ORANGEVALE,95662,CA,2,1,1690,Residential,Wed May 21 00:00:00 EDT 2008,334150,38.71147,-121.216214 4620 WELERA WAY,ELK GROVE,95757,CA,3,3,2725,Residential,Wed May 21 00:00:00 EDT 2008,335750,38.398609,-121.450148 9723 TERRAPIN CT,ELK GROVE,95757,CA,4,3,2354,Residential,Wed May 21 00:00:00 EDT 2008,335750,38.403492,-121.430224 2115 SMOKESTACK WAY,SACRAMENTO,95833,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,339500,38.602416,-121.542965 100 REBECCA WAY,FOLSOM,95630,CA,3,2,2185,Residential,Wed May 21 00:00:00 EDT 2008,344250,38.68479,-121.149199 9488 OAK VILLAGE WAY,ELK GROVE,95758,CA,4,2,1801,Residential,Wed May 21 00:00:00 EDT 2008,346210,38.41333,-121.404999 8495 DARTFORD DR,SACRAMENTO,95823,CA,3,3,1961,Residential,Wed May 21 00:00:00 EDT 2008,347029,38.448507,-121.421346 6708 PONTA DO SOL WAY,ELK GROVE,95757,CA,4,2,3134,Residential,Wed May 21 00:00:00 EDT 2008,347650,38.380635,-121.425538 4143 SEA MEADOW WAY,SACRAMENTO,95823,CA,4,3,1915,Residential,Wed May 21 00:00:00 EDT 2008,351300,38.46534,-121.457519 3020 RICHARDSON CIR,EL DORADO HILLS,95762,CA,3,2,0,Residential,Wed May 21 00:00:00 EDT 2008,352000,38.691299,-121.081752 8082 LINDA ISLE LN,SACRAMENTO,95831,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,370000,38.4772,-121.5215 15300 MURIETA SOUTH PKWY,RANCHO MURIETA,95683,CA,4,3,2734,Residential,Wed May 21 00:00:00 EDT 2008,370500,38.4874,-121.075129 11215 SHARRMONT CT,WILTON,95693,CA,3,2,2110,Residential,Wed May 21 00:00:00 EDT 2008,372000,38.35062,-121.228349 7105 DANBERG WAY,ELK GROVE,95757,CA,5,3,3164,Residential,Wed May 21 00:00:00 EDT 2008,375000,38.4019,-121.420388 5579 JERRY LITELL WAY,SACRAMENTO,95835,CA,5,3,3599,Residential,Wed May 21 00:00:00 EDT 2008,381300,38.677126,-121.500519 1050 FOXHALL WAY,SACRAMENTO,95831,CA,4,2,2054,Residential,Wed May 21 00:00:00 EDT 2008,381942,38.509819,-121.519661 7837 ABBINGTON WAY,ANTELOPE,95843,CA,4,2,1830,Residential,Wed May 21 00:00:00 EDT 2008,387731,38.709873,-121.339472 1300 F ST,SACRAMENTO,95814,CA,3,3,1627,Residential,Wed May 21 00:00:00 EDT 2008,391000,38.58355,-121.487289 6801 RAWLEY WAY,ELK GROVE,95757,CA,4,3,3440,Residential,Wed May 21 00:00:00 EDT 2008,394470,38.408351,-121.423925 1693 SHELTER COVE DR,GREENWOOD,95635,CA,3,2,2846,Residential,Wed May 21 00:00:00 EDT 2008,395000,38.945357,-120.908822 9361 WADDELL LN,ELK GROVE,95624,CA,4,3,2359,Residential,Wed May 21 00:00:00 EDT 2008,400186,38.450829,-121.349928 10 SEA FOAM CT,SACRAMENTO,95831,CA,3,3,2052,Residential,Wed May 21 00:00:00 EDT 2008,415000,38.487885,-121.545947 6945 RIO TEJO WAY,ELK GROVE,95757,CA,5,3,3433,Residential,Wed May 21 00:00:00 EDT 2008,425000,38.385638,-121.422616 4186 TULIP PARK WAY,RANCHO CORDOVA,95742,CA,5,3,3615,Residential,Wed May 21 00:00:00 EDT 2008,430000,38.550617,-121.23526 9278 DAIRY CT,ELK GROVE,95624,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,445000,38.420338,-121.363757 207 ORANGE BLOSSOM CIR Unit C,FOLSOM,95630,CA,5,3,2687,Residential,Wed May 21 00:00:00 EDT 2008,460000,38.646273,-121.175322 6507 RIO DE ONAR WAY,ELK GROVE,95757,CA,4,3,2724,Residential,Wed May 21 00:00:00 EDT 2008,461000,38.38253,-121.428007 7004 RAWLEY WAY,ELK GROVE,95757,CA,4,3,3440,Residential,Wed May 21 00:00:00 EDT 2008,489332,38.406421,-121.422081 6503 RIO DE ONAR WAY,ELK GROVE,95757,CA,5,4,3508,Residential,Wed May 21 00:00:00 EDT 2008,510000,38.38253,-121.428038 2217 APPALOOSA CT,FOLSOM,95630,CA,4,2,2462,Residential,Wed May 21 00:00:00 EDT 2008,539000,38.655167,-121.090178 868 HILDEBRAND CIR,FOLSOM,95630,CA,0,0,0,Residential,Wed May 21 00:00:00 EDT 2008,585000,38.670947,-121.097727 6030 PALERMO WAY,EL DORADO HILLS,95762,CA,4,3,0,Residential,Wed May 21 00:00:00 EDT 2008,600000,38.672761,-121.050378 4070 REDONDO DR,EL DORADO HILLS,95762,CA,4,3,0,Residential,Wed May 21 00:00:00 EDT 2008,606238,38.666807,-121.06483 4004 CRESTA WAY,SACRAMENTO,95864,CA,3,3,2325,Residential,Wed May 21 00:00:00 EDT 2008,660000,38.591618,-121.370626 315 JUMEL CT,EL DORADO HILLS,95762,CA,6,5,0,Residential,Wed May 21 00:00:00 EDT 2008,830000,38.669931,-121.05958 6272 LONGFORD DR Unit 1,CITRUS HEIGHTS,95621,CA,2,1,795,Condo,Tue May 20 00:00:00 EDT 2008,69000,38.680923,-121.313945 3432 Y ST,SACRAMENTO,95817,CA,4,2,1099,Residential,Tue May 20 00:00:00 EDT 2008,70000,38.554967,-121.468046 9512 EMERALD PARK DR Unit 3,ELK GROVE,95624,CA,2,1,840,Condo,Tue May 20 00:00:00 EDT 2008,71000,38.40573,-121.369832 3132 CLAY ST,SACRAMENTO,95815,CA,2,1,800,Residential,Tue May 20 00:00:00 EDT 2008,78000,38.624678,-121.439203 5221 38TH AVE,SACRAMENTO,95824,CA,2,1,746,Residential,Tue May 20 00:00:00 EDT 2008,78400,38.518044,-121.443555 6112 HERMOSA ST,SACRAMENTO,95822,CA,3,1,1067,Residential,Tue May 20 00:00:00 EDT 2008,80000,38.515125,-121.480416 483 ARCADE BLVD,SACRAMENTO,95815,CA,4,2,1316,Residential,Tue May 20 00:00:00 EDT 2008,89000,38.623571,-121.454884 671 SONOMA AVE,SACRAMENTO,95815,CA,3,1,1337,Residential,Tue May 20 00:00:00 EDT 2008,90000,38.622953,-121.450142 5980 79TH ST,SACRAMENTO,95824,CA,2,1,868,Residential,Tue May 20 00:00:00 EDT 2008,90000,38.518373,-121.411779 7607 ELDER CREEK RD,SACRAMENTO,95824,CA,3,1,924,Residential,Tue May 20 00:00:00 EDT 2008,92000,38.51055,-121.414768 5028 14TH AVE,SACRAMENTO,95820,CA,2,1,610,Residential,Tue May 20 00:00:00 EDT 2008,93675,38.53942,-121.446894 14788 NATCHEZ CT,RANCHO MURIETA,95683,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,97750,38.492287,-121.100032 1069 ACACIA AVE,SACRAMENTO,95815,CA,2,1,1220,Residential,Tue May 20 00:00:00 EDT 2008,98000,38.621998,-121.442238 5201 LAGUNA OAKS DR Unit 199,ELK GROVE,95758,CA,1,1,722,Condo,Tue May 20 00:00:00 EDT 2008,98000,38.423251,-121.444489 3847 LAS PASAS WAY,SACRAMENTO,95864,CA,3,1,1643,Residential,Tue May 20 00:00:00 EDT 2008,99000,38.588672,-121.373916 5201 LAGUNA OAKS DR Unit 172,ELK GROVE,95758,CA,1,1,722,Condo,Tue May 20 00:00:00 EDT 2008,100000,38.423251,-121.444489 1121 CREEKSIDE WAY,GALT,95632,CA,3,1,1080,Residential,Tue May 20 00:00:00 EDT 2008,106716,38.241514,-121.312199 5307 CABRILLO WAY,SACRAMENTO,95820,CA,3,1,1039,Residential,Tue May 20 00:00:00 EDT 2008,111000,38.52712,-121.435348 3725 DON JULIO BLVD,NORTH HIGHLANDS,95660,CA,3,1,1051,Residential,Tue May 20 00:00:00 EDT 2008,111000,38.67895,-121.379406 4803 MCCLOUD DR,SACRAMENTO,95842,CA,2,2,967,Residential,Tue May 20 00:00:00 EDT 2008,114800,38.682279,-121.352817 10542 SILVERWOOD WAY,RANCHO CORDOVA,95670,CA,3,1,1098,Residential,Tue May 20 00:00:00 EDT 2008,120108,38.587156,-121.295778 6318 39TH AVE,SACRAMENTO,95824,CA,3,1,1050,Residential,Tue May 20 00:00:00 EDT 2008,123225,38.518942,-121.430158 211 MCDANIEL CIR,SACRAMENTO,95838,CA,3,2,1110,Residential,Tue May 20 00:00:00 EDT 2008,123750,38.636565,-121.460383 3800 LYNHURST WAY,NORTH HIGHLANDS,95660,CA,3,1,888,Residential,Tue May 20 00:00:00 EDT 2008,125000,38.650445,-121.374861 6139 HERMOSA ST,SACRAMENTO,95822,CA,3,2,1120,Residential,Tue May 20 00:00:00 EDT 2008,125000,38.514665,-121.480411 2505 RHINE WAY,ELVERTA,95626,CA,3,2,1080,Residential,Tue May 20 00:00:00 EDT 2008,126000,38.717976,-121.407684 3692 PAYNE WAY,NORTH HIGHLANDS,95660,CA,3,1,957,Residential,Tue May 20 00:00:00 EDT 2008,129000,38.66654,-121.378298 604 MORRISON AVE,SACRAMENTO,95838,CA,2,1,952,Residential,Tue May 20 00:00:00 EDT 2008,134000,38.637678,-121.452476 648 SANTA ANA AVE,SACRAMENTO,95838,CA,3,2,1211,Residential,Tue May 20 00:00:00 EDT 2008,135000,38.658478,-121.450409 14 ASHLEY OAKS CT,SACRAMENTO,95815,CA,3,2,1264,Residential,Tue May 20 00:00:00 EDT 2008,135500,38.61779,-121.436765 3174 NORTHVIEW DR,SACRAMENTO,95833,CA,3,1,1080,Residential,Tue May 20 00:00:00 EDT 2008,140000,38.623817,-121.477827 840 TRANQUIL LN,GALT,95632,CA,3,2,1266,Residential,Tue May 20 00:00:00 EDT 2008,140000,38.270617,-121.299205 5333 PRIMROSE DR Unit 19A,FAIR OAKS,95628,CA,2,2,994,Condo,Tue May 20 00:00:00 EDT 2008,142500,38.662785,-121.276272 1035 MILLET WAY,SACRAMENTO,95834,CA,3,2,1202,Residential,Tue May 20 00:00:00 EDT 2008,143500,38.631056,-121.48508 5201 LAGUNA OAKS DR Unit 126,ELK GROVE,95758,CA,0,0,0,Condo,Tue May 20 00:00:00 EDT 2008,145000,38.423251,-121.444489 3328 22ND AVE,SACRAMENTO,95820,CA,2,1,722,Residential,Tue May 20 00:00:00 EDT 2008,145000,38.532727,-121.470783 8001 HARTWICK WAY,SACRAMENTO,95828,CA,4,2,1448,Residential,Tue May 20 00:00:00 EDT 2008,145000,38.488623,-121.410582 7812 HARTWICK WAY,SACRAMENTO,95828,CA,3,2,1188,Residential,Tue May 20 00:00:00 EDT 2008,145000,38.488611,-121.412808 4207 PAINTER WAY,NORTH HIGHLANDS,95660,CA,4,2,1183,Residential,Tue May 20 00:00:00 EDT 2008,146000,38.692915,-121.367497 7458 WINKLEY WAY,SACRAMENTO,95822,CA,3,1,1320,Residential,Tue May 20 00:00:00 EDT 2008,148500,38.487444,-121.491366 8354 SUNRISE WOODS WAY,SACRAMENTO,95828,CA,3,2,1117,Residential,Tue May 20 00:00:00 EDT 2008,149000,38.473288,-121.3963 8116 COTTONMILL CIR,SACRAMENTO,95828,CA,3,2,1364,Residential,Tue May 20 00:00:00 EDT 2008,150000,38.482876,-121.405912 4660 CEDARWOOD WAY,SACRAMENTO,95823,CA,4,2,1310,Residential,Tue May 20 00:00:00 EDT 2008,150000,38.484834,-121.449316 9254 HARROGATE WAY,ELK GROVE,95758,CA,2,2,1006,Residential,Tue May 20 00:00:00 EDT 2008,152000,38.420138,-121.412179 6716 TAREYTON WAY,CITRUS HEIGHTS,95621,CA,3,2,1104,Residential,Tue May 20 00:00:00 EDT 2008,156000,38.693724,-121.307169 2028 ROBERT WAY,SACRAMENTO,95825,CA,2,1,810,Residential,Tue May 20 00:00:00 EDT 2008,156000,38.609982,-121.419263 9346 AIZENBERG CIR,ELK GROVE,95624,CA,2,2,1123,Residential,Tue May 20 00:00:00 EDT 2008,156000,38.41875,-121.370019 4524 LOCH HAVEN WAY,SACRAMENTO,95842,CA,2,1,904,Residential,Tue May 20 00:00:00 EDT 2008,157788,38.67273,-121.359645 7140 BLUE SPRINGS WAY,CITRUS HEIGHTS,95621,CA,3,2,1156,Residential,Tue May 20 00:00:00 EDT 2008,161653,38.720653,-121.302241 4631 11TH AVE,SACRAMENTO,95820,CA,2,1,1321,Residential,Tue May 20 00:00:00 EDT 2008,161829,38.541965,-121.452132 3228 BAGGAN CT,ANTELOPE,95843,CA,3,2,1392,Residential,Tue May 20 00:00:00 EDT 2008,165000,38.715346,-121.388163 8515 DARTFORD DR,SACRAMENTO,95823,CA,3,2,1439,Residential,Tue May 20 00:00:00 EDT 2008,168000,38.448288,-121.420719 4500 TIPPWOOD WAY,SACRAMENTO,95842,CA,3,2,1159,Residential,Tue May 20 00:00:00 EDT 2008,169000,38.69951,-121.359989 2460 EL ROCCO WAY,RANCHO CORDOVA,95670,CA,3,2,1671,Residential,Tue May 20 00:00:00 EDT 2008,175000,38.591477,-121.31534 8244 SUNBIRD WAY,SACRAMENTO,95823,CA,3,2,1740,Residential,Tue May 20 00:00:00 EDT 2008,176250,38.457654,-121.431381 5841 VALLEY VALE WAY,SACRAMENTO,95823,CA,3,2,1265,Residential,Tue May 20 00:00:00 EDT 2008,179000,38.461283,-121.434322 7863 CRESTLEIGH CT,ANTELOPE,95843,CA,2,2,1007,Residential,Tue May 20 00:00:00 EDT 2008,180000,38.710889,-121.358876 7129 SPRINGMONT DR,ELK GROVE,95758,CA,3,2,1716,Residential,Tue May 20 00:00:00 EDT 2008,180400,38.417649,-121.420294 8284 RED FOX WAY,ELK GROVE,95758,CA,4,2,1685,Residential,Tue May 20 00:00:00 EDT 2008,182000,38.417182,-121.397231 2219 EL CANTO CIR,RANCHO CORDOVA,95670,CA,4,2,1829,Residential,Tue May 20 00:00:00 EDT 2008,184500,38.592383,-121.318669 8907 GEMWOOD WAY,ELK GROVE,95758,CA,3,2,1555,Residential,Tue May 20 00:00:00 EDT 2008,185000,38.435471,-121.441173 5925 MALEVILLE AVE,CARMICHAEL,95608,CA,4,2,1120,Residential,Tue May 20 00:00:00 EDT 2008,189000,38.666564,-121.325717 7031 CANEVALLEY CIR,CITRUS HEIGHTS,95621,CA,3,2,1137,Residential,Tue May 20 00:00:00 EDT 2008,194000,38.718693,-121.303619 3949 WILDROSE WAY,SACRAMENTO,95826,CA,3,1,1174,Residential,Tue May 20 00:00:00 EDT 2008,195000,38.543697,-121.366683 4437 MITCHUM CT,ANTELOPE,95843,CA,3,2,1393,Residential,Tue May 20 00:00:00 EDT 2008,200000,38.704407,-121.36113 2778 KAWEAH CT,CAMERON PARK,95682,CA,3,1,0,Residential,Tue May 20 00:00:00 EDT 2008,201000,38.694052,-120.995589 1636 ALLENWOOD CIR,LINCOLN,95648,CA,4,2,0,Residential,Tue May 20 00:00:00 EDT 2008,202500,38.879192,-121.309477 8151 QUAIL RIDGE CT,SACRAMENTO,95828,CA,3,2,1289,Residential,Tue May 20 00:00:00 EDT 2008,205000,38.461296,-121.390858 4899 WIND CREEK DR,SACRAMENTO,95838,CA,4,2,1799,Residential,Tue May 20 00:00:00 EDT 2008,205000,38.655887,-121.446119 2370 BIG CANYON CREEK RD,PLACERVILLE,95667,CA,3,2,0,Residential,Tue May 20 00:00:00 EDT 2008,205000,38.74458,-120.794254 6049 HAMBURG WAY,SACRAMENTO,95823,CA,4,3,1953,Residential,Tue May 20 00:00:00 EDT 2008,205000,38.443253,-121.431992 4232 71ST ST,SACRAMENTO,95820,CA,2,1,723,Residential,Tue May 20 00:00:00 EDT 2008,207000,38.536741,-121.42115 3361 BOW MAR CT,CAMERON PARK,95682,CA,2,2,0,Residential,Tue May 20 00:00:00 EDT 2008,210000,38.69437,-120.996602 1889 COLD SPRINGS RD,PLACERVILLE,95667,CA,2,1,948,Residential,Tue May 20 00:00:00 EDT 2008,211500,38.739774,-120.860243 5805 HIMALAYA WAY,CITRUS HEIGHTS,95621,CA,4,2,1578,Residential,Tue May 20 00:00:00 EDT 2008,215000,38.696489,-121.328555 7944 SYLVAN OAK WAY,CITRUS HEIGHTS,95610,CA,3,2,1317,Residential,Tue May 20 00:00:00 EDT 2008,215000,38.710388,-121.261096 3139 SPOONWOOD WAY Unit 1,SACRAMENTO,95833,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,215500,38.626582,-121.52151 6217 LEOLA WAY,SACRAMENTO,95824,CA,3,1,1360,Residential,Tue May 20 00:00:00 EDT 2008,222381,38.513066,-121.451909 2340 HURLEY WAY,SACRAMENTO,95825,CA,0,0,0,Condo,Tue May 20 00:00:00 EDT 2008,225000,38.588816,-121.408549 3035 BRUNNET LN,SACRAMENTO,95833,CA,3,2,1522,Residential,Tue May 20 00:00:00 EDT 2008,225000,38.624762,-121.522775 3025 EL PRADO WAY,SACRAMENTO,95825,CA,4,2,1751,Residential,Tue May 20 00:00:00 EDT 2008,225000,38.606603,-121.394147 9387 GRANITE FALLS CT,ELK GROVE,95624,CA,3,2,1465,Residential,Tue May 20 00:00:00 EDT 2008,225000,38.419214,-121.348533 9257 CALDERA WAY,SACRAMENTO,95826,CA,4,2,1605,Residential,Tue May 20 00:00:00 EDT 2008,228000,38.55821,-121.355022 441 ARLINGDALE CIR,RIO LINDA,95673,CA,4,2,1475,Residential,Tue May 20 00:00:00 EDT 2008,229665,38.702893,-121.454949 2284 LOS ROBLES RD,MEADOW VISTA,95722,CA,3,1,1216,Residential,Tue May 20 00:00:00 EDT 2008,230000,39.008159,-121.03623 8164 CHENIN BLANC LN,FAIR OAKS,95628,CA,2,2,1315,Residential,Tue May 20 00:00:00 EDT 2008,230000,38.665644,-121.259969 4620 CHAMBERLIN CIR,ELK GROVE,95757,CA,3,2,1567,Residential,Tue May 20 00:00:00 EDT 2008,230000,38.390557,-121.449805 5340 BIRK WAY,SACRAMENTO,95835,CA,3,2,1776,Residential,Tue May 20 00:00:00 EDT 2008,234000,38.672495,-121.515251 51 ANJOU CIR,SACRAMENTO,95835,CA,3,2,2187,Residential,Tue May 20 00:00:00 EDT 2008,235000,38.661658,-121.540633 2125 22ND AVE,SACRAMENTO,95822,CA,3,1,1291,Residential,Tue May 20 00:00:00 EDT 2008,236250,38.534596,-121.493121 611 BLOSSOM ROCK LN,FOLSOM,95630,CA,0,0,0,Condo,Tue May 20 00:00:00 EDT 2008,240000,38.6457,-121.1197 8830 ADUR RD,ELK GROVE,95624,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,242000,38.43742,-121.372876 7344 BUTTERBALL WAY,SACRAMENTO,95842,CA,3,2,1503,Residential,Tue May 20 00:00:00 EDT 2008,245000,38.699489,-121.361828 8219 GWINHURST CIR,SACRAMENTO,95828,CA,4,3,2491,Residential,Tue May 20 00:00:00 EDT 2008,245000,38.459711,-121.384283 3240 S ST,SACRAMENTO,95816,CA,2,1,1269,Residential,Tue May 20 00:00:00 EDT 2008,245000,38.562296,-121.467489 221 PICASSO CIR,SACRAMENTO,95835,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,250000,38.676658,-121.528128 5706 GREENACRES WAY,ORANGEVALE,95662,CA,3,2,1176,Residential,Tue May 20 00:00:00 EDT 2008,250000,38.669882,-121.213533 6900 LONICERA DR,ORANGEVALE,95662,CA,4,2,1456,Residential,Tue May 20 00:00:00 EDT 2008,250000,38.692199,-121.250975 419 DAWNRIDGE RD,ROSEVILLE,95678,CA,3,2,1498,Residential,Tue May 20 00:00:00 EDT 2008,250000,38.725283,-121.297953 5312 MARBURY WAY,ANTELOPE,95843,CA,3,2,1574,Residential,Tue May 20 00:00:00 EDT 2008,255000,38.710221,-121.341651 6344 BONHAM CIR,CITRUS HEIGHTS,95610,CA,5,4,2085,Multi-Family,Tue May 20 00:00:00 EDT 2008,256054,38.682358,-121.272876 8207 YORKTON WAY,SACRAMENTO,95829,CA,3,2,2170,Residential,Tue May 20 00:00:00 EDT 2008,257729,38.45967,-121.360461 7922 MANSELL WAY,ELK GROVE,95758,CA,4,2,1595,Residential,Tue May 20 00:00:00 EDT 2008,260000,38.409634,-121.410787 5712 MELBURY CIR,ANTELOPE,95843,CA,3,2,1567,Residential,Tue May 20 00:00:00 EDT 2008,261000,38.705849,-121.334701 632 NEWBRIDGE LN,LINCOLN,95648,CA,4,2,0,Residential,Tue May 20 00:00:00 EDT 2008,261800,38.879084,-121.298586 1570 GLIDDEN AVE,SACRAMENTO,95822,CA,4,2,1253,Residential,Tue May 20 00:00:00 EDT 2008,264469,38.482704,-121.500433 8108 FILIFERA WAY,ANTELOPE,95843,CA,4,3,1768,Residential,Tue May 20 00:00:00 EDT 2008,265000,38.717042,-121.35468 230 BANKSIDE WAY,SACRAMENTO,95835,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,270000,38.676937,-121.529244 5342 CALABRIA WAY,SACRAMENTO,95835,CA,4,3,2030,Residential,Tue May 20 00:00:00 EDT 2008,270000,38.671807,-121.498274 47 NAPONEE CT,SACRAMENTO,95835,CA,3,2,1531,Residential,Tue May 20 00:00:00 EDT 2008,270000,38.665704,-121.529096 4236 ADRIATIC SEA WAY,SACRAMENTO,95834,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,270000,38.647961,-121.543162 8864 REMBRANT CT,ELK GROVE,95624,CA,4,3,1653,Residential,Tue May 20 00:00:00 EDT 2008,275000,38.435288,-121.375703 9455 SEA CLIFF WAY,ELK GROVE,95758,CA,4,2,2056,Residential,Tue May 20 00:00:00 EDT 2008,275000,38.411522,-121.481406 9720 LITTLE HARBOR WAY,ELK GROVE,95624,CA,4,3,2494,Residential,Tue May 20 00:00:00 EDT 2008,280000,38.404934,-121.352405 8806 PHOENIX AVE,FAIR OAKS,95628,CA,3,2,1450,Residential,Tue May 20 00:00:00 EDT 2008,286013,38.660322,-121.230101 3578 LOGGERHEAD WAY,SACRAMENTO,95834,CA,4,2,2169,Residential,Tue May 20 00:00:00 EDT 2008,292000,38.633028,-121.526755 1416 LOCKHART WAY,ROSEVILLE,95747,CA,3,2,1440,Residential,Tue May 20 00:00:00 EDT 2008,292000,38.752399,-121.330328 5413 BUENA VENTURA WAY,FAIR OAKS,95628,CA,3,2,1527,Residential,Tue May 20 00:00:00 EDT 2008,293993,38.664552,-121.255937 37 WHITE BIRCH CT,ROSEVILLE,95678,CA,3,2,1401,Residential,Tue May 20 00:00:00 EDT 2008,294000,38.776327,-121.284514 405 MARLIN SPIKE WAY,SACRAMENTO,95838,CA,3,2,1411,Residential,Tue May 20 00:00:00 EDT 2008,296769,38.65783,-121.456842 1102 CHESLEY LN,LINCOLN,95648,CA,4,4,0,Residential,Tue May 20 00:00:00 EDT 2008,297500,38.864864,-121.313988 11281 STANFORD COURT LN Unit 604,GOLD RIVER,95670,CA,0,0,0,Condo,Tue May 20 00:00:00 EDT 2008,300000,38.625289,-121.260286 7320 6TH ST,RIO LINDA,95673,CA,3,1,1284,Residential,Tue May 20 00:00:00 EDT 2008,300000,38.700553,-121.452223 993 MANTON CT,GALT,95632,CA,4,3,2307,Residential,Tue May 20 00:00:00 EDT 2008,300000,38.272942,-121.289148 4487 PANORAMA DR,PLACERVILLE,95667,CA,3,2,1329,Residential,Tue May 20 00:00:00 EDT 2008,300000,38.694559,-120.848157 5651 OVERLEAF WAY,SACRAMENTO,95835,CA,4,2,1910,Residential,Tue May 20 00:00:00 EDT 2008,300500,38.677454,-121.494791 2015 PROMONTORY POINT LN,GOLD RIVER,95670,CA,3,2,1981,Residential,Tue May 20 00:00:00 EDT 2008,305000,38.628732,-121.261149 3224 PARKHAM DR,ROSEVILLE,95747,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,306500,38.772771,-121.364877 15 VANESSA PL,SACRAMENTO,95835,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,312500,38.668692,-121.54549 1312 RENISON LN,LINCOLN,95648,CA,5,3,0,Residential,Tue May 20 00:00:00 EDT 2008,315000,38.866409,-121.308485 8 RIVER RAFT CT,SACRAMENTO,95823,CA,4,2,2205,Residential,Tue May 20 00:00:00 EDT 2008,319789,38.447353,-121.434969 2251 LAMPLIGHT LN,LINCOLN,95648,CA,2,2,1449,Residential,Tue May 20 00:00:00 EDT 2008,330000,38.849924,-121.275729 106 FARHAM DR,FOLSOM,95630,CA,3,2,1258,Residential,Tue May 20 00:00:00 EDT 2008,330000,38.667834,-121.168578 5405 NECTAR CIR,ELK GROVE,95757,CA,3,2,2575,Residential,Tue May 20 00:00:00 EDT 2008,331000,38.387014,-121.440967 5411 10TH AVE,SACRAMENTO,95820,CA,2,1,539,Residential,Tue May 20 00:00:00 EDT 2008,334000,38.542727,-121.442449 3512 RAINSONG CIR,RANCHO CORDOVA,95670,CA,4,3,2208,Residential,Tue May 20 00:00:00 EDT 2008,336000,38.573488,-121.282809 1106 55TH ST,SACRAMENTO,95819,CA,3,1,1108,Residential,Tue May 20 00:00:00 EDT 2008,339000,38.563805,-121.436395 411 ILLSLEY WAY,FOLSOM,95630,CA,4,2,1595,Residential,Tue May 20 00:00:00 EDT 2008,339000,38.652002,-121.129504 796 BUTTERCUP CIR,GALT,95632,CA,4,2,2159,Residential,Tue May 20 00:00:00 EDT 2008,345000,38.279581,-121.300828 1230 SANDRA CIR,PLACERVILLE,95667,CA,4,3,2295,Residential,Tue May 20 00:00:00 EDT 2008,350000,38.738141,-120.784145 318 ANACAPA DR,ROSEVILLE,95678,CA,3,2,1838,Residential,Tue May 20 00:00:00 EDT 2008,356000,38.782094,-121.297133 3975 SHINING STAR DR,SACRAMENTO,95823,CA,4,2,1900,Residential,Tue May 20 00:00:00 EDT 2008,361745,38.487409,-121.461413 1620 BASLER ST,SACRAMENTO,95811,CA,4,2,1718,Residential,Tue May 20 00:00:00 EDT 2008,361948,38.591822,-121.478644 9688 NATURE TRAIL WAY,ELK GROVE,95757,CA,5,3,3389,Residential,Tue May 20 00:00:00 EDT 2008,370000,38.405224,-121.479275 5924 TANUS CIR,ROCKLIN,95677,CA,4,2,0,Residential,Tue May 20 00:00:00 EDT 2008,380000,38.778691,-121.204292 9629 CEDAR OAK WAY,ELK GROVE,95757,CA,5,4,3260,Residential,Tue May 20 00:00:00 EDT 2008,385000,38.405527,-121.431746 3429 FERNBROOK CT,CAMERON PARK,95682,CA,3,2,2016,Residential,Tue May 20 00:00:00 EDT 2008,399000,38.664225,-121.007173 2121 HANNAH WAY,ROCKLIN,95765,CA,4,2,2607,Residential,Tue May 20 00:00:00 EDT 2008,402000,38.805749,-121.280931 10104 ANNIE ST,ELK GROVE,95757,CA,4,3,2724,Residential,Tue May 20 00:00:00 EDT 2008,406026,38.390465,-121.443479 1092 MAUGHAM CT,GALT,95632,CA,5,4,3746,Residential,Tue May 20 00:00:00 EDT 2008,420000,38.271646,-121.286848 5404 ALMOND FALLS WAY,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,425000,38.527502,-121.233492 6306 CONEJO,RANCHO MURIETA,95683,CA,4,2,3192,Residential,Tue May 20 00:00:00 EDT 2008,425000,38.512602,-121.087233 14 CASA VATONI PL,SACRAMENTO,95834,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,433500,38.650221,-121.551704 1456 EAGLESFIELD LN,LINCOLN,95648,CA,4,3,0,Residential,Tue May 20 00:00:00 EDT 2008,436746,38.857635,-121.311375 4100 BOTHWELL CIR,EL DORADO HILLS,95762,CA,5,3,0,Residential,Tue May 20 00:00:00 EDT 2008,438700,38.679136,-121.034329 427 21ST ST,SACRAMENTO,95811,CA,2,1,1247,Residential,Tue May 20 00:00:00 EDT 2008,445000,38.582604,-121.47576 1044 GALSTON DR,FOLSOM,95630,CA,4,2,2581,Residential,Tue May 20 00:00:00 EDT 2008,450000,38.676306,-121.09954 4440 SYCAMORE AVE,SACRAMENTO,95841,CA,3,1,2068,Residential,Tue May 20 00:00:00 EDT 2008,460000,38.646374,-121.353658 1032 SOUZA DR,EL DORADO HILLS,95762,CA,3,2,0,Residential,Tue May 20 00:00:00 EDT 2008,460000,38.668239,-121.064437 9760 LAZULITE CT,ELK GROVE,95624,CA,4,3,3992,Residential,Tue May 20 00:00:00 EDT 2008,460000,38.403609,-121.335541 241 LANFRANCO CIR,SACRAMENTO,95835,CA,4,4,3397,Residential,Tue May 20 00:00:00 EDT 2008,465000,38.665696,-121.549437 5559 NORTHBOROUGH DR,SACRAMENTO,95835,CA,5,3,3881,Residential,Tue May 20 00:00:00 EDT 2008,471750,38.677225,-121.519687 2125 BIG SKY DR,ROCKLIN,95765,CA,5,3,0,Residential,Tue May 20 00:00:00 EDT 2008,480000,38.801637,-121.278798 2109 HAMLET PL,CARMICHAEL,95608,CA,2,2,1598,Residential,Tue May 20 00:00:00 EDT 2008,484000,38.602754,-121.329326 9970 STATE HIGHWAY 193,PLACERVILLE,95667,CA,4,3,1929,Residential,Tue May 20 00:00:00 EDT 2008,485000,38.787877,-120.816676 2901 PINTAIL WAY,ELK GROVE,95757,CA,4,3,3070,Residential,Tue May 20 00:00:00 EDT 2008,495000,38.398488,-121.473424 201 FIRESTONE DR,ROSEVILLE,95678,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,500500,38.770153,-121.300039 1740 HIGH ST,AUBURN,95603,CA,3,3,0,Residential,Tue May 20 00:00:00 EDT 2008,504000,38.891935,-121.08434 2733 DANA LOOP,EL DORADO HILLS,95762,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,541000,38.628459,-121.055078 9741 SADDLEBRED CT,WILTON,95693,CA,0,0,0,Residential,Tue May 20 00:00:00 EDT 2008,560000,38.408841,-121.198039 7756 TIGERWOODS DR,SACRAMENTO,95829,CA,5,3,3984,Residential,Tue May 20 00:00:00 EDT 2008,572500,38.47643,-121.309243 5709 RIVER OAK WAY,CARMICHAEL,95608,CA,4,2,2222,Residential,Tue May 20 00:00:00 EDT 2008,582000,38.602461,-121.330979 2981 WRINGER DR,ROSEVILLE,95661,CA,4,3,3838,Residential,Tue May 20 00:00:00 EDT 2008,613401,38.735373,-121.227072 8616 ROCKPORTE CT,ROSEVILLE,95747,CA,4,2,0,Residential,Tue May 20 00:00:00 EDT 2008,614000,38.742118,-121.359909 4128 HILL ST,FAIR OAKS,95628,CA,5,5,2846,Residential,Tue May 20 00:00:00 EDT 2008,680000,38.64167,-121.262099 1409 47TH ST,SACRAMENTO,95819,CA,5,2,2484,Residential,Tue May 20 00:00:00 EDT 2008,699000,38.563244,-121.446876 3935 EL MONTE DR,LOOMIS,95650,CA,4,4,1624,Residential,Tue May 20 00:00:00 EDT 2008,839000,38.813337,-121.133348 5840 WALERGA RD,SACRAMENTO,95842,CA,2,1,840,Condo,Mon May 19 00:00:00 EDT 2008,40000,38.673678,-121.357471 923 FULTON AVE,SACRAMENTO,95825,CA,1,1,484,Condo,Mon May 19 00:00:00 EDT 2008,48000,38.582279,-121.401482 261 REDONDO AVE,SACRAMENTO,95815,CA,3,1,970,Residential,Mon May 19 00:00:00 EDT 2008,61500,38.620685,-121.460539 4030 BROADWAY,SACRAMENTO,95817,CA,2,1,623,Residential,Mon May 19 00:00:00 EDT 2008,62050,38.546798,-121.460038 3660 22ND AVE,SACRAMENTO,95820,CA,2,1,932,Residential,Mon May 19 00:00:00 EDT 2008,65000,38.532718,-121.46747 3924 HIGH ST,SACRAMENTO,95838,CA,2,1,796,Residential,Mon May 19 00:00:00 EDT 2008,65000,38.638797,-121.435049 4734 14TH AVE,SACRAMENTO,95820,CA,2,1,834,Residential,Mon May 19 00:00:00 EDT 2008,68000,38.539447,-121.450858 4734 14TH AVE,SACRAMENTO,95820,CA,2,1,834,Residential,Mon May 19 00:00:00 EDT 2008,68000,38.539447,-121.450858 5050 RHODE ISLAND DR Unit 4,SACRAMENTO,95841,CA,2,1,924,Condo,Mon May 19 00:00:00 EDT 2008,77000,38.658739,-121.333561 4513 GREENHOLME DR,SACRAMENTO,95842,CA,2,1,795,Condo,Mon May 19 00:00:00 EDT 2008,82732,38.669104,-121.359008 3845 ELM ST,SACRAMENTO,95838,CA,3,1,1250,Residential,Mon May 19 00:00:00 EDT 2008,84000,38.637337,-121.432835 3908 17TH AVE,SACRAMENTO,95820,CA,2,1,984,Residential,Mon May 19 00:00:00 EDT 2008,84675,38.53728,-121.463531 7109 CHANDLER DR,SACRAMENTO,95828,CA,3,1,1013,Residential,Mon May 19 00:00:00 EDT 2008,85000,38.497237,-121.424187 7541 SKELTON WAY,SACRAMENTO,95822,CA,3,1,1012,Residential,Mon May 19 00:00:00 EDT 2008,90000,38.484274,-121.488851 9058 MONTOYA ST,SACRAMENTO,95826,CA,2,1,795,Condo,Mon May 19 00:00:00 EDT 2008,90000,38.559144,-121.368387 1016 CONGRESS AVE,SACRAMENTO,95838,CA,2,2,918,Residential,Mon May 19 00:00:00 EDT 2008,91000,38.630151,-121.442789 540 MORRISON AVE,SACRAMENTO,95838,CA,3,1,1082,Residential,Mon May 19 00:00:00 EDT 2008,95000,38.637704,-121.453946 5303 JERRETT WAY,SACRAMENTO,95842,CA,2,1,964,Residential,Mon May 19 00:00:00 EDT 2008,97500,38.663282,-121.359631 2820 DEL PASO BLVD,SACRAMENTO,95815,CA,4,2,1404,Multi-Family,Mon May 19 00:00:00 EDT 2008,100000,38.617718,-121.440089 3715 TALLYHO DR Unit 78HIGH,SACRAMENTO,95826,CA,1,1,625,Condo,Mon May 19 00:00:00 EDT 2008,100000,38.544627,-121.35796 6013 ROWAN WAY,CITRUS HEIGHTS,95621,CA,2,1,888,Residential,Mon May 19 00:00:00 EDT 2008,101000,38.675893,-121.2963 2987 PONDEROSA LN,SACRAMENTO,95815,CA,4,2,1120,Residential,Mon May 19 00:00:00 EDT 2008,102750,38.622243,-121.457863 3732 LANKERSHIM WAY,NORTH HIGHLANDS,95660,CA,3,1,1331,Residential,Mon May 19 00:00:00 EDT 2008,112500,38.68972,-121.378399 2216 DUNLAP DR,SACRAMENTO,95821,CA,3,1,1014,Residential,Mon May 19 00:00:00 EDT 2008,113000,38.623738,-121.41305 3503 21ST AVE,SACRAMENTO,95820,CA,4,2,1448,Residential,Mon May 19 00:00:00 EDT 2008,114000,38.53361,-121.469308 523 EXCHANGE ST,SACRAMENTO,95838,CA,3,1,966,Residential,Mon May 19 00:00:00 EDT 2008,114000,38.659414,-121.45408 8101 PORT ROYALE WAY,SACRAMENTO,95823,CA,2,1,779,Residential,Mon May 19 00:00:00 EDT 2008,114750,38.463929,-121.438667 8020 WALERGA RD,ANTELOPE,95843,CA,2,2,836,Condo,Mon May 19 00:00:00 EDT 2008,115000,38.71607,-121.364468 167 VALLEY OAK DR,ROSEVILLE,95678,CA,2,2,1100,Condo,Mon May 19 00:00:00 EDT 2008,115000,38.732429,-121.288069 7876 BURLINGTON WAY,SACRAMENTO,95832,CA,3,1,1174,Residential,Mon May 19 00:00:00 EDT 2008,116100,38.470093,-121.468347 3726 JONKO AVE,NORTH HIGHLANDS,95660,CA,3,2,1207,Residential,Mon May 19 00:00:00 EDT 2008,119250,38.656131,-121.377265 7342 GIGI PL,SACRAMENTO,95828,CA,4,4,1995,Multi-Family,Mon May 19 00:00:00 EDT 2008,120000,38.490704,-121.410176 2610 PHYLLIS AVE,SACRAMENTO,95820,CA,2,1,804,Residential,Mon May 19 00:00:00 EDT 2008,120000,38.53105,-121.479574 4200 COMMERCE WAY Unit 711,SACRAMENTO,95834,CA,2,2,958,Condo,Mon May 19 00:00:00 EDT 2008,120000,38.647523,-121.523217 4621 COUNTRY SCENE WAY,SACRAMENTO,95823,CA,3,2,1366,Residential,Mon May 19 00:00:00 EDT 2008,120108,38.470187,-121.448149 5380 VILLAGE WOOD DR,SACRAMENTO,95823,CA,2,2,901,Residential,Mon May 19 00:00:00 EDT 2008,121500,38.454949,-121.440578 2621 EVERGREEN ST,SACRAMENTO,95815,CA,3,1,696,Residential,Mon May 19 00:00:00 EDT 2008,121725,38.613103,-121.444085 201 CARLO CT,GALT,95632,CA,3,2,1080,Residential,Mon May 19 00:00:00 EDT 2008,122000,38.24227,-121.31032 6743 21ST ST,SACRAMENTO,95822,CA,3,2,1104,Residential,Mon May 19 00:00:00 EDT 2008,123000,38.50372,-121.490657 3128 VIA GRANDE,SACRAMENTO,95825,CA,2,1,972,Residential,Mon May 19 00:00:00 EDT 2008,125000,38.598321,-121.39161 2847 BELGRADE WAY,SACRAMENTO,95833,CA,4,2,1390,Residential,Mon May 19 00:00:00 EDT 2008,125573,38.617173,-121.482541 7741 MILLDALE CIR,ELVERTA,95626,CA,4,2,1354,Residential,Mon May 19 00:00:00 EDT 2008,126714,38.705834,-121.43919 9013 CASALS ST,SACRAMENTO,95826,CA,2,1,795,Condo,Mon May 19 00:00:00 EDT 2008,126960,38.557045,-121.37167 227 MAHAN CT Unit 1,ROSEVILLE,95678,CA,2,1,780,Condo,Mon May 19 00:00:00 EDT 2008,127000,38.749723,-121.27008 7349 FLETCHER FARM DR,SACRAMENTO,95828,CA,4,2,1587,Residential,Mon May 19 00:00:00 EDT 2008,127500,38.49069,-121.382619 7226 LARCHMONT DR,NORTH HIGHLANDS,95660,CA,3,2,1209,Residential,Mon May 19 00:00:00 EDT 2008,130000,38.699269,-121.376334 4114 35TH AVE,SACRAMENTO,95824,CA,2,1,1139,Residential,Mon May 19 00:00:00 EDT 2008,133105,38.520941,-121.459355 617 M ST,RIO LINDA,95673,CA,2,2,1690,Residential,Mon May 19 00:00:00 EDT 2008,136500,38.691104,-121.451832 7032 FAIR OAKS BLVD,CARMICHAEL,95608,CA,3,2,1245,Condo,Mon May 19 00:00:00 EDT 2008,139500,38.628563,-121.328297 2421 SANTINA WAY,ELVERTA,95626,CA,3,2,1416,Residential,Mon May 19 00:00:00 EDT 2008,140000,38.71865,-121.407763 2368 CRAIG AVE,SACRAMENTO,95832,CA,3,2,1300,Residential,Mon May 19 00:00:00 EDT 2008,140800,38.47807,-121.48114 2123 AMANDA WAY,SACRAMENTO,95822,CA,3,2,1120,Residential,Mon May 19 00:00:00 EDT 2008,145000,38.484896,-121.486948 7620 DARLA WAY,SACRAMENTO,95828,CA,4,2,1590,Residential,Mon May 19 00:00:00 EDT 2008,147000,38.478502,-121.403517 8344 FIELDPOPPY CIR,SACRAMENTO,95828,CA,3,2,1407,Residential,Mon May 19 00:00:00 EDT 2008,149600,38.479083,-121.400702 3624 20TH AVE,SACRAMENTO,95820,CA,5,2,1516,Residential,Mon May 19 00:00:00 EDT 2008,150000,38.534508,-121.467907 10001 WOODCREEK OAKS BLVD Unit 1415,ROSEVILLE,95747,CA,2,2,0,Condo,Mon May 19 00:00:00 EDT 2008,150000,38.795529,-121.328819 2848 PROVO WAY,SACRAMENTO,95822,CA,3,2,1646,Residential,Mon May 19 00:00:00 EDT 2008,150000,38.489759,-121.474754 6045 EHRHARDT AVE,SACRAMENTO,95823,CA,3,2,1676,Residential,Mon May 19 00:00:00 EDT 2008,155000,38.457157,-121.433065 1223 LAMBERTON CIR,SACRAMENTO,95838,CA,3,2,1370,Residential,Mon May 19 00:00:00 EDT 2008,155435,38.646677,-121.437573 1223 LAMBERTON CIR,SACRAMENTO,95838,CA,3,2,1370,Residential,Mon May 19 00:00:00 EDT 2008,155500,38.646677,-121.437573 6000 BIRCHGLADE WAY,CITRUS HEIGHTS,95621,CA,4,2,1351,Residential,Mon May 19 00:00:00 EDT 2008,158000,38.70166,-121.323249 7204 THOMAS DR,NORTH HIGHLANDS,95660,CA,3,2,1152,Residential,Mon May 19 00:00:00 EDT 2008,158000,38.697898,-121.377687 8363 LANGTREE WAY,SACRAMENTO,95823,CA,3,2,1452,Residential,Mon May 19 00:00:00 EDT 2008,160000,38.45356,-121.435959 1675 VERNON ST Unit 8,ROSEVILLE,95678,CA,2,1,990,Residential,Mon May 19 00:00:00 EDT 2008,160000,38.734136,-121.299639 6632 IBEX WOODS CT,CITRUS HEIGHTS,95621,CA,2,2,1162,Residential,Mon May 19 00:00:00 EDT 2008,164000,38.720868,-121.309855 117 EVCAR WAY,RIO LINDA,95673,CA,3,2,1182,Residential,Mon May 19 00:00:00 EDT 2008,164000,38.687659,-121.4633 6485 LAGUNA MIRAGE LN,ELK GROVE,95758,CA,2,2,1112,Residential,Mon May 19 00:00:00 EDT 2008,165000,38.42465,-121.430137 746 MOOSE CREEK WAY,GALT,95632,CA,3,2,1100,Residential,Mon May 19 00:00:00 EDT 2008,167000,38.283085,-121.302071 8306 CURLEW CT,CITRUS HEIGHTS,95621,CA,4,2,1280,Residential,Mon May 19 00:00:00 EDT 2008,167293,38.715781,-121.298519 8306 CURLEW CT,CITRUS HEIGHTS,95621,CA,4,2,1280,Residential,Mon May 19 00:00:00 EDT 2008,167293,38.715781,-121.298519 5217 ARGO WAY,SACRAMENTO,95820,CA,3,1,1039,Residential,Mon May 19 00:00:00 EDT 2008,168000,38.52774,-121.433669 7108 HEATHER TREE DR,SACRAMENTO,95842,CA,3,2,1159,Residential,Mon May 19 00:00:00 EDT 2008,170000,38.695677,-121.36022 2956 DAVENPORT WAY,SACRAMENTO,95833,CA,4,2,1917,Residential,Mon May 19 00:00:00 EDT 2008,170000,38.620687,-121.482619 10062 LINCOLN VILLAGE DR,SACRAMENTO,95827,CA,3,2,1520,Residential,Mon May 19 00:00:00 EDT 2008,170000,38.564,-121.320023 332 PALIN AVE,GALT,95632,CA,3,2,1204,Residential,Mon May 19 00:00:00 EDT 2008,174000,38.260467,-121.302636 4649 FREEWAY CIR,SACRAMENTO,95841,CA,3,2,1120,Residential,Mon May 19 00:00:00 EDT 2008,178000,38.658734,-121.357196 8593 DERLIN WAY,SACRAMENTO,95823,CA,3,2,1436,Residential,Mon May 19 00:00:00 EDT 2008,180000,38.447585,-121.426627 9273 PREMIER WAY,SACRAMENTO,95826,CA,3,2,1451,Residential,Mon May 19 00:00:00 EDT 2008,180000,38.55992,-121.352539 8032 DUSENBERG CT,SACRAMENTO,95828,CA,4,2,1638,Residential,Mon May 19 00:00:00 EDT 2008,180000,38.466499,-121.381119 7110 STELLA LN Unit 15,CARMICHAEL,95608,CA,2,2,1000,Condo,Mon May 19 00:00:00 EDT 2008,182000,38.637396,-121.300055 1786 PIEDMONT WAY,ROSEVILLE,95661,CA,3,1,1152,Residential,Mon May 19 00:00:00 EDT 2008,188325,38.72748,-121.256537 1347 HIDALGO CIR,ROSEVILLE,95747,CA,3,2,1154,Residential,Mon May 19 00:00:00 EDT 2008,191500,38.747878,-121.311279 212 CAPPUCINO WAY,SACRAMENTO,95838,CA,3,2,1353,Residential,Mon May 19 00:00:00 EDT 2008,192000,38.657811,-121.465327 5938 WOODBRIAR WAY,CITRUS HEIGHTS,95621,CA,3,2,1329,Residential,Mon May 19 00:00:00 EDT 2008,192700,38.706152,-121.325399 3801 WILDROSE WAY,SACRAMENTO,95826,CA,3,1,1356,Residential,Mon May 19 00:00:00 EDT 2008,195000,38.544368,-121.369979 508 SAMUEL WAY,SACRAMENTO,95838,CA,3,2,1505,Residential,Mon May 19 00:00:00 EDT 2008,197654,38.645689,-121.452766 6128 CARL SANDBURG CIR,SACRAMENTO,95842,CA,3,1,1009,Residential,Mon May 19 00:00:00 EDT 2008,198000,38.681541,-121.355616 1 KENNELFORD CIR,SACRAMENTO,95823,CA,3,2,1144,Residential,Mon May 19 00:00:00 EDT 2008,200345,38.46452,-121.427606 909 SINGINGWOOD RD,SACRAMENTO,95864,CA,2,1,930,Residential,Mon May 19 00:00:00 EDT 2008,203000,38.581471,-121.38839 6671 FOXWOOD CT,SACRAMENTO,95841,CA,4,2,1766,Residential,Mon May 19 00:00:00 EDT 2008,207000,38.687943,-121.328883 8165 AYN RAND CT,SACRAMENTO,95828,CA,4,3,1940,Residential,Mon May 19 00:00:00 EDT 2008,208000,38.468639,-121.403265 9474 VILLAGE TREE DR,ELK GROVE,95758,CA,4,2,1776,Residential,Mon May 19 00:00:00 EDT 2008,210000,38.413947,-121.408276 7213 CALVIN DR,CITRUS HEIGHTS,95621,CA,3,1,1258,Residential,Mon May 19 00:00:00 EDT 2008,212000,38.698154,-121.298375 8167 DERBY PARK CT,SACRAMENTO,95828,CA,4,2,1872,Residential,Mon May 19 00:00:00 EDT 2008,213675,38.460492,-121.373379 6344 LAGUNA MIRAGE LN,ELK GROVE,95758,CA,2,2,1112,Residential,Mon May 19 00:00:00 EDT 2008,213697,38.423963,-121.428875 2945 RED HAWK WAY,SACRAMENTO,95833,CA,4,2,1856,Residential,Mon May 19 00:00:00 EDT 2008,215000,38.619675,-121.496903 3228 I ST,SACRAMENTO,95816,CA,4,3,1939,Residential,Mon May 19 00:00:00 EDT 2008,215000,38.573844,-121.462839 308 ATKINSON ST,ROSEVILLE,95678,CA,3,1,998,Residential,Mon May 19 00:00:00 EDT 2008,215100,38.746794,-121.29971 624 HOVEY WAY,ROSEVILLE,95678,CA,3,2,1758,Residential,Mon May 19 00:00:00 EDT 2008,217500,38.756149,-121.306479 110 COPPER LEAF WAY,SACRAMENTO,95838,CA,3,2,2142,Residential,Mon May 19 00:00:00 EDT 2008,218000,38.658466,-121.460661 7535 ALMA VISTA WAY,SACRAMENTO,95831,CA,2,1,950,Residential,Mon May 19 00:00:00 EDT 2008,220000,38.48403,-121.507641 7423 WILSALL CT,ELK GROVE,95758,CA,4,3,1739,Residential,Mon May 19 00:00:00 EDT 2008,221000,38.417026,-121.416821 8629 VIA ALTA WAY,ELK GROVE,95624,CA,3,2,1516,Residential,Mon May 19 00:00:00 EDT 2008,222900,38.398245,-121.380615 3318 DAVIDSON DR,ANTELOPE,95843,CA,3,1,988,Residential,Mon May 19 00:00:00 EDT 2008,223139,38.705753,-121.388917 913 COBDEN CT,GALT,95632,CA,4,2,1555,Residential,Mon May 19 00:00:00 EDT 2008,225500,38.282001,-121.295902 4419 79TH ST,SACRAMENTO,95820,CA,3,2,1212,Residential,Mon May 19 00:00:00 EDT 2008,228327,38.534827,-121.412545 3012 SPOONWOOD WAY,SACRAMENTO,95833,CA,4,2,1871,Residential,Mon May 19 00:00:00 EDT 2008,230000,38.62478,-121.523474 8728 CRYSTAL RIVER WAY,SACRAMENTO,95828,CA,3,2,1302,Residential,Mon May 19 00:00:00 EDT 2008,230000,38.47547,-121.380055 4709 AMBER LN Unit 1,SACRAMENTO,95841,CA,2,1,756,Condo,Mon May 19 00:00:00 EDT 2008,230522,38.657789,-121.354994 4508 OLD DAIRY DR,ANTELOPE,95843,CA,4,3,2026,Residential,Mon May 19 00:00:00 EDT 2008,231200,38.72286,-121.358939 312 RIVER ISLE WAY,SACRAMENTO,95831,CA,3,2,1375,Residential,Mon May 19 00:00:00 EDT 2008,232000,38.49026,-121.550527 301 OLIVADI WAY,SACRAMENTO,95834,CA,2,2,1250,Condo,Mon May 19 00:00:00 EDT 2008,232500,38.644406,-121.549049 5636 25TH ST,SACRAMENTO,95822,CA,3,1,1058,Residential,Mon May 19 00:00:00 EDT 2008,233641,38.523828,-121.481139 8721 SPRUCE RIDGE WAY,ANTELOPE,95843,CA,3,2,1187,Residential,Mon May 19 00:00:00 EDT 2008,234000,38.727657,-121.391028 7461 WINDBRIDGE DR,SACRAMENTO,95831,CA,2,2,1324,Residential,Mon May 19 00:00:00 EDT 2008,234500,38.48797,-121.530229 8101 LEMON COVE CT,SACRAMENTO,95828,CA,4,3,1936,Residential,Mon May 19 00:00:00 EDT 2008,235000,38.462981,-121.408288 10949 SCOTSMAN WAY,RANCHO CORDOVA,95670,CA,5,4,2382,Multi-Family,Mon May 19 00:00:00 EDT 2008,236000,38.603686,-121.277844 617 WILLOW CREEK DR,FOLSOM,95630,CA,3,2,1427,Residential,Mon May 19 00:00:00 EDT 2008,236073,38.679626,-121.142609 3301 PARK DR Unit 1914,SACRAMENTO,95835,CA,3,2,1678,Condo,Mon May 19 00:00:00 EDT 2008,238000,38.665296,-121.531993 709 CIMMARON CT,GALT,95632,CA,4,2,1798,Residential,Mon May 19 00:00:00 EDT 2008,238861,38.277177,-121.303747 3305 RIO ROCA CT,ANTELOPE,95843,CA,4,3,2652,Residential,Mon May 19 00:00:00 EDT 2008,239700,38.725079,-121.387698 9080 BEDROCK CT,SACRAMENTO,95829,CA,4,2,1816,Residential,Mon May 19 00:00:00 EDT 2008,240000,38.456939,-121.362965 100 TOURMALINE CIR,SACRAMENTO,95834,CA,5,3,3076,Residential,Mon May 19 00:00:00 EDT 2008,240000,38.63437,-121.510779 6411 RED BIRCH WAY,ELK GROVE,95758,CA,4,2,1844,Residential,Mon May 19 00:00:00 EDT 2008,241000,38.43461,-121.429316 4867 LAGUNA DR,SACRAMENTO,95823,CA,3,2,1306,Residential,Mon May 19 00:00:00 EDT 2008,245000,38.46179,-121.445371 3662 RIVER DR,SACRAMENTO,95833,CA,4,3,2447,Residential,Mon May 19 00:00:00 EDT 2008,246000,38.604969,-121.54255 6943 WOLFGRAM WAY,SACRAMENTO,95828,CA,4,2,1176,Residential,Mon May 19 00:00:00 EDT 2008,247234,38.489215,-121.419546 77 RINETTI WAY,RIO LINDA,95673,CA,4,2,1182,Residential,Mon May 19 00:00:00 EDT 2008,247480,38.687021,-121.463151 1316 I ST,RIO LINDA,95673,CA,3,1,1160,Residential,Mon May 19 00:00:00 EDT 2008,249862,38.683674,-121.435204 2130 CATHERWOOD WAY,SACRAMENTO,95835,CA,3,2,1424,Residential,Mon May 19 00:00:00 EDT 2008,251000,38.675506,-121.510987 8304 JUGLANS DR,ORANGEVALE,95662,CA,4,2,1574,Residential,Mon May 19 00:00:00 EDT 2008,252155,38.691829,-121.249033 5308 MARBURY WAY,ANTELOPE,95843,CA,3,2,1830,Residential,Mon May 19 00:00:00 EDT 2008,254172,38.710221,-121.341707 9182 LAKEMONT DR,ELK GROVE,95624,CA,4,2,1724,Residential,Mon May 19 00:00:00 EDT 2008,258000,38.451353,-121.358776 2231 COUNTRY VILLA CT,AUBURN,95603,CA,2,2,1255,Condo,Mon May 19 00:00:00 EDT 2008,260000,38.931671,-121.097862 8491 CRYSTAL WALK CIR,ELK GROVE,95758,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,261000,38.416916,-121.407554 361 MAHONIA CIR,SACRAMENTO,95835,CA,4,3,2175,Residential,Mon May 19 00:00:00 EDT 2008,261000,38.676172,-121.509761 3427 LA CADENA WAY,SACRAMENTO,95835,CA,4,2,1904,Residential,Mon May 19 00:00:00 EDT 2008,261000,38.681194,-121.537351 955 BIG SUR CT,EL DORADO HILLS,95762,CA,4,2,1808,Residential,Mon May 19 00:00:00 EDT 2008,262500,38.664347,-121.076529 11826 DIONYSUS WAY,RANCHO CORDOVA,95742,CA,4,2,2711,Residential,Mon May 19 00:00:00 EDT 2008,266000,38.551046,-121.239411 5847 DEL CAMPO LN,CARMICHAEL,95608,CA,3,1,1713,Residential,Mon May 19 00:00:00 EDT 2008,266000,38.671995,-121.324339 5635 FOXVIEW WAY,ELK GROVE,95757,CA,3,2,1457,Residential,Mon May 19 00:00:00 EDT 2008,270000,38.395256,-121.438249 10372 VIA CINTA CT,ELK GROVE,95757,CA,4,3,2724,Residential,Mon May 19 00:00:00 EDT 2008,274425,38.380089,-121.428186 6286 LONETREE BLVD,ROCKLIN,95765,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,274500,38.805036,-121.293608 7744 SOUTHBREEZE DR,SACRAMENTO,95828,CA,3,2,1468,Residential,Mon May 19 00:00:00 EDT 2008,275336,38.476932,-121.378349 2242 ABLE WAY,SACRAMENTO,95835,CA,4,3,2550,Residential,Mon May 19 00:00:00 EDT 2008,277980,38.666074,-121.509743 1042 STARBROOK DR,GALT,95632,CA,4,2,1928,Residential,Mon May 19 00:00:00 EDT 2008,280000,38.285611,-121.293063 1219 G ST,SACRAMENTO,95814,CA,3,3,1922,Residential,Mon May 19 00:00:00 EDT 2008,284686,38.582818,-121.489096 6220 OPUS CT,CITRUS HEIGHTS,95621,CA,3,2,1343,Residential,Mon May 19 00:00:00 EDT 2008,284893,38.715853,-121.317095 5419 HAVENHURST CIR,ROCKLIN,95677,CA,3,2,1510,Residential,Mon May 19 00:00:00 EDT 2008,285000,38.786746,-121.209957 220 OLD AIRPORT RD,AUBURN,95603,CA,2,2,960,Multi-Family,Mon May 19 00:00:00 EDT 2008,285000,38.939802,-121.054575 4622 MEYER WAY,CARMICHAEL,95608,CA,4,2,1559,Residential,Mon May 19 00:00:00 EDT 2008,285000,38.64913,-121.310667 4885 SUMMIT VIEW DR,EL DORADO,95623,CA,3,2,1624,Residential,Mon May 19 00:00:00 EDT 2008,289000,38.673285,-120.879176 26 JEANROSS CT,SACRAMENTO,95832,CA,5,3,2992,Residential,Mon May 19 00:00:00 EDT 2008,295000,38.473162,-121.491085 4800 MAPLEPLAIN AVE,ELK GROVE,95758,CA,4,2,2109,Residential,Mon May 19 00:00:00 EDT 2008,296000,38.432848,-121.449237 10629 BASIE WAY,RANCHO CORDOVA,95670,CA,4,2,1524,Residential,Mon May 19 00:00:00 EDT 2008,296056,38.579,-121.292627 8612 WILLOW GROVE WAY,SACRAMENTO,95828,CA,3,2,1248,Residential,Mon May 19 00:00:00 EDT 2008,297359,38.464994,-121.386962 62 DE FER CIR,SACRAMENTO,95823,CA,4,2,1876,Residential,Mon May 19 00:00:00 EDT 2008,299940,38.49254,-121.463316 2513 OLD KENMARE RD,LINCOLN,95648,CA,5,3,0,Residential,Mon May 19 00:00:00 EDT 2008,304000,38.847396,-121.259586 3253 ABOTO WAY,RANCHO CORDOVA,95670,CA,4,3,1851,Residential,Mon May 19 00:00:00 EDT 2008,305000,38.57727,-121.285591 3072 VILLAGE PLAZA DR,ROSEVILLE,95747,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,307000,38.773094,-121.365905 251 CHANGO CIR,SACRAMENTO,95835,CA,4,2,2218,Residential,Mon May 19 00:00:00 EDT 2008,311328,38.68237,-121.539147 8205 WEYBURN CT,SACRAMENTO,95828,CA,3,2,1394,Residential,Mon May 19 00:00:00 EDT 2008,313138,38.47316,-121.403893 8788 LA MARGARITA WAY,SACRAMENTO,95828,CA,3,2,1410,Residential,Mon May 19 00:00:00 EDT 2008,316630,38.468185,-121.375694 5912 DEEPDALE WAY,ELK GROVE,95758,CA,5,3,3468,Residential,Mon May 19 00:00:00 EDT 2008,320000,38.439565,-121.436606 4712 PISMO BEACH DR,ANTELOPE,95843,CA,5,3,2346,Residential,Mon May 19 00:00:00 EDT 2008,320000,38.707705,-121.354153 4741 PACIFIC PARK DR,ANTELOPE,95843,CA,5,3,2347,Residential,Mon May 19 00:00:00 EDT 2008,325000,38.709299,-121.353056 310 GROTH CIR,SACRAMENTO,95834,CA,4,2,1659,Residential,Mon May 19 00:00:00 EDT 2008,328578,38.638764,-121.531827 6121 WILD FOX CT,ELK GROVE,95757,CA,3,3,2442,Residential,Mon May 19 00:00:00 EDT 2008,331000,38.406758,-121.431669 12241 CANYONLANDS DR,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,331500,38.557293,-121.217611 29 COOL FOUNTAIN CT,SACRAMENTO,95833,CA,4,2,2155,Residential,Mon May 19 00:00:00 EDT 2008,340000,38.606906,-121.54132 907 RIO ROBLES AVE,SACRAMENTO,95838,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,344755,38.664765,-121.445006 8909 BILLFISH WAY,SACRAMENTO,95828,CA,3,2,1810,Residential,Mon May 19 00:00:00 EDT 2008,345746,38.475433,-121.372584 6232 GUS WAY,ELK GROVE,95757,CA,4,2,2789,Residential,Mon May 19 00:00:00 EDT 2008,351000,38.388129,-121.43117 200 OAKWILDE ST,GALT,95632,CA,4,2,1606,Residential,Mon May 19 00:00:00 EDT 2008,353767,38.2535,-121.31812 1033 PARK STREAM DR,GALT,95632,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,355000,38.287785,-121.289903 200 ALLAIRE CIR,SACRAMENTO,95835,CA,4,2,2166,Residential,Mon May 19 00:00:00 EDT 2008,356035,38.68318,-121.53484 1322 SUTTER WALK,SACRAMENTO,95816,CA,0,0,0,Condo,Mon May 19 00:00:00 EDT 2008,360000,38.53805,-121.5047 5479 NICKMAN WAY,SACRAMENTO,95835,CA,4,2,1871,Residential,Mon May 19 00:00:00 EDT 2008,360552,38.672966,-121.502748 2103 BURBERRY WAY,SACRAMENTO,95835,CA,3,2,1800,Residential,Mon May 19 00:00:00 EDT 2008,362305,38.67342,-121.508542 2450 SAN JOSE WAY,SACRAMENTO,95817,CA,3,1,1683,Residential,Mon May 19 00:00:00 EDT 2008,365000,38.553596,-121.459483 7641 ROSEHALL DR,ROSEVILLE,95678,CA,3,2,0,Residential,Mon May 19 00:00:00 EDT 2008,367554,38.791617,-121.286147 1336 LAYSAN TEAL DR,ROSEVILLE,95747,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,368500,38.796121,-121.319963 2802 BLACK OAK DR,ROCKLIN,95765,CA,2,2,1596,Residential,Mon May 19 00:00:00 EDT 2008,370000,38.837006,-121.232024 2113 FALL TRAIL CT,PLACERVILLE,95667,CA,4,2,0,Residential,Mon May 19 00:00:00 EDT 2008,371086,38.733155,-120.748039 10112 LAMBEAU CT,ELK GROVE,95757,CA,3,2,1179,Residential,Mon May 19 00:00:00 EDT 2008,378000,38.390328,-121.448022 6313 CASTRO VERDE WAY,ELK GROVE,95757,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,383000,38.381102,-121.42901 3622 CURTIS DR,SACRAMENTO,95818,CA,3,1,1639,Residential,Mon May 19 00:00:00 EDT 2008,388000,38.541735,-121.480098 11817 OPAL RIDGE WAY,RANCHO CORDOVA,95742,CA,5,3,3281,Residential,Mon May 19 00:00:00 EDT 2008,395100,38.551083,-121.237476 170 LAGOMARSINO WAY,SACRAMENTO,95819,CA,3,2,1697,Residential,Mon May 19 00:00:00 EDT 2008,400000,38.574894,-121.435806 2743 DEAKIN PL,EL DORADO HILLS,95762,CA,3,2,0,Residential,Mon May 19 00:00:00 EDT 2008,400000,38.69288,-121.073551 3361 ALDER CANYON WAY,ANTELOPE,95843,CA,4,3,2085,Residential,Mon May 19 00:00:00 EDT 2008,408431,38.727649,-121.385656 2148 RANCH VIEW DR,ROCKLIN,95765,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,413000,38.837455,-121.289337 398 LINDLEY DR,SACRAMENTO,95815,CA,4,2,1744,Multi-Family,Mon May 19 00:00:00 EDT 2008,416767,38.622359,-121.457582 3013 BRIDLEWOOD DR,EL DORADO HILLS,95762,CA,4,3,0,Residential,Mon May 19 00:00:00 EDT 2008,420000,38.675519,-121.015862 169 BAURER CIR,FOLSOM,95630,CA,4,3,1939,Residential,Mon May 19 00:00:00 EDT 2008,423000,38.66695,-121.120729 2809 LOON CT,CAMERON PARK,95682,CA,4,2,0,Residential,Mon May 19 00:00:00 EDT 2008,423000,38.687072,-121.004729 1315 KONDOS AVE,SACRAMENTO,95814,CA,2,3,1788,Residential,Mon May 19 00:00:00 EDT 2008,427500,38.571943,-121.492106 4966 CHARTER RD,ROCKLIN,95765,CA,3,2,1691,Residential,Mon May 19 00:00:00 EDT 2008,430922,38.82553,-121.254698 9516 LAGUNA LAKE WAY,ELK GROVE,95758,CA,4,2,2002,Residential,Mon May 19 00:00:00 EDT 2008,445000,38.411258,-121.431348 5201 BLOSSOM RANCH DR,ELK GROVE,95757,CA,4,4,4303,Residential,Mon May 19 00:00:00 EDT 2008,450000,38.399436,-121.444041 3027 PALMATE WAY,SACRAMENTO,95834,CA,5,3,4246,Residential,Mon May 19 00:00:00 EDT 2008,452000,38.628955,-121.529269 500 WINCHESTER CT,ROSEVILLE,95661,CA,3,2,2274,Residential,Mon May 19 00:00:00 EDT 2008,470000,38.73988,-121.248929 5746 GELSTON WAY,EL DORADO HILLS,95762,CA,4,3,0,Residential,Mon May 19 00:00:00 EDT 2008,471000,38.677015,-121.034083 6935 ELM TREE LN,ORANGEVALE,95662,CA,4,4,3056,Residential,Mon May 19 00:00:00 EDT 2008,475000,38.693041,-121.23294 9605 GOLF COURSE LN,ELK GROVE,95758,CA,3,3,2503,Residential,Mon May 19 00:00:00 EDT 2008,484500,38.409689,-121.446059 719 BAYWOOD CT,EL DORADO HILLS,95762,CA,5,3,0,Residential,Mon May 19 00:00:00 EDT 2008,487500,38.647598,-121.077801 5954 TANUS CIR,ROCKLIN,95677,CA,3,3,0,Residential,Mon May 19 00:00:00 EDT 2008,488750,38.777585,-121.2036 100 CHELSEA CT,FOLSOM,95630,CA,3,2,1905,Residential,Mon May 19 00:00:00 EDT 2008,500000,38.69435,-121.177259 1500 ORANGE HILL LN,PENRYN,95663,CA,3,2,1320,Residential,Mon May 19 00:00:00 EDT 2008,506688,38.862708,-121.162092 408 KIRKWOOD CT,LINCOLN,95648,CA,2,2,0,Residential,Mon May 19 00:00:00 EDT 2008,512000,38.861615,-121.26869 1732 TUSCAN GROVE CIR,ROSEVILLE,95747,CA,5,3,0,Residential,Mon May 19 00:00:00 EDT 2008,520000,38.796683,-121.342555 2049 EMPIRE MINE CIR,GOLD RIVER,95670,CA,4,2,3037,Residential,Mon May 19 00:00:00 EDT 2008,528000,38.629299,-121.249021 9360 MAGOS RD,WILTON,95693,CA,5,2,3741,Residential,Mon May 19 00:00:00 EDT 2008,579093,38.416809,-121.240628 104 CATLIN CT,FOLSOM,95630,CA,4,3,2660,Residential,Mon May 19 00:00:00 EDT 2008,636000,38.684459,-121.145935 4734 GIBBONS DR,CARMICHAEL,95608,CA,4,3,3357,Residential,Mon May 19 00:00:00 EDT 2008,668365,38.63558,-121.353639 4629 DORCHESTER LN,GRANITE BAY,95746,CA,5,3,2896,Residential,Mon May 19 00:00:00 EDT 2008,676200,38.723545,-121.216025 2400 COUNTRYSIDE DR,PLACERVILLE,95667,CA,3,2,2025,Residential,Mon May 19 00:00:00 EDT 2008,677048,38.737452,-120.910963 12901 FURLONG DR,WILTON,95693,CA,5,3,3788,Residential,Mon May 19 00:00:00 EDT 2008,691659,38.413535,-121.188211 6222 CALLE MONTALVO CIR,GRANITE BAY,95746,CA,5,3,3670,Residential,Mon May 19 00:00:00 EDT 2008,760000,38.779435,-121.146676 20 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885327,-121.289412 24 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885132,-121.289405 28 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884936,-121.289397 32 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884741,-121.28939 36 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884599,-121.289406 40 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884535,-121.289619 44 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88459,-121.289835 48 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884667,-121.289896 52 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88478,-121.289911 68 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885236,-121.289928 72 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88535,-121.289926 76 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885464,-121.289922 80 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885578,-121.289919 84 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885692,-121.289915 88 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885806,-121.289911 92 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88592,-121.289908 96 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886024,-121.289859 100 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886091,-121.289744 434 1ST ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88653,-121.289406 3 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884692,-121.290288 11 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884879,-121.290257 19 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885017,-121.290262 27 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885173,-121.29027 35 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885328,-121.290275 43 E ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885483,-121.290277 51 E ST,LINCOLN,95648,CA,4,2,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885638,-121.290279 59 E ST,LINCOLN,95648,CA,3,2,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885794,-121.290281 75 E ST,LINCOLN,95648,CA,3,2,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886104,-121.290285 63 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885093,-121.289932 398 1ST ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88653,-121.288952 386 1ST ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886528,-121.288869 374 1ST ST,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886525,-121.288787 116 CRYSTALWOOD WAY,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886282,-121.289586 108 CRYSTALWOOD WAY,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886282,-121.289646 100 CRYSTALWOOD WAY,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886282,-121.289706 55 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884865,-121.289922 51 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884752,-121.289907 47 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884638,-121.289893 43 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884568,-121.289784 39 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884546,-121.289562 35 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884645,-121.289397 31 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.88479,-121.289392 27 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.884985,-121.289399 23 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885181,-121.289406 19 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885376,-121.289414 15 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885571,-121.289421 7 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885962,-121.289436 7 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.885962,-121.289436 3 CRYSTALWOOD CIR,LINCOLN,95648,CA,0,0,0,Residential,Mon May 19 00:00:00 EDT 2008,4897,38.886093,-121.289584 8208 WOODYARD WAY,CITRUS HEIGHTS,95621,CA,3,2,1166,Residential,Fri May 16 00:00:00 EDT 2008,30000,38.715322,-121.314787 113 RINETTI WAY,RIO LINDA,95673,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,30000,38.687172,-121.463933 15 LOORZ CT,SACRAMENTO,95823,CA,2,1,838,Residential,Fri May 16 00:00:00 EDT 2008,55422,38.471646,-121.435158 5805 DOTMAR WAY,NORTH HIGHLANDS,95660,CA,2,1,904,Residential,Fri May 16 00:00:00 EDT 2008,63000,38.672642,-121.380343 2332 CAMBRIDGE ST,SACRAMENTO,95815,CA,2,1,1032,Residential,Fri May 16 00:00:00 EDT 2008,65000,38.608085,-121.449651 3812 BELDEN ST,SACRAMENTO,95838,CA,2,1,904,Residential,Fri May 16 00:00:00 EDT 2008,65000,38.636833,-121.44164 3348 40TH ST,SACRAMENTO,95817,CA,2,1,1080,Residential,Fri May 16 00:00:00 EDT 2008,65000,38.544162,-121.460652 127 QUASAR CIR,SACRAMENTO,95822,CA,2,2,990,Residential,Fri May 16 00:00:00 EDT 2008,66500,38.493504,-121.475304 3812 CYPRESS ST,SACRAMENTO,95838,CA,2,1,900,Residential,Fri May 16 00:00:00 EDT 2008,71000,38.636877,-121.444948 5821 64TH ST,SACRAMENTO,95824,CA,2,1,861,Residential,Fri May 16 00:00:00 EDT 2008,75000,38.521202,-121.428146 8248 CENTER PKWY,SACRAMENTO,95823,CA,2,1,906,Condo,Fri May 16 00:00:00 EDT 2008,77000,38.459002,-121.428794 1171 SONOMA AVE,SACRAMENTO,95815,CA,2,1,1011,Residential,Fri May 16 00:00:00 EDT 2008,85000,38.6238,-121.439872 4250 ARDWELL WAY,SACRAMENTO,95823,CA,3,2,1089,Residential,Fri May 16 00:00:00 EDT 2008,95625,38.466938,-121.455631 3104 CLAY ST,SACRAMENTO,95815,CA,2,1,832,Residential,Fri May 16 00:00:00 EDT 2008,96140,38.62391,-121.439208 6063 LAND PARK DR,SACRAMENTO,95822,CA,2,1,800,Condo,Fri May 16 00:00:00 EDT 2008,104250,38.517029,-121.513809 4738 OAKHOLLOW DR,SACRAMENTO,95842,CA,4,2,1292,Residential,Fri May 16 00:00:00 EDT 2008,105000,38.679598,-121.356035 1401 STERLING ST,SACRAMENTO,95822,CA,2,1,810,Residential,Fri May 16 00:00:00 EDT 2008,108000,38.520319,-121.504727 3715 DIDCOT CIR,SACRAMENTO,95838,CA,4,2,1064,Residential,Fri May 16 00:00:00 EDT 2008,109000,38.635232,-121.460098 2426 RASHAWN DR,RANCHO CORDOVA,95670,CA,2,1,911,Residential,Fri May 16 00:00:00 EDT 2008,115000,38.610852,-121.273278 4800 WESTLAKE PKWY Unit 410,SACRAMENTO,95835,CA,1,1,846,Condo,Fri May 16 00:00:00 EDT 2008,115000,38.658812,-121.542345 3409 VIRGO ST,SACRAMENTO,95827,CA,3,2,1320,Residential,Fri May 16 00:00:00 EDT 2008,115500,38.563402,-121.327747 1110 PINEDALE AVE,SACRAMENTO,95838,CA,3,2,1410,Residential,Fri May 16 00:00:00 EDT 2008,115620,38.660173,-121.440216 2361 LA LOMA DR,RANCHO CORDOVA,95670,CA,3,2,1115,Residential,Fri May 16 00:00:00 EDT 2008,116000,38.59368,-121.316054 1455 64TH AVE,SACRAMENTO,95822,CA,3,2,1169,Residential,Fri May 16 00:00:00 EDT 2008,122000,38.492177,-121.503392 7328 SPRINGMAN ST,SACRAMENTO,95822,CA,3,2,1164,Residential,Fri May 16 00:00:00 EDT 2008,122500,38.491991,-121.477636 119 SAINT MARIE CIR,SACRAMENTO,95823,CA,4,2,1341,Residential,Fri May 16 00:00:00 EDT 2008,123000,38.481454,-121.446644 12 COSTA BRASE CT,SACRAMENTO,95838,CA,3,2,1219,Residential,Fri May 16 00:00:00 EDT 2008,124000,38.655554,-121.464275 6813 SCOTER WAY,SACRAMENTO,95842,CA,4,2,1127,Residential,Fri May 16 00:00:00 EDT 2008,124000,38.69043,-121.361035 6548 GRAYLOCK LN,NORTH HIGHLANDS,95660,CA,3,2,1272,Residential,Fri May 16 00:00:00 EDT 2008,124413,38.686061,-121.369949 1630 GLIDDEN AVE,SACRAMENTO,95822,CA,4,2,1253,Residential,Fri May 16 00:00:00 EDT 2008,125000,38.482717,-121.499683 7825 DALEWOODS WAY,SACRAMENTO,95828,CA,3,2,1120,Residential,Fri May 16 00:00:00 EDT 2008,130000,38.477297,-121.411513 4073 TRESLER AVE,NORTH HIGHLANDS,95660,CA,2,2,1118,Residential,Fri May 16 00:00:00 EDT 2008,131750,38.659016,-121.370457 4288 DYMIC WAY,SACRAMENTO,95838,CA,4,3,1890,Residential,Fri May 16 00:00:00 EDT 2008,137721,38.646541,-121.441139 1158 SAN IGNACIO WAY,SACRAMENTO,95833,CA,3,2,1260,Residential,Fri May 16 00:00:00 EDT 2008,137760,38.623045,-121.486279 4904 J PKWY,SACRAMENTO,95823,CA,3,2,1400,Residential,Fri May 16 00:00:00 EDT 2008,138000,38.487297,-121.44295 2931 HOWE AVE,SACRAMENTO,95821,CA,3,1,1264,Residential,Fri May 16 00:00:00 EDT 2008,140000,38.619012,-121.415329 5531 JANSEN DR,SACRAMENTO,95824,CA,3,1,1060,Residential,Fri May 16 00:00:00 EDT 2008,145000,38.522015,-121.438713 7836 ORCHARD WOODS CIR,SACRAMENTO,95828,CA,2,2,1132,Residential,Fri May 16 00:00:00 EDT 2008,145000,38.47955,-121.410867 4055 DEERBROOK DR,SACRAMENTO,95823,CA,3,2,1466,Residential,Fri May 16 00:00:00 EDT 2008,150000,38.472117,-121.459589 9937 BURLINE ST,SACRAMENTO,95827,CA,3,2,1092,Residential,Fri May 16 00:00:00 EDT 2008,150000,38.559641,-121.32316 6948 MIRADOR WAY,SACRAMENTO,95828,CA,4,2,1628,Residential,Fri May 16 00:00:00 EDT 2008,151000,38.493484,-121.42035 4909 RUGER CT,SACRAMENTO,95842,CA,3,2,960,Residential,Fri May 16 00:00:00 EDT 2008,155000,38.68747,-121.349234 7204 KERSTEN ST,CITRUS HEIGHTS,95621,CA,3,2,1075,Residential,Fri May 16 00:00:00 EDT 2008,155800,38.695863,-121.300814 3150 ROSEMONT DR,SACRAMENTO,95826,CA,3,2,1428,Residential,Fri May 16 00:00:00 EDT 2008,156142,38.554927,-121.35521 8200 STEINBECK WAY,SACRAMENTO,95828,CA,4,2,1358,Residential,Fri May 16 00:00:00 EDT 2008,158000,38.474854,-121.404726 8198 STEVENSON AVE,SACRAMENTO,95828,CA,6,4,2475,Multi-Family,Fri May 16 00:00:00 EDT 2008,159900,38.465271,-121.40426 6824 OLIVE TREE WAY,CITRUS HEIGHTS,95610,CA,3,2,1410,Residential,Fri May 16 00:00:00 EDT 2008,160000,38.689239,-121.267737 3536 SUN MAIDEN WAY,ANTELOPE,95843,CA,3,2,1711,Residential,Fri May 16 00:00:00 EDT 2008,161500,38.70968,-121.382328 4517 OLYMPIAD WAY,SACRAMENTO,95826,CA,4,2,1483,Residential,Fri May 16 00:00:00 EDT 2008,161600,38.536751,-121.359154 925 COBDEN CT,GALT,95632,CA,3,2,1140,Residential,Fri May 16 00:00:00 EDT 2008,162000,38.282047,-121.295812 8225 SCOTTSDALE DR,SACRAMENTO,95828,CA,4,2,1549,Residential,Fri May 16 00:00:00 EDT 2008,165000,38.487864,-121.402476 8758 LEMAS RD,SACRAMENTO,95828,CA,3,2,1410,Residential,Fri May 16 00:00:00 EDT 2008,165000,38.467487,-121.377055 6121 ALPINESPRING WAY,ELK GROVE,95758,CA,3,2,1240,Residential,Fri May 16 00:00:00 EDT 2008,167293,38.434075,-121.432623 5937 YORK GLEN WAY,SACRAMENTO,95842,CA,5,2,1712,Residential,Fri May 16 00:00:00 EDT 2008,168000,38.677003,-121.354454 6417 SUNNYFIELD WAY,SACRAMENTO,95823,CA,4,2,1580,Residential,Fri May 16 00:00:00 EDT 2008,168000,38.449153,-121.428272 4008 GREY LIVERY WAY,ANTELOPE,95843,CA,3,2,1669,Residential,Fri May 16 00:00:00 EDT 2008,168750,38.71846,-121.370862 8920 ROSETTA CIR,SACRAMENTO,95826,CA,3,1,1029,Residential,Fri May 16 00:00:00 EDT 2008,168750,38.544374,-121.370874 8300 LICHEN DR,CITRUS HEIGHTS,95621,CA,3,1,1103,Residential,Fri May 16 00:00:00 EDT 2008,170000,38.71641,-121.306239 8884 AMBERJACK WAY,SACRAMENTO,95828,CA,3,2,2161,Residential,Fri May 16 00:00:00 EDT 2008,170250,38.479343,-121.372553 4480 VALLEY HI DR,SACRAMENTO,95823,CA,3,2,1650,Residential,Fri May 16 00:00:00 EDT 2008,173000,38.466781,-121.450955 2250 FOREBAY RD,POLLOCK PINES,95726,CA,3,1,1320,Residential,Fri May 16 00:00:00 EDT 2008,175000,38.77491,-120.597599 3529 FABERGE WAY,SACRAMENTO,95826,CA,3,2,1200,Residential,Fri May 16 00:00:00 EDT 2008,176095,38.553275,-121.346218 1792 DAWNELLE WAY,SACRAMENTO,95835,CA,3,2,1170,Residential,Fri May 16 00:00:00 EDT 2008,176250,38.68271,-121.501697 7800 TABARE CT,CITRUS HEIGHTS,95621,CA,3,2,1199,Residential,Fri May 16 00:00:00 EDT 2008,178000,38.70799,-121.302979 8531 HERMITAGE WAY,SACRAMENTO,95823,CA,4,2,1695,Residential,Fri May 16 00:00:00 EDT 2008,179000,38.448452,-121.428536 2421 BERRYWOOD DR,RANCHO CORDOVA,95670,CA,3,2,1157,Residential,Fri May 16 00:00:00 EDT 2008,180000,38.60868,-121.27849 1005 MORENO WAY,SACRAMENTO,95838,CA,3,2,1410,Residential,Fri May 16 00:00:00 EDT 2008,180000,38.646206,-121.442767 1675 VERNON ST Unit 24,ROSEVILLE,95678,CA,3,2,1174,Residential,Fri May 16 00:00:00 EDT 2008,180000,38.734136,-121.299639 24 WINDCHIME CT,SACRAMENTO,95823,CA,3,2,1593,Residential,Fri May 16 00:00:00 EDT 2008,181000,38.44617,-121.427824 540 HARLING CT,RIO LINDA,95673,CA,3,2,1093,Residential,Fri May 16 00:00:00 EDT 2008,182000,38.68279,-121.453509 1207 CRESCENDO DR,ROSEVILLE,95678,CA,3,2,1770,Residential,Fri May 16 00:00:00 EDT 2008,182587,38.72446,-121.292829 7577 EDDYLEE WAY,SACRAMENTO,95822,CA,4,2,1436,Residential,Fri May 16 00:00:00 EDT 2008,185074,38.48291,-121.491509 8369 FOPPIANO WAY,SACRAMENTO,95829,CA,3,2,1124,Residential,Fri May 16 00:00:00 EDT 2008,185833,38.453839,-121.357919 8817 SAWTELLE WAY,SACRAMENTO,95826,CA,4,2,1139,Residential,Fri May 16 00:00:00 EDT 2008,186785,38.565322,-121.374251 1910 BONAVISTA WAY,SACRAMENTO,95832,CA,3,2,1638,Residential,Fri May 16 00:00:00 EDT 2008,187000,38.476048,-121.494961 8 TIDE CT,SACRAMENTO,95833,CA,3,2,1328,Residential,Fri May 16 00:00:00 EDT 2008,188335,38.609864,-121.492304 8952 ROCKY CREEK CT,ELK GROVE,95758,CA,3,2,1273,Residential,Fri May 16 00:00:00 EDT 2008,190000,38.431239,-121.44001 435 EXCHANGE ST,SACRAMENTO,95838,CA,3,1,1082,Residential,Fri May 16 00:00:00 EDT 2008,190000,38.659434,-121.455236 10105 MONTE VALLO CT,SACRAMENTO,95827,CA,4,2,1578,Residential,Fri May 16 00:00:00 EDT 2008,190000,38.573917,-121.316916 3930 ANNABELLE AVE,ROSEVILLE,95661,CA,2,1,796,Residential,Fri May 16 00:00:00 EDT 2008,190000,38.727609,-121.226494 4854 TANGERINE AVE,SACRAMENTO,95823,CA,3,2,1386,Residential,Fri May 16 00:00:00 EDT 2008,191250,38.478239,-121.446326 2909 SHAWN WAY,RANCHO CORDOVA,95670,CA,3,2,1452,Residential,Fri May 16 00:00:00 EDT 2008,193000,38.589925,-121.299059 4290 BLACKFORD WAY,SACRAMENTO,95823,CA,3,2,1513,Residential,Fri May 16 00:00:00 EDT 2008,193500,38.470494,-121.454162 5890 TT TRAK,FORESTHILL,95631,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,194818,39.020808,-120.821518 7015 WOODSIDE DR,SACRAMENTO,95842,CA,4,2,1578,Residential,Fri May 16 00:00:00 EDT 2008,195000,38.693071,-121.332365 6019 CHESHIRE WAY,CITRUS HEIGHTS,95610,CA,4,3,1736,Residential,Fri May 16 00:00:00 EDT 2008,195000,38.676437,-121.279165 3330 VILLAGE CT,CAMERON PARK,95682,CA,2,2,0,Residential,Fri May 16 00:00:00 EDT 2008,195000,38.690504,-120.996245 2561 VERNA WAY,SACRAMENTO,95821,CA,3,1,1473,Residential,Fri May 16 00:00:00 EDT 2008,195000,38.611055,-121.369964 3522 22ND AVE,SACRAMENTO,95820,CA,3,1,1150,Residential,Fri May 16 00:00:00 EDT 2008,198000,38.532725,-121.469078 2880 CANDIDO DR,SACRAMENTO,95833,CA,3,2,1127,Residential,Fri May 16 00:00:00 EDT 2008,199900,38.618019,-121.510215 6908 PIN OAK CT,FAIR OAKS,95628,CA,3,1,1144,Residential,Fri May 16 00:00:00 EDT 2008,200000,38.66424,-121.303675 5733 ANGELINA AVE,CARMICHAEL,95608,CA,3,1,972,Residential,Fri May 16 00:00:00 EDT 2008,201000,38.622634,-121.330846 7849 BONNY DOWNS WAY,ELK GROVE,95758,CA,4,2,2306,Residential,Fri May 16 00:00:00 EDT 2008,204918,38.42139,-121.411339 8716 LONGSPUR WAY,ANTELOPE,95843,CA,3,2,1479,Residential,Fri May 16 00:00:00 EDT 2008,205000,38.724083,-121.3584 6320 EL DORADO ST,EL DORADO,95623,CA,2,1,1040,Residential,Fri May 16 00:00:00 EDT 2008,205000,38.678758,-120.844118 2328 DOROTHY JUNE WAY,SACRAMENTO,95838,CA,3,2,1430,Residential,Fri May 16 00:00:00 EDT 2008,205878,38.641727,-121.412703 1986 DANVERS WAY,SACRAMENTO,95832,CA,4,2,1800,Residential,Fri May 16 00:00:00 EDT 2008,207000,38.47723,-121.492568 7901 GAZELLE TRAIL WAY,ANTELOPE,95843,CA,4,2,1953,Residential,Fri May 16 00:00:00 EDT 2008,207744,38.71174,-121.342675 6080 BRIDGECROSS DR,SACRAMENTO,95835,CA,3,2,1120,Residential,Fri May 16 00:00:00 EDT 2008,209000,38.681952,-121.505009 20 GROTH CIR,SACRAMENTO,95834,CA,3,2,1232,Residential,Fri May 16 00:00:00 EDT 2008,210000,38.640807,-121.533522 1900 DANBROOK DR,SACRAMENTO,95835,CA,1,1,984,Condo,Fri May 16 00:00:00 EDT 2008,210944,38.668433,-121.503471 140 VENTO CT,ROSEVILLE,95678,CA,3,2,0,Condo,Fri May 16 00:00:00 EDT 2008,212500,38.793533,-121.289685 8442 KEUSMAN ST,ELK GROVE,95758,CA,4,2,2329,Residential,Fri May 16 00:00:00 EDT 2008,213750,38.449651,-121.414704 9552 SUNLIGHT LN,ELK GROVE,95758,CA,3,2,1351,Residential,Fri May 16 00:00:00 EDT 2008,215000,38.410561,-121.404327 2733 YUMA CT,CAMERON PARK,95682,CA,2,2,0,Residential,Fri May 16 00:00:00 EDT 2008,215000,38.691215,-120.994949 1407 TIFFANY CIR,ROSEVILLE,95661,CA,4,1,1376,Residential,Fri May 16 00:00:00 EDT 2008,215000,38.736392,-121.2664 636 CRESTVIEW DR,DIAMOND SPRINGS,95619,CA,3,2,1300,Residential,Fri May 16 00:00:00 EDT 2008,216033,38.688255,-120.810235 1528 HESKET WAY,SACRAMENTO,95825,CA,4,2,1566,Residential,Fri May 16 00:00:00 EDT 2008,220000,38.593598,-121.403637 2327 32ND ST,SACRAMENTO,95817,CA,2,1,1115,Residential,Fri May 16 00:00:00 EDT 2008,220000,38.557433,-121.47034 1833 2ND AVE,SACRAMENTO,95818,CA,2,1,1032,Residential,Fri May 16 00:00:00 EDT 2008,220000,38.556818,-121.490669 7252 CARRIAGE DR,CITRUS HEIGHTS,95621,CA,4,2,1419,Residential,Fri May 16 00:00:00 EDT 2008,220000,38.698058,-121.294893 9815 PASO FINO WAY,ELK GROVE,95757,CA,3,2,1261,Residential,Fri May 16 00:00:00 EDT 2008,220000,38.404888,-121.443998 5532 ENGLE RD,CARMICHAEL,95608,CA,2,2,1637,Residential,Fri May 16 00:00:00 EDT 2008,220702,38.63173,-121.335286 1139 CLINTON RD,SACRAMENTO,95825,CA,4,2,1776,Multi-Family,Fri May 16 00:00:00 EDT 2008,221250,38.585291,-121.406824 9176 SAGE GLEN WAY,ELK GROVE,95758,CA,3,2,1338,Residential,Fri May 16 00:00:00 EDT 2008,222000,38.423913,-121.439115 9967 HATHERTON WAY,ELK GROVE,95757,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,222500,38.3052,-121.4033 9264 BOULDER RIVER WAY,ELK GROVE,95624,CA,5,2,2254,Residential,Fri May 16 00:00:00 EDT 2008,222750,38.421713,-121.345191 320 GROTH CIR,SACRAMENTO,95834,CA,3,2,1441,Residential,Fri May 16 00:00:00 EDT 2008,225000,38.638882,-121.531883 137 GUNNISON AVE,SACRAMENTO,95838,CA,4,2,1991,Residential,Fri May 16 00:00:00 EDT 2008,225000,38.650729,-121.466483 8209 RIVALLO WAY,SACRAMENTO,95829,CA,4,3,2126,Residential,Fri May 16 00:00:00 EDT 2008,228750,38.459524,-121.3501 8637 PERIWINKLE CIR,ELK GROVE,95624,CA,3,2,1094,Residential,Fri May 16 00:00:00 EDT 2008,229000,38.443184,-121.364388 3425 MEADOW WAY,ROCKLIN,95677,CA,3,2,1462,Residential,Fri May 16 00:00:00 EDT 2008,230095,38.798028,-121.235364 107 JARVIS CIR,SACRAMENTO,95834,CA,5,3,2258,Residential,Fri May 16 00:00:00 EDT 2008,232500,38.639891,-121.537603 2319 THORES ST,RANCHO CORDOVA,95670,CA,3,2,1074,Residential,Fri May 16 00:00:00 EDT 2008,233000,38.59675,-121.312716 8935 MOUNTAIN HOME CT,ELK GROVE,95624,CA,4,2,2111,Residential,Fri May 16 00:00:00 EDT 2008,233500,38.38751,-121.370276 2566 SERENATA WAY,SACRAMENTO,95835,CA,3,2,1686,Residential,Fri May 16 00:00:00 EDT 2008,239000,38.671556,-121.520916 4085 COUNTRY DR,ANTELOPE,95843,CA,4,3,1915,Residential,Fri May 16 00:00:00 EDT 2008,240000,38.706209,-121.369509 9297 TROUT WAY,ELK GROVE,95624,CA,4,2,2367,Residential,Fri May 16 00:00:00 EDT 2008,240000,38.420637,-121.375798 7 ARCHIBALD CT,SACRAMENTO,95823,CA,3,2,1962,Residential,Fri May 16 00:00:00 EDT 2008,240971,38.443305,-121.435296 11130 EEL RIVER CT,RANCHO CORDOVA,95670,CA,2,2,1406,Residential,Fri May 16 00:00:00 EDT 2008,242000,38.625932,-121.271517 8323 REDBANK WAY,SACRAMENTO,95829,CA,3,2,1789,Residential,Fri May 16 00:00:00 EDT 2008,243450,38.455753,-121.349273 16 BRONCO CREEK CT,SACRAMENTO,95835,CA,4,2,1876,Residential,Fri May 16 00:00:00 EDT 2008,243500,38.674226,-121.525497 8316 NORTHAM DR,ANTELOPE,95843,CA,3,2,1235,Residential,Fri May 16 00:00:00 EDT 2008,246544,38.720767,-121.376678 4240 WINJE DR,ANTELOPE,95843,CA,4,2,2504,Residential,Fri May 16 00:00:00 EDT 2008,246750,38.70884,-121.359559 3569 SODA WAY,SACRAMENTO,95834,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,247000,38.631139,-121.501879 5118 ROBANDER ST,CARMICHAEL,95608,CA,3,2,1676,Residential,Fri May 16 00:00:00 EDT 2008,247000,38.657267,-121.310352 5976 KYLENCH CT,CITRUS HEIGHTS,95621,CA,3,2,1367,Residential,Fri May 16 00:00:00 EDT 2008,249000,38.708966,-121.32467 9247 DELAIR WAY,ELK GROVE,95758,CA,4,3,1899,Residential,Fri May 16 00:00:00 EDT 2008,249000,38.422241,-121.458022 9054 DESCENDANT DR,ELK GROVE,95758,CA,3,2,1636,Residential,Fri May 16 00:00:00 EDT 2008,250000,38.428852,-121.415628 3450 WHITNOR CT,SACRAMENTO,95821,CA,3,2,1828,Residential,Fri May 16 00:00:00 EDT 2008,250000,38.627698,-121.369698 6288 LONETREE BLVD,ROCKLIN,95765,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,250000,38.804993,-121.293609 9355 MATADOR WAY,SACRAMENTO,95826,CA,4,2,1438,Residential,Fri May 16 00:00:00 EDT 2008,252000,38.555633,-121.350691 8671 SUMMER SUN WAY,ELK GROVE,95624,CA,3,2,1451,Residential,Fri May 16 00:00:00 EDT 2008,255000,38.442845,-121.373272 1890 GENEVA PL,SACRAMENTO,95825,CA,3,1,1520,Residential,Fri May 16 00:00:00 EDT 2008,255000,38.599449,-121.400305 1813 AVENIDA MARTINA,ROSEVILLE,95747,CA,3,2,1506,Residential,Fri May 16 00:00:00 EDT 2008,255000,38.776649,-121.339589 191 BARNHART CIR,SACRAMENTO,95835,CA,4,2,2605,Residential,Fri May 16 00:00:00 EDT 2008,257200,38.675594,-121.515878 6221 GREEN TOP WAY,ORANGEVALE,95662,CA,3,2,1196,Residential,Fri May 16 00:00:00 EDT 2008,260000,38.679409,-121.219107 2298 PRIMROSE LN,LINCOLN,95648,CA,3,2,1621,Residential,Fri May 16 00:00:00 EDT 2008,260000,38.89918,-121.322514 5635 LOS PUEBLOS WAY,SACRAMENTO,95835,CA,3,2,1811,Residential,Fri May 16 00:00:00 EDT 2008,263500,38.679191,-121.537622 10165 LOFTON WAY,ELK GROVE,95757,CA,3,2,1540,Residential,Fri May 16 00:00:00 EDT 2008,266510,38.387708,-121.436522 1251 GREEN RAVINE DR,LINCOLN,95648,CA,4,2,0,Residential,Fri May 16 00:00:00 EDT 2008,267750,38.88156,-121.301343 6001 SHOO FLY RD,PLACERVILLE,95667,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,270000,38.813546,-120.809254 3040 PARKHAM DR,ROSEVILLE,95747,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,271000,38.770835,-121.366996 2674 TAM O SHANTER DR,EL DORADO HILLS,95762,CA,4,2,0,Residential,Fri May 16 00:00:00 EDT 2008,272700,38.695801,-121.079216 6007 MARYBELLE LN,SHINGLE SPRINGS,95682,CA,0,0,0,Unkown,Fri May 16 00:00:00 EDT 2008,275000,38.64347,-120.888183 9949 NESTLING CIR,ELK GROVE,95757,CA,3,2,1543,Residential,Fri May 16 00:00:00 EDT 2008,275000,38.397455,-121.468391 2915 HOLDREGE WAY,SACRAMENTO,95835,CA,5,3,2494,Residential,Fri May 16 00:00:00 EDT 2008,276000,38.663728,-121.525833 2678 BRIARTON DR,LINCOLN,95648,CA,3,2,1650,Residential,Fri May 16 00:00:00 EDT 2008,276500,38.844116,-121.274806 294 SPARROW DR,GALT,95632,CA,4,3,2214,Residential,Fri May 16 00:00:00 EDT 2008,278000,38.258976,-121.321266 2987 DIORITE WAY,SACRAMENTO,95835,CA,5,3,2280,Residential,Fri May 16 00:00:00 EDT 2008,279000,38.667332,-121.528276 6326 APPIAN WAY,CARMICHAEL,95608,CA,3,2,1443,Residential,Fri May 16 00:00:00 EDT 2008,280000,38.66266,-121.316858 6905 COBALT WAY,CITRUS HEIGHTS,95621,CA,4,2,1582,Residential,Fri May 16 00:00:00 EDT 2008,280000,38.691393,-121.305215 8986 HAFLINGER WAY,ELK GROVE,95757,CA,3,2,1857,Residential,Fri May 16 00:00:00 EDT 2008,285000,38.397923,-121.450219 2916 BABSON DR,ELK GROVE,95758,CA,3,2,1735,Residential,Fri May 16 00:00:00 EDT 2008,288000,38.417191,-121.473897 10133 NEBBIOLO CT,ELK GROVE,95624,CA,4,3,2096,Residential,Fri May 16 00:00:00 EDT 2008,289000,38.391085,-121.347231 1103 COMMONS DR,SACRAMENTO,95825,CA,3,2,1720,Residential,Fri May 16 00:00:00 EDT 2008,290000,38.567865,-121.410699 4636 TEAL BAY CT,ANTELOPE,95843,CA,4,2,2160,Residential,Fri May 16 00:00:00 EDT 2008,290000,38.704554,-121.354753 1524 YOUNGS AVE,SACRAMENTO,95838,CA,4,2,1382,Residential,Fri May 16 00:00:00 EDT 2008,293996,38.644927,-121.43054 865 CONRAD CT,PLACERVILLE,95667,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,294000,38.729993,-120.802458 8463 TERRACOTTA CT,ELK GROVE,95624,CA,4,2,1721,Residential,Fri May 16 00:00:00 EDT 2008,294173,38.450548,-121.363002 5747 KING RD,LOOMIS,95650,CA,4,2,1328,Residential,Fri May 16 00:00:00 EDT 2008,295000,38.825096,-121.198432 8253 KEEGAN WAY,ELK GROVE,95624,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,298000,38.446286,-121.400817 9204 TROUT WAY,ELK GROVE,95624,CA,4,2,1982,Residential,Fri May 16 00:00:00 EDT 2008,298000,38.422221,-121.375799 1828 2ND AVE,SACRAMENTO,95818,CA,2,1,1144,Residential,Fri May 16 00:00:00 EDT 2008,299000,38.556844,-121.490769 1113 COMMONS DR,SACRAMENTO,95825,CA,2,2,1623,Residential,Fri May 16 00:00:00 EDT 2008,300000,38.567795,-121.410703 2341 BIG STRIKE TRL,COOL,95614,CA,3,2,1457,Residential,Fri May 16 00:00:00 EDT 2008,300000,38.905927,-120.975169 9452 RED SPRUCE WAY,ELK GROVE,95624,CA,6,3,2555,Residential,Fri May 16 00:00:00 EDT 2008,300000,38.404505,-121.346938 5776 TERRACE DR,ROCKLIN,95765,CA,3,2,1577,Residential,Fri May 16 00:00:00 EDT 2008,300567,38.800539,-121.260979 5908 MCLEAN DR,ELK GROVE,95757,CA,5,3,2592,Residential,Fri May 16 00:00:00 EDT 2008,303000,38.38912,-121.434389 8215 PEREGRINE WAY,CITRUS HEIGHTS,95610,CA,3,2,1401,Residential,Fri May 16 00:00:00 EDT 2008,305000,38.715493,-121.26293 1104 HILLSDALE LN,LINCOLN,95648,CA,4,2,0,Residential,Fri May 16 00:00:00 EDT 2008,306000,38.865017,-121.32302 2949 PANAMA AVE,CARMICHAEL,95608,CA,3,2,1502,Residential,Fri May 16 00:00:00 EDT 2008,310000,38.618369,-121.326187 1356 HARTLEY WAY,FOLSOM,95630,CA,3,2,1327,Residential,Fri May 16 00:00:00 EDT 2008,310000,38.651617,-121.131674 633 HANISCH DR,ROSEVILLE,95678,CA,4,3,1800,Residential,Fri May 16 00:00:00 EDT 2008,310000,38.76349,-121.275881 63 ANGEL ISLAND CIR,SACRAMENTO,95831,CA,4,2,2169,Residential,Fri May 16 00:00:00 EDT 2008,311518,38.490408,-121.547664 1571 WILD OAK LN,LINCOLN,95648,CA,5,3,2457,Residential,Fri May 16 00:00:00 EDT 2008,312000,38.844144,-121.274174 5222 COPPER SUNSET WAY,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,313000,38.529181,-121.224755 5601 SPINDRIFT LN,ORANGEVALE,95662,CA,4,2,2004,Residential,Fri May 16 00:00:00 EDT 2008,315000,38.668289,-121.192316 652 FIFTEEN MILE DR,ROSEVILLE,95678,CA,4,3,2212,Residential,Fri May 16 00:00:00 EDT 2008,315000,38.775872,-121.298864 7921 DOE TRAIL WAY,ANTELOPE,95843,CA,5,3,3134,Residential,Fri May 16 00:00:00 EDT 2008,315000,38.711927,-121.343608 4204 LUSK DR,SACRAMENTO,95864,CA,3,2,1360,Residential,Fri May 16 00:00:00 EDT 2008,315000,38.606569,-121.368424 5321 DELTA DR,ROCKLIN,95765,CA,4,2,0,Residential,Fri May 16 00:00:00 EDT 2008,315000,38.815493,-121.262908 5608 ROSEDALE WAY,SACRAMENTO,95822,CA,3,2,1276,Residential,Fri May 16 00:00:00 EDT 2008,320000,38.525115,-121.518689 3372 BERETANIA WAY,SACRAMENTO,95834,CA,4,3,2962,Residential,Fri May 16 00:00:00 EDT 2008,322000,38.64977,-121.53448 2422 STEFANIE DR,ROCKLIN,95765,CA,4,2,1888,Residential,Fri May 16 00:00:00 EDT 2008,325000,38.82273,-121.26424 3232 PARKHAM DR,ROSEVILLE,95747,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,325500,38.772821,-121.364821 448 ELMWOOD CT,ROSEVILLE,95678,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,326951,38.771917,-121.304439 1214 DAWNWOOD DR,GALT,95632,CA,3,2,1548,Residential,Fri May 16 00:00:00 EDT 2008,328370,38.290119,-121.286023 1440 EMERALD LN,LINCOLN,95648,CA,2,2,0,Residential,Fri May 16 00:00:00 EDT 2008,330000,38.861864,-121.267478 3349 CORVINA DR,RANCHO CORDOVA,95670,CA,4,3,2109,Residential,Fri May 16 00:00:00 EDT 2008,330000,38.580545,-121.279016 10254 JULIANA WAY,SACRAMENTO,95827,CA,4,2,2484,Residential,Fri May 16 00:00:00 EDT 2008,331200,38.56803,-121.309966 149 OPUS CIR,SACRAMENTO,95834,CA,4,3,2258,Residential,Fri May 16 00:00:00 EDT 2008,332000,38.6354,-121.53499 580 REGENCY PARK CIR,SACRAMENTO,95835,CA,3,3,2212,Residential,Fri May 16 00:00:00 EDT 2008,334000,38.674864,-121.4958 5544 CAMAS CT,ORANGEVALE,95662,CA,3,2,1616,Residential,Fri May 16 00:00:00 EDT 2008,335000,38.667703,-121.209456 5102 ARCHCREST WAY,SACRAMENTO,95835,CA,4,2,2372,Residential,Fri May 16 00:00:00 EDT 2008,341000,38.66841,-121.494639 5725 BALFOR RD,ROCKLIN,95765,CA,5,3,2606,Residential,Fri May 16 00:00:00 EDT 2008,346375,38.807816,-121.270008 7697 ROSEHALL DR,ROSEVILLE,95678,CA,5,3,0,Residential,Fri May 16 00:00:00 EDT 2008,347225,38.79218,-121.28595 4821 HUTSON WAY,ELK GROVE,95757,CA,5,3,2877,Residential,Fri May 16 00:00:00 EDT 2008,349000,38.386239,-121.448159 4509 WINJE DR,ANTELOPE,95843,CA,3,2,2960,Residential,Fri May 16 00:00:00 EDT 2008,350000,38.709513,-121.359357 1965 LAURELHURST LN,LINCOLN,95648,CA,2,2,0,Residential,Fri May 16 00:00:00 EDT 2008,350000,38.853869,-121.271742 6709 ROSE BRIDGE DR,ROSEVILLE,95678,CA,3,2,2172,Residential,Fri May 16 00:00:00 EDT 2008,350000,38.792461,-121.275711 281 SPYGLASS HL,ROSEVILLE,95678,CA,3,2,2100,Condo,Fri May 16 00:00:00 EDT 2008,350000,38.762153,-121.283451 7709 RIVER VILLAGE DR,SACRAMENTO,95831,CA,3,2,1795,Residential,Fri May 16 00:00:00 EDT 2008,351000,38.483212,-121.54019 4165 BRISBANE CIR,EL DORADO HILLS,95762,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,356200,38.686067,-121.073413 506 BEDFORD CT,ROSEVILLE,95661,CA,4,2,2295,Residential,Fri May 16 00:00:00 EDT 2008,360000,38.733985,-121.236766 9048 PINTO CANYON WAY,ROSEVILLE,95747,CA,4,3,2577,Residential,Fri May 16 00:00:00 EDT 2008,367463,38.792493,-121.331899 2274 IVY BRIDGE DR,ROSEVILLE,95747,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,375000,38.778561,-121.362008 14004 WALNUT AVE,WALNUT GROVE,95690,CA,3,1,1727,Residential,Fri May 16 00:00:00 EDT 2008,380000,38.247659,-121.515129 6905 FRANKFORT CT,ELK GROVE,95758,CA,3,2,1485,Residential,Fri May 16 00:00:00 EDT 2008,380578,38.429139,-121.423444 3621 WINTUN DR,CARMICHAEL,95608,CA,3,2,1655,Residential,Fri May 16 00:00:00 EDT 2008,386222,38.629929,-121.323086 201 KIRKLAND CT,LINCOLN,95648,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,389000,38.867125,-121.319085 12075 APPLESBURY CT,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,390000,38.5357,-121.2249 1975 SIDESADDLE WAY,ROSEVILLE,95661,CA,3,2,2049,Residential,Fri May 16 00:00:00 EDT 2008,395500,38.737872,-121.249025 5420 ALMOND FALLS WAY,RANCHO CORDOVA,95742,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,396000,38.527384,-121.233531 9677 PILLITERI CT,ELK GROVE,95757,CA,5,3,2875,Residential,Fri May 16 00:00:00 EDT 2008,397000,38.405571,-121.445186 1515 EL CAMINO VERDE DR,LINCOLN,95648,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,400000,38.904869,-121.32075 556 PLATT CIR,EL DORADO HILLS,95762,CA,4,2,2199,Residential,Fri May 16 00:00:00 EDT 2008,400000,38.656299,-121.079783 1792 DIAMOND WOODS CIR,ROSEVILLE,95747,CA,4,3,0,Residential,Fri May 16 00:00:00 EDT 2008,412500,38.808581,-121.32785 1124 PERKINS WAY,SACRAMENTO,95818,CA,2,1,1304,Residential,Fri May 16 00:00:00 EDT 2008,413500,38.551611,-121.504437 4748 SALEM WAY,CARMICHAEL,95608,CA,3,2,2334,Residential,Fri May 16 00:00:00 EDT 2008,415000,38.634111,-121.353376 1484 RADCLIFFE WAY,AUBURN,95603,CA,4,3,2278,Residential,Fri May 16 00:00:00 EDT 2008,420454,38.935579,-121.079018 51 AIKEN WAY,SACRAMENTO,95819,CA,3,1,1493,Residential,Fri May 16 00:00:00 EDT 2008,425000,38.579326,-121.44252 2818 KNOLLWOOD DR,CAMERON PARK,95682,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,425000,38.669805,-120.999007 1536 STONEY CROSS LN,LINCOLN,95648,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,433500,38.860007,-121.310946 509 CASTILLIAN CT,ROSEVILLE,95747,CA,5,3,0,Residential,Fri May 16 00:00:00 EDT 2008,438000,38.804773,-121.341195 700 HUNTER PL,FOLSOM,95630,CA,5,3,2787,Residential,Fri May 16 00:00:00 EDT 2008,441000,38.66051,-121.163689 1240 FAY CIR,SACRAMENTO,95831,CA,5,3,2824,Residential,Fri May 16 00:00:00 EDT 2008,445000,38.506371,-121.514456 1113 SANDWICK WAY,FOLSOM,95630,CA,4,3,3261,Residential,Fri May 16 00:00:00 EDT 2008,446000,38.673882,-121.105077 3108 DELWOOD WAY,SACRAMENTO,95821,CA,4,2,2053,Residential,Fri May 16 00:00:00 EDT 2008,450000,38.621566,-121.370882 3212 CORNICHE LN,ROSEVILLE,95661,CA,4,3,2379,Residential,Fri May 16 00:00:00 EDT 2008,455000,38.750577,-121.232768 2159 BECKETT DR,EL DORADO HILLS,95762,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,460000,38.680092,-121.036467 4320 FOUR SEASONS RD,PLACERVILLE,95667,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,475000,38.690867,-120.693641 6401 MARSHALL RD,GARDEN VALLEY,95633,CA,3,2,0,Residential,Fri May 16 00:00:00 EDT 2008,490000,38.84255,-120.8754 2089 BECKETT DR,EL DORADO HILLS,95762,CA,4,2,0,Residential,Fri May 16 00:00:00 EDT 2008,493000,38.681778,-121.035838 6196 EDGEHILL DR,EL DORADO HILLS,95762,CA,5,4,0,Residential,Fri May 16 00:00:00 EDT 2008,508000,38.676131,-121.038931 200 HILLSFORD CT,ROSEVILLE,95747,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,511000,38.780051,-121.378718 8217 PLUMERIA AVE,FAIR OAKS,95628,CA,3,2,3173,Residential,Fri May 16 00:00:00 EDT 2008,525000,38.650735,-121.258628 4841 VILLAGE GREEN DR,EL DORADO HILLS,95762,CA,4,3,0,Residential,Fri May 16 00:00:00 EDT 2008,533000,38.664066,-121.056735 3863 LAS PASAS WAY,SACRAMENTO,95864,CA,3,1,1348,Residential,Fri May 16 00:00:00 EDT 2008,545000,38.588936,-121.373606 820 DANA CT,AUBURN,95603,CA,4,3,0,Residential,Fri May 16 00:00:00 EDT 2008,560000,38.865246,-121.094869 1165 37TH ST,SACRAMENTO,95816,CA,2,1,1252,Residential,Fri May 16 00:00:00 EDT 2008,575000,38.568438,-121.457854 203 CASCADE FALLS DR,FOLSOM,95630,CA,4,3,3229,Residential,Fri May 16 00:00:00 EDT 2008,575000,38.703962,-121.1871 9880 IZILDA CT,SACRAMENTO,95829,CA,5,4,3863,Residential,Fri May 16 00:00:00 EDT 2008,598695,38.45326,-121.32573 1800 AVONDALE DR,ROSEVILLE,95747,CA,5,3,0,Residential,Fri May 16 00:00:00 EDT 2008,600000,38.798448,-121.344054 4620 BROMWICH CT,ROCKLIN,95677,CA,4,3,0,Residential,Fri May 16 00:00:00 EDT 2008,600000,38.772672,-121.220232 620 KESWICK CT,GRANITE BAY,95746,CA,4,3,2356,Residential,Fri May 16 00:00:00 EDT 2008,600000,38.732096,-121.219142 4478 GREENBRAE RD,ROCKLIN,95677,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,600000,38.781134,-121.222801 8432 BRIGGS DR,ROSEVILLE,95747,CA,5,3,3579,Residential,Fri May 16 00:00:00 EDT 2008,610000,38.78861,-121.339495 200 CRADLE MOUNTAIN CT,EL DORADO HILLS,95762,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,622500,38.6478,-121.0309 2065 IMPRESSIONIST WAY,EL DORADO HILLS,95762,CA,0,0,0,Residential,Fri May 16 00:00:00 EDT 2008,680000,38.682961,-121.033253 2982 ABERDEEN LN,EL DORADO HILLS,95762,CA,4,3,0,Residential,Fri May 16 00:00:00 EDT 2008,879000,38.706692,-121.058869 9401 BARREL RACER CT,WILTON,95693,CA,4,3,4400,Residential,Fri May 16 00:00:00 EDT 2008,884790,38.415298,-121.194858 3720 VISTA DE MADERA,LINCOLN,95648,CA,3,3,0,Residential,Fri May 16 00:00:00 EDT 2008,1551,38.851645,-121.231742 14151 INDIO DR,SLOUGHHOUSE,95683,CA,3,4,5822,Residential,Fri May 16 00:00:00 EDT 2008,2000,38.490447,-121.129337 7401 TOULON LN,SACRAMENTO,95828,CA,4,2,1512,Residential,Thu May 15 00:00:00 EDT 2008,56950,38.488628,-121.387759 9127 NEWHALL DR Unit 34,SACRAMENTO,95826,CA,1,1,611,Condo,Thu May 15 00:00:00 EDT 2008,60000,38.542419,-121.359904 5937 BAMFORD DR,SACRAMENTO,95823,CA,2,1,876,Residential,Thu May 15 00:00:00 EDT 2008,61000,38.471139,-121.432255 5672 HILLSDALE BLVD,SACRAMENTO,95842,CA,2,1,933,Condo,Thu May 15 00:00:00 EDT 2008,62000,38.670467,-121.359799 3920 39TH ST,SACRAMENTO,95820,CA,2,1,864,Residential,Thu May 15 00:00:00 EDT 2008,68566,38.539213,-121.46393 701 JESSIE AVE,SACRAMENTO,95838,CA,2,1,1011,Residential,Thu May 15 00:00:00 EDT 2008,70000,38.643978,-121.449562 83 ARCADE BLVD,SACRAMENTO,95815,CA,4,2,1158,Residential,Thu May 15 00:00:00 EDT 2008,80000,38.618716,-121.466327 601 REGGINALD WAY,SACRAMENTO,95838,CA,3,2,1092,Residential,Thu May 15 00:00:00 EDT 2008,85500,38.64472,-121.452228 550 DEL VERDE CIR,SACRAMENTO,95833,CA,2,1,956,Condo,Thu May 15 00:00:00 EDT 2008,92000,38.627147,-121.500799 4113 DAYSTAR CT,SACRAMENTO,95824,CA,2,2,1139,Residential,Thu May 15 00:00:00 EDT 2008,93600,38.520469,-121.458606 7374 TISDALE WAY,SACRAMENTO,95822,CA,3,1,1058,Residential,Thu May 15 00:00:00 EDT 2008,95000,38.488238,-121.472561 3348 RIO LINDA BLVD,SACRAMENTO,95838,CA,3,2,1040,Residential,Thu May 15 00:00:00 EDT 2008,97750,38.628842,-121.446127 3935 LIMESTONE WAY,SACRAMENTO,95823,CA,3,2,1354,Residential,Thu May 15 00:00:00 EDT 2008,104000,38.484374,-121.463157 6208 GRATTAN WAY,NORTH HIGHLANDS,95660,CA,3,1,1051,Residential,Thu May 15 00:00:00 EDT 2008,105000,38.679279,-121.376615 739 E WOODSIDE LN Unit E,SACRAMENTO,95825,CA,1,1,682,Condo,Thu May 15 00:00:00 EDT 2008,107666,38.578675,-121.409951 4225 46TH AVE,SACRAMENTO,95824,CA,3,1,1161,Residential,Thu May 15 00:00:00 EDT 2008,109000,38.511893,-121.457676 1434 BELL AVE,SACRAMENTO,95838,CA,3,1,1004,Residential,Thu May 15 00:00:00 EDT 2008,110000,38.647398,-121.432914 5628 GEORGIA DR,NORTH HIGHLANDS,95660,CA,3,1,1229,Residential,Thu May 15 00:00:00 EDT 2008,110000,38.669587,-121.379879 7629 BETH ST,SACRAMENTO,95832,CA,3,2,1249,Residential,Thu May 15 00:00:00 EDT 2008,112500,38.480126,-121.487869 2277 BABETTE WAY,SACRAMENTO,95832,CA,3,2,1161,Residential,Thu May 15 00:00:00 EDT 2008,114800,38.479593,-121.48434 6561 WEATHERFORD WAY,SACRAMENTO,95823,CA,3,1,1010,Residential,Thu May 15 00:00:00 EDT 2008,116000,38.465551,-121.42661 3035 ESTEPA DR Unit 5C,CAMERON PARK,95682,CA,0,0,0,Condo,Thu May 15 00:00:00 EDT 2008,119000,38.681393,-120.996713 5136 CABOT CIR,SACRAMENTO,95820,CA,4,2,1462,Residential,Thu May 15 00:00:00 EDT 2008,121500,38.528479,-121.411806 7730 ROBINETTE RD,SACRAMENTO,95828,CA,3,2,1269,Residential,Thu May 15 00:00:00 EDT 2008,122000,38.47709,-121.410569 87 LACAM CIR,SACRAMENTO,95820,CA,2,2,1188,Residential,Thu May 15 00:00:00 EDT 2008,123675,38.532359,-121.41167 1691 NOGALES ST,SACRAMENTO,95838,CA,4,2,1570,Residential,Thu May 15 00:00:00 EDT 2008,126854,38.631925,-121.427775 3118 42ND ST,SACRAMENTO,95817,CA,3,2,1093,Residential,Thu May 15 00:00:00 EDT 2008,127059,38.546091,-121.457745 7517 50TH AVE,SACRAMENTO,95828,CA,3,1,962,Residential,Thu May 15 00:00:00 EDT 2008,128687,38.507339,-121.416267 4071 EVALITA WAY,SACRAMENTO,95823,CA,3,2,1089,Residential,Thu May 15 00:00:00 EDT 2008,129500,38.466388,-121.458861 7928 36TH AVE,SACRAMENTO,95824,CA,3,2,1127,Residential,Thu May 15 00:00:00 EDT 2008,130000,38.52049,-121.411383 6631 DEMARET DR,SACRAMENTO,95822,CA,4,2,1309,Residential,Thu May 15 00:00:00 EDT 2008,131750,38.506382,-121.483574 7043 9TH AVE,RIO LINDA,95673,CA,2,1,970,Residential,Thu May 15 00:00:00 EDT 2008,132000,38.695589,-121.444133 97 KENNELFORD CIR,SACRAMENTO,95823,CA,3,2,1144,Residential,Thu May 15 00:00:00 EDT 2008,134000,38.462376,-121.426556 2636 TRONERO WAY,RANCHO CORDOVA,95670,CA,3,1,1000,Residential,Thu May 15 00:00:00 EDT 2008,134000,38.593049,-121.30304 1530 TOPANGA LN Unit 204,LINCOLN,95648,CA,0,0,0,Condo,Thu May 15 00:00:00 EDT 2008,138000,38.88415,-121.270277 3604 KODIAK WAY,ANTELOPE,95843,CA,3,2,1206,Residential,Thu May 15 00:00:00 EDT 2008,142000,38.706175,-121.379776 2149 COTTAGE WAY,SACRAMENTO,95825,CA,3,1,1285,Residential,Thu May 15 00:00:00 EDT 2008,143012,38.603593,-121.417011 8632 PRAIRIEWOODS DR,SACRAMENTO,95828,CA,3,2,1543,Residential,Thu May 15 00:00:00 EDT 2008,145846,38.477563,-121.384382 612 STONE BLVD,WEST SACRAMENTO,95691,CA,2,1,884,Residential,Thu May 15 00:00:00 EDT 2008,147000,38.563084,-121.535579 4180 12TH AVE,SACRAMENTO,95817,CA,3,1,1019,Residential,Thu May 15 00:00:00 EDT 2008,148750,38.54117,-121.458129 8025 ARROYO VISTA DR,SACRAMENTO,95823,CA,4,2,1392,Residential,Thu May 15 00:00:00 EDT 2008,150000,38.46654,-121.419029 5754 WALERGA RD Unit 4,SACRAMENTO,95842,CA,2,1,924,Condo,Thu May 15 00:00:00 EDT 2008,150454,38.672567,-121.356754 8 LA ROCAS CT,SACRAMENTO,95823,CA,3,2,1217,Residential,Thu May 15 00:00:00 EDT 2008,151087,38.46616,-121.448283 8636 LONGSPUR WAY,ANTELOPE,95843,CA,3,2,1670,Residential,Thu May 15 00:00:00 EDT 2008,157296,38.725873,-121.35856 1941 EXPEDITION WAY,SACRAMENTO,95832,CA,3,2,1302,Residential,Thu May 15 00:00:00 EDT 2008,157500,38.473775,-121.493777 4351 TURNBRIDGE DR,SACRAMENTO,95823,CA,3,2,1488,Residential,Thu May 15 00:00:00 EDT 2008,160000,38.502034,-121.456027 6513 HOLIDAY WAY,NORTH HIGHLANDS,95660,CA,3,2,1373,Residential,Thu May 15 00:00:00 EDT 2008,160000,38.685361,-121.376938 8321 MISTLETOE WAY,CITRUS HEIGHTS,95621,CA,4,2,1381,Residential,Thu May 15 00:00:00 EDT 2008,161250,38.717738,-121.308322 5920 VALLEY GLEN WAY,SACRAMENTO,95823,CA,3,2,1265,Residential,Thu May 15 00:00:00 EDT 2008,164000,38.462821,-121.433135 2601 SAN FERNANDO WAY,SACRAMENTO,95818,CA,2,1,881,Residential,Thu May 15 00:00:00 EDT 2008,165000,38.556178,-121.476256 501 POPLAR AVE,WEST SACRAMENTO,95691,CA,0,0,0,Residential,Thu May 15 00:00:00 EDT 2008,165000,38.584526,-121.534609 8008 SAINT HELENA CT,SACRAMENTO,95829,CA,4,2,1608,Residential,Thu May 15 00:00:00 EDT 2008,165750,38.467012,-121.359969 6517 DONEGAL DR,CITRUS HEIGHTS,95621,CA,3,1,1344,Residential,Thu May 15 00:00:00 EDT 2008,166000,38.681554,-121.312934 1001 RIO NORTE WAY,SACRAMENTO,95834,CA,3,2,1202,Residential,Thu May 15 00:00:00 EDT 2008,169000,38.634292,-121.485106 604 P ST,LINCOLN,95648,CA,3,2,1104,Residential,Thu May 15 00:00:00 EDT 2008,170000,38.893168,-121.305398 10001 WOODCREEK OAKS BLVD Unit 815,ROSEVILLE,95747,CA,2,2,0,Condo,Thu May 15 00:00:00 EDT 2008,170000,38.795529,-121.328819 7351 GIGI PL,SACRAMENTO,95828,CA,4,2,1859,Multi-Family,Thu May 15 00:00:00 EDT 2008,170000,38.490606,-121.410173 7740 DIXIE LOU ST,SACRAMENTO,95832,CA,3,2,1232,Residential,Thu May 15 00:00:00 EDT 2008,170000,38.475853,-121.477039 7342 DAVE ST,SACRAMENTO,95828,CA,3,1,1638,Residential,Thu May 15 00:00:00 EDT 2008,170725,38.490822,-121.401643 7687 HOWERTON DR,SACRAMENTO,95831,CA,2,2,1177,Residential,Thu May 15 00:00:00 EDT 2008,171750,38.480859,-121.539745 26 KAMSON CT,SACRAMENTO,95833,CA,3,2,1582,Residential,Thu May 15 00:00:00 EDT 2008,172000,38.622794,-121.499173 7045 PEEVEY CT,SACRAMENTO,95823,CA,2,2,904,Residential,Thu May 15 00:00:00 EDT 2008,173056,38.502254,-121.451444 8916 GABLES MILL PL,ELK GROVE,95758,CA,3,2,1340,Residential,Thu May 15 00:00:00 EDT 2008,174000,38.433919,-121.422347 1140 EDMONTON DR,SACRAMENTO,95833,CA,3,2,1204,Residential,Thu May 15 00:00:00 EDT 2008,174250,38.62457,-121.486913 8879 APPLE PEAR CT,ELK GROVE,95624,CA,4,2,1477,Residential,Thu May 15 00:00:00 EDT 2008,176850,38.44574,-121.3725 9 WIND CT,SACRAMENTO,95823,CA,4,2,1497,Residential,Thu May 15 00:00:00 EDT 2008,179500,38.45073,-121.427528 8570 SHERATON DR,FAIR OAKS,95628,CA,3,1,960,Residential,Thu May 15 00:00:00 EDT 2008,185000,38.667254,-121.240708 1550 TOPANGA LN Unit 207,LINCOLN,95648,CA,0,0,0,Condo,Thu May 15 00:00:00 EDT 2008,188000,38.88417,-121.270222 1080 RIO NORTE WAY,SACRAMENTO,95834,CA,3,2,1428,Residential,Thu May 15 00:00:00 EDT 2008,188700,38.634335,-121.486098 5501 VALLETTA WAY,SACRAMENTO,95820,CA,3,1,1039,Residential,Thu May 15 00:00:00 EDT 2008,189000,38.530144,-121.43749 5624 MEMORY LN,FAIR OAKS,95628,CA,3,1,1529,Residential,Thu May 15 00:00:00 EDT 2008,189000,38.66745,-121.2364 6622 WILLOWLEAF DR,CITRUS HEIGHTS,95621,CA,4,3,1892,Residential,Thu May 15 00:00:00 EDT 2008,189836,38.699714,-121.311635 27 MEGAN CT,SACRAMENTO,95838,CA,4,2,1887,Residential,Thu May 15 00:00:00 EDT 2008,190000,38.649258,-121.465308 6601 WOODMORE OAKS DR,ORANGEVALE,95662,CA,3,2,1294,Residential,Thu May 15 00:00:00 EDT 2008,191250,38.687006,-121.254319 1973 DANVERS WAY,SACRAMENTO,95832,CA,3,2,1638,Residential,Thu May 15 00:00:00 EDT 2008,191675,38.477568,-121.492574 8001 ARROYO VISTA DR,SACRAMENTO,95823,CA,3,2,1677,Residential,Thu May 15 00:00:00 EDT 2008,195500,38.46734,-121.419843 7409 VOYAGER WAY,CITRUS HEIGHTS,95621,CA,3,1,1073,Residential,Thu May 15 00:00:00 EDT 2008,198000,38.700717,-121.3133 815 CROSSWIND DR,SACRAMENTO,95838,CA,3,2,1231,Residential,Thu May 15 00:00:00 EDT 2008,200000,38.651386,-121.45042 5509 LAGUNA CREST WAY,ELK GROVE,95758,CA,3,2,1175,Residential,Thu May 15 00:00:00 EDT 2008,200000,38.42442,-121.440357 8424 MERRY HILL WAY,ELK GROVE,95624,CA,3,2,1416,Residential,Thu May 15 00:00:00 EDT 2008,200000,38.452075,-121.366461 1525 PENNSYLVANIA AVE,WEST SACRAMENTO,95691,CA,0,0,0,Residential,Thu May 15 00:00:00 EDT 2008,200100,38.569943,-121.527539 5954 BRIDGECROSS DR,SACRAMENTO,95835,CA,3,2,1358,Residential,Thu May 15 00:00:00 EDT 2008,201528,38.68197,-121.500025 8789 SEQUOIA WOOD CT,ELK GROVE,95624,CA,4,2,1609,Residential,Thu May 15 00:00:00 EDT 2008,204750,38.438818,-121.37443 6600 SILVERTHORNE CIR,SACRAMENTO,95842,CA,4,3,1968,Residential,Thu May 15 00:00:00 EDT 2008,205000,38.68607,-121.342369 2221 2ND AVE,SACRAMENTO,95818,CA,2,2,1089,Residential,Thu May 15 00:00:00 EDT 2008,205000,38.555781,-121.485331 3230 SMATHERS WAY,CARMICHAEL,95608,CA,3,2,1296,Residential,Thu May 15 00:00:00 EDT 2008,205900,38.623372,-121.347665 5209 LAGUNA CREST WAY,ELK GROVE,95758,CA,2,2,1189,Residential,Thu May 15 00:00:00 EDT 2008,207000,38.424421,-121.443915 416 LEITCH AVE,SACRAMENTO,95815,CA,2,1,795,Residential,Thu May 15 00:00:00 EDT 2008,207973,38.612694,-121.456669 2100 BEATTY WAY,ROSEVILLE,95747,CA,3,2,1371,Residential,Thu May 15 00:00:00 EDT 2008,208250,38.737882,-121.308142 6920 GILLINGHAM WAY,NORTH HIGHLANDS,95660,CA,3,1,1310,Residential,Thu May 15 00:00:00 EDT 2008,208318,38.694279,-121.373395 82 WILDFLOWER DR,GALT,95632,CA,3,2,1262,Residential,Thu May 15 00:00:00 EDT 2008,209347,38.259708,-121.311616 8652 BANTON CIR,ELK GROVE,95624,CA,4,2,1740,Residential,Thu May 15 00:00:00 EDT 2008,211500,38.444,-121.370993 8428 MISTY PASS WAY,ANTELOPE,95843,CA,3,2,1517,Residential,Thu May 15 00:00:00 EDT 2008,212000,38.722959,-121.347115 7958 ROSEVIEW WAY,SACRAMENTO,95828,CA,3,2,1450,Residential,Thu May 15 00:00:00 EDT 2008,213000,38.467836,-121.410366 9020 LUKEN CT,ELK GROVE,95624,CA,3,2,1416,Residential,Thu May 15 00:00:00 EDT 2008,216000,38.451398,-121.366614 7809 VALLECITOS WAY,SACRAMENTO,95828,CA,3,1,888,Residential,Thu May 15 00:00:00 EDT 2008,216021,38.508217,-121.411207 8445 OLD AUBURN RD,CITRUS HEIGHTS,95610,CA,3,2,1882,Residential,Thu May 15 00:00:00 EDT 2008,219000,38.715423,-121.246743 10085 ATKINS DR,ELK GROVE,95757,CA,3,2,1302,Residential,Thu May 15 00:00:00 EDT 2008,219794,38.390893,-121.437821 9185 CERROLINDA CIR,ELK GROVE,95758,CA,3,2,1418,Residential,Thu May 15 00:00:00 EDT 2008,220000,38.424497,-121.426595 9197 CORTINA CIR,ROSEVILLE,95678,CA,3,2,0,Condo,Thu May 15 00:00:00 EDT 2008,220000,38.793152,-121.290025 5429 HESPER WAY,CARMICHAEL,95608,CA,4,2,1319,Residential,Thu May 15 00:00:00 EDT 2008,220000,38.665104,-121.315901 1178 WARMWOOD CT,GALT,95632,CA,4,2,1770,Residential,Thu May 15 00:00:00 EDT 2008,220000,38.289544,-121.284607 4900 ELUDE CT,SACRAMENTO,95842,CA,4,2,1627,Residential,Thu May 15 00:00:00 EDT 2008,223000,38.69674,-121.350519 3557 SODA WAY,SACRAMENTO,95834,CA,0,0,0,Residential,Thu May 15 00:00:00 EDT 2008,224000,38.631026,-121.501879 3528 SAINT GEORGE DR,SACRAMENTO,95821,CA,3,1,1040,Residential,Thu May 15 00:00:00 EDT 2008,224000,38.629468,-121.376445 7381 WASHBURN WAY,NORTH HIGHLANDS,95660,CA,3,1,960,Residential,Thu May 15 00:00:00 EDT 2008,224252,38.70355,-121.375103 2181 WINTERHAVEN CIR,CAMERON PARK,95682,CA,3,2,0,Residential,Thu May 15 00:00:00 EDT 2008,224500,38.69757,-120.995739 7540 HICKORY AVE,ORANGEVALE,95662,CA,3,1,1456,Residential,Thu May 15 00:00:00 EDT 2008,225000,38.703056,-121.235221 5024 CHAMBERLIN CIR,ELK GROVE,95757,CA,3,2,1450,Residential,Thu May 15 00:00:00 EDT 2008,228000,38.389756,-121.446246 2400 INVERNESS DR,LINCOLN,95648,CA,3,2,1358,Residential,Thu May 15 00:00:00 EDT 2008,229027,38.897814,-121.324691 5 BISHOPGATE CT,SACRAMENTO,95823,CA,4,2,1329,Residential,Thu May 15 00:00:00 EDT 2008,229500,38.467936,-121.445477 5601 REXLEIGH DR,SACRAMENTO,95823,CA,4,2,1715,Residential,Thu May 15 00:00:00 EDT 2008,230000,38.445342,-121.441504 1909 YARNELL WAY,ELK GROVE,95758,CA,3,2,1262,Residential,Thu May 15 00:00:00 EDT 2008,230000,38.417382,-121.484325 9169 GARLINGTON CT,SACRAMENTO,95829,CA,4,3,2280,Residential,Thu May 15 00:00:00 EDT 2008,232425,38.457679,-121.35962 6932 RUSKUT WAY,SACRAMENTO,95823,CA,3,2,1477,Residential,Thu May 15 00:00:00 EDT 2008,234000,38.499893,-121.45889 7933 DAFFODIL WAY,CITRUS HEIGHTS,95610,CA,3,2,1216,Residential,Thu May 15 00:00:00 EDT 2008,235000,38.708824,-121.256803 8304 RED FOX WAY,ELK GROVE,95758,CA,4,2,1685,Residential,Thu May 15 00:00:00 EDT 2008,235301,38.417,-121.397424 3882 YELLOWSTONE LN,EL DORADO HILLS,95762,CA,3,2,1362,Residential,Thu May 15 00:00:00 EDT 2008,235738,38.655245,-121.075915 snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/000077500000000000000000000000001361131222100231635ustar00rootroot00000000000000snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/Snakefile000066400000000000000000000005731361131222100250140ustar00rootroot00000000000000localrules: all rule all: input: '.done' rule fails_sometimes: output: '.done' resources: mem=lambda wildcards, attempt: 100 * attempt shell: r""" echo {resources.mem} if [[ ! -f ".first" ]]; then touch .first exit 1 else echo {resources.mem} > {output} fi """ snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/expected-results/000077500000000000000000000000001361131222100264635ustar00rootroot00000000000000snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/expected-results/.done000066400000000000000000000000041361131222100274030ustar00rootroot00000000000000200 snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/qsub000077500000000000000000000002071361131222100240620ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM sh $1 exit 0 snakemake-5.10.0/tests/test_restartable_job_cmd_exit_1/qsub.py000077500000000000000000000004771361131222100245220ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import os import random from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) with open("qsub.log", "a") as log: print(job_properties, file=log) print(random.randint(1, 100)) os.system("sh {}".format(jobscript)) snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/000077500000000000000000000000001361131222100233725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/Snakefile000066400000000000000000000001711361131222100252150ustar00rootroot00000000000000localrules: all rule all: input: '.done' rule fails_sometimes: output: '.done' shell: r""" touch .done """ snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/expected-results/000077500000000000000000000000001361131222100266725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/expected-results/.done000066400000000000000000000000001361131222100276060ustar00rootroot00000000000000snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/qsub000077500000000000000000000003451361131222100242740ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # ensure to fail on the first submission if [[ ! -f ".first" ]]; then touch .first exit 1 fi # simulate printing of job id by a random number echo $RANDOM sh $1 snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/qsub.log000066400000000000000000000000351361131222100250450ustar00rootroot00000000000000Mo 12. Dez 14:36:23 CET 2016 snakemake-5.10.0/tests/test_restartable_job_qsub_exit_1/qsub.py000077500000000000000000000004771361131222100247310ustar00rootroot00000000000000#!/usr/bin/env python3 import sys import os import random from snakemake.utils import read_job_properties jobscript = sys.argv[1] job_properties = read_job_properties(jobscript) with open("qsub.log", "a") as log: print(job_properties, file=log) print(random.randint(1, 100)) os.system("sh {}".format(jobscript)) snakemake-5.10.0/tests/test_rule_defined_in_for_loop/000077500000000000000000000000001361131222100227375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_rule_defined_in_for_loop/Snakefile000066400000000000000000000003761361131222100245710ustar00rootroot00000000000000rule all: input: 'iteration-02.txt' for i in range(2, 4): rule: output: fasta='iteration-{nr:02d}.txt'.format(nr=i) input: fasta='iteration-{nr:02d}.txt'.format(nr=i-1) run: shell("cp -p {input.fasta} {output.fasta}") snakemake-5.10.0/tests/test_rule_defined_in_for_loop/expected-results/000077500000000000000000000000001361131222100262375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_rule_defined_in_for_loop/expected-results/iteration-01.txt000066400000000000000000000000001361131222100312020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_rule_defined_in_for_loop/expected-results/iteration-02.txt000066400000000000000000000000001361131222100312030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_rule_defined_in_for_loop/iteration-01.txt000066400000000000000000000000001361131222100257020ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/000077500000000000000000000000001361131222100173505ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/1.a000066400000000000000000000000001361131222100176400ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/2.a000066400000000000000000000000001361131222100176410ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/3.a000066400000000000000000000000001361131222100176420ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/4.a000066400000000000000000000000001361131222100176430ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/5.a000066400000000000000000000000001361131222100176440ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledag/Snakefile000066400000000000000000000006051361131222100211750ustar00rootroot00000000000000 rule all: input: expand("{id}.d", id="1 2 3 4 5".split()) rule rule1: input: "{id}.c" output: "{id}.d" shell: "touch {output}" rule rule2: input: lambda wildcards: (expand("{id}.b", id="1 2 3".split()) if wildcards.id == "1" else expand("{id}.b", id=wildcards.id)) output: "{id}.c" shell: "touch {output}" rule rule3: input: "{id}.a" output: "{id}.b" shell: "touch {output}" snakemake-5.10.0/tests/test_ruledeps/000077500000000000000000000000001361131222100175505ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledeps/Snakefile000066400000000000000000000005711361131222100213770ustar00rootroot00000000000000 rule all: input: "test.out" rule a: output: "{sample,.+}.in" shell: "touch {output}" rule b: input: rules.a.output output: "{sample}.inter" shell: "touch {output}" rule c: input: rules.a.output output: "{sample}.inter" shell: "exit 1" rule d: input: rules.b.output output: "{sample}.out" shell: "touch {output}" snakemake-5.10.0/tests/test_ruledeps/expected-results/000077500000000000000000000000001361131222100230505ustar00rootroot00000000000000snakemake-5.10.0/tests/test_ruledeps/expected-results/test.out000066400000000000000000000000001361131222100245460ustar00rootroot00000000000000snakemake-5.10.0/tests/test_run_namedlist/000077500000000000000000000000001361131222100205715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_run_namedlist/Snakefile000066400000000000000000000001521361131222100224130ustar00rootroot00000000000000rule: output: txt='file.txt' run: shell('touch {output.txt}') os.stat(output.txt) snakemake-5.10.0/tests/test_run_namedlist/expected-results/000077500000000000000000000000001361131222100240715ustar00rootroot00000000000000snakemake-5.10.0/tests/test_run_namedlist/expected-results/file.txt000066400000000000000000000000001361131222100255370ustar00rootroot00000000000000snakemake-5.10.0/tests/test_same_wildcard/000077500000000000000000000000001361131222100205235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_same_wildcard/Snakefile000066400000000000000000000002121361131222100223420ustar00rootroot00000000000000 rule: input: "test_test.out" rule: input: "{name}_{name}.in" output: "{name}_{name}.out" shell: "echo {wildcards.name} > {output}" snakemake-5.10.0/tests/test_same_wildcard/expected-results/000077500000000000000000000000001361131222100240235ustar00rootroot00000000000000snakemake-5.10.0/tests/test_same_wildcard/expected-results/test_test.out000066400000000000000000000000051361131222100265650ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_same_wildcard/test_test.in000066400000000000000000000000051361131222100230640ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_schema.py000066400000000000000000000070011361131222100175350ustar00rootroot00000000000000import json import pytest from snakemake.utils import validate import pandas as pd CONFIG_SCHEMA = """$schema: "http://json-schema.org/draft-07/schema#" description: Configuration schema type: object properties: param: type: object description: parameter settings # Necessary in case config is empty! default: {} properties: foo: type: string default: bar """ BAR_SCHEMA = """definitions: bar: type: string description: bar entry default: foo """ BAR_JSON_SCHEMA = { "definitions": { "jsonbar": {"type": "string", "description": "bar entry", "default": "foo"} } } DF_SCHEMA = """$schema: "http://json-schema.org/draft-04/schema#" description: an entry in the sample sheet properties: sample: type: string description: sample name/identifier condition: type: string description: sample condition that will be compared during differential expression analysis (e.g. a treatment, a tissue time, a disease) case: type: boolean default: true description: boolean that indicates if sample is case or control date: type: string format: date-time description: collection date default: "2018-10-10" required: - sample - condition """ @pytest.fixture def schemadir(tmpdir): p = tmpdir.mkdir("schema") return p @pytest.fixture def bar_schema(schemadir): p = schemadir.join("bar.schema.yaml") p.write(BAR_SCHEMA) return p @pytest.fixture def json_bar_schema(schemadir): p = schemadir.join("bar.schema.json") p.write(json.dumps(BAR_JSON_SCHEMA)) return p @pytest.fixture def df_schema(schemadir): p = schemadir.join("df.schema.yaml") p.write(DF_SCHEMA) return p @pytest.fixture def config_schema(schemadir): p = schemadir.join("config.schema.yaml") p.write(CONFIG_SCHEMA) return p @pytest.fixture def config_schema_ref(schemadir, bar_schema, json_bar_schema): p = schemadir.join("config.ref.schema.yaml") p.write( CONFIG_SCHEMA + "\n".join( [ " bar:", ' default: "yaml"', ' $ref: "{bar}"'.format( bar=str(bar_schema) + "#/definitions/bar" ), " jsonbar:", ' default: "json"', ' $ref: "{bar}"'.format( bar=str(json_bar_schema) + "#/definitions/jsonbar" ), "", ] ) ) return p def test_config(config_schema): config = {} validate(config, str(config_schema), False) assert config == {} validate(config, str(config_schema)) assert dict(config) == {"param": {"foo": "bar"}} def test_config_ref(config_schema_ref): config = {} validate(config, str(config_schema_ref)) assert config["param"]["foo"] == "bar" assert config["param"]["bar"] == "yaml" assert config["param"]["jsonbar"] == "json" # Make sure regular validator works config["param"]["bar"] = 1 config["param"]["jsonbar"] = 2 from snakemake import WorkflowError with pytest.raises(WorkflowError): validate(config, str(config_schema_ref), False) def test_dataframe(df_schema): df = pd.DataFrame([{"sample": "foo", "condition": "bar"}]) validate(df, str(df_schema), False) assert sorted(df.columns) == sorted(["sample", "condition"]) validate(df, str(df_schema)) assert sorted(df.columns) == sorted(["sample", "condition", "case", "date"]) assert df.case.loc[0] snakemake-5.10.0/tests/test_script/000077500000000000000000000000001361131222100172315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_script/Snakefile000066400000000000000000000015261361131222100210610ustar00rootroot00000000000000 configfile: "config.yaml" rule all: input: "test.out", "test.html", "rel_source.out", "julia.out" rule: input: "test.in" output: txt="test.out" params: xy=True conda: "envs/r.yaml" script: "scripts/test.R" rule: output: "test.in" script: "scripts/test.py" rule: output: "test.html" params: test="testparam" conda: "envs/r.yaml" script: "scripts/test.Rmd" rule: output: "rel_source.out" conda: "envs/r.yaml" script: "scripts/rel_source.R" rule: input: "test.in", named_input="test.in" output: "julia.out" params: integer=123 conda: "envs/julia.yaml" script: "scripts/test.jl" snakemake-5.10.0/tests/test_script/config.yaml000066400000000000000000000000131361131222100213540ustar00rootroot00000000000000test: true snakemake-5.10.0/tests/test_script/envs/000077500000000000000000000000001361131222100202045ustar00rootroot00000000000000snakemake-5.10.0/tests/test_script/envs/julia.yaml000066400000000000000000000000771361131222100222000ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - julia snakemake-5.10.0/tests/test_script/envs/r.yaml000066400000000000000000000002631361131222100213320ustar00rootroot00000000000000channels: - conda-forge - bioconda dependencies: - r-base =3.6.1 - r-rmarkdown =1.17 - xorg-libxrender - xorg-libxext - xorg-libxau - xorg-libxdmcp - xorg-libsm snakemake-5.10.0/tests/test_script/expected-results/000077500000000000000000000000001361131222100225315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_script/expected-results/julia.out000066400000000000000000000000251361131222100243630ustar00rootroot00000000000000Julia test succeded! snakemake-5.10.0/tests/test_script/expected-results/rel_source.out000066400000000000000000000000021361131222100254140ustar00rootroot00000000000000Hisnakemake-5.10.0/tests/test_script/expected-results/test.html000066400000000000000000030307171361131222100244110ustar00rootroot00000000000000 Test Report

R Markdown

This is an R Markdown document.

Test include from snakemake testparam.

snakemake-5.10.0/tests/test_script/expected-results/test.in000066400000000000000000000000061361131222100240340ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_script/expected-results/test.out000066400000000000000000000000061361131222100242350ustar00rootroot000000000000001 2 3 snakemake-5.10.0/tests/test_script/scripts/000077500000000000000000000000001361131222100207205ustar00rootroot00000000000000snakemake-5.10.0/tests/test_script/scripts/rel_source.R000066400000000000000000000001051361131222100232010ustar00rootroot00000000000000snakemake@source("source_me.R") cat(hi(),file=snakemake@output[[1]]) snakemake-5.10.0/tests/test_script/scripts/source_me.R000066400000000000000000000000371361131222100230240ustar00rootroot00000000000000hi <- function(){return("Hi")} snakemake-5.10.0/tests/test_script/scripts/test.R000066400000000000000000000005541361131222100220260ustar00rootroot00000000000000print(snakemake@wildcards) print(snakemake@threads) print(snakemake@log) print(snakemake@config) print(snakemake@params) if(snakemake@params[["xy"]] != TRUE) { stop("Error evaluating param.") } if(snakemake@config[["test"]] != TRUE) { stop("Error evaluating config.") } values <- scan(snakemake@input[[1]]) write(values, file = snakemake@output[["txt"]]) snakemake-5.10.0/tests/test_script/scripts/test.Rmd000066400000000000000000000005471361131222100223510ustar00rootroot00000000000000--- title: "Test Report" author: "Mattias" date: "March 22, 2017" output: html_document: highlight: tango number_sections: no theme: default toc: yes toc_depth: 3 toc_float: collapsed: no smooth_scroll: yes --- ## R Markdown This is an R Markdown document. Test include from snakemake `r snakemake@params[["test"]]`. snakemake-5.10.0/tests/test_script/scripts/test.jl000066400000000000000000000005161361131222100222300ustar00rootroot00000000000000println("Julia script executing!") @assert snakemake.config["test"] == true @assert snakemake.params["integer"] == 123 @assert snakemake.output[1] == "julia.out" @assert snakemake.input[1] == "test.in" @assert snakemake.input["named_input"] == "test.in" f = open(snakemake.output[1], "w") println(f, "Julia test succeded!") close(f)snakemake-5.10.0/tests/test_script/scripts/test.py000066400000000000000000000001111361131222100222420ustar00rootroot00000000000000with open(snakemake.output[0], "w") as out: print(1, 2, 3, file=out) snakemake-5.10.0/tests/test_shadow/000077500000000000000000000000001361131222100172125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shadow/Snakefile000066400000000000000000000077161361131222100210510ustar00rootroot00000000000000rule all: input: "simple_shallow.out", "simple_full.out", "absolute_exists.out", "touched.out", "log_exists.out", "protected.out", "benchmark_exists.out", "minimal_ok.out" rule shallow: input: "test.in" output: "simple_shallow.out" shadow: "shallow" shell: """ echo 1 > junk.out cat {input} >> {output} echo simple_shallow >> {output} test ! -f more_junk.out """ rule full: input: "test.in" output: "simple_full.out" shadow: "full" shell: """ echo 1 > more_junk.out cat {input} > {output} echo simple_full >> {output} test ! -f junk.out """ rule absolute_link: input: "test.in" output: "absolute.out", "absolute_link.out" shadow: "shallow" shell: """ echo 1 > absolute.out DIRNAME=$(perl -e 'use Cwd "abs_path"; print abs_path(shift)' .) ln -s $DIRNAME/absolute.out absolute_link.out """ # Snakemake has check_broken_symlink so we just need to test existence rule absolute_stat: input: "absolute_link.out" output: touch("absolute_exists.out") shell: """ test -f {input} """ rule log_stat: input: "touched.out" output: touch("log_exists.out") shell: """ test -f shadow.log """ rule touch: output: touch("touched.out") log: "shadow.log" shadow: "shallow" shell: """ echo 1 > {log} """ rule protected: output: protected("protected.out") shadow: "shallow" shell: """ touch {output} """ rule benchmark: output: touch("benchmark.out") benchmark: "benchmark.txt" shadow: "shallow" shell: """ touch {output} """ rule benchmark_stat: input: "benchmark.out" output: touch("benchmark_exists.out") shell: """ test -f benchmark.txt """ # Setup files for testing of shadow: "minimal" rule minimal_setup: input: "test.in" output: "subdir1/subdir2/test.in", "subdir1/subdir2/test.symbolic.in" shell: """ cp -P {input} {output[0]} cd subdir1/subdir2 ln -s test.in test.symbolic.in """ # Tests relative inputs/outputs and in the current dir rule minimal_rel_curdir: input: "test.in" output: protected("simple_minimal.out") benchmark: "benchmark_minimal.txt" log: "minimal.log" shadow: "minimal" shell: """ touch minimal_junk.out cat {input} >> {output} echo simple_minimal >> {output} echo minimal_log > {log} """ # Tests relative inputs/outputs in subdirectories rule minimal_rel_subdir: input: "subdir1/subdir2/test.in" output: "outdir/minimal.out" shadow: "minimal" shell: """ touch outdir/minimal_junk.out touch {output} """ # Tests symbolic input/output rule minimal_symbolic: input: "subdir1/subdir2/test.symbolic.in" output: "outdir/minimal_real.out", "outdir/minimal_symbolic.out" shadow: "minimal" shell: """ touch outdir/minimal_real.out cd outdir ln -s minimal_real.out minimal_symbolic.out """ # Tests absolute input/output rule minimal_absolute: input: os.path.join(os.getcwd(),"test.in") output: os.path.join(os.getcwd(),"outdir/minimal_absolute.out") shadow: "minimal" shell: """ touch {output} """ # Aggregates tests for shadow: "minimal" rule minimal_ok: input: "simple_minimal.out", "outdir/minimal.out", "outdir/minimal_symbolic.out", os.path.join(os.getcwd(),"outdir/minimal_absolute.out") output: "minimal_ok.out" shell: """ #test ! -w {input[0]} test -f benchmark_minimal.txt test -f minimal.log test ! -f minimal_junk.out test ! -f outdir/minimal_junk.out touch {output} """ snakemake-5.10.0/tests/test_shadow/expected-results/000077500000000000000000000000001361131222100225125ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shadow/expected-results/simple_full.out000066400000000000000000000000171361131222100255540ustar00rootroot00000000000000in simple_full snakemake-5.10.0/tests/test_shadow/expected-results/simple_minimal.out000066400000000000000000000000221361131222100262340ustar00rootroot00000000000000in simple_minimal snakemake-5.10.0/tests/test_shadow/expected-results/simple_shallow.out000066400000000000000000000000221361131222100262570ustar00rootroot00000000000000in simple_shallow snakemake-5.10.0/tests/test_shadow/test.in000066400000000000000000000000031361131222100205120ustar00rootroot00000000000000in snakemake-5.10.0/tests/test_shadow_prefix/000077500000000000000000000000001361131222100205675ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shadow_prefix/Snakefile000066400000000000000000000002131361131222100224070ustar00rootroot00000000000000rule all: input: "test.in" output: "shadow_prefix" shadow: "shallow" shell: "test -h shadowdir && cp -L {input} {output}" snakemake-5.10.0/tests/test_shadow_prefix/expected-results/000077500000000000000000000000001361131222100240675ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shadow_prefix/expected-results/shadow_prefix000066400000000000000000000000031361131222100266450ustar00rootroot00000000000000in snakemake-5.10.0/tests/test_shadow_prefix/qsub000077500000000000000000000001751361131222100214720ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM $1 snakemake-5.10.0/tests/test_shadow_prefix/test.in000066400000000000000000000000031361131222100220670ustar00rootroot00000000000000in snakemake-5.10.0/tests/test_shell/000077500000000000000000000000001361131222100170345ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shell/Snakefile000066400000000000000000000003321361131222100206560ustar00rootroot00000000000000 rule: input: "test.in" output: "test.out" run: test = shell("echo 42;", read=True) assert int(test) == 42 with open(output[0], "w") as f: for l in shell("cat {input}", iterable=True): print(l, file=f) snakemake-5.10.0/tests/test_shell/expected-results/000077500000000000000000000000001361131222100223345ustar00rootroot00000000000000snakemake-5.10.0/tests/test_shell/expected-results/test.out000066400000000000000000000000101361131222100240330ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_shell/test.in000066400000000000000000000000101361131222100203320ustar00rootroot00000000000000foo bar snakemake-5.10.0/tests/test_singularity/000077500000000000000000000000001361131222100202775ustar00rootroot00000000000000snakemake-5.10.0/tests/test_singularity/Snakefile000066400000000000000000000006201361131222100221210ustar00rootroot00000000000000 rule a: input: "test.txt" output: "test.out" singularity: "docker://bash" shell: 'ls {input}; echo "test" > {output}' rule b: output: "test.txt" shell: "touch {output}" rule c: output: "invalid.txt" singularity: "docker://invalid/image url even with whitespace" shell: "touch {output}" snakemake-5.10.0/tests/test_singularity/expected-results/000077500000000000000000000000001361131222100235775ustar00rootroot00000000000000snakemake-5.10.0/tests/test_singularity/expected-results/test.out000066400000000000000000000000051361131222100253020ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_singularity_conda/000077500000000000000000000000001361131222100214435ustar00rootroot00000000000000snakemake-5.10.0/tests/test_singularity_conda/Snakefile000066400000000000000000000003631361131222100232710ustar00rootroot00000000000000 singularity: "docker://continuumio/miniconda3" rule a: output: "test.out" conda: "test-env.yaml" shell: "ls /singularity; " # this fails if not in singularity container "bwa 2> {output} || true" snakemake-5.10.0/tests/test_singularity_conda/expected-results/000077500000000000000000000000001361131222100247435ustar00rootroot00000000000000snakemake-5.10.0/tests/test_singularity_conda/expected-results/test.out000066400000000000000000000022341361131222100264540ustar00rootroot00000000000000 Program: bwa (alignment via Burrows-Wheeler transformation) Version: 0.7.17-r1188 Contact: Heng Li Usage: bwa [options] Command: index index sequences in the FASTA format mem BWA-MEM algorithm fastmap identify super-maximal exact matches pemerge merge overlapping paired ends (EXPERIMENTAL) aln gapped/ungapped alignment samse generate alignment (single ended) sampe generate alignment (paired ended) bwasw BWA-SW for long queries shm manage indices in shared memory fa2pac convert FASTA to PAC format pac2bwt generate BWT from PAC pac2bwtgen alternative algorithm for generating BWT bwtupdate update .bwt to the new format bwt2sa generate SA from BWT and Occ Note: To use BWA, you need to first index the genome with `bwa index'. There are three alignment algorithms in BWA: `mem', `bwasw', and `aln/samse/sampe'. If you are not sure which to use, try `bwa mem' first. Please `man ./bwa.1' for the manual. snakemake-5.10.0/tests/test_singularity_conda/test-env.yaml000066400000000000000000000001061361131222100240710ustar00rootroot00000000000000channels: - bioconda - conda-forge dependencies: - bwa ==0.7.17 snakemake-5.10.0/tests/test_spaces_in_fnames/000077500000000000000000000000001361131222100212225ustar00rootroot00000000000000snakemake-5.10.0/tests/test_spaces_in_fnames/Snakefile000066400000000000000000000011221361131222100230420ustar00rootroot00000000000000rule indelrealigner: input: bam="{base}.bam",intervals="{base}.intervals",bai='{base}.bam.bai' output: bam=temp("{base} realigned.bam") params: batch="-q ccr -l nodes=1:gpfs" threads: 1 shell: "touch {output:q}" rule realignertargetcreator: input: "{base}.bam", "{base}.bam.bai" output: temp("{base}.intervals") params: batch="-q ccr -l nodes=1:gpfs" threads: 32 shell: "touch {output:q}" rule indexbam: threads: 1 input: '{base}.bam' output: temp('{base}.bam.bai') params: batch="-q ccr -l nodes=1:gpfs" shell: 'touch {output:q}' snakemake-5.10.0/tests/test_spaces_in_fnames/expected-results/000077500000000000000000000000001361131222100245225ustar00rootroot00000000000000snakemake-5.10.0/tests/test_spaces_in_fnames/expected-results/test bam file realigned.bam000066400000000000000000000000001361131222100315030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_spaces_in_fnames/qsub000077500000000000000000000001751361131222100221250ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM $1 snakemake-5.10.0/tests/test_spaces_in_fnames/test bam file.bam000066400000000000000000000000001361131222100242700ustar00rootroot00000000000000snakemake-5.10.0/tests/test_speed/000077500000000000000000000000001361131222100170255ustar00rootroot00000000000000snakemake-5.10.0/tests/test_speed/Snakefile000066400000000000000000000006551361131222100206570ustar00rootroot00000000000000 rule all: input: expand("step3/{sample}.txt", sample=range(100)) rule a: output: "step1/{sample}.txt" shell: "touch {output}" rule b: input: "step1/{sample}.txt" output: "step2/{sample}.txt" shell: "cp {input} {output}" rule c: input: "step2/{sample}.txt" output: "step3/{sample}.txt" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_srcdir/000077500000000000000000000000001361131222100172135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_srcdir/Snakefile000066400000000000000000000001711361131222100210360ustar00rootroot00000000000000rule: output: "test.out" params: srcdir("script.sh") shell: "sh {params} > {output}" snakemake-5.10.0/tests/test_srcdir/expected-results/000077500000000000000000000000001361131222100225135ustar00rootroot00000000000000snakemake-5.10.0/tests/test_srcdir/expected-results/test.out000066400000000000000000000000051361131222100242160ustar00rootroot00000000000000test snakemake-5.10.0/tests/test_srcdir/script.sh000066400000000000000000000000241361131222100210470ustar00rootroot00000000000000#!/bin/sh echo test snakemake-5.10.0/tests/test_static_remote/000077500000000000000000000000001361131222100205675ustar00rootroot00000000000000snakemake-5.10.0/tests/test_static_remote/S3MockedForStaticTest.py000066400000000000000000000115301361131222100252300ustar00rootroot00000000000000__author__ = "Christopher Tomkins-Tinch" __copyright__ = "Copyright 2015, Christopher Tomkins-Tinch" __email__ = "tomkinsc@broadinstitute.org" __license__ = "MIT" # built-ins import os, sys from contextlib import contextmanager import pickle import time import threading import functools # intra-module from snakemake.remote.S3 import RemoteObject as S3RemoteObject, RemoteProvider as S3RemoteProvider from snakemake.remote.S3 import S3Helper from snakemake.decorators import dec_all_methods from snakemake.exceptions import WorkflowError from snakemake.logging import logger try: # third-party import boto3 from moto import mock_s3 import filechunkio except ImportError as e: raise WorkflowError("The Python 3 packages 'moto', boto' and 'filechunkio' " + "need to be installed to use S3Mocked remote() file functionality. %s" % e.msg) def noop(): pass def pickled_moto_wrapper(func): """ This is a class decorator that in turn decorates all methods within a class to mock out boto calls with moto-simulated ones. Since the moto backends are not presistent across calls by default, the wrapper also pickles the bucket state after each function call, and restores it before execution. This way uploaded files are available for follow-on tasks. Since snakemake may execute with multiple threads it also waits for the pickled bucket state file to be available before loading it in. This is a hackey alternative to using proper locks, but works ok in practice. """ def wrapper_func(self, *args, **kwargs): moto_context_file = "motoState.p" moto_context = mock_s3() moto_context.start() moto_context.backends["global"].reset = noop # load moto buckets from pickle if os.path.isfile(moto_context_file) and os.path.getsize(moto_context_file) > 0: with file_lock(moto_context_file): with open( moto_context_file, "rb" ) as f: moto_context.backends["global"].buckets = pickle.load( f ) mocked_function = moto_context(func) retval = mocked_function(self, *args, **kwargs) with file_lock(moto_context_file): with open( moto_context_file, "wb" ) as f: pickle.dump(moto_context.backends["global"].buckets, f) moto_context.stop() return retval functools.update_wrapper(wrapper_func, func) wrapper_func.__wrapped__ = func return wrapper_func @dec_all_methods(pickled_moto_wrapper, prefix=None) class RemoteProvider(S3RemoteProvider): def __init__(self, *args, **kwargs): super(RemoteProvider, self).__init__(*args, **kwargs) @dec_all_methods(pickled_moto_wrapper, prefix=None) class RemoteObject(S3RemoteObject): """ This is a derivative of the S3 remote provider that mocks out boto-based S3 calls using the "moto" Python package. Only the initializer is different; it "uploads" the input test file to the moto-simulated bucket at the start. """ def __init__(self, *args, keep_local=False, provider=None, **kwargs): super(RemoteObject, self).__init__(*args, keep_local=keep_local, provider=provider, **kwargs) bucket_name = 'test-static-remote-bucket' test_files = ('test.txt', 'out1.txt', 'out2.txt') s3 = boto3.resource('s3') s3.create_bucket(Bucket=bucket_name) # "Upload" files that should be in S3 before tests... s3c = S3Helper() for test_file in test_files: if not s3c.exists_in_bucket(bucket_name, test_file): logger.debug("Pre-populating remote bucket {} with file {}".format( bucket_name, test_file)) s3c.upload_to_s3(bucket_name, test_file) def mtime(self): if self.s3_key == 'test.txt': logger.debug("test.txt is new") return float('inf') elif self.s3_key.startswith('out'): logger.debug("{} is old".format(self.s3_key)) # For some reason this breaks if you return 0 return 5 else: logger.debug("Using real mtime for {}".format(self.s3_key)) return super().mtime() # ====== Helpers ===== def touch(fname, mode=0o666, dir_fd=None, **kwargs): # create lock file faster # https://stackoverflow.com/a/1160227 flags = os.O_CREAT | os.O_APPEND with os.fdopen(os.open(fname, flags=flags, mode=mode, dir_fd=dir_fd)) as f: os.utime(f.fileno() if os.utime in os.supports_fd else fname, dir_fd=None if os.supports_fd else dir_fd, **kwargs) @contextmanager def file_lock(filepath): lock_file = filepath + ".lock" while os.path.isfile(lock_file): time.sleep(2) touch(lock_file) try: yield finally: if os.path.isfile(lock_file): os.remove(lock_file) snakemake-5.10.0/tests/test_static_remote/Snakefile000066400000000000000000000027461361131222100224240ustar00rootroot00000000000000#import re, os, sys from S3MockedForStaticTest import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider() # remote dynamic file test # This makes use of a special provider that mocks up S3 using the moto # library so that boto calls hit local "buckets" rule all: input: # only keeping the files so we can copy them out to the cwd S3.remote("test-static-remote-bucket/out1.txt", keep_local=True), S3.remote("test-static-remote-bucket/out2.txt", keep_local=True) run: shell("for f in {input}; do mv $f ./; done") # This rule should run rule run_no_static: input: S3.remote('test-static-remote-bucket/test.txt', static=False) output: S3.remote('test-static-remote-bucket/out1.txt'), run: shell('''echo '{input} > {output}'; cat {input} | tee {output}''') # This rule should NOT run because Snakemake should assume that the # input file is static and has not changed. rule run_static: input: S3.remote('test-static-remote-bucket/test.txt', static=True) output: S3.remote('test-static-remote-bucket/out2.txt'), run: shell('''echo '{input} > {output}'; cat {input} | tee {output}''') raise Exception("This rule should never run") # after we finish, we need to remove the pickle storing # the local moto "buckets" so we are starting fresh # next time this test is run. This file is created by # the moto wrapper defined in S3Mocked.py onsuccess: shell("rm ./motoState.p") onerror: shell("rm ./motoState.p") snakemake-5.10.0/tests/test_static_remote/__init__.py000066400000000000000000000000001361131222100226660ustar00rootroot00000000000000snakemake-5.10.0/tests/test_static_remote/expected-results/000077500000000000000000000000001361131222100240675ustar00rootroot00000000000000snakemake-5.10.0/tests/test_static_remote/expected-results/out1.txt000066400000000000000000000000661361131222100255220ustar00rootroot00000000000000This file should overwrite out1.txt but not out2.txt. snakemake-5.10.0/tests/test_static_remote/expected-results/out2.txt000066400000000000000000000000621361131222100255170ustar00rootroot00000000000000This file should not be overwritten by the input. snakemake-5.10.0/tests/test_static_remote/out1.txt000066400000000000000000000000421361131222100222140ustar00rootroot00000000000000This file should get overwritten. snakemake-5.10.0/tests/test_static_remote/out2.txt000066400000000000000000000000621361131222100222170ustar00rootroot00000000000000This file should not be overwritten by the input. snakemake-5.10.0/tests/test_static_remote/test.txt000066400000000000000000000000661361131222100223110ustar00rootroot00000000000000This file should overwrite out1.txt but not out2.txt. snakemake-5.10.0/tests/test_subworkflows/000077500000000000000000000000001361131222100204745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_subworkflows/Snakefile000066400000000000000000000004541361131222100223230ustar00rootroot00000000000000import os.path subworkflow test02: workdir: config["subworkdir"] configfile: "subconfig.yaml" # test optional config files rule: input: "test.out" def test_in(wildcards): return test02("test.out") rule: input: test_in output: "test.out" shell: "cp {input} {output}" snakemake-5.10.0/tests/test_subworkflows/expected-results/000077500000000000000000000000001361131222100237745ustar00rootroot00000000000000snakemake-5.10.0/tests/test_subworkflows/expected-results/test.out000066400000000000000000000000101361131222100254730ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test_subworkflows/subconfig.yaml000066400000000000000000000000171361131222100233350ustar00rootroot00000000000000test: "xy" snakemake-5.10.0/tests/test_symlink_temp/000077500000000000000000000000001361131222100204405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_temp/Snakefile000066400000000000000000000011721361131222100222650ustar00rootroot00000000000000rule all: input: 'a.out', 'b.out' rule one: #the first rule in the work-flow input: '{sample}' output: temp('1.{sample}') shell: 'touch {output}' rule two: #this rule simply creates symbol link, following rule one input: '1.{sample}' output: temp('2.{sample}') shell: 'ln -s {input} {output}' rule three: #this rule simply creates symbol link, following rule two input: '2.{sample}' output: temp('3.{sample}') shell: 'ln -s {input} {output}' rule four: #this rule creates the final output, following rule three input: '3.{sample}' output: '{sample}.out' shell: 'touch {output}' snakemake-5.10.0/tests/test_symlink_temp/a000066400000000000000000000000001361131222100205710ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_temp/b000066400000000000000000000000001361131222100205720ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_temp/expected-results/000077500000000000000000000000001361131222100237405ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_temp/expected-results/.gitignore000066400000000000000000000000001361131222100257160ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_time_handling/000077500000000000000000000000001361131222100222755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_time_handling/Snakefile000066400000000000000000000051201361131222100241170ustar00rootroot00000000000000# vim: ft=python """Snakemake should work where the input/output of a rule is a symlink, no problem. Here we test that updates to symlinks are recognised and that the timestamps on symlinks are updated properly. Unfortunately this does not work on some Python builds where os.supports_follow_symlinks does not include utime(). This includes the Miniconda build used on continuum.io. Therefore this test is an expected failure on those systems. In practise, the impact of the bug is low - lutime() on a link will just be a no-op and users will see a warning message. Anyone for whom this is a real problem should install a fully-working version of Python. """ """Description of the test: input_file is a file that is 1 hour old input_link is a link to input file that is 4 hours old output_link is a symlink to input_link that is 2 hours old So output_link needs to be re-evaluated since the contents of input_file changed. rule main outputs the time difference between inout and output in hours which can be checked by Nose to ensure output_link gets touched by Snakemake but neither input_file nor input_link are changed. """ import os import time #Slightly crude way to stop this running twice if not os.path.exists("input_file"): def timestr(hr_delta=0): return time.strftime("%Y%m%d%H%M", time.localtime(time.time() - (hr_delta * 3600) - 5)) shell("touch -t {} input_file".format(timestr(1))) shell("ln -s input_file input_link") shell("touch -h -t {} input_link".format(timestr(4))) shell("ln -s input_link output_link") shell("touch -h -t {} output_link".format(timestr(2))) shell("ls -lR > /dev/stderr") rule main: output: "time_diff.txt" input: "output_link" run: time_diff1 = int( ( os.stat("output_link", follow_symlinks=False).st_mtime - os.stat("input_link", follow_symlinks=False).st_mtime ) / (60*60) ) time_diff2 = int( ( os.stat("output_link", follow_symlinks=False).st_mtime - os.stat("input_file", follow_symlinks=False).st_mtime ) / (60*60) ) # I expect the result "4 1" shell("ls -lR > /dev/stderr") shell("echo {time_diff1} {time_diff2} | tee time_diff.txt 2>&1") rule make_output: output: "output_link" input: "input_link" run: #shell("rm -f {output}") - no longer necessary shell("ln -s {input} {output}") #This next command should be undone by Snakemake which now touches all outpout shell("touch -h -t 201604011600 {output}") snakemake-5.10.0/tests/test_symlink_time_handling/expected-results/000077500000000000000000000000001361131222100255755ustar00rootroot00000000000000snakemake-5.10.0/tests/test_symlink_time_handling/expected-results/time_diff.txt000066400000000000000000000000041361131222100302560ustar00rootroot000000000000004 1 snakemake-5.10.0/tests/test_temp/000077500000000000000000000000001361131222100166725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp/Snakefile000066400000000000000000000011141361131222100205130ustar00rootroot00000000000000rule indelrealigner: input: bam="{base}.bam",intervals="{base}.intervals",bai='{base}.bam.bai' output: bam=temp("{base}.realigned.bam") params: batch="-q ccr -l nodes=1:gpfs" threads: 1 shell: "touch {output}" rule realignertargetcreator: input: "{base}.bam", "{base}.bam.bai" output: temp("{base}.intervals") params: batch="-q ccr -l nodes=1:gpfs" threads: 32 shell: "touch {output}" rule indexbam: threads: 1 input: '{base}.bam' output: temp('{base}.bam.bai') params: batch="-q ccr -l nodes=1:gpfs" shell: 'touch {output}' snakemake-5.10.0/tests/test_temp/expected-results/000077500000000000000000000000001361131222100221725ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp/expected-results/test.realigned.bam000066400000000000000000000000001361131222100255510ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp/qsub000077500000000000000000000001751361131222100175750ustar00rootroot00000000000000#!/bin/bash echo `date` >> qsub.log tail -n1 $1 >> qsub.log # simulate printing of job id by a random number echo $RANDOM $1 snakemake-5.10.0/tests/test_temp/test.bam000066400000000000000000000000001361131222100203200ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp_expand/000077500000000000000000000000001361131222100202315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp_expand/Snakefile000066400000000000000000000003231361131222100220530ustar00rootroot00000000000000 rule: input: "a.txt" output: "test.txt" shell: "touch {output}" rule: output: temp(expand("{ds}.txt", ds="a b c".split())) shell: "touch {output}" snakemake-5.10.0/tests/test_temp_expand/expected-results/000077500000000000000000000000001361131222100235315ustar00rootroot00000000000000snakemake-5.10.0/tests/test_temp_expand/expected-results/test.txt000066400000000000000000000000001361131222100252370ustar00rootroot00000000000000snakemake-5.10.0/tests/test_threads/000077500000000000000000000000001361131222100173575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_threads/Snakefile000066400000000000000000000002021361131222100211750ustar00rootroot00000000000000rule a: output: "test.out" threads: 20 run: print(threads) shell("echo {threads} > {output}") snakemake-5.10.0/tests/test_threads/expected-results/000077500000000000000000000000001361131222100226575ustar00rootroot00000000000000snakemake-5.10.0/tests/test_threads/expected-results/test.out000066400000000000000000000000031361131222100243600ustar00rootroot0000000000000020 snakemake-5.10.0/tests/test_threads0/000077500000000000000000000000001361131222100174375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_threads0/Snakefile000066400000000000000000000001451361131222100212630ustar00rootroot00000000000000rule a: output: "test.out" threads: 0 shell: "echo {threads} > {output}" snakemake-5.10.0/tests/test_threads0/expected-results/000077500000000000000000000000001361131222100227375ustar00rootroot00000000000000snakemake-5.10.0/tests/test_threads0/expected-results/test.out000066400000000000000000000000021361131222100244370ustar00rootroot000000000000000 snakemake-5.10.0/tests/test_tibanna/000077500000000000000000000000001361131222100173415ustar00rootroot00000000000000snakemake-5.10.0/tests/test_tibanna/README.md000066400000000000000000000007351361131222100206250ustar00rootroot00000000000000``` # the following has been performed by aws admin already tibanna deploy_unicorn --usergroup=johannes --buckets=snakemake-tibanna-test,snakemake-tibanna-test2 ``` ``` # run the following to do a test run pip install -U tibanna python cleanup.py # first delete output files that are already on s3 export TIBANNA_DEFAULT_STEP_FUNCTION_NAME=tibanna_unicorn_johannes snakemake --tibanna --use-conda --configfile=config.json --default-remote-prefix=snakemake-tibanna-test/1 ``` snakemake-5.10.0/tests/test_tibanna/Snakefile000066400000000000000000000035561361131222100211760ustar00rootroot00000000000000# vim: syntax=python # # step1 and step1a are grouped. step1b must run concurrently with step1+step1a. # step2 waits for step1, step1a and step1b to finish. # all is the final rule. # step1, step1a and step1b use a conda environment. # Input of step1, step1b and all are set remote. # Output of step2 is set remote. from snakemake.remote.S3 import RemoteProvider as S3RemoteProvider S3 = S3RemoteProvider() rule all: input: final = S3.remote("snakemake-tibanna-test/1/final_message"), # final = "snakemake-tibanna-test/1/final_message" final2 = S3.remote("snakemake-tibanna-test2/1/final_message") rule step1: conda: "env.yml" group: "mygroup" input: S3.remote("snakemake-tibanna-test/1/somefile") output: message = "message1" shell: """ echo {config[message]} > message1 cat snakemake-tibanna-test/1/somefile >> message1 """ rule step1a: conda: "env.yml" group: "mygroup" input: message = "message1" output: next_message = "next_message" shell: """ cat message1 message1 > next_message """ rule step1b: conda: "env.yml" input: S3.remote("snakemake-tibanna-test/1/somefile2") output: message = "message2" log: "log.txt" benchmark: "benchmark.txt" threads: 1 resources: mem_mb=1024 shell: """ echo "abcdefg" > message2 cat snakemake-tibanna-test/1/somefile2 >> message2 """ rule step2: input: message1 = "next_message", message2 = "message2" output: final = S3.remote("snakemake-tibanna-test/1/final_message"), # final = "snakemake-tibanna-test/1/final_message" final2 = S3.remote("snakemake-tibanna-test2/1/final_message") script: "scripts/step2.py" snakemake-5.10.0/tests/test_tibanna/cleanup.py000066400000000000000000000011501361131222100213370ustar00rootroot00000000000000import subprocess import boto3 # clean up local subprocess.call(['rm', '-rf', '.snakemake']) subprocess.call(['rm', '-rf', 'snakemake-tibanna-test']) # clean up s3 s3 = boto3.client('s3') s3.delete_objects(Bucket='snakemake-tibanna-test', Delete={'Objects': [{'Key': '1/message1'}, {'Key': '1/message2'}, {'Key': '1/next_message'}, {'Key': '1/final_message'}]}) s3.delete_objects(Bucket='snakemake-tibanna-test2', Delete={'Objects': [{'Key': '1/final_message'}]}) snakemake-5.10.0/tests/test_tibanna/config.json000066400000000000000000000000271361131222100215000ustar00rootroot00000000000000{"message": "hahaha"} snakemake-5.10.0/tests/test_tibanna/env.yml000066400000000000000000000000751361131222100206560ustar00rootroot00000000000000name: pairix channels: - bioconda dependencies: - pairix snakemake-5.10.0/tests/test_tibanna/expected-results/000077500000000000000000000000001361131222100226415ustar00rootroot00000000000000snakemake-5.10.0/tests/test_tibanna/expected-results/.gitkeep000066400000000000000000000000011361131222100242610ustar00rootroot00000000000000 snakemake-5.10.0/tests/test_tibanna/scripts/000077500000000000000000000000001361131222100210305ustar00rootroot00000000000000snakemake-5.10.0/tests/test_tibanna/scripts/step2.py000066400000000000000000000002331361131222100224350ustar00rootroot00000000000000from shutil import copyfile copyfile("message2", "snakemake-tibanna-test/1/final_message") copyfile("message2", "snakemake-tibanna-test2/1/final_message") snakemake-5.10.0/tests/test_touch/000077500000000000000000000000001361131222100170475ustar00rootroot00000000000000snakemake-5.10.0/tests/test_touch/Snakefile000066400000000000000000000001101361131222100206630ustar00rootroot00000000000000rule: output: touch("test.out") shell: "exit 0" snakemake-5.10.0/tests/test_touch/expected-results/000077500000000000000000000000001361131222100223475ustar00rootroot00000000000000snakemake-5.10.0/tests/test_touch/expected-results/test.out000066400000000000000000000000001361131222100240450ustar00rootroot00000000000000snakemake-5.10.0/tests/test_unpack_dict/000077500000000000000000000000001361131222100202115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_unpack_dict/Snakefile000066400000000000000000000010511361131222100220320ustar00rootroot00000000000000rule all: input: 'prefix.out1.txt', 'prefix.out2.txt', def get_in(wildcards): result = {'one': '{wildcards.prefix}.in1.txt', 'two': '{wildcards.prefix}.in2.txt'} return {key: value.format(wildcards=wildcards) for key, value in result.items()} def get_out(): return {'one': '{prefix}.out1.txt', 'two': '{prefix}.out2.txt'} rule copy_files: input: unpack(get_in) output: **get_out() shell: r""" cp {input.one} {output.one} cp {input.two} {output.two} """ snakemake-5.10.0/tests/test_unpack_dict/expected-results/000077500000000000000000000000001361131222100235115ustar00rootroot00000000000000snakemake-5.10.0/tests/test_unpack_dict/expected-results/prefix.out1.txt000066400000000000000000000000021361131222100264260ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_unpack_dict/expected-results/prefix.out2.txt000066400000000000000000000000021361131222100264270ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_unpack_dict/prefix.in1.txt000066400000000000000000000000021361131222100227250ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_unpack_dict/prefix.in2.txt000066400000000000000000000000021361131222100227260ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_unpack_list/000077500000000000000000000000001361131222100202415ustar00rootroot00000000000000snakemake-5.10.0/tests/test_unpack_list/Snakefile000066400000000000000000000007561361131222100220750ustar00rootroot00000000000000rule all: input: 'prefix.out1.txt', 'prefix.out2.txt', def get_in(wildcards): result = ['{wildcards.prefix}.in1.txt', '{wildcards.prefix}.in2.txt'] return [x.format(wildcards=wildcards) for x in result] def get_out(): return ['{prefix}.out1.txt', '{prefix}.out2.txt'] rule copy_files: input: unpack(get_in) output: *get_out() shell: r""" cp {input[0]} {output[0]} cp {input[1]} {output[1]} """ snakemake-5.10.0/tests/test_unpack_list/expected-results/000077500000000000000000000000001361131222100235415ustar00rootroot00000000000000snakemake-5.10.0/tests/test_unpack_list/expected-results/prefix.out1.txt000066400000000000000000000000021361131222100264560ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_unpack_list/expected-results/prefix.out2.txt000066400000000000000000000000021361131222100264570ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_unpack_list/prefix.in1.txt000066400000000000000000000000021361131222100227550ustar00rootroot000000000000001 snakemake-5.10.0/tests/test_unpack_list/prefix.in2.txt000066400000000000000000000000021361131222100227560ustar00rootroot000000000000002 snakemake-5.10.0/tests/test_until/000077500000000000000000000000001361131222100170605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/Snakefile000066400000000000000000000023201361131222100207010ustar00rootroot00000000000000 rule all: input: "levelthree.txt", "independent.txt", expand("test{num}.final", num=[1, 2]) rule levelone: output: "levelone.txt" shell: "touch {output}" rule leveltwo_first: input: rules.levelone.output output: "leveltwo_first.txt" shell: "cp -f {input} {output}" rule leveltwo_second: input: rules.levelone.output output: "leveltwo_second.txt" shell: "cp -f {input} {output}" rule levelthree: # should not be created input: rules.leveltwo_first.output, rules.leveltwo_second.output output: "levelthree.txt" shell: "cat {input} > {output}" rule independent: # should be created in --omit-from but not --until output: "independent.txt" shell: "touch {output}" ###### Wildcard Rules ####### rule zeroth_wildcard: output: "test{num}.txt" shell: "touch {output}" rule first_wildcard: input: 'test{num}.txt' output: 'test{num}.first' shell: 'cp -f {input} {output}' rule second_wildcard: input: 'test{num}.first' output: 'test{num}.second' shell: 'cp -f {input} {output}' rule final_wildcard: input: 'test{num}.second' output: 'test{num}.final' shell: 'cp -f {input} {output}' snakemake-5.10.0/tests/test_until/expected-results/000077500000000000000000000000001361131222100223605ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/expected-results/levelone.txt000066400000000000000000000000001361131222100247200ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/expected-results/leveltwo_first.txt000066400000000000000000000000001361131222100261570ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/expected-results/leveltwo_second.txt000066400000000000000000000000001361131222100263030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/expected-results/test1.second000066400000000000000000000000001361131222100246030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_until/expected-results/test2.second000066400000000000000000000000001361131222100246040ustar00rootroot00000000000000snakemake-5.10.0/tests/test_update_config/000077500000000000000000000000001361131222100205345ustar00rootroot00000000000000snakemake-5.10.0/tests/test_update_config/Snakefile000066400000000000000000000004211361131222100223550ustar00rootroot00000000000000import yaml include: "echo.rule" include: "cp.rule" configfile: "test.yaml" rule all: input: "test.out.copy" output: yaml = "config.yaml" run: with open(str(output.yaml), "w") as fh: fh.write(yaml.dump(config, default_flow_style=False)) snakemake-5.10.0/tests/test_update_config/cp.rule000066400000000000000000000005041361131222100220260ustar00rootroot00000000000000# -*- snakemake -*- from snakemake.utils import update_config c = { 'cp': { 'options': '-f', }, } update_config(config, c) rule cp: params: options = config['cp']['options'] input: '{prefix}.out' output: temporary('{prefix}.out.copy') shell: 'cp {params.options} {input} {output}' snakemake-5.10.0/tests/test_update_config/echo.rule000066400000000000000000000002761361131222100223500ustar00rootroot00000000000000# -*- snakemake -*- configfile: 'echo.yaml' rule echo: params: options = config['echo']['options'] output: temporary('{prefix}.out') shell: 'echo "{params.options}" > {output}' snakemake-5.10.0/tests/test_update_config/echo.yaml000066400000000000000000000000241361131222100223320ustar00rootroot00000000000000echo: options: '' snakemake-5.10.0/tests/test_update_config/expected-results/000077500000000000000000000000001361131222100240345ustar00rootroot00000000000000snakemake-5.10.0/tests/test_update_config/expected-results/config.yaml000066400000000000000000000000501361131222100261600ustar00rootroot00000000000000cp: options: -u echo: options: echo snakemake-5.10.0/tests/test_update_config/test.yaml000066400000000000000000000000501361131222100223720ustar00rootroot00000000000000cp: options: -u echo: options: echo snakemake-5.10.0/tests/test_url_include/000077500000000000000000000000001361131222100202325ustar00rootroot00000000000000snakemake-5.10.0/tests/test_url_include/Snakefile000066400000000000000000000001701361131222100220540ustar00rootroot00000000000000 include: "https://github.com/snakemake/snakemake/raw/master/tests/test05/Snakefile" rule: input: "test.predictions" snakemake-5.10.0/tests/test_url_include/expected-results/000077500000000000000000000000001361131222100235325ustar00rootroot00000000000000snakemake-5.10.0/tests/test_url_include/expected-results/test.1.inter000066400000000000000000000000321361131222100257060ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.1.inter2000066400000000000000000000000321361131222100257700ustar00rootroot00000000000000testz0r Part test.1.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.2.inter000066400000000000000000000000321361131222100257070ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.2.inter2000066400000000000000000000000321361131222100257710ustar00rootroot00000000000000testz0r Part test.2.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.3.inter000066400000000000000000000000321361131222100257100ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.3.inter2000066400000000000000000000000321361131222100257720ustar00rootroot00000000000000testz0r Part test.3.inter snakemake-5.10.0/tests/test_url_include/expected-results/test.predictions000066400000000000000000000001161361131222100267540ustar00rootroot00000000000000testz0r Part test.1.inter testz0r Part test.2.inter testz0r Part test.3.inter snakemake-5.10.0/tests/test_url_include/test.in000066400000000000000000000000101361131222100215300ustar00rootroot00000000000000testz0r snakemake-5.10.0/tests/test_validate/000077500000000000000000000000001361131222100175165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_validate/Snakefile000066400000000000000000000006301361131222100213410ustar00rootroot00000000000000import pandas as pd from snakemake.utils import validate configfile: "config.yaml" validate(config, "config.schema.yaml") samples = pd.read_table(config["samples"]).set_index("sample", drop=False) validate(samples, "samples.schema.yaml") rule all: input: expand("test.{sample}.txt", sample=samples.index) rule a: output: "test.{sample}.txt" shell: "touch {output}" snakemake-5.10.0/tests/test_validate/config.fail.yaml000066400000000000000000000000441361131222100225570ustar00rootroot00000000000000samples: samples.tsv adapter: ACGTX snakemake-5.10.0/tests/test_validate/config.schema.yaml000066400000000000000000000003411361131222100231040ustar00rootroot00000000000000$schema: "http://json-schema.org/draft-06/schema#" description: snakemake configuration file type: object properties: samples: type: string adapter: type: string pattern: "^[ACGT]+$" required: - samples snakemake-5.10.0/tests/test_validate/config.yaml000066400000000000000000000000431361131222100216440ustar00rootroot00000000000000samples: samples.tsv adapter: ACGT snakemake-5.10.0/tests/test_validate/expected-results/000077500000000000000000000000001361131222100230165ustar00rootroot00000000000000snakemake-5.10.0/tests/test_validate/expected-results/test.A.txt000066400000000000000000000000001361131222100247030ustar00rootroot00000000000000snakemake-5.10.0/tests/test_validate/expected-results/test.B.txt000066400000000000000000000000001361131222100247040ustar00rootroot00000000000000snakemake-5.10.0/tests/test_validate/samples.schema.yaml000066400000000000000000000005741361131222100233130ustar00rootroot00000000000000$schema: "https://json-schema.org/draft-06/schema#" description: an entry in the sample sheet properties: sample: type: string description: sample name/identifier condition: type: string description: sample condition that will be compared during differential expression analysis (e.g. a treatment, a tissue time, a disease) required: - sample - condition snakemake-5.10.0/tests/test_validate/samples.tsv000066400000000000000000000000411361131222100217130ustar00rootroot00000000000000sample condition A tumor B blood snakemake-5.10.0/tests/test_wildcard_count_ambiguity/000077500000000000000000000000001361131222100230005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_count_ambiguity/Snakefile000066400000000000000000000002401361131222100246200ustar00rootroot00000000000000 rule all: input: "test.out" rule a: output: "{prefix}.out" shell: "touch {output}" rule b: output: "test.out" shell: "touch {output}" snakemake-5.10.0/tests/test_wildcard_count_ambiguity/expected-results/000077500000000000000000000000001361131222100263005ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_count_ambiguity/expected-results/test.out000066400000000000000000000000001361131222100277760ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/000077500000000000000000000000001361131222100212625ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/Snakefile000066400000000000000000000021301361131222100231020ustar00rootroot00000000000000 rule all: input: "stringbar.txt", "localbar.txt", "globalbar.txt", "ambig.u.ous.log" ##: NB: setting output: "{foo}.in" only works for globalwildcard rule ## since constraints will be set to "globalbar" rule infile: output: temp("{foo,(globalbar|localbar|stringbar)}.in") shell: "touch {output}" rule stringwildcard: input: "{foo}.in" output: "{foo,stringbar}.txt" log: "{foo}.log" shell: "echo {input} {output} {log} > {output}; touch {log}" rule localwildcard: input: "{foo}.in" output: "{foo}.txt" wildcard_constraints: foo="localbar" log: "{foo}.log" shell: "echo {input} {output} {log} > {output}; touch {log}" rule globalwildcard: input: "{foo}.in" output: "{foo}.txt" log: "{foo}.log" shell: "echo {input} {output} {log} > {output}; touch {log}" rule ambigouslog: output: "{foo}.{bar}.txt" log: "{foo}.{bar}.log" wildcard_constraints: foo="ambig", bar=".+" shell: "echo a={wildcards.foo} b={wildcards.bar} > {output}; touch {log}" wildcard_constraints: foo='globalbar', bar='out/{SM,[a-z]+}_{PU,[0-9]+}' snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/000077500000000000000000000000001361131222100245625ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/ambig.u.ous.txt000066400000000000000000000000201361131222100274420ustar00rootroot00000000000000a=ambig b=u.ous snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/globalbar.log000066400000000000000000000000001361131222100272000ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/globalbar.txt000066400000000000000000000000511361131222100272440ustar00rootroot00000000000000globalbar.in globalbar.txt globalbar.log snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/localbar.log000066400000000000000000000000001361131222100270320ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/localbar.txt000066400000000000000000000000461361131222100271020ustar00rootroot00000000000000localbar.in localbar.txt localbar.log snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/stringbar.log000066400000000000000000000000001361131222100272460ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wildcard_keyword/expected-results/stringbar.txt000066400000000000000000000000511361131222100273120ustar00rootroot00000000000000stringbar.in stringbar.txt stringbar.log snakemake-5.10.0/tests/test_wrapper/000077500000000000000000000000001361131222100174055ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wrapper/Snakefile000066400000000000000000000002021361131222100212230ustar00rootroot00000000000000rule compress_vcf: input: "test.vcf" output: "test.vcf.gz" wrapper: "0.42.0/bio/vcf/compress" snakemake-5.10.0/tests/test_wrapper/expected-results/000077500000000000000000000000001361131222100227055ustar00rootroot00000000000000snakemake-5.10.0/tests/test_wrapper/expected-results/test.vcf.gz000066400000000000000000000006671361131222100250140ustar00rootroot00000000000000BCMO@W4bB.˂ޫ" .\0%ayfaIzˌ5{áޝ[WZ8N4ќeoix$D/Y<Ⱥq^V&C }~9vAne8aqf{^G9'"KWeRQr*uE0TG3{mVxa`/}xz@ݥ؁H)Hbg 8Q'fHT lP`k{xh7tN)(cp|gAAsA+Y&l:9MMM6~6m"ۓN[*liԢ*5!jBZbZe+|)BօsPLvfq 3 nn `6)+~WBCsnakemake-5.10.0/tests/test_wrapper/test.vcf000066400000000000000000000021271361131222100210660ustar00rootroot00000000000000##fileformat=VCFv4.1 ##INFO= ##INFO= ##INFO= ##FORMAT= ##FORMAT= ##FORMAT= ##FORMAT= #CHROM POS ID REF ALT QUAL FILTER INFO FORMAT one two 19 3111939 rs1234 A AG . PASS S1=string1;N1=1;F1=1.0 GT:FS1:FN1 ./1:LongString1:1 1/1:ss1:2 19 3113255 rs2345 G GC . PASS S1=string2;N1=2;F1=2.0 GT:FS1:FN1 1|1:LongString2:1 1/1:ss2:2 19 3113259 rs2345 G GC . PASS S1=string3;N1=3;F1=3.0 GT:FS1:FN1 0/1:.:1 1/1:ss3:2 19 3113262 rs2345 G GC . PASS S1=string4;N1=4;F1=4.0 GT:FS1:FN1 0|1:LongString4:1 1/1:.:2 19 3113268 rs2345 G GC . PASS S1=string5;N1=5;F1=5.0 GT:FS1:FN1 1|.:evenlength:1 1/1:veenlength:2 19 3113272 rs2345 G GC . PASS S1=string6;N1=6;F1=6.0 GT:FS1:FN1 1/1:ss6:1 1/1:longstring6:2 snakemake-5.10.0/tests/test_xrootd/000077500000000000000000000000001361131222100172445ustar00rootroot00000000000000snakemake-5.10.0/tests/test_xrootd/Snakefile000066400000000000000000000016511361131222100210730ustar00rootroot00000000000000from snakemake.remote.XRootD import RemoteProvider as XRootDRemoteProvider XRootD = XRootDRemoteProvider(stay_on_remote=True) remote_path = 'root://eospublic.cern.ch//eos/opendata/lhcb/MasterclassDatasets/' my_files, = XRootD.glob_wildcards(remote_path+'D0lifetime/2014/mclasseventv2_D0_{n}.root') rule all: input: expand('access_remotely/mclasseventv2_D0_{n}.root', n=my_files), expand('access_locally/mclasseventv2_D0_{n}.root', n=my_files) rule access_remotely: input: XRootD.remote(remote_path+'D0lifetime/2014/mclasseventv2_D0_{n}.root') output: 'access_remotely/mclasseventv2_D0_{n}.root' shell: 'xrdcp {input[0]} {output[0]}' rule access_locally: input: XRootD.remote(remote_path+'D0lifetime/2014/mclasseventv2_D0_{n}.root', stay_on_remote=False) output: 'access_locally/mclasseventv2_D0_{n}.root' shell: 'cp {input[0]} {output[0]}' snakemake-5.10.0/tests/test_yaml_config/000077500000000000000000000000001361131222100202145ustar00rootroot00000000000000snakemake-5.10.0/tests/test_yaml_config/Snakefile000066400000000000000000000001531361131222100220370ustar00rootroot00000000000000 configfile: "test.yaml" rule: output: config["outfile"] shell: "touch {output}" snakemake-5.10.0/tests/test_yaml_config/expected-results/000077500000000000000000000000001361131222100235145ustar00rootroot00000000000000snakemake-5.10.0/tests/test_yaml_config/expected-results/test.out000066400000000000000000000000001361131222100252120ustar00rootroot00000000000000snakemake-5.10.0/tests/test_yaml_config/test.yaml000066400000000000000000000000251361131222100220540ustar00rootroot00000000000000--- outfile: test.outsnakemake-5.10.0/tests/testapi.py000066400000000000000000000017231361131222100167140ustar00rootroot00000000000000""" Tests for Snakemake’s API """ from snakemake import snakemake import tempfile import os.path from textwrap import dedent def test_keep_logger(): with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "Snakefile") with open(path, "w") as f: print("rule:\n output: 'result.txt'\n shell: 'touch {output}'", file=f) snakemake(path, workdir=tmpdir, keep_logger=True) def test_run_script_directive(): with tempfile.TemporaryDirectory() as tmpdir: path = os.path.join(tmpdir, "Snakefile") with open(path, "w") as f: print( dedent( """ rule: output: 'result.txt' run: with open(output[0], 'w') as f: print("hello", file=f) """ ), file=f, ) snakemake(path, workdir=tmpdir) snakemake-5.10.0/tests/tests.py000066400000000000000000000645651361131222100164220ustar00rootroot00000000000000__authors__ = ["Tobias Marschall", "Marcel Martin", "Johannes Köster"] __copyright__ = "Copyright 2015-2019, Johannes Köster" __email__ = "koester@jimmy.harvard.edu" __license__ = "MIT" import sys import os import shutil from os.path import join from subprocess import call import tempfile import hashlib import urllib from shutil import rmtree, which from shlex import quote import pytest import subprocess from snakemake import snakemake from snakemake.shell import shell def dpath(path): """get path to a data file (relative to the directory this test lives in)""" return os.path.realpath(join(os.path.dirname(__file__), path)) def md5sum(filename): data = open(filename, "rb").read() return hashlib.md5(data).hexdigest() # test skipping def is_connected(): try: urllib.request.urlopen("http://www.google.com", timeout=1) return True except urllib.request.URLError: return False def is_ci(): return "CI" in os.environ def has_gcloud_service_key(): return "GCLOUD_SERVICE_KEY" in os.environ def has_gcloud_cluster(): return "GCLOUD_CLUSTER" in os.environ gcloud = pytest.mark.skipif( not is_connected() or not has_gcloud_service_key() or not has_gcloud_cluster(), reason="Skipping GCLOUD tests because not on " "CI, no inet connection or not logged " "in to gcloud.", ) connected = pytest.mark.skipif(not is_connected(), reason="no internet connection") ci = pytest.mark.skipif(not is_ci(), reason="not in CI") not_ci = pytest.mark.skipif(is_ci(), reason="skipped in CI") def copy(src, dst): if os.path.isdir(src): shutil.copytree(src, os.path.join(dst, os.path.basename(src))) else: shutil.copy(src, dst) def run( path, shouldfail=False, snakefile="Snakefile", subpath=None, no_tmpdir=False, check_md5=True, cores=3, set_pythonpath=True, cleanup=True, **params ): """ Test the Snakefile in path. There must be a Snakefile in the path and a subdirectory named expected-results. If cleanup is False, we return the temporary directory to the calling test for inspection, and the test should clean it up. """ if set_pythonpath: # Enforce current workdir (the snakemake source dir) to also be in PYTHONPATH # when subprocesses are invoked in the tempdir defined below. os.environ["PYTHONPATH"] = os.getcwd() elif "PYTHONPATH" in os.environ: del os.environ["PYTHONPATH"] results_dir = join(path, "expected-results") snakefile = join(path, snakefile) assert os.path.exists(snakefile) assert os.path.exists(results_dir) and os.path.isdir( results_dir ), "{} does not exist".format(results_dir) # If we need to further check results, we won't cleanup tmpdir tmpdir = next(tempfile._get_candidate_names()) tmpdir = os.path.join(tempfile.gettempdir(), "snakemake-%s" % tmpdir) os.mkdir(tmpdir) config = {} # handle subworkflow if subpath is not None: # set up a working directory for the subworkflow and pass it in `config` # for now, only one subworkflow is supported assert os.path.exists(subpath) and os.path.isdir( subpath ), "{} does not exist".format(subpath) subworkdir = os.path.join(tmpdir, "subworkdir") os.mkdir(subworkdir) # copy files for f in os.listdir(subpath): copy(os.path.join(subpath, f), subworkdir) config["subworkdir"] = subworkdir # copy files for f in os.listdir(path): print(f) copy(os.path.join(path, f), tmpdir) # run snakemake success = snakemake( snakefile, cores=cores, workdir=path if no_tmpdir else tmpdir, stats="stats.txt", config=config, verbose=True, **params ) if shouldfail: assert not success, "expected error on execution" else: assert success, "expected successful execution" for resultfile in os.listdir(results_dir): if resultfile in [".gitignore", ".gitkeep"] or not os.path.isfile( os.path.join(results_dir, resultfile) ): # this means tests cannot use directories as output files continue targetfile = join(tmpdir, resultfile) expectedfile = join(results_dir, resultfile) assert os.path.exists(targetfile), 'expected file "{}" not produced'.format( resultfile ) if check_md5: # if md5sum(targetfile) != md5sum(expectedfile): # import pdb; pdb.set_trace() if md5sum(targetfile) != md5sum(expectedfile): with open(targetfile) as target: content = target.read() assert False, 'wrong result produced for file "{}":\n{}'.format( resultfile, content ) if not cleanup: return tmpdir shutil.rmtree(tmpdir) def test_list_untracked(): run(dpath("test_list_untracked")) def test_delete_all_output(): run(dpath("test_delete_all_output")) def test_github_issue_14(): """Add cleanup_scripts argument to allow the user to keep scripts""" # Return temporary directory for inspection - we should keep scripts here tmpdir = run(dpath("test_github_issue_14"), cleanup=False, cleanup_scripts=False) assert os.listdir(os.path.join(tmpdir, ".snakemake", "scripts")) shutil.rmtree(tmpdir) # And not here tmpdir = run(dpath("test_github_issue_14"), cleanup=False) assert not os.listdir(os.path.join(tmpdir, ".snakemake", "scripts")) shutil.rmtree(tmpdir) def test_issue956(): run(dpath("test_issue956")) def test01(): run(dpath("test01")) def test02(): run(dpath("test02")) def test03(): run(dpath("test03"), targets=["test.out"]) def test04(): run(dpath("test04"), targets=["test.out"]) def test05(): run(dpath("test05")) def test06(): run(dpath("test06"), targets=["test.bla.out"]) def test07(): run(dpath("test07"), targets=["test.out", "test2.out"]) def test08(): run(dpath("test08"), targets=["test.out", "test2.out"]) def test09(): run(dpath("test09"), shouldfail=True) def test10(): run(dpath("test10")) def test11(): run(dpath("test11")) def test12(): run(dpath("test12")) def test13(): run(dpath("test13")) def test14(): run(dpath("test14"), snakefile="Snakefile.nonstandard", cluster="./qsub") def test15(): run(dpath("test15")) def test_directory(): run( dpath("test_directory"), targets=[ "downstream", "symlinked_input", "child_to_input", "some/dir-child", "some/shadow", ], ) run(dpath("test_directory"), targets=["file_expecting_dir"], shouldfail=True) run(dpath("test_directory"), targets=["dir_expecting_file"], shouldfail=True) run(dpath("test_directory"), targets=["child_to_other"], shouldfail=True) def test_ancient(): run(dpath("test_ancient"), targets=["D", "old_file"]) def test_report(): run(dpath("test_report"), report="report.html", check_md5=False) def test_dynamic(): run(dpath("test_dynamic")) def test_params(): run(dpath("test_params")) def test_same_wildcard(): run(dpath("test_same_wildcard")) def test_conditional(): run( dpath("test_conditional"), targets="test.out test.0.out test.1.out test.2.out".split(), ) def test_unpack_dict(): run(dpath("test_unpack_dict")) def test_unpack_list(): run(dpath("test_unpack_list")) def test_shell(): run(dpath("test_shell")) def test_temp(): run(dpath("test_temp"), cluster="./qsub", targets="test.realigned.bam".split()) def test_keyword_list(): run(dpath("test_keyword_list")) def test_subworkflows(): run(dpath("test_subworkflows"), subpath=dpath("test02")) def test_globwildcards(): run(dpath("test_globwildcards")) def test_local_import(): run(dpath("test_local_import")) def test_ruledeps(): run(dpath("test_ruledeps")) def test_persistent_dict(): try: import pytools run(dpath("test_persistent_dict")) except ImportError: pass @connected def test_url_include(): run(dpath("test_url_include")) def test_touch(): run(dpath("test_touch")) def test_config(): run(dpath("test_config")) def test_update_config(): run(dpath("test_update_config")) def test_wildcard_keyword(): run(dpath("test_wildcard_keyword")) def test_benchmark(): run(dpath("test_benchmark"), check_md5=False) def test_temp_expand(): run(dpath("test_temp_expand")) def test_wildcard_count_ambiguity(): run(dpath("test_wildcard_count_ambiguity")) def test_srcdir(): run(dpath("test_srcdir")) def test_multiple_includes(): run(dpath("test_multiple_includes")) def test_yaml_config(): run(dpath("test_yaml_config")) def test_remote(): run(dpath("test_remote"), cores=1) def test_cluster_sync(): run(dpath("test14"), snakefile="Snakefile.nonstandard", cluster_sync="./qsub") @pytest.mark.skip(reason="This does not work reliably in CircleCI.") def test_symlink_temp(): run(dpath("test_symlink_temp"), shouldfail=True) def test_empty_include(): run(dpath("test_empty_include")) def test_script(): run(dpath("test_script"), use_conda=True) def test_shadow(): run(dpath("test_shadow")) def test_shadow_prefix(): run(dpath("test_shadow_prefix"), shadow_prefix="shadowdir") run(dpath("test_shadow_prefix"), shadow_prefix="shadowdir", cluster="./qsub") def test_until(): run( dpath("test_until"), until=[ "leveltwo_first", # rule name "leveltwo_second.txt", # file name "second_wildcard", ], ) # wildcard rule def test_omitfrom(): run( dpath("test_omitfrom"), omit_from=[ "leveltwo_first", # rule name "leveltwo_second.txt", # file name "second_wildcard", ], ) # wildcard rule def test_nonstr_params(): run(dpath("test_nonstr_params")) def test_delete_output(): run(dpath("test_delete_output"), cores=1) def test_input_generator(): run(dpath("test_input_generator")) def test_symlink_time_handling(): # See Snakefile for notes on why this fails on some systems if os.utime in os.supports_follow_symlinks: run(dpath("test_symlink_time_handling")) def test_protected_symlink_output(): run(dpath("test_protected_symlink_output")) def test_issue328(): try: import pytools run(dpath("test_issue328"), forcerun=["split"]) except ImportError: # skip test if import fails pass def test_conda(): if conda_available(): run(dpath("test_conda"), use_conda=True) def test_conda_custom_prefix(): if conda_available(): run( dpath("test_conda_custom_prefix"), use_conda=True, conda_prefix="custom", set_pythonpath=False, ) def test_wrapper(): if conda_available(): run(dpath("test_wrapper"), use_conda=True) def conda_available(): return which("conda") def test_get_log_none(): run(dpath("test_get_log_none")) def test_get_log_both(): run(dpath("test_get_log_both")) def test_get_log_stderr(): run(dpath("test_get_log_stderr")) def test_get_log_stdout(): run(dpath("test_get_log_stdout")) def test_get_log_complex(): run(dpath("test_get_log_complex")) def test_spaces_in_fnames(): run( dpath("test_spaces_in_fnames"), # cluster="./qsub", targets=["test bam file realigned.bam"], printshellcmds=True, ) # TODO deactivate because of problems with moto and boto3. # def test_static_remote(): # import importlib # try: # importlib.reload(boto3) # importlib.reload(moto) # # only run the remote file test if the dependencies # # are installed, otherwise do nothing # run(dpath("test_static_remote"), cores=1) # except ImportError: # pass @connected def test_remote_ncbi_simple(): try: import Bio # only run the remote file test if the dependencies # are installed, otherwise do nothing run(dpath("test_remote_ncbi_simple")) except ImportError: pass @connected def test_remote_ncbi(): try: import Bio # only run the remote file test if the dependencies # are installed, otherwise do nothing run(dpath("test_remote_ncbi")) except ImportError: pass @ci def test_remote_irods(): run(dpath("test_remote_irods")) def test_deferred_func_eval(): run(dpath("test_deferred_func_eval")) def test_format_params(): run(dpath("test_format_params"), check_md5=True) def test_rule_defined_in_for_loop(): # issue 257 run(dpath("test_rule_defined_in_for_loop")) def test_issue381(): run(dpath("test_issue381")) def test_format_wildcards(): run(dpath("test_format_wildcards")) def test_with_parentheses(): run(dpath("test (with parenthese's)")) def test_dup_out_patterns(): """Duplicate output patterns should emit an error Duplicate output patterns can be detected on the rule level """ run(dpath("test_dup_out_patterns"), shouldfail=True) def test_restartable_job_cmd_exit_1_no_restart(): """Test the restartable job feature on ``exit 1`` The shell snippet in the Snakemake file will fail the first time and succeed the second time. """ run( dpath("test_restartable_job_cmd_exit_1"), cluster="./qsub", restart_times=0, shouldfail=True, ) def test_restartable_job_cmd_exit_1_one_restart(): # Restarting once is enough run( dpath("test_restartable_job_cmd_exit_1"), cluster="./qsub", restart_times=1, printshellcmds=True, ) def test_restartable_job_qsub_exit_1(): """Test the restartable job feature when qsub fails The qsub in the sub directory will fail the first time and succeed the second time. """ # Even two consecutive times should fail as files are cleared run( dpath("test_restartable_job_qsub_exit_1"), cluster="./qsub", restart_times=0, shouldfail=True, ) run( dpath("test_restartable_job_qsub_exit_1"), cluster="./qsub", restart_times=0, shouldfail=True, ) # Restarting once is enough run( dpath("test_restartable_job_qsub_exit_1"), cluster="./qsub", restart_times=1, shouldfail=False, ) def test_threads(): run(dpath("test_threads"), cores=20) def test_threads0(): run(dpath("test_threads0")) def test_dynamic_temp(): run(dpath("test_dynamic_temp")) # TODO this currently hangs. Has to be investigated (issue #660). # def test_ftp_immediate_close(): # try: # import ftputil # # # only run the remote file test if the dependencies # # are installed, otherwise do nothing # run(dpath("test_ftp_immediate_close")) # except ImportError: # pass def test_issue260(): run(dpath("test_issue260")) @not_ci def test_default_remote(): run( dpath("test_default_remote"), cores=1, default_remote_provider="S3Mocked", default_remote_prefix="test-remote-bucket", ) def test_run_namedlist(): run(dpath("test_run_namedlist")) @connected @not_ci def test_remote_gs(): run(dpath("test_remote_gs")) @pytest.mark.skip(reason="Need to choose how to provide billable project") @connected @not_ci def test_gs_requester_pays( requesting_project=None, requesting_url="gcp-public-data-landsat/LC08/01/001/003/LC08_L1GT_001003_20170430_20170501_01_RT/LC08_L1GT_001003_20170430_20170501_01_RT_MTL.txt", ): """ Tests pull-request 79 / issue 96 for billable user projects on GS If requesting_project None, behaves as test_remote_gs(). Parameters ---------- requesting_project: Optional[str] User project to bill for download. None will not provide project for requester-pays as is the usual default requesting_url: str URL of bucket to download. Default will match expected output, but is a bucket that doesn't require requester pays. """ # create temporary config file with tempfile.NamedTemporaryFile(suffix=".yaml") as handle: # specify project and url for download if requesting_project is None: handle.write(b"project: null\n") else: handle.write('project: "{}"\n'.format(requesting_project).encode()) handle.write('url: "{}"\n'.format(requesting_url).encode()) # make sure we can read them handle.flush() # run the pipeline run(dpath("test_gs_requester_pays"), configfiles=[handle.name], forceall=True) @pytest.mark.skip(reason="We need free azure access to test this in CircleCI.") @connected @ci def test_remote_azure(): run(dpath("test_remote_azure")) def test_remote_log(): run(dpath("test_remote_log"), shouldfail=True) @connected @pytest.mark.xfail def test_remote_http(): run(dpath("test_remote_http")) @connected @pytest.mark.xfail def test_remote_http_cluster(): run(dpath("test_remote_http"), cluster=os.path.abspath(dpath("test14/qsub"))) def test_profile(): run(dpath("test_profile")) @connected def test_singularity(): run(dpath("test_singularity"), use_singularity=True) def test_singularity_invalid(): run( dpath("test_singularity"), targets=["invalid.txt"], use_singularity=True, shouldfail=True, ) @connected def test_singularity_conda(): run(dpath("test_singularity_conda"), use_singularity=True, use_conda=True) def test_issue612(): run(dpath("test_issue612"), dryrun=True) def test_bash(): run(dpath("test_bash")) def test_inoutput_is_path(): run(dpath("test_inoutput_is_path")) def test_archive(): run(dpath("test_archive"), archive="workflow-archive.tar.gz") def test_log_input(): run(dpath("test_log_input")) @pytest.fixture(scope="module") def gcloud_cluster(): class Cluster: def __init__(self): self.cluster = os.environ["GCLOUD_CLUSTER"] self.bucket_name = "snakemake-testing-{}".format(self.cluster) shell( """ $GCLOUD container clusters create {self.cluster} --num-nodes 3 --scopes storage-rw --zone us-central1-a --machine-type f1-micro $GCLOUD container clusters get-credentials {self.cluster} --zone us-central1-a $GSUTIL mb gs://{self.bucket_name} """ ) def delete(self): shell( """ $GCLOUD container clusters delete {self.cluster} --zone us-central1-a --quiet || true $GSUTIL rm -r gs://{self.bucket_name} || true """ ) def run(self, test="test_kubernetes", **kwargs): try: run( dpath(test), kubernetes="default", default_remote_provider="GS", default_remote_prefix=self.bucket_name, no_tmpdir=True, **kwargs ) except Exception as e: shell( "for p in `kubectl get pods | grep ^snakejob- | cut -f 1 -d ' '`; do kubectl logs $p; done" ) raise e def reset(self): shell("$GSUTIL rm -r gs://{self.bucket_name}/* || true") cluster = Cluster() yield cluster cluster.delete() @gcloud @pytest.mark.skip( reason="reenable once we have figured out how to fail if available core hours per month are exceeded" ) @pytest.mark.xfail def test_gcloud_plain(gcloud_cluster): gcloud_cluster.reset() gcloud_cluster.run() @gcloud @pytest.mark.skip(reason="need a faster cloud compute instance to run this") def test_gcloud_conda(gcloud_cluster): gcloud_cluster.reset() gcloud_cluster.run(use_conda=True) @gcloud @pytest.mark.skip(reason="need a faster cloud compute instance to run this") def test_gcloud_singularity(gcloud_cluster): gcloud_cluster.reset() gcloud_cluster.run(use_singularity=True) @gcloud @pytest.mark.skip(reason="need a faster cloud compute instance to run this") def test_gcloud_conda_singularity(gcloud_cluster): gcloud_cluster.reset() gcloud_cluster.run(use_singularity=True, use_conda=True) @gcloud() @pytest.mark.skip(reason="need a faster cloud compute instance to run this") def test_issue1041(gcloud_cluster): gcloud_cluster.reset() gcloud_cluster.run(test="test_issue1041") @connected def test_cwl(): run(dpath("test_cwl")) @connected def test_cwl_singularity(): run(dpath("test_cwl"), use_singularity=True) def test_issue805(): run(dpath("test_issue805"), shouldfail=True) def test_pathlib(): run(dpath("test_pathlib")) def test_pathlib_missing_file(): run(dpath("test_pathlib_missing_file"), shouldfail=True) def test_group_jobs(): run(dpath("test_group_jobs"), cluster="./qsub") def test_group_job_fail(): run(dpath("test_group_job_fail"), cluster="./qsub", shouldfail=True) def test_pipes(): run(dpath("test_pipes")) def test_pipes_fail(): run(dpath("test_pipes_fail"), shouldfail=True) def test_validate(): run(dpath("test_validate")) def test_validate_fail(): run( dpath("test_validate"), configfiles=[dpath("test_validate/config.fail.yaml")], shouldfail=True, ) def test_issue854(): # output and benchmark have inconsistent wildcards # this should fail when parsing run(dpath("test_issue854"), shouldfail=True) def test_issue850(): run(dpath("test_issue850"), cluster="./qsub") def test_issue860(): run(dpath("test_issue860"), cluster="./qsub", targets=["done"]) def test_issue894(): run(dpath("test_issue894")) def test_issue584(): run(dpath("test_issue584")) def test_issue912(): run(dpath("test_issue912")) def test_job_properties(): run(dpath("test_job_properties"), cluster="./qsub.py") def test_issue916(): run(dpath("test_issue916")) def test_issue930(): run(dpath("test_issue930"), cluster="./qsub") def test_issue635(): run(dpath("test_issue635"), use_conda=True, check_md5=False) # TODO remove skip @pytest.mark.skip( reason="Temporarily disable until the stable container image becomes available again." ) def test_convert_to_cwl(): workdir = dpath("test_convert_to_cwl") # run(workdir, export_cwl=os.path.join(workdir, "workflow.cwl")) shell( "cd {workdir}; PYTHONPATH={src} python -m snakemake --export-cwl workflow.cwl", src=os.getcwd(), ) shell("cd {workdir}; cwltool --singularity workflow.cwl") assert os.path.exists(os.path.join(workdir, "test.out")) def test_issue1037(): run(dpath("test_issue1037"), dryrun=True, cluster="qsub", targets=["Foo_A.done"]) def test_issue1046(): run(dpath("test_issue1046")) def test_checkpoints(): run(dpath("test_checkpoints")) def test_checkpoints_dir(): run(dpath("test_checkpoints_dir")) def test_issue1092(): run(dpath("test_issue1092")) def test_issue1093(): run(dpath("test_issue1093"), use_conda=True) def test_issue958(): run(dpath("test_issue958"), cluster="dummy", dryrun=True) def test_issue471(): run(dpath("test_issue471")) def test_issue1085(): run(dpath("test_issue1085"), shouldfail=True) def test_issue1083(): run(dpath("test_issue1083"), use_singularity=True) def test_pipes2(): run(dpath("test_pipes2")) @pytest.mark.skip( reason="The AWS Access Key Id you provided does not exist in our records." ) def test_tibanna(): workdir = dpath("test_tibanna") subprocess.check_call(["python", "cleanup.py"], cwd=workdir) run( workdir, use_conda=True, configfiles=[os.path.join(workdir, "config.json")], default_remote_prefix="snakemake-tibanna-test/1", tibanna_sfn="tibanna_unicorn_johannes", ) def test_expand_flag(): run(dpath("test_expand_flag"), shouldfail=True) def test_default_resources(): from snakemake.resources import DefaultResources run( dpath("test_default_resources"), default_resources=DefaultResources( ["mem_mb=max(2*input.size, 1000)", "disk_mb=max(2*input.size, 1000)"] ), ) def test_issue1284(): run(dpath("test_issue1284")) def test_issue1281(): run(dpath("test_issue1281")) def test_filegraph(): workdir = dpath("test_filegraph") dot_path = os.path.abspath("fg.dot") pdf_path = "fg.pdf" # make sure the calls work shell("cd {workdir}; python -m snakemake --filegraph > {dot_path}") # make sure the output can be interpreted by dot with open(dot_path, "rb") as dot_file, open(pdf_path, "wb") as pdf_file: pdf_file.write( subprocess.check_output(["dot", "-Tpdf"], stdin=dot_file, cwd=workdir) ) # make sure the generated pdf file is not empty assert os.stat(pdf_path).st_size > 0 def test_batch(): from snakemake.dag import Batch run(dpath("test_batch"), batch=Batch("aggregate", 1, 2)) def test_batch_final(): from snakemake.dag import Batch run(dpath("test_batch_final"), batch=Batch("aggregate", 1, 1)) def test_batch_fail(): from snakemake.dag import Batch run(dpath("test_batch"), batch=Batch("aggregate", 2, 2), shouldfail=True) def test_github_issue52(): run(dpath("test_github_issue52"), shouldfail=True) run(dpath("test_github_issue52"), snakefile="other.smk", shouldfail=True) def test_github_issue78(): run(dpath("test_github_issue78"), use_singularity=True) def test_github_issue105(): run(dpath("test_github_issue105")) def test_output_file_cache(): test_path = dpath("test_output_file_cache") os.environ["SNAKEMAKE_OUTPUT_CACHE"] = os.path.join(test_path, "cache") run(test_path, cache=["a", "b", "c"]) run(test_path, cache=["invalid_multi"], targets="invalid1.txt", shouldfail=True) def test_output_file_cache_remote(): test_path = dpath("test_output_file_cache_remote") os.environ["SNAKEMAKE_OUTPUT_CACHE"] = "cache" run( test_path, cache=["a", "b", "c"], default_remote_provider="S3Mocked", default_remote_prefix="test-remote-bucket", ) def test_multiext(): run(dpath("test_multiext")) def test_core_dependent_threads(): run(dpath("test_core_dependent_threads")) def test_env_modules(): run(dpath("test_env_modules"), use_env_modules=True) snakemake-5.10.0/versioneer.py000066400000000000000000002060031361131222100162600ustar00rootroot00000000000000 # Version: 0.18 """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) * [![Build Status] (https://travis-ci.org/warner/python-versioneer.png?branch=master) ](https://travis-ci.org/warner/python-versioneer) This is a tool for managing a recorded version number in distutils-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install * `pip install versioneer` to somewhere to your $PATH * add a `[versioneer]` section to your setup.cfg (see below) * run `versioneer install` in your source tree, commit the results ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes. The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/warner/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other langauges) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/warner/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/warner/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ### Unicode version strings While Versioneer works (and is continually tested) with both Python 2 and Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. Newer releases probably generate unicode version strings on py2. It's not clear that this is wrong, but it may be surprising for applications when then write these strings to a network connection or include them in bytes-oriented APIs like cryptographic checksums. [Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates this question. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the Creative Commons "Public Domain Dedication" license (CC0-1.0), as described in https://creativecommons.org/publicdomain/zero/1.0/ . """ from __future__ import print_function try: import configparser except ImportError: import ConfigParser as configparser import errno import json import os import re import subprocess import sys class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. me = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(me)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir: print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py)) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") parser = configparser.SafeConfigParser() with open(setup_cfg, "r") as f: parser.readfp(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): if parser.has_option("versioneer", name): return parser.get("versioneer", name) return None cfg = VersioneerConfig() cfg.VCS = VCS cfg.style = get(parser, "style") or "" cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") if cfg.tag_prefix in ("''", '""'): cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%%s*" %% tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%%d" %% pieces["distance"] else: # exception #1 rendered = "0.post.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(manifest_in, versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [manifest_in, versionfile_source] if ipy: files.append(ipy) try: me = __file__ if me.endswith(".pyc") or me.endswith(".pyo"): me = os.path.splitext(me)[0] + ".py" versioneer_file = os.path.relpath(me) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") for line in f.readlines(): if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True f.close() except EnvironmentError: pass if not present: f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.18) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except EnvironmentError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/warner/python-versioneer/issues/52 cmds = {} # we add "version" to both distutils and setuptools from distutils.core import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in both distutils and setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # we override different "build_py" commands for both environments if "setuptools" in sys.modules: from setuptools.command.build_py import build_py as _build_py else: from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.distutils_buildexe import py2exe as _py2exe # py3 except ImportError: from py2exe.build_exe import py2exe as _py2exe # py2 class cmd_py2exe(_py2exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # we override different "sdist" commands for both environments if "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist else: from distutils.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ INIT_PY_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ def do_setup(): """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (EnvironmentError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except EnvironmentError: old = "" if INIT_PY_SNIPPET not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(INIT_PY_SNIPPET) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make sure both the top-level "versioneer.py" and versionfile_source # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so # they'll be copied into source distributions. Pip won't be able to # install the package without this. manifest_in = os.path.join(root, "MANIFEST.in") simple_includes = set() try: with open(manifest_in, "r") as f: for line in f: if line.startswith("include "): for include in line.split()[1:]: simple_includes.add(include) except EnvironmentError: pass # That doesn't cover everything MANIFEST.in can do # (http://docs.python.org/2/distutils/sourcedist.html#commands), so # it might give some false negatives. Appending redundant 'include' # lines is safe, though. if "versioneer.py" not in simple_includes: print(" appending 'versioneer.py' to MANIFEST.in") with open(manifest_in, "a") as f: f.write("include versioneer.py\n") else: print(" 'versioneer.py' already in MANIFEST.in") if cfg.versionfile_source not in simple_includes: print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source) with open(manifest_in, "a") as f: f.write("include %s\n" % cfg.versionfile_source) else: print(" versionfile_source already in MANIFEST.in") # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(manifest_in, cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": errors = do_setup() errors += scan_setup_py() if errors: sys.exit(1)